Hlavní navigace

Arduino: webový server i klient do ruky

27. 7. 2010
Doba čtení: 11 minut

Sdílet

Ve třetím dílu seriálu o Arduinu a internetových věcech si ukážeme zbývající teoretickou část, kterou pro připojení Arduina k internetu potřebujeme. Využijeme schopností Ethernet shieldu a dodávaných knihoven pro práci s ním a ukážeme si zapojení (a programové vybavení), které funguje jako jednoduchý server.

Autorská předmluva

V komentářích k minulému dílu zazněla výtka, že v seriálu nejdeme do patřičné hloubky (o míře patřičnosti můžeme diskutovat) a že děláme z Arduina něco, co „přece zvládne každý“, aniž bychom si řekli o problematice víc či vysvětlili patřičně základy elektroniky. Chtěl bych na tomto místě upozornit, že přesně to je cílem seriálu: Ukázat, že s Arduinem můžete pracovat jako se skládačkou či stavebnicí, aniž byste znali princip fungování tranzistoru, aniž byste věděli o rozdílu mezi bipolární a unipolární technologií, aniž byste dokázali navrhnout binární čítač z diskrétních hradel, aniž byste uměli nastavit pracovní bod tranzistoru, nebo dokonce, i když to mnohým bude připadat jako svatokrádež, aniž byste znali Ohmův zákon! Nic z toho nepotřebujete, abyste s Arduinem něco základního postavili. Samosebou pokud se pustíte do vlastních experimentů, na tyto problémy můžete časem narazit a budete se muset něco doučit.

Můj názor je ten, že člověk se vždy učí líp, když ví, proč se učí; když si učením odpovídá na otázky o světě okolo sebe. Prosím zkušené elektroniky – ruku na srdce: Začali jste se o elektroniku zajímat jen tak, sami od sebe, nebo poté, co jste si zapojili svou první žárovku na baterku a rozmontovali první rádio, protože vás zajímalo, jak to uvnitř funguje?

Disclaimer: Při psaní tohoto článku nebyl zničen ani poškozen žádný integrovaný obvod.

Ethernet

K vytvoření jednoduchého webserveru s Arduinem a Ethernet shieldem opravdu nepotřebujeme vědět nic, co by neznal lehce pokročilý uživatel Linuxu – tedy nějakou tu teorii okolo Ethernetu, TCP, IP a jazyk C.

Pro Arduino existuje víc knihoven pro práci se sítí, s různými schopnostmi a pro různé řadiče. Pro člověka, který není v elektronice zcela zběhlý, je použití hotové knihovny  dodávané s IDE a Ethernet shieldu nejschůdnějším řešením – vzhledem k rozšíření těchto modulů je pravděpodobné, že najde odpověď na problém snáz, než kdyby použil nějakou méně běžnou kombinaci. Základní knihovny Ethernet se tedy přidržíme i my.

Každé zařízení, které chceme připojit na internet, musí mít svou vlastní MAC adresu – musí ji mít síťová karta v počítači stejně jako router, musí ji mít i zařízení s Arduinem. MAC adresa musí být v síti unikátní. Při experimentech v domácí síti stačí použít „nějakou nekonfliktní MAC adresu“, ale pokud chcete své zařízení vystavit na internet, bude lépe zvolit nějakou „globálně unikátní“.

Kde sehnat MAC adresu?

Toto je pasáž, z níž se pravověrní síťaři pravděpodobně otřesou odporem a budou volat po jejím odstranění ze stránek Roota. Říkám to na rovinu a předem, abyste tyto informace brali cum grano salis, jako tip pro vlastní domácí experimenty, rozhodně ne jako doporučení pro vlastní vývoj zařízení.

Totiž: Když si koupíte síťovou kartu, můžete ji zapojit, aniž byste se o MAC adresu starali – síťová karta ji má od výrobce přidělenou. Arduino ani Ethernet shield žádnou přiřazenou MAC adresu nemá, musíte si tedy nějakou „vymyslet“. Otázka je: jakou?

MAC adresa se skládá z čísla, které je přiřazeno výrobci zařízení organizací IEEE (OUI), a z vlastního čísla zařízení, které generuje výrobce (zjednodušeně řečeno). Naprosto čistý postup pro získání MAC adres pro vaše zařízení spočívá v žádosti o přidělení OUI a generování vlastních čísel zařízení. Pokud hodláte vyrábět větší série síťových zařízení, je to jediná možnost. Ovšem pro hraní s jedním Arduinem je to poněkud „kanón na vrabce“.

