Kursy • Poradniki • Inspirujące DIY • Forum
Jak zwykle pierwszy krok, to wykorzystanie gotowych przykładów. Przeglądając dostępne możliwości znajdziemy, dwie biblioteki, które maja wiele wspólnego z internetem: Ethernet oraz WiFi. Moduł Intel Edison nie posiada interfejsu Ethernet (to określenie kablowej sieci lokalnej), ma natomiast sieć bezprzewodową WiFi.
Jednak jeśli zajrzymy do kodów bibliotek okaże się, że obie będą działać na naszej płytce i będą wykorzystywały sieć bezprzewodową.
Co więcej moduł Ethernet znacznie lepiej będzie pasował do naszych zastosowań. WiFi oferuje dodatkowe możliwości związane z konfiguracją sieci bezprzewodowej, jednak my już ją skonfigurowaliśmy i nie potrzebujemy zmian.
Edison nie posiada interfejsu Ethernet,
ale biblioteka o tej nazwie działa równeiż z siecią bezprzewodową.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Elementy konieczne do wykonania ćwiczeń zebrane zostały w gotowe zestawy, które można nabyć w Botlandzie. W kuferku znajdziecie ponad 180 elementów w tym moduł Intel Edison!
Zamów w Botland.com.pl »Intel Edison - przykładowy serwer
Na początek otwieramy przykładowy program WebServer z biblioteki Ethernet, czyli opcję Plik > Przykłady > Ethernet > WebServer. Jeśli po prostu spróbujemy program uruchomić, niestety nie zadziała. Domyślny port dla stron www, czyli 80 jest już zajęty. Musimy wybrać inny port, przykładowo 8080.
W tym celu szukamy następującego fragmentu kodu:
1 |
EthernetServer server(80); |
Następnie zamieniamy go na:
1 |
EthernetServer server(8080); |
Teraz możemy skompilować i uruchomić przykład. Aby sprawdzić, czy wszystko działa poprawnie należy w przeglądarce wpisać adres modułu wraz z odpowiednim numerem portu. W moim przypadku jest to adres: http://192.168.1.30:8080
Jak łatwo się domyślić są to odczyty z wejść analogowych A0 - A5. Ponieważ nie podłączyliśmy nic do nich, odczyty są dość losowe. Spróbujmy więc podłączyć do wejść A0 i A1 fotorezystory wg. poniższego schematu montażowego:
Jeśli teraz sprawdzimy wyniki, będą one zależały od poziomu oświetlenia. Zawartość strony jest odświeżana co 5 sekund, możemy więc na bieżąco obserwować jak działa nasz pierwszy internetowy odczyt danych.
Warto na chwilę uruchomić monitor portu szeregowego - zobaczymy na nim dane otrzymane z przeglądarki internetowej:
Jest to protokół http, który wykorzystują wszystkie przeglądarki internetowe. Nawet czytając niniejszy kurs dokładnie tak samo wyglądają zapytania przesyłane przez przeglądarkę do serwera Forbot-a (linia "User-Agent" tłumaczy skąd biorą się informacje z jakiego systemu i przeglądarki korzystają użytkownicy).
Serwer po otrzymaniu takiego zapytania przygotowuje i odsyła
do klienta odpowiedź, czyli treść strony.
Własny program korzystający z Internetu
Kod programu przykładowego jest dość długi, więc zamiast go analizować napiszemy własny program od początku. Będziemy potrzebowali bibliotekę Ethernet oraz obiekt serwera.
Tworząc serwer, podajemy jako parametr numer wykorzystywanego portu. Podobnie jak poprzednio, wykorzystamy port 8080.
1 2 3 |
#include <Ethernet.h> EthernetServer server(8080); |
Teraz musimy napisać kod konfigurujący serwer, czyli funkcję setup.
1 2 3 4 5 6 |
void setup() { Serial.begin(9600); Ethernet.begin(NULL); server.begin(); } |
Nie musimy definiować adresu MAC - jest on ignorowany przez bibliotekę, a tylko komplikuje kod. Więc zamiast niego podajemy NULL jako parametr wywołania Ethernet.begin().
Praca serwera www polega na czekaniu na połączenia od przeglądarki internetowej, odbieraniu zapytania oraz odsyłaniu odpowiedzi. Zaczniemy więc niejako od końca i napiszemy pętlę główną programu:
1 2 3 4 5 6 7 8 9 |
void loop() { EthernetClient client = server.available(); if (client) { process_query(client); send_answer(client); client.stop(); } } |
Sprawdzenie, czy przeglądarka chce się połączyć realizuje wywołanie server.available(). Zwróci ono połączenie z klientem (EthernetClient), gdy przeglądarka nawiąże połączenie. Musimy napisać dwie procedury: process_query oraz send_answer.
Pierwsza jest nieco skomplikowana, a jej w pełni poprawne zaimplementowanie to prawdziwa sztuka. Powinna odbierać nagłówek protokołu http i analizować wszystkie jego pola. My napiszemy bardzo uproszczoną wersję - będzie wczytywała kolejne linie, aż do napotkania pustej, która sygnalizuje koniec danych.
Nie będziemy więc na razie korzystać z danych przysłanych przez przeglądarkę. Jedynie, tak jak kod przykładowy wyślemy je przez port szeregowy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void process_query(EthernetClient& client) { String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") break; if (c == '\n') line = ""; } } } |
Teraz możemy już napisać funkcję odsyłającą dane do serwera. Pierwsza wersja będzie prosta:
1 2 3 4 |
void send_answer(EthernetClient& client) { client.println("Hello World!"); } |
Cały program wygląda w chwili obecnej następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <Ethernet.h> EthernetServer server(8080); void setup() { Serial.begin(9600); Ethernet.begin(NULL); server.begin(); } void send_answer(EthernetClient& client) { client.println("Hello World!"); } void process_query(EthernetClient& client) { String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") break; if (c == '\n') line = ""; } } } void loop() { EthernetClient client = server.available(); if (client) { process_query(client); send_answer(client); client.stop(); } } |
W przeglądarce możemy zobaczyć rezultat:
Co ciekawe nasza strona wcale nie została napisana w HTML-u. Na razie to zwykły tekst - dlatego przeglądarka wykorzystuje czcionkę o stałej szerokości znaków (Courier).
Zwracanie kodu HTML
W poprzednim przykładzie funkcja send_answer() była ekstremalnie uproszczona. Jak widzieliśmy działała, jednak w internecie standardem jest wykorzystywanie HTMLa zamiast przesyłania plików tekstowych. Spróbujemy więc nieco rozbudować naszą funkcję, tak aby uzyskać ładniejszy efekt.
Większość osób wie jak wyglądają strony w html-u, więc nie będziemy tego omawiać. Ponieważ to co tworzy funkcja send_answer() jest wysyłane przez protokół http, musimy jeszcze dodać odpowiedni nagłówek.
Podstawowa wersja wygląda więc następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void send_answer(EthernetClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<body>"); client.println("<h1>Hello World!</h1>"); client.println("</body>"); client.println("</html>"); } |
Jeśli przetestujemy rezultat w przeglądarce, zobaczymy, że jest już dużo lepiej - rezultat wygląda jak strona www, chociaż pięknem nie powala:
Stacja pogodowa
Mamy już podłączone dwa fotorezystory, dodajmy jeszcze czujnik HDC1008. Z jego obsługą zapoznaliśmy się w poprzedniej części kursu. Układ powinien wyglądać następująco:
Teraz możemy rozszerzyć naszą funkcję wysyłającą kod html. Ponieważ strona jest coraz bardziej rozbudowana, dodaliśmy do niej nowe elementy:
- nagłówek został zdefiniowany w znacznikach <head>. Ustalany jest tytuł strony (<title>), kodowanie (UTF-8) oraz odświeżanie strony co 5 s,
- w treści dokumentu odczytujemy dane z czujników i wysyłamy wyniki pomiarów oświetlenia z dwóch fotorezystorów oraz temperaturę i wilgotność odczytane przez HDC1008.
Kod programu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#include <Wire.h> #include <Adafruit_HDC1000.h> #include <Ethernet.h> Adafruit_HDC1000 hdc; EthernetServer server(8080); void setup() { Serial.begin(9600); if (!hdc.begin()) { Serial.println("Brak HDC1008"); while (1); } Ethernet.begin(NULL); server.begin(); } void send_answer(EthernetClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head>"); client.println("<meta charset=\"utf-8\" />"); client.println("<meta http-equiv=\"refresh\" content=\"5\">"); client.println("<title>Stacja pogodowa v0.1</title>"); client.println("</head>"); client.println("<body>"); client.println("<h1>Stacja pogodowa v0.1</h1>"); client.print("<p>Nasłonecznienie 1: "); client.print(analogRead(0)); client.println("</p>"); client.print("<p>Nasłonecznienie 2: "); client.print(analogRead(1)); client.println("</p>"); client.print("<p>Temperatura: "); client.print(hdc.readTemperature()); client.println("</p>"); client.print("<p>Wilgotność: "); client.print(hdc.readHumidity()); client.println("</p>"); client.println("</body>"); client.println("</html>"); } void process_query(EthernetClient& client) { String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") break; if (c == '\n') line = ""; } } } void loop() { EthernetClient client = server.available(); if (client) { process_query(client); send_answer(client); client.stop(); } } |
Efekt działania:
Niewątpliwie należałoby popracować nad wyglądem strony - bez CSS nie prezentuje się szczególnie okazale. Jednak takie zmiany wydłużają program.
Zadanie 7.1
Dodaj do przykładowego programu obsługę stylów CSS, zmień wygląd strony. Jeśli nie miałeś nigdy styczności z kaskadowymi arkuszami stylów, to możesz zapoznać się z darmowym kursem.
Rozpoznawanie adresów URL
Nasz dotychczasowy serwer www był bardzo uproszczony. Nie analizowaliśmy treści zapytania od przeglądarki, więc nie było różnicy jaką stronę chciał zobaczyć użytkownik - i tak zawsze dostawał jedyną dostępną. Możemy sprawdzić jak działa nasz serwer. Jak wiemy, strona jest dostępna pod adresem http://192.168.1.30:8080/. Jednak jest też dostępna, jeśli spróbujemy odwoływać się do innych stron np. http://192.168.1.30:8080/test, czy http://192.168.1.30:8080/cokolwiek/.
Oczywiście prawdziwy serwer www nie powinien się tak zachowywać.
Dodajmy więc do naszego serwera rozpoznawanie żądanego przez przeglądarkę adresu. Przy adresie domyślnym, czyli "/" będziemy zwracać naszą stronę, a w pozostałych informację o braku strony (słynny błąd 404).
Najpierw musimy wrócić do nagłówka http. W nim interesuje nas linia z poleceniem "GET". Przykładowo: GET /test HTTP/1.1
Jak widzimy po GET jest jedna spacja, a następnie adres zasobu, który chciał zobaczyć użytkownik. Dalej może być kolejna spacja oraz wersja protokołu (HTTP/1.1). Zmieńmy definicję naszej funkcji process_query(), tak aby zwracała nazwę strony którą chce zobaczyć użytkownik.
Kod tego rozwiązania widoczny jest poniżej. Wygląda dość zawile, jednak jego zadanie jest dosć proste. W przypadku problemów ze zrozumieniem jego działania piszcie w komentarzach!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
String process_query(EthernetClient& client) { String get_url; String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") return get_url; if (c == '\n') { if (line.startsWith("GET ")) { get_url = line.substring(4); int pos = get_url.indexOf(' '); if (pos != -1) { get_url = get_url.substring(0, pos); } get_url.trim(); } line = ""; } } } } |
Teraz możemy wrócić do pętli głównej programu i dodać sprawdzanie nazw żądanych plików:
1 2 3 4 5 6 7 8 9 10 11 12 |
void loop() { EthernetClient client = server.available(); if (client) { String url = process_query(client); if (url == "/") send_answer(client); else send_error(client); client.stop(); } } |
Brakuje nam jeszcze funkcji send_error(). Jej kod jest prosty:
1 2 3 4 5 |
void send_error(EthernetClient& client) { client.println("HTTP/1.1 404 Not Found"); client.println(); } |
Cały, dość rozbudowany, program wygląda teraz tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
#include <Wire.h> #include <Adafruit_HDC1000.h> #include <Ethernet.h> Adafruit_HDC1000 hdc; EthernetServer server(8080); void setup() { Serial.begin(9600); if (!hdc.begin()) { Serial.println("Brak HDC1008"); while (1); } Ethernet.begin(NULL); server.begin(); } void send_error(EthernetClient& client) { client.println("HTTP/1.1 404 Not Found"); client.println(); } void send_answer(EthernetClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head>"); client.println("<meta charset=\"utf-8\" />"); client.println("<meta http-equiv=\"refresh\" content=\"5\">"); client.println("<title>Stacja pogodowa v0.1</title>"); client.println("</head>"); client.println("<body>"); client.println("<h1>Stacja pogodowa v0.1</h1>"); client.print("<p>Nasłonecznienie 1: "); client.print(analogRead(0)); client.println("</p>"); client.print("<p>Nasłonecznienie 2: "); client.print(analogRead(1)); client.println("</p>"); client.print("<p>Temperatura: "); client.print(hdc.readTemperature()); client.println("</p>"); client.print("<p>Wilgotność: "); client.print(hdc.readHumidity()); client.println("</p>"); client.println("</body>"); client.println("</html>"); } String process_query(EthernetClient& client) { String get_url; String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") return get_url; if (c == '\n') { if (line.startsWith("GET ")) { get_url = line.substring(4); int pos = get_url.indexOf(' '); if (pos != -1) { get_url = get_url.substring(0, pos); } get_url.trim(); } line = ""; } } } } void loop() { EthernetClient client = server.available(); if (client) { String url = process_query(client); if (url == "/") send_answer(client); else send_error(client); client.stop(); } } |
Teraz tylko strona główna będzie dostępna.
Zadanie 7.2
Dodaj obsługiwanie dodatkowej strony, np. "/test.html".
Zadanie 7.3
Zamiast komunikatu o błędzie wyświetlaj stronę z informacją o braku żądanego zasobu.
Intel Edison - zdalne sterowanie
Umiemy już odczytywać dane z czujników. Teraz spróbujemy wykorzystać nową wiedzę do sterowania. Ponieważ program robi się coraz dłuższy, usuniemy z niego fragmenty związane ze stacją pogodową. Nic nie przeszkadza jednak w napisaniu jednego programu, który zarówno odczytuje dane z czujników, jak i steruje wyjściami.
Podłączmy diodę do układu, podobnie jak w przykładzie z poprzedniej części kursu:
Teraz dodamy zmienną, która będzie przechowywała stan urządzenia - informację, czy dioda jest zapalona:
1 |
int led_state = 0; |
Następnie napiszemy dwie funkcje, które będą wywoływane, gdy użytkownik wybierze odpowiedni adres w przeglądarce:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void turn_on(EthernetClient& client) { digitalWrite(LED_PIN, HIGH); led_state = 1; send_answer(client); } void turn_off(EthernetClient& client) { digitalWrite(LED_PIN, LOW); led_state = 0; send_answer(client); } |
Ich treść jest bardzo prosta - zapalają lub gaszą diodę oraz aktualizują zmienną stanu, a następnie odsyłają treść strony do przeglądarki.
Musimy teraz napisać funkcję send_answer(). Będzie to prosta funkcja, która utworzy stronę HTML z linkiem do zmiany stanu diody:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void send_answer(EthernetClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head><meta charset=\"utf-8\" /></head>"); client.println("<body>"); client.print("<p>Dioda jest "); if (led_state) client.print("<b>zapalona</b>"); else client.print("<b>zgaszona</b>"); client.println(".</p>"); client.print("<p>"); if (led_state) client.println("<p><a href=\"/off\">Zgaś diodę</a></p>"); else client.println("<p><a href=\"/on\">Zapal diodę</a></p>"); client.println("</body>"); client.println("</html>"); } |
Jak widzimy najważniejsza jej część to wyświetlenie informacji o stanie urządzenia (dioda zapalona / zgaszona) oraz udostępnienie linka do zmiany stanu.
Dodajemy wywołanie nowych funkcji do pętli głównej programu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void loop() { EthernetClient client = server.available(); if (client) { String url = process_query(client); if (url == "/") send_answer(client); else if (url == "/on") turn_on(client); else if (url == "/off") turn_off(client); else send_error(client); client.stop(); } } |
Mamy już gotowy cały program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
#include <Ethernet.h> const int LED_PIN = 2; EthernetServer server(8080); int led_state = 0; void setup() { Serial.begin(9600); Ethernet.begin(NULL); server.begin(); pinMode(LED_PIN, OUTPUT); } void send_error(EthernetClient& client) { client.println("HTTP/1.1 404 Not Found"); client.println(); } void send_answer(EthernetClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head><meta charset=\"utf-8\" /></head>"); client.println("<body>"); client.print("<p>Dioda jest "); if (led_state) client.print("<b>zapalona</b>"); else client.print("<b>zgaszona</b>"); client.println(".</p>"); client.print("<p>"); if (led_state) client.println("<p><a href=\"/off\">Zgaś diodę</a></p>"); else client.println("<p><a href=\"/on\">Zapal diodę</a></p>"); client.println("</body>"); client.println("</html>"); } void turn_on(EthernetClient& client) { digitalWrite(LED_PIN, HIGH); led_state = 1; send_answer(client); } void turn_off(EthernetClient& client) { digitalWrite(LED_PIN, LOW); led_state = 0; send_answer(client); } String process_query(EthernetClient& client) { String get_url; String line; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); line += c; if (line == "\r\n") return get_url; if (c == '\n') { if (line.startsWith("GET ")) { get_url = line.substring(4); int pos = get_url.indexOf(' '); if (pos != -1) { get_url = get_url.substring(0, pos); } get_url.trim(); } line = ""; } } } } void loop() { EthernetClient client = server.available(); if (client) { String url = process_query(client); if (url == "/") send_answer(client); else if (url == "/on") turn_on(client); else if (url == "/off") turn_off(client); else send_error(client); client.stop(); } } |
Gdy wejdziemy na stronę naszego modułu, będziemy mogli za pomocą przeglądarki zapalać i gasić diodę podłączoną do układu:
Zadanie 5.4
Dodaj sterowanie diodą do programu stacji pogodowej.
Podsumowanie
W tej części kursu zobaczyliśmy jak wygląda niskopoziomowa komunikacja z modułem przy wykorzystaniu protokołu http. Programy są dość proste, chociaż ich długość szybko rośnie. Większość funkcji działa w podobny sposób - można więc wykorzystać odpowiednie biblioteki, zamiast pisać wszystko od podstaw.
W następnej części zobaczymy gotowe rozwiązanie dostarczane przez Intela. Za jego pomocą będziemy mogli znacznie uprościć sterowanie i odczytywanie danych.
Nawigacja kursu
Nie chcesz przeoczyć kolejnych części kursu? Skorzystaj z poniższego formularza i zapisz się na powiadomienia o nowych artykułach!
Autor kursu: Piotr (Elvis) Bugalski
Redakcja: Damian (Treker) Szymański
Powiązane wpisy
arduino, Edison, Intel, kurs, kursEdison, programowanie
Trwa ładowanie komentarzy...