Hlavní navigace

Software inteligentního internetového termostatu

10. 6. 2015
Doba čtení: 14 minut

Sdílet

Dnes se podíváme na oživení minule popsaného hardware softwarem. Nejdřív probereme možnosti, jak a v čem pro Arduino i jiné mikrokontroléry programovat, ukážeme si, že Vim nikdy není mimo hru, a pak se už vrhneme na konkrétní dílky softwarové stavebnice, ze kterých je termostat poskládán jako z kostiček Lega.

Úvodem softwarového dílu mi dovolte dvě poznámky. Jelikož jsme v minulém díle postavili termostat na Arduino hardware (byť klonovaném), obě poznámky se budou týkat programování na Arduinu, v Arduino IDE (integrovaném vývojovém prostředí).

První věc, kterou bych rád zdůraznil, je, že stejně jako není na místě posmívat/ušklíbat se použití Arduina místo AVR mikrokontroléru v hotovém řešení, není ani na místě posmívat se či ohrnovat nos nad projekty naprogramovanými v Arduino IDE „v tom jeho podivném, zjednodušenému Céčku podobném jazyce, kterému se říká Wiring“. Pravda je, že tak jako Arduino není nic jiného než AVR mikrokontrolér, krystal, pár kondenzátorů a případný regulátor napájecího napětí (čili totéž, co použijí „profíci“ plivající na Arduino), tak ani programovací jazyk v Arduino IDE není žádné zjednodušené C pro umělce, ale normální plnotučné C++, jen s automaticky skrytou funkcí main(), automatickým dopředným deklarováním vašich funkcí a proměnných a automatickému vložení hlavičkového souboru s Arduino API. Čili pokud třeba v Arduino IDE naprogramujete následující kód (jehož smyslem je pouze přidat třetí funkci ke dvěma výchozím, abych mohl něco předvést, nic víc v tom nehledejte):

void setup()
{
    pinMode(13, OUTPUT);
}

bool sviti;
void loop()
{
    sviti = svetlo(sviti);
}

bool svetlo(bool stav)
{
    digitalWrite(13, stav);
    return !stav;
}

Tak Arduino IDE v momentě, kdy kliknete na tlačítko „Verify“/„Ověřit“, z toho na pozadí (aniž to uvidíte) udělá pouhým připojením pár řádků na začátek přibližně následující soubor, který podstrčí klasickému GCC a ten ho samozřejmě bez problémů zkompiluje, neboť jde o normální Céčkový soubor/program:

#include <Arduino.h>

// automaticky vygenerované dopředné deklarace
void setup();
void loop();
bool svetlo(bool stav);

// automaticky doplněná prefabrikovaná funkce main(), která nedělá nic jiného než že jednou zavolá setup() a pak už furt volá loop()
int main()
{
    setup();
    while(1) {
        loop();
        // případně nějaké vnitřní hrátky se sériovým portem, které nás nemusejí zajímat
    }
    return 0; // nikdy sem nepřijde
}

// dál následuje náš nijak nemodifikovaný zdrojový text, který je už ale teď normálně zkompilovatelný v jakémkoliv C kompileru
void setup()
{
    pinMode(13, OUTPUT);
}

bool sviti;
void loop()
{
    sviti = svetlo(sviti);
}

bool svetlo(bool stav)
{
    digitalWrite(13, stav);
    return !stav;
}

Omlouvám se těm, kteří tento fakt důvěrně znají, ale bohužel i 10 let po založení platformy Arduino stále narážím na další a další lidi, kteří se budou do krve hádat, že Arduino se neprogramuje v Céčku/C++, ale v <doplňte si sami nelichotivá slova>. Přál bych si, aby k programování Arduina lidé přistupovali bez zbytečných předsudků…