V ukázkách knihovny je použita MAC adresa DE:AD:BE:EF:FE:ED. Je hezká, dobře se pamatuje a pro experimenty v maličké domácí síti, kde máte na routeru připojené dva počítače a Arduino, rozhodně stačí. Pokud budete mít větší síť, více Arduin či pokud budete chtít zařízení vystavit „ven“, tak bude lépe zvolit jinou. Některé tutoriály doporučují podívat se na MAC adresu síťového rozhraní vašeho počítače a k poslednímu bajtu přidat 1 – pravděpodobnost, že ve stejné síti bude zapojené síťové zařízení od stejného výrobce s číslem o 1 vyšším je poměrně nízká.

Já navrhnu pro experimenty s Arduinem ještě jiné řešení, které je sice taky „nečisté“, ale poměrně bezpečné z hlediska síťového provozu – totiž koupit si nejlevnější síťovou kartu, zkopírovat její MAC adresu a tu používat s Arduinem. (Samosebou kartu nesmíte zapojit do jiného počítače; uložte ji do šuplíku a označte nálepkou „Nepoužívat!!!“)

Zapojujeme

Po nezbytné pauze, kterou si vzali znalci síťové problematiky na rozdýchání předchozího svatokrádežného doporučení, můžeme pokračovat. Kromě MAC adresy budeme potřebovat i IP adresu, a protože předpokládám, že všichni používáme nějakou domácí síť a jsme jejími administrátory, tak nebude problém si nějakou vyhradit. Já v příkladech budu používat 192.168.0.88, vy si doplňte vhodnou dle své sítě.

Připojíme Ethernet shield k Arduinu, zapojíme síťový kabel, a můžeme napsat první program:

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 88 };

void setup()
{
  Ethernet.begin(mac, ip);
}

void loop ()
{
  ;
}

Přeložíme, nahrajeme do Arduina (tlačítkem Upload) a zkusíme si PINGnout. Pokud vše funguje jak má, tak zařízení odpovídá (a při každém pingu bliknou LED, oznamující příjem a vysílání dat).

O obsluhu ICMP paketů PING se stará řadič WIZnet W5100. My jsme řadiči pouze sdělili, jakou má použít MAC adresu a jakou IP adresu, a to pomocí funkce Ethernet.begin(). Při pohledu na zdrojový kód vidíme, že jsme nejprve vložili hlavičkový soubor pro ethernet (jako v C). MAC adresu i IP definujeme jako pole bajtů (šesti, resp. čtyř u IP). Tato konstantní pole předáme funkci Ethernet.begin jako parametry.

Funkce Ethernet.begin umožňuje nastavit nejen MAC a IP, ale i specifikovat gateway, popř. i masku podsítě.

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 88 };
byte gateway[] = { 192, 168, 0, 254 };
byte mask[] = { 255, 255, 255, 0 };

void setup()
{
  Ethernet.begin(mac, ip, gateway, mask);
}

void loop ()
{
  ;
}

Jednoduchý telnet server

Při pohledu do dokumentace knihovny Ethernet zjistíme, že kromě třídy Ethernet nabízí i třídy Client a Server. Tyto třídy slouží ke čtení dat, které přicházejí po síti, resp. k posílání vlastních. Třída Server se stará o posílání dat, třída Client o čtení dat z příchozích spojení a o navázání spojení se vzdáleným serverem.

Obdobou blikání LEDky z minulého dílu bude tentokrát „echo“ na portu 23, což je port vyhrazený pro Telnet.

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 88 };

Server server = Server(23);

void setup()
{
  Ethernet.begin(mac, ip);
  server.begin();
}

void loop ()
{
  Client client = server.available();
  if (client) {
    server.write(client.read());
  }

}

V definicích přibyla proměnná server typu Server a byl jí přiřazen objekt třídy Server (přidržíme se terminologie známé z OOP), vytvořený s parametrem 23  – parametr udává číslo TCP portu, na němž bude náš server naslouchat. Ve funkci setup() je pak tento server spuštěn pomocí metody begin(). Od té chvíle je připraven přijímat příchozí spojení.

