Pokud se vám nechce ráno vylézat z postele, příčin by mohlo být hned několik. O jedné z nich se v širší veřejnosti začíná mluvit až v posledních letech, ačkoliv se nejedná o nic nového. Možná jste si prostě jen pořádně nevyvětrali.
Nebohá lidská schránka vypouští během noci do svého okolí všemožné chemické substance a jednou z nich je i oxid uhličitý – CO2. Zatímco v přírodě tvoří jeho koncentrace ve vzduchu asi 0,04 %, plyn, který vydechujete, je tímto oxidem nasycený až z několika procent!
Podívejte se na animaci přírodní koncentrace CO2:
Je to problém? Jak se to vezme. Stačí si vzpomenout na snímek Apollo 13 s Tomem Hanksem v hlavní roli a snahu posádky z všemožných cetek na palubě kosmické lodi opravit filtr CO2. Pokud by se jim to tehdy nepodařilo, NASA se reálně obávala, že při koncentraci nad 6 % by posádka mohla postupně začít blouznit a ztrácet vědomí. Ale nebojte, to se vám v ložnici nestane, i kdybyste vydechovali celou noc o sto šest.
Přesto se však doporučuje, aby koncentrace CO2 třeba v kancelářích, ve kterých sedíme celý den, nepřekračovala hladinu zhruba 0,1-0,2 % CO2. Ne že by vás snad vyšší podíl oxidu uhličitého přizabil, ale u citlivějších osob se postupně může objevovat pocit únavy, horší soustředění, nevolnost a tak dále.
Levný senzor oxidu uhličitého MH-Z19B
I proto se na některých pracovištích a v progresivních školách objevují stále častěji senzory CO2. Jeden takový si můžete za relativně malý peníz pořídit i do své ložnice. Jmenuje se MH-Z19B (PDF dokumentace), vyrábí jej čínská fabrika Zhengzhou Winsen Electronics Technology a jedná se do jisté míry o kopii švédských čidel SenseAir.






Senzor CO2 Tondy Trčálka s otvory pro piny a zároveň jednotným konektorem JST. Bílými plochami filtrů na horní a boční straně proniká do měřící komory v nitru okolní vzduch.
MH-Z19B v kostce:
- Princip měření: NDIR
- Rozsah: 0-5 000 ppm (přesnost 50 ppm)
- Napájení: 4,5-5,5 V
- Odběr proudu: 60 mA (průměr), ve špičce až 100+ mA
- I/O logika: 3,3 V (tolerantní s 5 V)
- Komunikace: UART, PWM, analog (0-2V)
- Cena: okolo 600 Kč (eBay aspol.)
Oproti mnoha ostatním čidlům – třeba teploměrům – začátečníka nejprve překvapí cena. Změřit koncentraci CO2 je totiž hotová věda a z mnoha důvodů jsou tato čidla poměrně drahá. Cena těch značkových se bez problému vyšplhá až na několik tisíc korun!
Technologie NDIR
MH-Z19B k měření úrovně CO2 používá technologii NDIR (Nondispersive infrared). Senzor se skládá z komory, do které neustále proudí okolní vzduch. Komoru poté každých několik sekund prozáří červená IR dioda a detektor na opačné straně pracuje jako jednoduchý spektrální analyzátor. Jednoduše řečeno, koncentrace CO2 v komoře pozmění vlastnosti infračerveného světla a detektor tuto změnu zaznamená.