Druhá má úvodní poznámka se týká samotného Arduino IDE. Toto rádoby integrované vývojové prostředí je pro snad každého programátora přicházejícího prakticky z jakéhokoliv jiného vývojového prostředí něco mezi studenou sprchou a fackou. Lidé kritizují například delší první start (je to napsáno v Javě, takže…), doslova primitivní editor zdrojového textu (který ale jde nahradit externím), chybějící prakticky všechny vymoženosti z jiných, dnes běžných IDE atd. Rád bych zde nejdříve obhájil jeho smysl, a pak ho hned opustil. Tak tedy, díky tomu, že je napsán v Javě, je přenositelný. Zas jednou jeden projekt, který vypadá a běží stejně na MS Windows, Apple OS X a GNU/Linuxu. Nikdo není nucen kvůli programování Arduin měnit svou oblíbenou platformu. A to je pro začátečníka důležitá výhoda (má dost starostí s tím popasovat se s novým HW, a ne tak ještě, aby musel měnit svůj OS).

Dále, pro uživatele platforem bez balíčkovacího systému automaticky řešícího závislosti (ano, mám na mysli pořád ještě majoritní desktopový OS) je Arduino IDE úžasně jednoduchý způsob, jak si doma začátečník naráz nainstaluje celý AVR-GCC toolchain, avrdude a další věci potřebné k vývoji SW pro mikrokontroléry a nahrávání hotového firmware/software do těch malých potvůrek. Prostě si stáhne Arduino IDE a ono to hned jede (většinou). A jakmile dozraje k přehození svého desktopového operačního systému za svobodný, není Arduino IDE překážkou změny, neboť na linuxu vypadá stejně jako na Windows, a funguje častokrát ještě lépe.

Pro ostřílené linuxáky však Arduino IDE nepřináší zásadní výhody. Kolikrát mnohem většího komfortu, zvlášť pokud jste přátelé příkazové řádky, můžete dosáhnout jednoduše následujícím příkazem:

# apt-get install arduino-mk

Čtu seznamy balíků… Hotovo
Vytváří se strom závislostí
Čtu stavové informace… Hotovo
Následující extra balíky budou instalovány:
  arduino-core avr-libc avrdude binutils binutils-avr cpp cpp-4.7 gcc gcc-4.7
  gcc-avr libc-dev-bin libc6-dev libconfig-yaml-perl libftdi1 libgmp10 libitm1
  libmpc2 libmpfr4 libquadmath0 libyaml-perl linux-libc-dev manpages-dev
  perl-doc
Navrhované balíky:
  avrdude-doc binutils-doc cpp-doc gcc-4.7-locales gcc-multilib make autoconf
  automake1.9 libtool flex bison gdb gcc-doc gcc-4.7-multilib
  libmudflap0-4.7-dev gcc-4.7-doc libgcc1-dbg libgomp1-dbg libitm1-dbg
  libquadmath0-dbg libmudflap0-dbg libcloog-ppl0 libppl-c2 libppl7
  binutils-gold task-c-devel gcc-4.2 glibc-doc libyaml-shell-perl groff
Následující NOVÉ balíky budou nainstalovány:
  arduino-core arduino-mk avr-libc avrdude binutils binutils-avr cpp cpp-4.7
  gcc gcc-4.7 gcc-avr libc-dev-bin libc6-dev libconfig-yaml-perl libftdi1
  libgmp10 libitm1 libmpc2 libmpfr4 libquadmath0 libyaml-perl linux-libc-dev
  manpages-dev perl-doc
0 aktualizováno, 24 nově instalováno, 0 k odstranění a 0 neaktualizováno.
Potřebuji stáhnout 58,9 MB archivů.
Po této operaci bude na disku použito dalších 163 MB.
Chcete pokračovat [Y/n]?

Arduino-mk je balíček, který svými závislostmi napoví APT, aby postahoval a nainstaloval vše potřebné pro vývoj na Arduinu, plus doplní do systému Arduino knihovny, příklady a Makefile s další podporou pro kompilování software pro Arduino. Stačí pak nasymlinkovat dodaný Makefile do adresáře projektu:

$ cd ~/arduino/Termostat
$ ln -s /usr/share/arduino/Arduino.mk Makefile

a rázem člověk může buďto zcela opustit Arduino IDE a přivítat starý dobrý ‚make‘ (a samozřejmě editovat kód ve ‚vim‘, na což pomůže nastavení C++ syntaxe pro zvýrazňování/obarvování i pro soubory s koncovkou .ino), anebo může dokonce oba přístupy libovolně kombinovat. K tomuto se občas z mně nepochopitelných důvodů uchyluji sám – chvíli programuji v Arduino IDE a chvíli ve vimu a překládám to buďto v Arduino IDE, nebo v příkazové řádce pomocí ‚make‘. Je to opravdu zcela transparentní a můžu přecházet oběma směry bez omezení.

Poznámka k tomu symlinkování Makefile: v novějších verzích balíčku Arduino-mk se cosi změnilo, takže ten symlink už takto nefunguje. Je potřeba snížit se k tomu, že člověk udělá jednořádkový Makefile soubor v adresáři svého Arduino projektu a do souboru umístí řádek s include /usr/share/arduino/Arduino.mk  – a je to. Kromě tohoto v Makefile můžeme předefinovat celou řadu parametrů, pro které má jinak Arduino-mk nějaké výchozí hodnoty, které však nemusí pokaždé vyhovovat (vizte níže make help_vars).

Makefile z balíčku arduino-mk neumí jen kompilovat, ale nahradí Arduino IDE i v celé řadě dalších činností, jak se můžete přesvědčit po zadání následujícího příkazu:

$ make help
Available targets:
  make                  - no upload
  make upload           - upload
  make clean            - remove all our dependencies
  make depends          - update dependencies
  make reset            - reset the Arduino by tickling DTR on the serial port
  make raw_upload       - upload without first resetting
  make show_boards      - list all the boards defined in boards.txt
  make monitor          - connect to the Arduino's serial port
  make size             - show the size of the compiled output (relative to
                          resources, if you have a patched avr-size)
  make disasm           - generate a .lss file in build-cli that contains
                          disassembly of the compiled file interspersed
                          with your original source code.
  make verify_size      - Verify that the size of the final file is less than
                          the capacity of the micro controller.
  make eeprom           - upload the eep file
  make raw_eeprom       - upload the eep file without first resetting
  make burn_bootloader  - burn bootloader and fuses
  make set_fuses        - set fuses without burning bootloader
  make help_vars        - print all variables that can be overridden
  make help             - show this help
Please refer to /usr/share/arduino/Arduino.mk for more details.

Ještě jedno dobré slovo mám k Arduino IDE. To od historických verzí 018–022 a dlouhou dobu aktuální řady 1.0.x urazilo poměrně velký kus cesty a především v posledních dnech, pod tlakem z rozpadající se značky „Arduino“ (kvůli souboji Arduino LLCArduino SRL), se významně otevřelo třetím stranám! Od verze 1.6.4 nejenom, že přestalo prudit, pokud člověk neměl „echt originál Arduino desku“, ale navíc – a to může být velmi zajímavé pro fandy ARMů či RISCů místo AVR – je nyní mimořádně jednoduché včlenit do Arduino IDE podporu i úplně jiného než Atmel hardware. Dokonce sami autoři Arduino IDE povzbuzují třetí strany, aby vyráběly jednoduché JSON definiční soubory, které umožní pod Arduino IDE programovat úplně nové mikroprocesory a desky. Díky tomu je možné například využít veškeré znalosti Arduino knihoven při programování např. ARM mikrokontrolérů, anebo dokonce i hvězdy levného HW ESP8266 WiFi modulu s RISCovým srdcem. Ano, polopatický pinMode() a digitalWrite() teď fungují na kde čem, díky čemuž jdou přenášet celé projekty prý téměř beze změn z AVR na úplně jiné procesory. Paráda, což? Tudíž ani nemám výčitek, že v tomto seriálu je termostat postaven na Arduinu, neboť díky výše uvedeným změnám bude případně v budoucnu velmi jednoduché přenést termostat na ARM či jinou platformu, v podstatě pouhým překompilováním a to dokonce ve stejném IDE.

