Arduino + Ethernet - Webserver - formuláre - EEPROM
Arduino s Ethernetom je vhodné pre rôzne sieťové aplikácie založené web technológiách - HTTP (nepodporuje HTTPS), alebo rôznych priemyselných službách založených na: Modbus, MQTT, UDP, TCP protokoloch. Ethernet shield Wiznet W5100 je možné použiť na všetky R3 Arduino dosky, je teda plne kompatibilný pre Arduino Uno, Mega 1280 / 2560.
Ethernet shield, respektíve modul je možné prevádzkovať v dvoch základných režimoch:
- WebClient - Klient v sieti, ktorý je schopný pripojiť sa na vzdialený server - odosielať a prijímať dáta od servera
- Webserver - server, ktorý je schopný prijímať dáta od klientov (spracovať ich), odpovedať na dopyty klientov
Dnes si ukážeme, ako použiť jednoduchý webserver na platforme Arduino + Ethernet (W5100 / W5500). Webserver umožňuje spustenie viacerých HTML podstránok, ktoré je možné klientom otvoriť a získať HTML výstup. Takýto výstup je vhodný pre statické aplikácie pre výpis textu, bez dynamiky na strane servera.
Vzorový príklad pre webserver (jednoduchý, statický) je možné nájsť na adrese: https://www.arduino.cc/en/Tutorial/WebServer
Problémom však býva pokročilá implementácia, napríklad s formulárom, prostredníctvom ktorého je možné zadať hodnoty a uložiť ich na webserveri permanentne. Problémové je predovšetkým spracovanie dát, nakoľko Arduino nedokáže spustiť backend jazyk typu PHP. Ukladanie dát na webserveri pre ich permanentné uloženie je využitie EEPROM pamäte.
EEPROM pamäť v prípade Arduina UNO má veľkosť 512B. EEPROM je limitovaná na 10 až 100 tisíc prepisov, pričom je energeticky nezávislá. Dáta sa na webserveri uchovajú a v prípade výpadku elektrickej energie. Riešenie pre všetky tieto faktory je možné nájsť v mojej implementácii. Dáta z formulára sú spracované druhým HTML súborom, ktorý umožňuje prevziať argumenty formulára vrátane name pre jednotlivé polia formulára.
Argumenty sú interne spracované Arduinom (nemá súvis s HTML stránkou), dáta z argumentov sú uložené do EEPROM pamäte a používateľ je následne z HTML stránky spracovania (s výpisom prebratých argumentov) presmerovaný na HTML stránku predchádzajúcu s formulárom - presmerovanie v HTML vytvorené prestredníctvom meta refresh tagu s relatívnym umiestnením hlavnej stránky.
Tento príklad bol pôvodne vyvinutý pre 12 posuvných registrov 74HC595, ktoré ovládali 3 displeje - program obsahuje iba časť zápisu a čítania z EEPROM pamäte. Každý z posuvných registrov 74HC595 ovládal osmicu led diód - reprezentujúce číslo 0-8. Každý displej obsahoval výpis 4 čísel, celkovo teda 4x 74HC595 posuvných registrov na displej. Každý posuvný register ovláda jednu cifru zadaného čísla, ktoré je vždy 4 miestne.
Aby sa zachovala 4-miestnosť čísla, využíva sa formulár s textovým vstupom pre zadanie čísel (pre typ formulára s číselným vstupom nie je možné aplikovať obmedzenie počtu znakov v poli a taktiež by sa číslo 0002 skonvertovalo na 2). Do HTML stránok je možné pridať aj jednoduchú grafiku v CSS, ale taktiež prostredníctvom link tagov v hlavičke HTML dokumentov priradiť aj externé CSS typu Bootstrap a iné a skrášliť tak vizuál stránky. Moja implementácia nevyužíva žiadne CSS súbory, štýly, classy.
Ethernet shield má mnoho nastavení, ktoré je možné použiť z hľadiska konektivity. IP adresu verzie 4 je možné definovať staticky, priradiť aj ďalšie dôležité sieťové umiestnenia ako:
- brána (gateway)
- subnet
- dns server
- dns server 2
Pôvodný formulár s dátami z EEPROM pamäte:
Po odoslaní nových dát prostredníctvom formuláru (zápis do EEPROm a následne presmerovanie späť):
Presmerovanie na pôvodnú HTML stránku s formulárom (nové hodnoty v EEPROM pamäti):
Viac zaujímavých projektov so zdrojovým kódom je možné nájsť na adrese: https://arduino.php5.sk/, rôzne programové implementácie (nielen) pre Arduino sú dostupné na Github-e: https://github.com/martinius96
Program pre webserver s funkcionalitou opísanou vyššie:
/*|----------------------------------------------------------|*/ /*|HTTP webserver - FORM - HTML - PROCESSING - EEPROM |*/ /*|AUTHOR: Martin Chlebovec |*/ /*|EMAIL: martinius96@gmail.com |*/ /*|WEBSITE: https://arduino.php5.sk |*/ /*|----------------------------------------------------------|*/ #include#include #include void writeString(char add, String data); String read_String(char add); byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address byte ip[] = { 192, 168, 4, 1 }; // STATICKA IP V LAN SIETI byte subnet[] = { 255, 255, 255, 0 }; //subnet mask EthernetServer server(80); //server port const char terminator1[2] = " "; const char terminator2[2] = "/"; const char terminator3[2] = "?"; const char terminator4[2] = "&"; const char terminator5[2] = "="; void setup() { // Ethernet.begin(mac); //NASTAVENIE IP ADRESY DYNAMICKY CEZ DHCP Ethernet.begin(mac, ip, subnet); //NASTAVENIE IP ADRESY STATICKY server.begin(); Serial.begin(115200); Serial.println("Ready"); Serial.println("Ethernet shield na IP:"); Serial.println(Ethernet.localIP()); } void loop() { EthernetClient client = server.available(); if (client) { while (client.connected()) { if (client.available()) { String line = client.readStringUntil('\n'); // Serial.println("Request od klienta"); // Serial.println(line); char str[line.length() + 1]; line.toCharArray(str, line.length()); // Serial.println("Request ako pole znakov"); // Serial.println(str); char *method; char *request; method = strtok(str, terminator1); // Serial.println("Metóda:"); //Serial.println(method); request = strtok(NULL, terminator1); // Serial.println("Request:"); // Serial.println(request); if (String(request) == "/") { //HLAVNA ROOT HTTP STRANKA client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); client.println(""); client.println(""); client.println(""); client.println(""); //client.println(" "); client.println("
HTTP webserver - Arduino + Ethernet "); client.println(""); client.println(""); client.println("Zadajte dáta pre webserver (budú uložené do EEPROM):
"); client.println(""); client.println(""); client.println(""); delay(1); client.stop(); client.flush(); } else if (String(request) == "/get_data/") { //PODSTRANKA PRE VYCITANIE DAT (INYM MIKROKONTROLEROM) client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); client.println(""); client.println(""); client.println(""); client.println(""); //client.println(""); client.println("
HTTP webserver - Arduino + Ethernet "); client.println(""); client.println(""); client.println("" + read_String(10) + " "); client.println("" + read_String(100) + " "); client.println("" + read_String(200) + " "); client.println(""); client.println(""); delay(1); client.stop(); client.flush(); } else if (String(request) == "/favicon.ico") { //fix chybajuceho faviconu client.stop(); } else { String myString = String(request); if (myString.startsWith("/action.html")) { char* parameter; char* value; char* hodnota1; char* hodnota2; char* hodnota3; parameter = strtok(request, terminator3); Serial.println(parameter); value = strtok(NULL, terminator3); hodnota1 = strtok(value, terminator4); hodnota2 = strtok(NULL, terminator4); hodnota3 = strtok(NULL, terminator4); char* H_1; char* H_2; char* H_3; strtok(hodnota1, terminator5); H_1 = strtok(NULL, terminator5); strtok(hodnota2, terminator5); H_2 = strtok(NULL, terminator5); strtok(hodnota3, terminator5); H_3 = strtok(NULL, terminator5); hodnota2 = strtok(NULL, terminator4); writeString(10, String(H_1)); writeString(100, String(H_2)); writeString(200, String(H_3)); client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); client.println(""); client.println(""); client.println(""); client.println(""); client.println(""); client.println("HTTP webserver - Arduino + Ethernet "); client.println(""); client.println(""); client.println("Server prijal data:
"); client.println("Hodnota 1: " + String(H_1) + " "); client.println("Hodnota 2: " + String(H_2) + " "); client.println("Hodnota 3: " + String(H_3) + " "); client.println("Presmerovanie... Prosim cakajte"); client.println(""); client.println(""); delay(1); client.stop(); client.flush(); } } } } } } void writeString(char add, String data) { int _size = data.length(); int i; for (i = 0; i < _size; i++) { EEPROM.write(add + i, data[i]); } EEPROM.write(add + _size, '\0'); //Add termination null character for String Data } String read_String(char add) { int i; char data[100]; //Max 100 Bytes int len = 0; unsigned char k; k = EEPROM.read(add); while (k != '\0' && len < 500) //Read until null character { k = EEPROM.read(add + len); data[len] = k; len++; } data[len] = '\0'; return String(data); }