Základní schéma detektoru plynů, který používá technologii NDIR
Senzory NDIR jsou zpravidla energeticky mnohem úspornější než chemické detektory, které měří koncentraci chemickou cestou. Díky NDIR potřebuje MH-Z19 k chodu jen zhruba 4-5 V a v průměru spaluje desítky mA elektrického proudu.
MH-Z19B je v tomto směru opravdu superlevný model, na eBayi a mnoha čínských tržištích jej totiž najdete za částku zhruba okolo 600 korun. Při nákupu si ale dávejte dobrý pozor na to, jestli se nejedná o konstrukčně prakticky identický senzor MH-Z19 (bez písmene B na konci), který je o něco hloupější, chybí mu totiž vlastní automatická kalibrace nebo třeba analogový výstup.
MH-Z19B pod napětím a patrné blikání detektoru NDIR:
Co je to PPM
Senzory plynů a prachových částic zpravidla neměří koncentraci v okolním vzduchu v procentech, ale v jednotce ppm (parts per million – dílů/částic na jeden milion). Takže platí:
- 100 % = 1 000 000 ppm
- 1 % = 10 000 ppm
- 0,04 % = 400 ppm
Přírodní koncentrace CO2 ve vzduchu je proto zhruba 360-400 ppm a neustále roste. Ještě na počátku 20. století se pohybovala pod 300 ppm.
Pro přehlednost si přepočtěme na ppm všechny limitní hodnoty, o kterých tu byla zatím řeč. V kanceláři nebo u vás v obývacím pokoji – prostě v uzavřených prostorách – by měla být v ideálním případě do 1 000 až 2 000 ppm, vydechovaný vzduch má koncentraci až několika desítek tisíc ppm a během havárie Apolla 13 se NASA obávala, aby úroveň v kosmické lodi nepřekročila okolo 60 000 ppm, která už je životu nebezpečná.
Senzor může spustit třeba ventilaci
Podobný senzor tedy může typicky v ložnici měřit periodicky hodnotu ppm, a pokud překročí kritickou mez, upozorní vás, že je vhodné vyvětrat. Ještě sofistikovanější a chytřejší systémy to pak učiní za vás, a když v ložnici v noci vzroste koncentrace třeba nad 2 000 ppm, spustí ventilaci, anebo pomocí nějakého aktuátoru pootevřou okno.
Nutno podotknout, že CO2 je těžší než vzduch jako takový, čili se bude během spánku (nikdo nevíří vzduch) držet spíše při zemi. Čidlo umístěné kdesi na skříni výše než je vaše postel proto může zaznamenávat o něco nižší hodnoty.
5 000+ ppm po bujaré noci? Žádný problém!
MH-Z19B umí měřit CO2 až do koncentrace 5 000 ppm (0,5 %) a mohu vám zaručit, že když se v zimě večer hermeticky uzavřete v ložnici a usnete, i v jedné osobě této hladiny nad ránem bez problému dosáhnete. Bohatě stačí, když si před tím otevřete láhev dobrého vína, anebo zajdete s přáteli na jedno. Odbourávání alkoholu z krve totiž nebude zadarmo, ale projeví se vyšší aktivitou metabolismu a tedy i zrychleným dechem. Za jednotku času tedy do okolí vypustíte mnohem více CO2, než kdybyste si namísto toho dali večer mátový čaj a přečetli sbírku poezie.

Jarní noci okolo Velikonoc. Během večera jsem měl zavřené okno a postupně vydýchal čerstvý vzduch. V noci jsem však vždy pootevřel okno a hodnoty se srovnaly. Na velikonoční pondělí jsem to však pod tíhou předchozích oslav neudělal a i díky zrychlenému metabolismu nad ránem koncentrace dosáhla maximální změřitelné hladiny 5 000 ppm.
Ve dvou osobách pak ložnici obvyklých rozměrů (panelák, bytový dům) vydýcháte za polovinu času a právě v zimě, kdy asi nebudete mít pootevřené okno celou noc, bude nad ránem čidlo MH-Z19B zaznamenávat plných 5 000 ppm. Více už nezměří, čili skutečná koncentrace může být ještě mnohem vyšší.
No, a v těchto chvílích už skutečně může nastupovat zvýšená únava, nechuť radostně vyskočit z postele a utíkat v pondělí ráno nadšeně za dalšími pracovními povinnostmi. Nutno však podotknout, že reakce organizmu je při těchto koncentracích stále individuální, protože jsme dalece od skutečně nebezpečných hodnot. Například autor článku v pondělí ráno nerad vstává při jakékoliv koncentraci CO2.