Po tomto delším ale důležitém úvodníku můžeme směle začít programovat digitální síťový termostat v klasickém Arduino IDE stylu (do funkcí setup() a loop()), neboť případný přechod ať už do čistého C anebo dokonce na jinou platformu je jednoduchý a vysvětlen výše. Nejdříve využijeme jedné z hlavních výhod Arduino platformy, a tou je podpora téměř všeho myslitelného přídavného hardware díky knihovnám různorodé kvality dostupným všemožně na Internetu (dnes už většinou na GitHubu). V tomto projektu se knihoven sešla celá plejáda:

  • knihovna DallasTemperature pro komunikaci s čidly DS18B20
  • knihovna OneWire pro komunikaci po 1-Wire sběrnici
  • knihovna Adafruit_GFX pro vykreslování textů a grafiky na (obecný) displej
  • nízkoúrovňová knihovna pro displej s řadičem ILI9225B (v podstatě „ovladač grafiky“ pro Adafruit_GFX)
  • knihovna TouchScreen pro dotykovou vrstvu, abych mohl snímat dotyky prstů (resp. nehtů) na obrazovce
  • knihovna DHTlib, aby termostat uměl číst teplotu a vlhkost z vnitřního čidla DHT11
  • knihovna Time, aby měl termostat datum a čas, a hlavně uměl spočítat den v týdnu
  • knihovna Tasker, aby termostat mohl dělat víc věcí naráz přehledně
  • standardní Arduino knihovna „Ethernet“, aby termostat mohl komunikovat v LAN a dál
  • standardní Arduino knihovna „SPI“, aby knihovna „Ethernet“ uměla komunikovat přes SPI
  • standardní Arduino knihovna „EEPROM“, aby termostat uměl ukládat informace do EEPROM

Je zřejmé, že co šlo někde najít, jsem vzal a neztrácel čas znovuvynalézáním kola – děkuji všem autorům software uvolněného pod některou ze svobodných licencí. Díky nim je možné tvořit rychle nové věci, super! Vlastně jsem musel naprogramovat akorát lowlevel rutiny pro řadič ILI9225B (resp. opravit a odladit zdroják dostupný mimo Arduino svět). Tomu se lehce vyhnete koupí displeje, který má řadič s již dostupnou Arduino knihovnou. Většinou totiž člověk najde vhodný HW modul i s hotovou knihovnou na Adafruit Industries anebo na SparkFun Electronics, a pak už záleží jen na něm, jestli to koupí přímo u nich, anebo se podívá po amerických, českých či asijských aukčních síních, které dnes slouží spíš jako prodejny malým překupníkům s levným HW (jenž, má-li stejný řadič či čip, bude nejspíš fungovat stejně dobře s danou knihovnou jako „originál“ z USA).

Z dalších mých úprav bych zmínil hack pro SELČ v knihovně Time (který jsem ještě nikde nestihl publikovat). Také jsem doplnil a opravil nějaké drobnosti v knihovně pro čidla DS18B20 (mám to na mém githubu, ale není tam ještě up-to-date verze) a naprogramoval knihovnu Tasker, o které povím pár slov později.

Každou z knihoven je potřeba rozbalit do adresáře ~/arduino/libraries/, přičemž knihovny stažené z GitHubu je ještě nutné přejmenovat, aby v názvu adresáře neobsahovaly „-Master“. Každá knihovna má u sebe příklady použití, podle kterých jde bleskově rozfungovat. Stačí v Arduino IDE v menu zajít do SouborPříklady → název knihovny a tam už se můžete dívat, jak se která knihovna používá. Naprogramování celého termostatu (či jiného Arduino projektu) se pak z větší části sestává ze sbírání kousků z jednotlivých příkladů a slepení do jednoho vašeho programu. Následně stačí přidat trochu vlastní logiky, u displeje ještě trochu vlastní grafiky a už to jede.