Vlastní práce je opět vykonávána ve funkci loop(). Nejprve je pomocí metody available vrácen objekt třídy Client, který slouží ke čtení dat z příchozího spojení – pokud tedy nějaké je. Pokud není, je vrácena hodnota false. Pokud je, uděláme jednoduché echo – pošleme na spojení (server.write()) to, co přišlo (client.read()).

Spusťte si Telnet na IP adresu Arduina – a voila! Co napíšeme, to se nám vrátí. Ovšem má to drobnou nevýhodu, kterou odhalíme, pokud si spustíme Telnet dvakrát: Co na jednom napíšeme, to se do OBOU vrátí. Vida, máme vlastní chat server! Takto spolu mohou po Telnetu komunikovat přes Arduino dva, tři, čtyři, pět a více lidí. Tedy – pokud jich bude víc než 4, budou se muset o telnet podělit, protože Arduino zvládne pouze čtyři současně otevřená spojení.

Chat je hezká věc, ale poněkud nepraktická, pokud chceme, aby se odpovědi od serveru dočkal ten, kdo se ptal, a nikoli celý svět. Vysvětlení nalezneme opět v dokumentaci: Metody třídy Server (write(), print, println) totiž posílají data na VŠECHNA otevřená příchozí spojení, je to tedy takový server broadcast. Pokud chceme poslat data na konkrétní příchozí spojení, musíme použít stejnojmenné metody třídy Client.

Nahradíme v předchozím kódu server.write() voláním client.write() – takto: client.write(client.read()). Po přeložení a otestování vidíme, že jednotlivá telnetová spojení jsou na sobě nezávislá.

HTTP klient

Vytvoření klienta, který se připojí na server a něco z něj přečte je podobně přímočaré. Pouze využijeme třídu Client. V konstruktoru specifikujeme IP adresu serveru a port. Metodou connect() iniciujeme spojení se serverem, a pokud jej navážeme, můžeme začít posílat a číst data. Postup bude povědomý každému, kdo kdy zkusil přistupovat ke vzdálenému serveru pomocí socketů.

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 88 };
byte gateway[] = { 192, 168, 0, 254 };
byte mask[] = { 255, 255, 255, 0 };

byte root[] = { 91, 213, 160, 118 }; // Root

Client client(root, 80);


void setup()
{
  Ethernet.begin(mac, ip, gateway, mask);
  Serial.begin(115200);

  delay(1000);

  Serial.println("connecting...");

  if (client.connect()) {
    Serial.println("connected");
    client.println("GET / HTTP/1.1");
    client.println("Host: www.root.cz");
    client.println("Connection: Close");
    client.println();
  } else {
    Serial.println("connection failed");
  }
}

void loop ()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

Kód si probereme jen stručně. Začátek už známe, tentokrát jen pozor na to, abychom nezapomněli na gateway, protože budeme přistupovat ven z naší sítě. (DHCP pro Arduino existuje, ale necháme si jej na příště.) Musíme znát IP adresu serveru, na který chceme přistupovat – zvolil jsem IP adresu WWW serveru root.cz, z něhož si načteme titulní stránku. V konstruktoru tedy zadáme IP Roota a port 80. Spustíme connect() – pokud vrátí true, znamená to, že jsme se úspěšně připojili a můžeme poslat požadavek na HTTP server běžným způsobem – pošleme hlavičky požadavku a prázdný řádek.

Hlavní smyčka loop() se stará o vrácená data. Pokud jsou nějaká dostupná, opíše je na sériový port. V případě, že je spojení uzavřeno, vypíšeme informaci, zastavíme klienta (čímž potvrdíme zavřené spojení) a skončíme (v nekonečné smyčce). Když si kód přeložíme, spustíme a zapneme monitor sériové linky (nezapomeňte na nastavení správné přenosové rychlosti), dostaneme něco takového:

Root.cz po sériové lince… Vypadá hezky, že?

WWW server

Na závěr si ukážeme, jak lze v Arduinu spustit WWW server s podporou PHP a mod_rewrite a nainstalujeme si WordPress. Ehm, samosebou šlo o nejapný žert (zatím?) – zůstaneme prozatím u přízemního vypsání stránky. Úkol bude o něco složitější než předchozí telnetový chat, protože musíme ošetřit HTTP požadavek. Nebudeme jej nijak zpracovávat – server stejně bude umět vrátit jen jednu stránku, pokud lze nevalidní nadpis Hello World označit vůbec za stránku. Zatím tedy vše, co přijde od klienta, zahodíme a budeme čekat na prázdný řádek. to bude pro nás signál, že můžeme začít posílat obsah.

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 88 };