S příchodem teplých jarních dnů a okna otevřeného permanentně na ventilaci se vnitřní koncentrace srovnává s tou venkovní prakticky neustále. Jen jedna noc za poslední dny byla opravdu chladná a koncentrace CO2 kvůli zavřenému oknu v noční ložnici vystřelila opět vzhůru.
Senzor bude ukládat hodnoty do tabulky na Google Drive
Tak, po malém medicínském okénku se konečně pusťme do bastlení, teď si totiž naprogramujeme primitivní obvod s jedním čidlem MH-Z9B a Wi-Fi destičkou NodeMCU „v3“ s čipem ESP8266.
Řídící čip se každých pět minut probudí (po nahrání programu je třeba propojit vodičem jeho piny RST a D0/WAKE), vyžádá si od čidla aktuální koncentraci CO2, údaj skrze Google Apps Script uloží do tabulkového dokumentu na úložišti Google Drive a zase usne.
MH-Z19B má k dispozici několik komunikačních rozhraní. Analogovou hodnotu můžeme přečíst na pinu Vo jako napětí (0 až 2 V). Dále je tu pin PWM pro dekódování koncentrace pomocí pulzně-šířkové modulace. Čidlo tedy na tomto pinu neustále pulzuje, přičemž z délky pulzu lze spočítat koncentraci. A nakonec tu jsou piny RX a TX pro oboustrannou komunikaci skrze sériovou linku a přesně tu použijeme i my.
Sériová linka
MH-Z19B používá 3,3V komunikační logiku (tolerantní na 5V), nicméně jej musíte napájet o něco vyšším napětím – ideálně 5V. Modifikovaná deska NodeMCU „v3“ má na desce vyvedený pin označený jako VU (VUSB), který je připojený k USB konektoru desky, takže nabídne 5V. Případně bychom mohli použít třeba maličkou destičku Wemos D1 Mini, která tento pin má také a rovnou označený jako 5V. S oběma už jsme si v našem seriálu pohráli hned několikrát.
Připojíme čidlo pomocí SoftwareSerial
Piny na senzoru RX a TX nepřipojíme křížově na totožné piny na naší řídící desce, čip ESP8266 totiž při startu vypisuje do sériové linky provozní údaje a tato aktivita by mohla čidlo oxidu uhličitého zmást, co po něm vlastně chceme. Zároveň během vývoje nejspíše budeme chtít skrze základní sériovou linku napojenou na USB vypisovat data do počítače, takže ji potřebujeme ponechat volnou.
V Arduinu můžeme u těchto desek využít ještě přemapování sériové linky na piny D7 a D8 a mezi tímto nastavením pak přepínat příkazem Serial.swap(). Čídlo tedy připojíme na tyto alternativní piny a v úvodu programu zavoláme funkci swap. Díky tomu by čip při startu do senzoru neposílal žádná provozní data.




Připojení MH-Z19B k destičce NodeMCU na piny D5 a D6 pomocí knihovny SoftwareSerial
My ale uděláme ještě něco jiného. Použijeme oblíbenou knihovnu SoftwareSerial, která vytvoří virtuální RX a TX na libovolných dvou pinech GPIO, čili základní sériovou linku můžeme použít k vypisování dat do počítače. Ale pozor, pro čip ESP8266 nemůžete použít vestavěnou verzi knihovny v Arduinu, ale musíte si stáhnout a nainstalovat tuto knihovnu z GitHubu.
Kód pro inicializaci čidla připojeného na piny D5 a D6 pomocí knihovny SoftwareSerial může vypadat třeba takto:
// https://github.com/plerup/espsoftwareserial
#include <SoftwareSerial.h>
// D5 -> TX na MH-Z19B
// D6 -> RX na MH-Z19B
SoftwareSerial co2_senzor(D5, D6);
void setup() {
co2_senzor.begin(9600);
}
Komunikujeme s čidlem MH-Z19B
Doposud jsme pro čtení dat ze senzorů používali nejrůznější knihovny třetích stran. Aplikační rozhraní měřáku CO2 MH-Z19B je ale docela jednoduché, a tak si vše na té nejnižší úrovni pošéfujeme sami.
Komunikace s čidlem je opravdu primitivní – skládá se vždy z devět bajtů dlouhého příkazu a případně stejně dlouhé odpovědi. Příkaz je tedy pole devíti bajtů – osmibitových čísel 0-255. Každý bajt má nějakou funkci, takže podle našich potřeb změníme jeho hodnotu, no a nakonec celé pole pošleme jedním jediným příkazem do čidla, které poté provede, co jsme mu přikázali.
Podívejte se, jak vypadá obecný blok devíti bajtů pro příkaz:

