Działanie serwera WWW
Współpraca serwera WWW z klientem – którym w opisywanej sytuacji jest przeglądarka internetowa – została w sposób uproszczony zobrazowana na rysunku 1. Wymianę komunikatów inicjuje przeglądarka, wysyłając do serwera zapytanie (HTTP Get) o zasób, czyli o stronę internetową. W odpowiedzi serwer zaczyna przesyłać dane żądanej strony (HTTP Response). Po przesłaniu wszystkich składowych witryny – takich jak kod HTML, skrypt Java Scriptu, pliki graficzne, kaskadowe arkusze stylów CSS itp. – połączenie jest kończone. Jeżeli klient chce pobrać kolejną stronę, po kliknięciu np. w link wysyła do serwera kolejne zapytanie. Jeśli serwerem jest moduł ESP32, musi on mieć zapisane w swojej pamięci dane wszystkich obsługiwanych stron. Częścią zapytania – stanowiącego adres URL zasobu – może być również umieszczony w nim dodatkowy kod, np. „/led_on”. Sygnalizuje on modułowi, że pełniąca funkcję interfejsu dioda LED ma zostać załączona; w takim przypadku – oprócz przesłania zawartości strony – moduł włącza zasilanie diody.
Budowa oprogramowania serwera WWW na ESP32
W podanym przykładzie sterowana będzie jedna dioda LED podłączona do portu 2 modułu ESP32. Aby oprogramowanie zaczęło działać, należy je przepisać w kolejności wskazywanej przez listingi. Aplikacja napisana została dla ESP-IDF w wersji 4.4, natomiast sam moduł będzie przystosowany do pracy w sieci jako stacja.
Jak pokazano na listingu 1, na początku kodu znajdują się wszystkie niezbędne pliki nagłówkowe. Oprócz tego kod zawiera deklaracje stałych i zmiennych używanych w programie. Do LED_PIN przypisany został numer linii portu IO sterującego diodą LED, w tym przykładzie jest to wyprowadzenie numer 2. W deklaracjach EXAMPLE_ESP_WIFI_SSID oraz EXAMPLE_ESP_WIFI_PASS podajemy nazwę sieci Wi-Fi, w której będziemy pracować, oraz hasło logowania do niej. Z kolei EXAMPLE_ESP_MAXIMUM_RETRY to deklaracja liczby prób nawiązania (lub – w przypadku jego utraty – odzyskania) połączenia z siecią. Zmienna led_state przechowuje aktualny stan portu IO sterującego LED-em.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include <esp_http_server.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "driver/gpio.h"
#include <lwip/sockets.h>
#include <lwip/sys.h>
#include <lwip/api.h>
#include <lwip/netdb.h>
#define LED_PIN 2
static const char *TAG = "espressif"; // TAG for debug
int led_state = 0;
#define EXAMPLE_ESP_WIFI_SSID "ssid_twojej_sieci"//WIFI SSID
#define EXAMPLE_ESP_WIFI_PASS "haslo_twojej_sieci"//PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY 5//CONFIG_ESP_MAXIMUM_RETRY
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* – we are connected to the AP with an IP
* – we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
Listing 1. Niezbędne pliki nagłówkowe i definicje stałych
Listing 2 zawiera kod HTML generowanej przez serwer strony. Zależnie od stanu portu IO sterującego diodą LED witryna tworzona jest w dwóch wariantach: gdy dioda jest wyłączona – i gdy jest zaświecona. Co do zasady, dane obydwu wariantów są takie same, z wyjątkiem jednej linii
GPIO2
GPIO state: ON
Dla większej przejrzystości kodu miejsce danych, które należy przekopiować z tablicy on_resp[] do off_resp[], oznaczono symbolami (…).
char on_resp[] = "<!DOCTYPE html><html><head> "\
"<title>ESP32 WEB SERVER</title> "\
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> "\
"<style> "\
"html { font-family: Arial; display: inline-block; margin: 0px auto; "\
"text-align: center;} "\
".card {box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); transition: 0.3s; width: 50%;} "\
".container {padding: 12px 16px;} "\
".button { display: inline-block; background-color: #b30000; //red color "\
"border: none; border-radius: 4px; color: white; padding: 16px 40px; "\
"font-size: 30px; margin: 2px;}"\
".button2 { background-color: #364cf4; //blue color}."\
"</style> "\
"</head> "\
"<body> "\
"<div align=\"center\"> "\
"<h2>ESP32 WEB SERVER</h2> "\
"<div align=\"center\" class=\"card\"> "\
"<p><strong>GPIO2</strong></p><p>GPIO state: <strong> ON</strong></p> "\
"<div class=\"container\"> "\
"<a href=\"/led2on\"><button class=\"container button\">ON</button></a> "\
"<a href=\"/led2off\"><button class=\"button button2\">OFF</button></a> "\
"</div></div></div>"
"</body> "\
"</html> ";
char off_resp[] =
(...)
"<p><strong>GPIO2</strong></p><p>GPIO state: <strong> OFF</strong></p> "\
(...)
Listing 2. Kod HTML strony generowanej przez serwer
Budowa strony jest bardzo prosta i typowa. W sekcji „style” umieszczono definicje wyglądu: użytych czcionek, dwóch przycisków sterujących włączeniem i wyłączeniem LED-a oraz kontenera, w którym klawisze zostały umieszczone. Na listingu 2a można zapoznać się ze szczegółami tej sekcji.
<style>
html { font-family: Arial; display: inline-block; margin: 0px auto; text-align: center;}
.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
width: 50%;
}
.container {
padding: 12px 16px;
}
.button { display: inline-block; background-color: #b30000; //red
border: none; border-radius: 4px; color: white; padding: 16px 40px; font-size:
30px; margin: 2px;}
.button2 { background-color: #364cf4; //blue}
</style>
Listing 2a. Sekcja kodu strony WWW konfigurująca styl CSS
Natomiast na listingu 2b pokazano kod działającej strony. Najpierw wyświetlane są napisy, w tym informacja o stanie pinu GPIO sterującego diodą LED („GPIO state:”), a następnie kod dwóch klawiszy „ON” i „OFF”. Po naciśnięciu klawisza „ON” z przeglądarki do serwera wysłane zostaje żądanie GET odświeżenia danych strony wraz z komunikatem „/led2on”, natomiast naciśnięcie klawisza „OFF” wysyła komunikat „/led2off”.
<body>
<div align="center">
<h2>ESP32 WEB SERVER</h2>
<div align="center" class="card">
<p><strong>GPIO2</strong></p>
<p>GPIO state: <strong> OFF</strong></p>
<div class="container">
<a href="/led2on"><button class="container button">ON</button></a>
<a href="/led2off"><button class="button button2">OFF</button></a>
</div>
</div>
</div>
</body>
Listing 2b. Kod strony WWW
Na listingu 3 pokazano funkcję obsługi zdarzeń. Uwzględnione zostały 3 zdarzenia, rozróżniane przekazywanym w wywołaniu funkcji identyfikatorem.
- WIFI_EVENT_STA_START jest zdarzeniem inicjacji dostępu do sieci,
- WIFI_EVENT_STA_DISCONNECTED oznacza zakończoną niepowodzeniem kolejną próbę dostępu do sieci. Po przekroczeniu określonej w EXAMPLE_ESP_MAXIMUM_RETRY liczby prób wysyłany jest na monitor komunikat o błędzie i procedura ponawiania ulega przerwaniu,
- IP_EVENT_STA_GOT_IP jest zdarzeniem uzyskania dostępu do sieci. Na monitor wysyłany jest wówczas komunikat o numerze IP przydzielonym serwerowi w ramach sieci i serwer przechodzi do nasłuchiwania skierowanych do niego zapytań z przeglądarki.
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
}
else
{
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG, "connect to the AP fail");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
Listing 3. Procedura obsługi zdarzeń