Server server(80);

void setup()
{
  Ethernet.begin(mac, ip);
  server.begin();
}

void loop()
{
  Client client = server.available();
  if (client) { //Připojil se klient?
    boolean current_line_is_blank = true;
    while (client.connected()) {
      if (client.available()) { //poslal data?
        char c = client.read();
        // Pokud je přijatý znak konec řádku (\n) a řádek je stále prázdný,
        // znamená to, že HTTP požadavek skončil a je na nás, abychom vrátili
        // nějaká data
        if (c == '\n' && current_line_is_blank) {
          // nejprve pošleme HTTP hlavičku
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();

          // oddělíme ji prázdným řádkem a vypíšeme Hello world.
          client.print("<html><head></head><body>");
          client.print("<h1>");
          client.print("Hello world!");
          client.print("</h1></body></html>");
          break;
        }
        if (c == '\n') {
          // Přišel znak nového řádku, nastavíme si příznak
          current_line_is_blank = true;
        } else if (c != '\r') {
          // přišel znak jiný než CR nebo LF?
          // Poznamenáme si, že řádek není prázdný
          current_line_is_blank = false;
        }
      }
    }
    // dáme nějaký čas na zpracování dat a zavřeme spojení
    delay(1);
    client.stop();
  }
}

Je to opravdu jednoduchý server, který vrací jednu jedinou HTML stránku na libovolný dotaz. Na řádku s Hello World můžeme změnit vypisovanou hodnotu – např. si můžeme vypsat hodnotu analogového vstupu ( analogRead(0)).

Co dál?

Základní knihovna Ethernet umožňuje vytvořit jednoduchý síťový server i klient. Na jakoukoli složitější práci je ale poněkud těžkopádná. Naštěstí existují rozšiřující knihovny, které práci s webem poněkud usnadňují. Jednou z nich je webduino. S touto knihovnou můžete vytvořit s minimem práce např. server, který dokáže zpracovat požadavky na různé stránky a parsovat parametry předané v GET požadavku (např. „/analogport?num=2&format=hex“). V příkladech je i ukázka jednoduchého serveru používajícího AJAX, nebo posílání obrázků.

K dalšímu experimentování se vám mohou hodit i knihovny Bonjour, DHCP a DNS od George Kaindla. Využijete jistě i některé ze standardních knihoven ze stránek Arduina.

Dnes končíme úvodní, obecnou část seriálu o Arduinu, v níž jsme si představili v hrubých rysech práci s Arduinem a Ethernet shieldem. Doufám, že vás možnosti tohoto kitu zaujaly a pobídly k vlastním experimentům. Od příštího dílu se budeme setkávat nepravidelněji, zato nad – doufejme – zajímavými či zábavnými zapojeními. Pokud se budete chtít pochlubit s vlastním zapojením či zajímavou funkcí, napište nám do redakce – heslo „Arduino“.

Pro zájemce o hobby elektroniku a zapojení s jednočipy lze doporučit např. česky psané servery Pandatron a MCU.cz, kde se čas od času objeví i nějaký tip přímo pro Arduino. Při experimentech a vymýšlení možných zapojení oceníte kromě obchodů s elektrosoučástkami např. i nabídku obchodů CzechDuino a HW Kitchen. Zde seženete kity, které jsou vhodné pro experimentování s Arduinem a s nepájivým kontaktním polem – mnohé z moderních obvodů jsou totiž v miniaturních pouzdrech, leckdy bez vývodů, a v domácích podmínkách se s nimi bez patřičného vybavení a cviku velmi těžko experimentuje.

CS24_early

Přeji všem zájemcům o elektroniku a experimentování s jednočipy hodně štěstí, hodně inspirativních nápadů a co nejmíň zničených obvodů.

Příště si zapojíme k WWW serveru jednoduchý teploměr (s obvodem DS18B20) a budeme přes web ukazovat, jaký máme doma chládek, zatímco ostatní trpí vedrem.

Děkujeme provozovateli obchodu Czechduino za laskavé zapůjčení Arduina a Ethernet Shieldu pro účely testování a psaní tohoto článku.

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

Autor článku

Martin Malý je autorem serveru Bloguje, mikroblogu Teidu či služby pro zkracování odkazů Jdem.cz. Vedl také magazín Zdroják.