První dva bajty jsou vždy stejné (červeně), ve třetím je pak číslo povelu, který má čidlo vykonat (zeleně), následuje pět bajtů dat, ve kterých mohou být pomocné parametry (modře), a vše uzavírá bajt s kontrolním součtem (žlutě).
Díky kontrolnímu součtu máme jistotu, že celá řada bajtů došla v pořádku a žádný není poškozený, protože by jinak jeho výpočet nedopovídal.
Tak a teď prakticky. Takhle vypadá instrukce, která dává čidlu povel, aby nám odpovědělo, jaká je koncentrace CO2 v okolním vzduchu:

Všimněte si, že povel k přečtení koncentrace má v šestnáctkové soustavě číslo 0x86 (v desítkové 134). V datové části žádné další parametry nejsou a poslední bajt s kontrolním součtem má šestnáctkovou hodnotu 0x79, protože platí:
(0xFF – (0x01 + 0x86)) + 0x01 = 0x79
V Arduinu by kód k odeslání této instrukce vypadal následovně:
// Odesli 9B instrukci s zadosti o koncentraci CO2
uint8_t prikaz[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
co2_senzor.write(prikaz, sizeof(prikaz));
Krátce na to čidlo odešle devět bajtů dlouhou odpověď, která by mohla vypadat v noční ložnici třeba takto:

První bajt 0xFF je opět totožný, na druhé pozici je ale potvrzení povelu 0x86 (přečtení koncentrace). Když tedy bude mít odpověď v úvodu právě tyto dva bajty, víme, že se jedná o zprávu s koncentrací. Ta je rozložená do dalších dvou bajtů a za nimi jsou opět samé nulové bajty a jako poslední bajt kontrolní součet, jehož opětovným výpočtem si můžeme (ale nemusíme) ověřit, jestli se data po cestě třeba nepoškodila.
Koncentrace je v tomto případě rozložená do dvou bajtů, tedy samostatných osmibitových čísel, s hodnotami 0x05 a 0x1A, které potřebujeme složit do jednoho 16bitového čísla 0x051A (1 306 ppm). Můžeme k tomu použít operátory pro práci s jednotlivými bity, kterým jsme se věnovali už v jednom z předchozích dílů našeho seriálu o programování:
uint8_t bajt2 = 0x05;
uint8_t bajt3 = 0x1A;
// Promenna co2 bude mit po tomto vypoctu hodnotu 1306
uint16_t co2 = (bajt2 << 8) ^ bajt3;
Do šestnáctibitové proměnné co2 nejprve zkopírujeme první osmibitové číslo a operátorem << jej posuneme o osm pozic doleva. Na uvolněné místo pak operátorem ^ vložíme druhé osmibitové číslo.
Pokud jsou však pro vás tyto operátory španělskou vesnicí, můžete použít i normální matematiku a výpočet zmíněný i v oficiální dokumentaci:
uint16_t co2 = (bajt2 * 256) + bajt3;
Výsledek bude naprosto stejný, namísto pohybů jednotlivých bitů totiž s bajty pracujeme jako s čísly:
uint16_t co2 = (5 * 256) + 26;
Celý kód s příkazem k odeslání aktuální koncentrace a jejím přečtením bude vypadat následovně:
// Odesli 9B instrukci s zadosti o koncentraci CO2
uint8_t prikaz[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
co2_senzor.write(prikaz, sizeof(prikaz));
// Precti 9B odpoved ze seriove linky
uint8_t odpoved[9];
co2_senzor.readBytes(odpoved, sizeof(odpoved));
// Pokud je 0. bajt roven 0xFF a 1. bajt roven 0x86, pokracuj
if ((odpoved[0] == 0xFF) && (odpoved[1] == 0x86)) {
// Spocitej kontrolni soucet
uint8_t ks = 0;
for (uint8_t i = 1; i < 8; i++) {
ks += odpoved[i];
}
ks = (255 - ks) + 1;
// Pokud kontrolni soucet odpovida 8. bajtu,
// zprava je v poradku
if (odpoved[8] == ks) {
// Spoj 2. a 3. bajt dohromady a ziskej koncentraci
uint16_t co2 = (odpoved[2] << 8) ^ odpoved[3];
}
}
Automatická kalibrace
Jak už jsem napsal výše, MH-Z19B je oproti svému předchůdci vybavený systémem automatické kalibrace ABC. Kalibrace? Ano, zatímco čidla teploty, vlhkosti nebo luxmetry, se kterými jsme si v našem seriálu doposud hráli, jsou kalibrované od výroby a tato kalibrace je v čase stálá, u měření CO2 pomocí technologie NDIR je to mnohem složitější.
Stručně řečeno, čidlo se čas od času potřebuje dozvědět, jak vypadá bazální hodnota – tedy přírodní koncentrace 400 ppm. U starého modelu MH-Z19 jste tedy museli občas (no vlastně poměrně často) otevřít okno, pořádně vyvětrat, nebo umístit čidlo na půl hodiny na balkon, a odeslat příkaz ke kalibraci. MH-Z19 si pak řekl: „Aha, to, co jsem právě změřil, je 400 ppm“ a od této hodnoty mohl odvíjet další výpočty.
Automatická kalibrace by měla být od výroby zapnutá. Ručně ji můžete aktivovat/deaktivovat těmito instrukcemi:

Automatická kalibrace funguje tak, že si čidlo tuto bazální hladinu 400 ppm nastavuje samo z naměřených dat v posledních několika desítkách hodin.
Je to logické, výrobce totiž předpokládá, že si čas od času pořádně vyvětráte, čidlo tento pokles svým průběžným měřením zaznamená a nastaví jako bazální hladinu. Po prvním připojení čidla je tedy dobré vyvětrat, aby si mohlo vytvořit novou kalibraci. V opačném případě bude používat tu z výroby, která nemusí odpovídat místním podmínkám.
Přesnost čidla se zároveň díky této průběžné kalibraci může v čase měnit a zlepšovat, takže pokud se zpočátku leknete podivných hodnot, nemusí to být hned chyba. Jen dejte čidlu pár dnů čas.
Na stranu druhou, ze stejného důvodu je automatická kalibrace naprosto nevhodná pro použití v dlouhodobě nevětraných/nevětratelných prostorách. Jednoduše proto, že nejnižší změřená hodnota možná nikdy nedosáhne přírodních 400 ppm, a tak se na ni čip nedokáže ani zkalibrovat.
Ruční kalibrace
Pokud automatika selže, můžete čidlo ke kalibraci přinutit. Opět je tu primitivní cesta, kdy stačí na pinu HD nejméně na 7 sekund nastavit logickou nulu, ale zároveň ta chytřejší skrze instrukci odeslanou přes sériovou linku. Vypadá takto:

Výrobce udává, že před ručně vyvolanou kalibrací by měl senzor pracovat alespoň dvacet minut v podmínkách blízkých 400 ppm, protože okolní vzduch musí proniknout skrze filtry do komory čidla, což může pár minut trvat.
Další praktické instrukce
Ačkoliv oficiální i neoficiální dokumentace skýtá hromadu dalších možných instrukcí, zmíním z těch (možná) praktických ještě změnu měřícího rozsahu. Čidlo totiž nemusí měřit jen v rozsahu 0-5000 ppm, ale třeba jen do 2 000 ppm.

Jak odeslat změřená data na Google Drive
Tak, čidlo už ovládáme, čili zbývá jediná věc. Změřenou hodnotu musíme odeslat na web, přičemž já si oblíbil Google Drive a jeho tabulkový dokument. Jednoduše řečeno, každých pět minut se v tabulkovém dokumentu automaticky přidá nový řádek s časem a hodnotou CO2 v ppm. Jelikož webový tabulkový procesor funguje podobně jako Excel, můžete si nad změřenými hodnotami kreslit grafy, zpracovávat statistiky atp.

Mějme jednoho Čížka, jednu redakci a jednu sobotu, během které se snaží vydýchat všechen čerstvý vzduch. Je to poznat, po několika hodinách bez větrání totiž koncentrace CO2 dosáhla bezmála 1 000 ppm
S tabulkovým dokumentem se spojíme skrze Google Apps Script dostupný ne veřejné adrese (kterou však budeme znát jen my). K této adrese přidáme běžný URL parametr, třeba ?co2=XXX, a když pak čip ESP8266 celou tuto adresu i s parametrem, do kterého vložíme aktuální hodnotu koncentrace, zavolá, skript kdesi v útrobách datového centra Googlu hodnotu konečně uloží do tabulkového dokumentu.


Příprava Apps Scriptu (vytvoříte jej ve svém Google Drive v nabídce Přidat) a jeho zprovoznění jako webové aplikace dostupné na veřejné WWW adrese, se kterou se spojí náš čip ESP8266. Apps Script bude ukládat hodnoty do tabulkového dokumentu, který určíme podle jeho identifikátoru (část WWW adresy). Skript musíme ještě autorizovat (třeba klepnutím na šipku Spustit a potvrdit jeho práva).
ESP8266 a HTTP klient
Jednoduché jako facka. Tedy až na jednu drobnost. Váš veřejný Apps Script je z bezpečnostních důvodů dostupný výhradně na šifrovaném protokolu HTTPS. To je dnes už pochopitelně standard na většině webových serverů, ale drobný čip ESP8266 s maličkou pamětí nemá prostředky k tomu, aby si držel jako velký operační systém hromadu kořenových šifrovacích certifikátů. Namísto toho se tedy používá tzv. fingerprint.
Ale pěkně popořadě. Když budete chtít zavolat nějakou adresu a získat její odpověď (HTML kód, JSON, cokoliv), stačí v případě čipu ESP8266 použít vestavěnou knihovnu ESP8266HttpClient (pod odkazem najdete GitHub s příklady).
Základní kód pro přečtení webové adresy a stažení obsahu do objektu Arduino String by mohl vypadat takto:
HTTPClient http;
http.begin("http://www.nejakyweb.cz");
int http_kod = http.GET();
if(http_kod == HTTP_CODE_OK){
// V data je nyni obsah, ktery vraci adresa www.nejakyweb.cz
// Treba HTML kod stranky.
String data = http.getString();
}
Podstatné je nicméně to, že stahujeme (třeba) HTML kód stránky, která běží na protokolu HTTP a nikoliv šifrovaném HTTPS. S ním se takto jednoduše spojit nemůžeme.
Jak se spojit s HTTP serverem šifrovaně
Kdybychom komunikovali s webem na HTTPS, musíme použít na začátku příkaz:
http.begin("https://nejakyweb.cz", "7a9cf4db40d3625a6e21bc5ccc66c83ea1455938");
Druhý parametr je v tomto případě onen fingerprint – jedinečný kryptografický otisk – konkrétního bezpečnostního certifikátu, který zjistíte po jeho otevření na kartě Podrobnosti, anebo třeba pomocí webového vyhledávače na webu grc.com/fingerprints.htm (fingerprint můžete napsat i ve formátu XX XX, nebo XX:XX apod.). Zbytek kódu pak bude totožný.


Získání fingerprintu certifikátu z prohlížeče
Bohužel, ani toto není úplně trvalé řešení. Stačí, aby se na cílovém webovém serveru změnil certifikát a změní se i jeho fingerprint a váš kód přestane fungovat. Budete jej tedy muset průběžně měnit. Nicméně ani to nemusí stačit.
Jak se spojit s naším Apps Scriptem
Některé servery mohou používat více certifikátů, případně některé pokročilejší, které nemusí knihovny Arduina pro ESP8266 vůbec podporovat, takže i když fingerprint dohledáte, volání http.begin selže s tím, že se nepodařilo otevřít šifrované spojení. Stejně dopadnete i v případě pokusu komunikace se servery Googlu.
Namísto funkce Esp8266HttpClient si tedy napíšeme vlastní rutinu založenou na další vestavěné a nižší knihovně WiFiClientSecure. V podstatě uděláme to samé, co knihovna ESP8266HttpClient, ovšem v našem případě odstraníme kontrolu platnosti certifikátu pomocí fingerprintu. Náš kód vychází z příkladu pro knihovnu WiFiClientSecure, který najdete na oficiálním GitHubu.
bool odesliKoncentraci(uint16_t co2){
const char* https_domena = "script.google.com";
String https_url = "/macros/s/xxxxxxxxxxxxxxxx/exec?co2=" + String(co2);
const int https_port = 443;
WiFiClientSecure klient;
if (!klient.connect(https_domena, https_port)) {
Serial.println("Nelze se spojit");
// Pokud nelze navazat spojeni, vrat false
return false;
}
/* Ted by prisla rada na kontrolu duveryhodnosti certifikatu
pomoci fingerprintu. Tuto cast komunikace ale ignorujeme.
Snizime zabezpeceni, ale bude to fungovat.
*/
// HTTP GET prikaz s adresou skriptu a hodnotou CO2
klient.print(String("GET ") + https_url + " HTTP/1.1\r\n" +
"Host: " + String(https_domena) + "\r\n" +
"User-Agent: CO2SenzorZBrna\r\n" +
"Connection: close\r\n\r\n");
// Pokud server odpovi 200 OK, vse probehlo v poradku
// Zahod zbytek a vrat true
while (klient.connected()) {
String line = klient.readStringUntil('\n');
if (line.indexOf("200 OK")) {
klient.flush();
return true;
}
}
// V opacnem pripade vrat false
return false;
}
V praxi tedy dojde k tomu, že se naše destička skrze Wi-Fi spojí se servery Googlu, otevře se šifrované spojení, ale neprovede se kontrola důvěryhodnosti certifikátu. Tímto zákrokem tedy snížíme zabezpečení a principiálně se jedná o designovou chybu, ale je to hack, jak docílit toho, abychom se dokázali napřímo spojit se serverem Google Apps Script.
(Pokud zkušení čtenáři najdou lepší techniku, rád si ji přečtu v komentářích, anebo mně napište e-mail.)
A to je všechno! Teď už jen celý kód
A jsme u konce! Pokud vše proběhne, jak má, každých pět minut se v tabulce na Google Drive objeví nová hodnota koncentrace včetně času měření. Dalšími úpravami bychom mohli docílit třeba toho, že se budou v tabulce automaticky mazat údaje starší několik dnů, protože webový tabulkový procesor Googlu pochopitelně není ideální řešení pro ukládání většího množství dat. Není to klasická a k tomu určená databáze, která pojme desítky tisíc měření třeba po ročním provozu.

Výpis informací do sériové linky PC během chodu programu
Na závěr nesmí chybět kompletní kód. Nejprve kód firmwaru pro desku NodeMCU s čipem ESp8266, poté kód pro Google Apps, který budete muset ještě autorizovat a nastavit, aby byl veřejně dostupný z internetu.
Kód hlavního programu:
// https://github.com/plerup/espsoftwareserial
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
const char* wifi_ap = "MalyKloboucek";
const char* wifi_heslo = "mameradikubucizka";
// Priprava softwarové seriove linky
// D5 -> TX na MH-Z19B
// D6 -> RX na MH-Z19B
SoftwareSerial co2_senzor(D5, D6);
// Jen ukazka, v praxi rucni kalibraci dnes volat nebudu
void kalibrace() {
uint8_t prikaz[9] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78};
co2_senzor.write(prikaz, sizeof(prikaz));
}
// Funkce pro zmereni koncentrace CO2
int16_t zmeritKoncentraci() {
// Odesli 9B instrukci s zadosti o koncentraci CO2
uint8_t prikaz[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
co2_senzor.write(prikaz, sizeof(prikaz));
// Precti 9B odpoved ze seriove linky
uint8_t odpoved[9];
co2_senzor.readBytes(odpoved, sizeof(odpoved));
// Pokud je 0. bajt roven 0xFF a 1. bajt roven 0x86, pokracuj
if ((odpoved[0] == 0xFF) && (odpoved[1] == 0x86)) {
// Spocitej kontrolni soucet
uint8_t ks = 0;
for (uint8_t i = 1; i < 8; i++) {
ks += odpoved[i];
}
ks = (255 - ks) + 1;
// Pokud kontrolni soucet odpovida 8. bajtu,
// zprava je v poradku, takze spoj dva bajty
// a konecne ziskej koncentraci
if (odpoved[8] == ks) {
uint16_t ppm = (odpoved[2] << 8) ^ odpoved[3];
return ppm;
}
else {
return -1;
}
}
else {
return -1;
}
}
// Funkce pro odeslani koncentrace do tabulky na Google Drive
bool odeslatNaGoogleDrive(uint16_t co2) {
const char* https_domena = "script.google.com";
String https_url = "/macros/s/AKfycbxqdsdfdfsfgedgrgergerhehrhhp4xHZmnudtBzg/exec?co2=" + String(co2);
const int https_port = 443;
WiFiClientSecure klient;
if (!klient.connect(https_domena, https_port)) {
Serial.println("Nelze se spojit");
// Pokud nelze navazat spojeni, vrat false
return false;
}
/* Ted by prisla rada na kontrolu duveryhodnosti certifikatu
pomoci fingerprintu. Tuto cast komunikace ale ignorujeme.
Snizime zabezpeceni, ale bude to fungovat.
*/
// HTTP GET prikaz s adresou skriptu a hodnotou CO2
klient.print(String("GET ") + https_url + " HTTP/1.1\r\n" +
"Host: " + String(https_domena) + "\r\n" +
"User-Agent: CO2SenzorZBrna\r\n" +
"Connection: close\r\n\r\n");
// Pokud server odpovi 200 OK, vse probehlo v poradku
// Zahod zbytek a vrat true
while (klient.connected()) {
String line = klient.readStringUntil('\n');
if (line.indexOf("200 OK")) {
klient.flush();
return true;
}
}
// V opacnem pripade vrat false
return false;
}
// Hlavni funkce, ktera se spusti po pripojeni desky k napajeni
void setup() {
// Nastartuj seriovou linku do PC
Serial.begin(115200);
// Nastartuj seriovou linku do MH-Z19B
co2_senzor.begin(9600);
// Pripoj se k Wi-Fi
WiFi.mode(WIFI_OFF);
WiFi.mode(WIFI_STA);
WiFi.begin(wifi_ap, wifi_heslo);
Serial.print("\r\nPripojuji se k Wi-Fi ");
Serial.print(wifi_ap);
uint32_t start = millis();
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
// Pokud se ani po 30 sekundach nemuzes pripojit,
// restartuj cip a zacni znovu
if ((millis() - start) > 30e3) {
// Pri prvnim spusteni po flashnuti noveho firmwaru nebude restart fungovat
// Dale uz ano
ESP.restart();
}
delay(500);
}
// Pro kontrolu napis IP adresu, kterou jsi dostal od DHCP
Serial.println(" OK");
Serial.println(WiFi.localIP());
// Zmer koncentraci CO2
int16_t co2 = zmeritKoncentraci();
if (co2 == -1) {
Serial.println("Ty tragikomiku, mas to spravne zapojeny? Nelze precist koncentraci :-(");
}
else {
Serial.printf("Koncentrace CO2: %d ppm\r\n", co2);
// Uloz udaj skrze Apps Script do tabulky na Google Drive
if (odeslatNaGoogleDrive(co2)) {
Serial.println("Uspesne odeslano na Google Drive!");
}
else {
Serial.println("Jsi disfunkcni jelito a mas nekde chybu, protoze to nejde odeslat na Google Drive :-(");
}
}
// Uspani cipu na cca pet minut (300 000 000 us)
// Je treba propojit piny RST a WAKE/D0,
// jinak se cip neprobudi. Piny kabelem propojte
// az po nahrani programu do cipu!
ESP.deepSleep(300e6);
}
// Funkce loop je prazdna, cip totiz usina
void loop() {
;
}
Kód Apps Scriptu:
/* SKRIPT PŘISTUPUJE K TABULKOVÉMU DOKUMENTU
A POTŘEBUJE K TOMU PRÁVA. TA MU UDĚLÍTE TŘEBA TAK,
ŽE NEJPRVE SKRIPT SPUSTITE PŘIMO Z EDITORU „NA PRÁZDNO“
GOOGLE VÁS POTÉ VYZVE, ABYSTE MU UDĚLILI PRÁVA
*/
// Vestavěná funkce doGet
// Zpracuje se v případě HTTP GET požadavku,
// pokud je skript publikovaný jako webová aplikace
function doGet(e){
// Převeď URL parametr co2 na číslo (www-adresa-skriptu?co2=XXX)
var co2 = parseInt(e.parameter.co2);
// Pokud se podařil převod na číslo, pokračuj
if(!isNaN(co2)){
// Zjisti aktuální středoevropský čas/datum ve formátu HH:mm:ss dd.MM. yyyy
var cas = Utilities.formatDate(new Date(), "Europe/Prague", "HH:mm:ss dd.MM. yyyy");
// Otevři tabulkový dokument podle jeho ID
// ID získáte třeba tak, že otevřete dokument v prohlížeči – je to část jeho URL
var doc = SpreadsheetApp.openById("XXXXXXXXXXX");
// Získej první list tabulkového dokumnetu
var list = doc.getSheets()[0];
// Přidej nový řádek
// V první buňce bude čas, ve druhé hodnota CO2
list.appendRow([cas, co2]);
}
}