Teď je tu ještě otázka, jak termostat vlastně programovat poté, co bude přišroubován na zdi v obýváku (případně rovnou vestavěn ve zdi, nebo obecně na špatně přístupném místě). Asi by nebylo nikterak cool natahovat od něj k nejbližšímu počítači sériový kabel pokaždé, když člověk potřebuje poladit pixely na displeji či poslat na zkoušku novější verzi algoritmu pro řízení vytápění. Proto jsem pohledal a našel skvělou věc – možnost programovat Arduino na dálku přes ethernet. Stačí v Arduinu vyměnit bootloader za jiný, který po restartu procesoru očekává nový program nikoliv na sériovém portu, ale na ethernetovém rozhraní. Jsou dokonce dva různé přístupy k síťovému nahrávání nové verze programu – jeden je, že si Arduino vyhledá v LAN TFTP server a stáhne si odtamtud nový program, který si do sebe zapíše, zatímco druhý přístup pustí server přímo na Arduinu a potom člověk nahraje ze svého počítače do Arduina novou verzi programu – a to standardním nástrojem avrdude, takže klidně i přímo z Arduino IDE.

Zalíbila se mi ta druhá možnost, takže si od těch dob „vypaluji“ do všech Arduin, které budou mít u sebe ethernetový modul, bootloader s podporou W5100 ethernetu. Tento bootloader zabírá ve flash paměti 1 kB, takže pro náš vlastní program zbývá ještě 31 kB místa. Aby tento bootloader věděl, na jakých IP adresách má server pro nahrávání nové verze SW spustit, musí mít v EEPROM paměti na určitých adresách zadané MAC a IP adresy (serveru, masky a brány). Doma to řeším tak, že jsem si na routeru, který mi dělá DNS a DHCP server, natvrdo vyhradil některé IP adresy právě pro tato Arduina s ethernetem, a tyto vyhrazené adresy jim pak i zapisuju natvrdo do kódu programu. Předvedu:

void Ewrite(byte offset, byte value)
{
    if (EEPROM.read(offset) != value)
        EEPROM.write(offset, value);
}

void setup_ethernet_bootloader_address()
{
    // gateway IP
    Ewrite(0x10, 192);
    Ewrite(0x11, 168);
    Ewrite(0x12, 1);
    Ewrite(0x13, 1);

    // netmask
    Ewrite(0x14, 255);
    Ewrite(0x15, 255);
    Ewrite(0x16, 255);
    Ewrite(0x17, 0);

    // MAC
    Ewrite(0x18, 0xDE);
    Ewrite(0x19, 0xAD);
    Ewrite(0x1A, 0xBE);
    Ewrite(0x1B, 0xEF);
    Ewrite(0x1C, 0xFE);
    Ewrite(0x1D, 0xED);

    // Arduino IP
    Ewrite(0x1E, 192);
    Ewrite(0x1F, 168);
    Ewrite(0x20, 1);
    Ewrite(0x21, 100);
}

V kódu funkce setup() pak pokaždé volám i funkci setup_ethernet_bootloader_address(), abych měl v EEPROM vždy dobré hodnoty pro případný restart do módu, ve kterém bootloader spustí server a nechá si nahrát novou verzi software. Vím, že by bylo logičtější nemít IP adresy zadrátované v kódu, ale nahrát je až do EEPROM, ovšem bohužel zrovna tento bootloader v honbě za co nejmenší velikostí svého kódu podporu změny obsahu EEPROM přes avrdude nemá.

