V dnešním článku si ukážeme, jak pracovat s Ethernet shieldem, což je zajímavé rozšíření pro Arduino, které nám přináší nové možnosti interakce Arduina se sítí i internetem.
Ethernet Shield
Ethernet shield si (stejně jako celé Arduino) prošel poměrně dlouhým vývojem, proto se setkáme hned s několika jeho verzemi. My budeme pracovat s jeho nejnovější verzí Arduino Ethernet Rev3 WITH PoE.
Dominantním prvkem desky je RJ45 konektor pro připojení Ethernet kabelu. Mimo něj ale na shieldu nalezneme i slot na SD kartu, jejíž používání jsme si popsali v minulém dílu. Ovládání čipu (W5100) i SD karty probíhá přes SPI rozhraní. Rychlost síťové komunikace 10/100 MB není v dnešní době zrovna strhující, ale pro naše účely je zcela dostačující. Než shield připojíme k Arduinu, otočíme jej.
Na jeho spodní straně nalezneme nálepku s MAC adresou (unikátní identifikační číslo síťového zařízení). Tu si někam poznamenáme pro pozdější použití. Když máme MAC adresu zapsanou, můžeme shield připojit na Arduino (v našem případě Arduino Mega).
Funkce
Pro programování Ethernet shieldu se používá hned několik tříd. Třída Ethernet slouží k základnímu nastavení.
Název | Zápis | Funkce |
Ethernet.begin(mac) | Ethernet.begin(mac) Ethernet.begin(mac, ip) Ethernet.begin(mac, ip, dns) Ethernet.begin(mac, ip, dns, gateway) Ethernet.begin(mac, ip, dns, gateway, subnet) |
Slouží k zahájení komunikace shieldu s okolím (většinou router či switch). Vlevo vidíte použití různých parametrů. Nejčastěji se používají pouze mac a ip. Když parametr ip nepoužijeme, je IP adresa přidělena automaticky DHCP serverem. |
Ethernet.localIP() | – | Vrátí IP adresu shieldu. Tato funkce se nepoužívá, pokud IP adresu přiřazujeme manuálně, ale když ji necháme přes DHCP přidělit automaticky. |
Ethernet.maintain() | – | Pokud má shield přiřazenou adresu automaticky, může touto funkcí požádat o její obnovení. Přidělená adresa může být v závislosti na nastavení routeru stejná i nová. |
Jakousi pomocnou třídou je třída IPAddress. Ta slouží k uchování IP adresy.
Název | Zápis | Funkce |
IPAddress() | IPAddress jmeno(a,b,c,d) | Tato funkce uloží IP adresu. Zápis adresy ve tvaru a.b.c.d (např. 10.0.0.1) se jménem mojeIP se provede vytvořením objektu IPAddress mojeIP(a,b,c,d). |
Třída Server slouží k odesílání a přijímání dat mezi shieldem a připojenými klienty (programy na jiných zařízeních, které se připojují k serveru).
Název | Zápis | Funkce |
Server() | EthernetServer mujSvr = EthernetServer(port) | Vytvoří server, který naslouchá příchozím připojením na vybraném portu (80 pro HTTP, 23 pro telnet…). |
mujSvr.begin() | – | Spustí vybraný server. |
mujSvr.available() | EthernetClient client = server.available() | Vrátí objekt Client, který je připojen k našemu serveru a odesílá data ke čtení. |
mujSvr.write() | mujSvr.write(val)mujSvr.write(buf, len) | Pošle data všem klientům připojeným k serveru. Data mohou být typu char, byte, nebo pole těchto typů (buf je poté délka tohoto pole). |
mujSvr.print() | mujSvr.print(data)mujSvr.print(data, BASE) | Stejné jako .write(), jen data převádí na text. Parametr BASE může nabývat hodnot: BIN (dvojková soustava), OCT (osmičková soustava), DEC (dekadická soustava), HEX (šestnáctková soustava). |
mujSvr.println() | mujSvr.println(data)mujSvr.println(data, BASE) | Stejné jako .print(), jen na konec přidá zalomení řádku. |
Ke zpracování dat ze serveru slouží třída Client. Tato třída vytvoří ze shieldu klienta, který se může připojit k jiným serverům.
Název | Zápis | Funkce |
EthernetClient ja | – | Vytvoří klienta s názvem ja. |
ja | while(!ja){delay(1)} | Počká, dokud není klient připojen. |
ja.connected() | – | Vrátí true, pokud klient odesílá data (v dobu zpracování už nemusí být přítomen, ale musí být od něj přijaty data). |
ja.connect() | ja.connect(ip, port) ja.connect(URL, port) |
Připojí se k vybrané ip, nebo URL přes zadaný port. |
ja.write() | ja.write(val) ja.write(buf, len) |
Pošle data serveru, ke kterému je shield připojen. |
ja.print() | ja.print(data) ja.print(data, BASE) |
Pošle data serveru jako text. |
ja.println() | ja.println(data) ja.println(data, BASE) |
Pošle data serveru jako text končící zalomením řádku. |
ja.available() | – | Vrátí počet bytů přijatých klientem od serveru. |
ja.read() | – | Přečte a vrátí hodnotu přijatého bytu. |
ja.flush() | – | Vyprázdní frontu přijatých a nepřečtených dat. |
ja.stop() | – | Odpojí se od serveru. |
Poslední třída EthernetUDP slouží k přijímání a odesílání UDP zpráv. My se jí však nebudeme zabývat. Pro více informací navštivte dokumentaci.
Použití
Když už jsme si popsali všechny potřebné funkce, můžeme se pustit do programování.
Vytváříme server
Na úvod si naprogramujeme jednoduchý server. Než ale začneme, musíme pochopit, jak spolu komunikuje server s klientem. Komunikace zde probíhá na principu dotaz-odpověď. Tyto dotazy a odpovědi mají formát pouhého textu. Jakým způsobem musí být text zapsán, definuje HTTP protokol. Nejjednodušší bude si vše předvést na ukázce komunikace. Když se chce klient připojit na server, zašle mu požadavek přibližně v tomto tvaru:
GET / HTTP/1.1 Host: 10.0.0.15 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml, application/xml;q=0.9, image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36 Referer: http://10.0.0.15/ Accept-Encoding: gzip,deflate,sdch Accept-Language: cs,en;q=0.8,de;q=0.6,sk;q=0.4
Tato zpráva obsahuje vše potřebné pro server pro odeslání vodných dat klientovi. Obsahuje informaci o tom, s jakou verzí HTTP pracujeme, jaký je pro klienta přijatelný formát, o jakého klienta se jedná, jaké kódování používá a jaký jazyk očekává. Prázdný desátý řádek zde není z nepozornosti. Tímto způsobem se označuje, že je požadavek ukončen.
Server poté musí požadavek zpracovat a odeslat zpět odpověď v HTML tvaru s vhodnou HTTP hlavičkou. Hlavička obsahuje informace o verzi HTTP, o stavu připojení po ukončení přenosu, nebo o automatickém obnovování. Tyto informace nám nyní stačí. Existuje ale samozřejmě celá řada dalších HTTP příkazů, jejichž seznam nalezneme například na wikipedii. Hlavička odpovědi tedy vypadá takto (opět s volným řádkem).
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 1
Po hlavičce a volném řádku následuje kód stránky ve formátu HTML. Představme si nyní základní strukturu stránky.
<!DOCTYPE HTML> - říkáme prohlížeči, že pracujeme s HTML <html> <head> mezi značky head se píší základní informace o stránce (kódování, CSS styly...) <title>Titulek stránky </title> - zobrazí se v záložce </head> <body> sem patří obsah stránky (ten, který vidíme v prohlížeči) </body> </html>
Se všemi získanými informacemi už můžeme poskládat program jednoduchého serveru, který bude měnit barvu pozadí pomocí css stylů podle toho, jestli je nebo není stisknuto tlačítko připojené k Arduinu na pinu 45. Tlačítko budeme kontrolovat každou vteřinu. Mimo tlačítka budeme potřebovat ještě 10k resistor a několik vodičů.
V programu použijeme také jednoduché css stylování. My budeme chtít, aby pozadí celé stránky bylo zelené nebo červené. To se v html provede tak, že se k elementu <body> připojí style=“background: red/green“. Dvojité uvozovky by ale program Arduina mohly mást, proto se používá tzv. escapování, kdy se před vybraný znak dá zpětné lomítko. Ten se poté projeví až při zpracování prohlížečem. Výsledný element body tedy bude vypadat třeba takto: <body style=\“background: green\“>
#include <SPI.h> #include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7 }; IPAddress ip(10,0,0,15); //ip serveru je 10.0.0.15 EthernetServer mujSvr(80); //vytvoříme server na portu 80 void setup(){ Ethernet.begin(mac); mujSvr.begin(); //spustíme server pinMode(45, INPUT); } void loop(){ EthernetClient client = mujSvr.available(); if (client){ boolean prazdnyRadek = true; while (client.connected() && client.available()){ //dokud klient něco odesílá (HTTP požadavek) char c = client.read(); //přečti byte od klienta if(c == '\n' && prazdnyRadek){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println("Refresh: 1"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head>"); client.println("<title>Zkoumame HTML a HTTP</title>"); client.println("</head>"); if(digitalRead(45) == HIGH){ client.println("<body style=\"background:green\">"); } else{ client.println("<body style=\"background:red\">"); } client.println("</body>"); client.println("</html>"); } if(c == '\n'){ prazdnyRadek = true; } else if(c != '\r'){ prazdnyRadek = false; /*dokud klient nepošle dvakrát za sebou \r a \n znamená to, že stále odesílá data*/ } } delay(1); //dáme klientovi čas na zpracování client.stop(); //komunikace je u konce } }
Sosáme data
V druhém příkladu se připojíme k serveru a budeme po něm požadovat nějaká data, která si vypíšeme po sériové lince. Konkrétně to bude server ahwk.ic.cz, po kterém budeme chtít soubor ahoj.txt. Ten je uložen v kořenovém adresáři serveru, tedy přímo na adrese ahwk.ic.cz/ahoj.txt. Na začátek si sestavíme HTTP požadavek.
GET /ahoj.txt HTTP/1.1 Host: ahwk.ic.cz Connection: close
Slovy: Dej mi soubor ahoj.txt přes protokol HTTP verze 1.1 z ahwk.ic.cz a potom ukonči spojení.
#include <SPI.h> #include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7}; char server[] = "ahwk.ic.cz"; //server, kam se připojujeme EthernetClient client; void setup(){ Serial.begin(9600); Ethernet.begin(mac); delay(1000); Serial.println("Spojuji..."); if(client.connect(server, 80)){ //připojí se k serveru Serial.println("Pripojeno!"); client.println("GET /ahoj.txt HTTP/1.1"); client.println("Host: ahwk.ic.cz"); client.println("Connection: close"); client.println(); } else { Serial.println("Spojeni se nepovedlo."); } } void loop(){ if(client.available()) { char c = client.read(); Serial.print(c); //vypíše přijatá data } if(!client.connected()) { Serial.println(); Serial.println("Odpojuji."); client.stop(); while(true){} //zastaví činnost shieldu } }
Ovládání přes síť
V posledním příkladu si ukážeme, jak Arduino ovládat pomocí prohlížeče, či jiného síťového zařízení. Budeme ovládat čtyři LED připojené na pinech 3, 4, 5 a 6. Informaci o tom, která LED bude svítit, předáme shieldu pomocí parametru v URL (to co píšeme do adresního řádku). Parametr se píše za ?. My tedy budeme v HTTP požadavku klienta hledat ? a poté čísla za ním následující. Náš program poté rozsvítí postupně LED diody daných čísel. Pokud napíšeme do prohlížeče: 10.0.0.15/?3456, HTTP požadavek odeslaný na server vypadá nějak takto.
GET /?3456 HTTP/1.1 Host: 10.0.0.15 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml, application/xml;q=0.9, image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: cs,en;q=0.8,de;q=0.6,sk;q=0.4
Jediná věc, která nás teď zajímá je první řádek, a to až za otazníkem. Poté už nás další informace nezajímají. Data, která chceme, tedy začínají otazníkem a končí mezerou. Pozor na to, že jsou tu i číslice kódovány jako ASCII.
#include <Ethernet.h> #include <SPI.h> boolean zacatekCteni = false; byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7 }; IPAddress ip(10,0,0,15); EthernetServer mujSvr = EthernetServer(80); void setup(){ pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); Ethernet.begin(mac, ip); mujSvr.begin(); } void loop(){ EthernetClient client = mujSvr.available(); if(client){ boolean prazdnyRadek = true; boolean hlavickaPoslana = false; while(client.connected() && client.available()){ if(!hlavickaPoslana){ //jednou pošleme hlavičku client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); hlavickaPoslana = true; } char c = client.read(); if(zacatekCteni && c == ' '){ //ukončí čtení zacatekCteni = false; } if(c == '?'){ //začne čtení zacatekCteni = true; } if(zacatekCteni){ if(c == '3'){ blikni(3, client); } else if(c == '4'){ blikni(4, client); } else if(c == '5'){ blikni(5, client); } else if(c == '6'){ blikni(6, client); } } if (c == '\n') { prazdnyRadek = true; } else if (c != '\r'){ prazdnyRadek = false; } } delay(1); client.stop(); } } void blikni(int pin, EthernetClient client){ client.print("Sviti LED na pinu "); client.print(pin); client.print("<br>"); //zalomení řádku digitalWrite(pin, HIGH); delay(250); digitalWrite(pin, LOW); delay(250); }
Tímto jsme získali základní přehled o tom, co Ethernet shield umí. To ale samozřejmě není vše. Můžeme ho například naučit komunikovat s Twitterem, zjišťovat čas podle atomových hodin a další. Několik zajímavých příkladů nalezneme v oficiální dokumentaci.
V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.