K tomu restartu Arduina do módu, kdy bootloader spustí server a čeká, je potřeba zavolat následující kód, který mazaně nastaví watchdog procesoru a pak se zasekne v nekonečné smyčce. Watchdog pak procesor během chvilky restartuje, což je přesně to, co potřebujeme:

#include <avr/wdt.h>
void reboot_to_ethernet_bootloader()
{
    Ewrite(0x22, 0x55); // set bootloader flag
    wdt_enable(WDTO_250MS);
    while(1);     // loop forever until watchdog resets us
}

Funkce reboot_to_ethernet_bootloader() je volána poté, co Arduino přijme příslušný pokyn přes servisní rozhraní na portu 8000, na kterém mi v termostatu poslouchá jeden primitivní „web server“. Druhý, normální webový server pak poslouchá na standardním portu 80. Toto je zrovna nějaká historická záležitost, kdy se mi nechtělo původně psát kompletní parsování parametrů v požadavku GET od prohlížeče, takže jsem si raději spustil druhý server, na kterém přijímám pouze servisní příkazy.

Taky mi tehdy přišlo bezpečnější tuto servisní funkci skrýt na jiném portu, aby mi doma termostat někdo nehackl, kdyby mě odposlouchával, jak se s s ním bavím na běžném http portu. Jakmile totiž termostat restartuje do bootloaderu, který spustí server pro nahrání nového software, je systém zranitelný, protože kdo první začne do termostatu kód nahrávat, ten vyhrává. Proto si tajné servisní rozhraní chráním stylem „Security through obscurity“ a věřím, že ta černá dodávka na ulici pod okny, která tam už třetí den jakoby náhodou stojí, nic nezjistí…

V tomto díle jsme si prosvištěli základy programování na Arduinu a vypsal jsem seznam všech softwarových komponent, ze kterých je můj termostat poskládán. Teď bych se mohl věnovat podrobněji kterékoliv z nich, nebo rovnou všem a tento seriál natáhnout až do žní, což by ale nikdo nevydržel. Proto se v příštím dílu podíváme heslovitě jen na to nejdůležitější (síťová komunikace, displej, limity) a seriál se pokusím se ctí uzavřít. Pokud by v dnešní diskusi převážil nějaký konkrétní dotaz, můžu se mu příště také pověnovat, ať po termostatickém seriálu nezůstane víc otázek než odpovědí.

root_podpora

Na závěr dnešního dílu ještě perlička: přestože jste na desce termostatu viděli připájené čidlo DHT11 pro měření vlhkosti, a také ještě jedno vnitřní čidlo DS18B20, které mělo sloužit jako záložní pro případ výpadku obou 1-Wire sítí termočidel, už v software ani v seriálu dál nebudou vystupovat. Ukázalo se totiž, že i přes maximální množství větracích otvorů v krabičce termostatu obě čidla ukazují minimálně o 3 až 5 stupňů vyšší teplotu než čidla v zásuvkách RJ45 nad podlahou. I naměřená vlhkost byla podezřele nízká (v porovnání s komerční meteostanicí).

Domnívám se, že za tím stojí teplo generované ethernetovým modulem, které krabičku vyhřeje a zkreslí naměřené hodnoty obou čidel k nepoužití. Druhá možnost je, že ve výšce 150 cm je skutečně o tolik tepleji než 10 cm nad podlahou. Kvůli tomuhle plánuji postavit výškový srovnávací teploměr s displejem a pěti termočidly rozmístěnými po 50 cm na tyči ne nepodobné berle mrazilce Dědy Mráze, a pak s tou tyčí procházet místnostmi doma i na návštěvách a měřit, jak má kdo to teplo výškově rozvrstvené. Ale to je zas další projekt čekající ve frontě podobných nápadů…

Byl pro vás článek přínosný?

Autor článku

Petr Stehlík vystudoval aplikovanou informatiku a pracuje jako vývojář webových aplikací a administrátor linuxových serverů. Provozuje vlastní server tvpc.cz.