ESP8266:Примеры/Веб-сервер, защищенный паролем

Материал из Онлайн справочника
Перейти к навигации Перейти к поиску

Перевод: Максим Кузьмин (Cubewriter) Перевел 364226 статей для сайта.</br>Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Pixel Art Mini Meow Animated.gif Черновик


Веб-сервер, защищенный паролем

В этом руководстве мы создадим на базе ESP8266 веб-сервер, защищенный паролем. Доступ к нему можно будет получить с любого устройства, имеющего браузер. Это значит, что вы сможете управлять GPIO-контактами ESP8266 с ноутбука, смартфона, планшета и т.д.

Наш веб-сервер будет управлять двумя светодиодами. Но это лишь демонстрационный проект, и потом эти светодиоды можно будет заменить на устройство PowerSwitch Tail (это модуль управления питанием) или реле, и с их помощью управлять каким угодно электронным устройством.

Esp8266 secure web server main page 1.PNG

Необходимое оборудование

Как работает код

В самом начале скетча мы подключаем библиотеку «ESP8266WiFi», а в следующих двух строчках задаем SSID и пароль для своей WiFi-сети, чтобы ESP8266 мог к ней подключиться.

1 // Подключаем библиотеку «ESP8266WiFi»:
2 #include <ESP8266WiFi.h>
3 
4 // Пишем SSID и пароль для своей WiFi-сети:
5 const char* ssid = "YOUR_NETWORK_NAME";
6 const char* password = "YOUR_NETWORK_PASSWORD";

Далее создаем объект веб-сервера и задаем ему порт «8888»:

1 // Создаем объект веб-сервера с номером порта «8888»:
2 WiFiServer server(8888);

Создаем переменную «header» для хранения заголовка ответа на запрос, переменные «gpio5_state» и «gpio4_state» для хранения текущего состояния этих GPIO-контактов, а также переменные «gpio5_pin» и «gpio4_pin» для хранения номеров контактов GPIO4 и GPIO5.

1 // Создаем несколько переменных:
2 String header;
3 String gpio5_state = "Off";
4 String gpio4_state = "Off";
5 int gpio5_pin = 5;
6 int gpio4_pin = 4;

Далее создаем блок setup(), код в котором будет запущен только раз – при запуске ESP8266.

1 // Код в этом блоке будет запущен только один раз:
2 void setup() {
3 }

Запускаем последовательную коммуникацию на скорости 115200 бод (для отладки). Переключаем оба GPIO-контакта в режим вывода данных (OUTPUT) и задаем им значение «LOW».

 1 // Этот код будет запущен только один раз:
 2 void setup() {
 3   // Инициализируем последовательную коммуникацию (для отладки): 
 4   Serial.begin(115200);
 5   delay(10);
 6 
 7   // Подготавливаем GPIO-контакты:
 8   pinMode(gpio5_pin, OUTPUT);
 9   digitalWrite(gpio5_pin, LOW);
10   pinMode(gpio4_pin, OUTPUT);
11   digitalWrite(gpio4_pin, LOW);

В следующем фрагменте кода запускаем подключение к WiFi-сети, ждем успешного подключения и печатаем в мониторе порта IDE Arduino IP-адрес ESP8266.

 1 // Подключаемся к WiFi-сети:
 2   Serial.println();
 3   Serial.print("Connecting to ");
 4            //  "Подключаемся к "
 5 
 6   Serial.println(ssid);
 7 
 8   WiFi.begin(ssid, password);
 9 
10   while (WiFi.status() != WL_CONNECTED) {
11   delay(500);
12   Serial.print(".");
13   }
14   Serial.println("");
15   Serial.println("WiFi connected");
16              //  "Подключились к WiFi"
17 
18   // Запускаем веб-сервер:
19   server.begin();
20   Serial.println("Web server running. Waiting for the ESP IP...");
21              //  "Веб-сервер запущен. 
22              //  "Ждем получения IP-адреса ESP8266..."
23   delay(1000);
24 
25   // Печатаем IP-адрес ESP8266:
26   Serial.println(WiFi.localIP());
27 }

Блок loop() отвечает за то, что будет происходить, когда новый клиент установит соединение с веб-сервером. То есть код в loop() будет постоянно прослушивать новых клиентов. Соответственно, если к веб-серверу подключится новый клиент, он начнет подключаться к нему.

1 // Код в блоке loop() будет постоянно повторяться:
2 void loop() {
3 // Прослушиваем новых клиентов:
4  WiFiClient client = server.available();

Булева переменная «blank_line» ниже служит для того, чтобы определить конец HTTP-запроса. Кроме того, в этом фрагменте кода используется цикл while(), который будет работать все то время, пока клиент будет подключен к веб-серверу.

1 if (client) {
2     Serial.println("New client");  //  "Новый клиент"
3     // Булева переменная, предназначенная
4     // для определения конца HTTP-запроса:
5     boolean blank_line = true;
6     while (client.connected()) {
7       if (client.available()) {

Теперь давайте добавим в веб-сервер механизм аутентификации, чтобы сделать его более безопасным. После этого любому, кто попытается получить доступ к веб-серверу, придется ввести имя пользователя и пароль.

По умолчанию именем пользователя будет «user», а паролем – «pass». Чуть ниже я расскажу о том, как их поменять. Если пользователь введет правильные имя пользователя и пароль, то получит доступ к веб-странице для управления GPIO-контактами ESP8266.

1 // Проверяем, правильные ли введены имя пользователя и пароль:
2 if(header.indexOf("dXNlcjpwYXNz") >= 0) {

В следующем фрагменте кода проверяется, на какую кнопку веб-страницы нажал пользователь. По сути, в нем проверяется, какой вы хотите открыть URL.

К примеру, кликая на кнопку «OFF» для контакта GPIO5, вы открываете ссылку «http://192.168.1.22:8888/gpio5off». Код смотрит на эту ссылку и при помощи нескольких else if() сверяется с тем, что ему нужно сделать. В случае со ссылкой «http://192.168.1.22:8888/gpio5off» он переключит контакт GPIO5 («gpio5_pin») в состояние «LOW».

 1           // Проверяем, правильные ли введены
 2           // имя пользователя и пароль:
 3           if(header.indexOf("dXNlcjpwYXNz") >= 0) {
 4             // Залогинивание прошло успешно:
 5             client.println("HTTP/1.1 200 OK");
 6             client.println("Content-Type: text/html");
 7             client.println("Connection: close");
 8                        //  "Соединение: отключено"
 9             client.println();
10             // Включаем/выключаем GPIO-контакты:
11             if(header.indexOf("GET / HTTP/1.1") >= 0) {
12                 Serial.println("Main Web Page");
13                            //  "Главная веб-страница"
14             }   
15             else if(header.indexOf("GET /gpio5on HTTP/1.1") >= 0){
16                 Serial.println("GPIO 5 On");
17                            //  "Контакт GPIO5 включен"
18                 gpio5_state = "On";
19                 digitalWrite(gpio5_pin, HIGH);
20             }
21             else if(header.indexOf("GET /gpio5off HTTP/1.1") >= 0){
22                 Serial.println("GPIO 5 Off");
23                            //  "Контакт GPIO5 выключен"
24                 gpio5_state = "Off";
25                 digitalWrite(gpio5_pin, LOW);
26             }
27             else if(header.indexOf("GET /gpio4on HTTP/1.1") >= 0){
28                 Serial.println("GPIO 4 On");
29                            //  "Контакт GPIO4 включен"
30                 gpio4_state = "On";
31                 digitalWrite(gpio4_pin, HIGH);
32             }
33             else if(header.indexOf("GET /gpio4off HTTP/1.1") >= 0){
34                 Serial.println("GPIO 4 Off");
35                            //  "Контакт GPIO4 выключен"
36                 gpio4_state = "Off";
37                 digitalWrite(gpio4_pin, LOW);
38             }

Страница отправляется клиенту при помощи метода client.println(). Это очень простая веб-страница, использующая фреймворк Bootstrap (см. код ниже).

Более подробно о Bootstrap можно почитать по этой ссылке.

На веб-странице будет 4 кнопки для включения/выключения (переключения между состояниями «HIGH» и «LOW») двух светодиодов, подключенных к контактам GPIO5 и GPIO4.

Кнопки – это HTML-теги <a href=””></a> с CSS-классом, который придает им вид кнопки. Когда вы нажимаете на кнопку, вам открывается другая страница с URL, соответствующим этой кнопке. Именно так ESP8266 и узнает, что ей нужно сделать (т.е. включить или выключить светодиод, и какой именно).

 1 // Ваша веб-страница:
 2 client.println("<!DOCTYPE HTML>");
 3 client.println("<html>");
 4 client.println("<head>");
 5 client.println("<meta name=\"viewport\" content=\"width=device-width, initialscale=1\">");
 6 client.println("<link rel=\"stylesheet\"
 7 href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css\">");
 8 client.println("</head><div class=\"container\">");
 9 client.println("<h1>Web Server</h1>");
10 client.println("<h2>GPIO 5 - Current State: " + gpio5_state);
11 client.println("<div class=\"row\">");
12 client.println("<div class=\"col-md-2\"><a href=\"/gpio5on\" class=\"btn btn-block
13 btn-lg btn-success\" role=\"button\">ON</a></div>");
14 client.println("<div class=\"col-md-2\"><a href=\"/gpio5off\" class=\"btn btn-block
15 btn-lg btn-danger\" role=\"button\">OFF</a></div>");
16 client.println("</div>");
17 client.println("<h2>GPIO 4 - Current State: " + gpio4_state);
18 client.println("<div class=\"row\">");
19 client.println("<div class=\"col-md-2\"><a href=\"/gpio4on\" class=\"btn btn-block
20 btn-lg btn-success\" role=\"button\">ON</a></div>");
21 client.println("<div class=\"col-md-2\"><a href=\"/gpio4off\" class=\"btn btn-block
22 btn-lg btn-danger\" role=\"button\">OFF</a></div>");
23 client.println("</div></div></html>");

Если вы введете неправильные имя пользователя и пароль, в браузере будет напечатано сообщение «Authentication failed» («Аутентификация не удалась»).

1 // Неправильные имя пользователя и пароль,
2 // поэтому HTTP-запрос завершился неудачей...
3 else {
4  client.println("HTTP/1.1 401 Unauthorized");
5  client.println("WWW-Authenticate: Basic realm=\"Secure\"");
6  client.println("Content-Type: text/html");
7  client.println();
8  client.println("<html>Authentication failed</html>");
9 }

Строчки кода в конце скетча очищают переменную «header», завершают цикл loop() и завершают подключение.

 1         header = "";
 2         break;
 3         }
 4         if (c == '\n') {
 5           // Когда нужно перейти к следующей строчке:
 6           blank_line = true;
 7         }
 8         else if (c != '\r') {
 9           // Когда в текущей строке найден символ: 
10           blank_line = false;
11         }
12       }
13     }  
14     // Завершаем подключение к клиенту:
15     delay(1);
16     client.stop();
17     Serial.println("Client disconnected.");
18                //  "Клиент отключился."
19   }
20 }

Как задать собственные имя пользователя и пароль

В скетче, который мы разобрали выше, именем пользователя является «user», а паролем – «pass». Уверен, вы захотите задать вместо них собственные имя пользователя и пароль. Пройдите по этой ссылке. В первом поле впишите следующее:

your_username:your_password

Примечание: Обратите внимание, что между именем пользователя и паролем нужно вписать «:».

В моем случае это «user:pass».

Затем нажмите на зеленую кнопку «Encode» – это сгенерирует строку в формате Base64. В моем случае это «dXNlcjpwYXNz».

Скопируйте ее и вставьте в это место скетча:

1 // Проверяем, правильные ли введены имя пользователя и пароль:
2 if(header.indexOf("dXNlcjpwYXNz") >= 0) {

Совет: Эта 80-ая строчка скетча, и в ней также присутствует оператор if().

Загружаем код

Подключите ESP8266 к ПК и откройте IDE Arduino. Кликните по «Инструменты» > «Плата» (Tools > Board) и выберите подключенную плату.

Выберите COM-порт, к которому подключена ваша плата.

Проверив все настройки, нажмите на кнопку «Загрузка» (Upload):

Upload button.png

Подождите несколько секунд, пока не увидите сообщение «Загрузка завершена» (Done uploading) в левом нижнем углу IDE Arduino.

Копируем IP-адрес ESP8266

Загрузив веб-серверный скетч на ESP8266, кликните в IDE Arduino на «Инструменты» > «Монитор порта» (Tools > Serial Monitor). Монитор порта – это окно, где ESP8266, загрузившись, напечатает свой IP-адрес.

Мой IP-адрес – это «192.168.1.22» (см. на скриншоте ниже). Ваш IP-адрес, скорее всего, будет другим. Сохраните его, т.к. позднее вы с его помощью перейдете на страницу веб-сервера.

Esp8266 protected web server serial monitor ip address 1.PNG

Важно: Выставьте скорость монитора порта на 115200 бод, иначе вы просто ничего не увидите.

Проблема – В мониторе порта не печатается IP-адрес

Если у вас возникла такая проблема, можно попробовать сделать следующее:

  1. Оставьте монитор порта открытым;
  2. ESP-12E (или FTDI-программатор) должен оставаться подключенным к ПК;
  3. Перезапустите ESP-12E, нажав на встроенную в него кнопку.

Спустя секунду ESP8266 должна напечатать в мониторе порта свой IP-адрес.

Устанавливаем программу для сканирования IP-адресов

Если IP-адрес в мониторе порта по-прежнему не появляется, то вам нужно будет установить на свой ПК программу для сканирования IP-адресов. Она будет искать все девайсы в вашей сети.

  1. Загрузите ее (это бесплатно). Можете выбрать между Advanced IP Scanner (Windows) и Angry IP Scanner (MAC OS X, Windows или Linux);
  2. Установите программу (в это время ESP8266 должен быть включен, и на нем должен работать скетч, написанный нами выше);
  3. Откройте программу для сканирования IP-адресов и нажмите на кнопку «Сканировать» (Scan);

Дайте процессу завершиться (это должно занять несколько минут).

Заходим на веб-сервер

Перед заходом на веб-сервер нужно сделать следующее:

  • Перезапустите ESP8266;
  • Откройте браузер;
  • Впишите в его адресную строку IP-адрес, скопированный ранее из монитора порта IDE Arduino, и добавьте в конце «8888» (в моем случае – «192.168.1.22:8888»);
  • Нажмите на  ↵ Enter .

После этого в браузере должно выскочить окно с запросом ввести имя пользователя и пароль:

Esp8266 secure web server auth window 1.PNG
  • Введите имя пользователя и пароль
  • Залогиньтесь

В результате в браузере должна загрузиться примерно такая страница:

Esp8266 secure web server main page 1.PNG

Примечание: Чтобы получить доступ к веб-серверу, устройство с браузером должно быть подключено к тому же роутеру, что и ESP8266.

Схема

Загрузив код на ESP8266, подключите компоненты друг к другу, как показано на схеме ниже (для подключения светодиодов можно воспользоваться резисторами номиналом между 270 Ом и 470 Ом).

Esp8266 two leds protected web server 1.png

Код

  1 /*********
  2   Rui Santos
  3   Complete project details at http://randomnerdtutorials.com  
  4 *********/
  5 
  6 // Including the ESP8266 WiFi library
  7 #include <ESP8266WiFi.h>
  8 
  9 // Replace with your network details
 10 const char* ssid = "YOUR_NETWORK_NAME";
 11 const char* password = "YOUR_NETWORK_PASSWORD";
 12 
 13 // Web Server on port 8888
 14 WiFiServer server(8888);
 15 
 16 // variables
 17 String header;
 18 String gpio5_state = "Off";
 19 String gpio4_state = "Off";
 20 int gpio5_pin = 5;
 21 int gpio4_pin = 4;
 22 
 23 // only runs once
 24 void setup() {
 25   // Initializing serial port for debugging purposes
 26   Serial.begin(115200);
 27   delay(10);
 28   
 29   // preparing GPIOs
 30   pinMode(gpio5_pin, OUTPUT);
 31   digitalWrite(gpio5_pin, LOW);
 32   pinMode(gpio4_pin, OUTPUT);
 33   digitalWrite(gpio4_pin, LOW);
 34   
 35   // Connecting to WiFi network
 36   Serial.println();
 37   Serial.print("Connecting to ");
 38   Serial.println(ssid);
 39   
 40   WiFi.begin(ssid, password);
 41   
 42   while (WiFi.status() != WL_CONNECTED) {
 43     delay(500);
 44     Serial.print(".");
 45   }
 46   Serial.println("");
 47   Serial.println("WiFi connected");
 48   
 49   // Starting the web server
 50   server.begin();
 51   Serial.println("Web server running. Waiting for the ESP IP...");
 52   delay(1000);
 53   
 54   // Printing the ESP IP address
 55   Serial.println(WiFi.localIP());
 56 }
 57 
 58 // runs over and over again
 59 void loop() {
 60   // Listenning for new clients
 61   WiFiClient client = server.available();
 62   
 63   if (client) {
 64     Serial.println("New client");
 65     // boolean to locate when the http request ends
 66     boolean blank_line = true;
 67     while (client.connected()) {
 68       if (client.available()) {
 69         char c = client.read();
 70         header += c;
 71   
 72         if (c == '\n' && blank_line) {
 73 
 74           // checking if header is valid
 75           // dXNlcjpwYXNz = 'user:pass' (user:pass) base64 encode
 76     
 77           Serial.print(header);
 78           
 79           // Finding the right credential string
 80           if(header.indexOf("dXNlcjpwYXNz") >= 0) {
 81             //successful login
 82             client.println("HTTP/1.1 200 OK");
 83             client.println("Content-Type: text/html");
 84             client.println("Connection: close");
 85             client.println();
 86             // turns the GPIOs on and off
 87             if(header.indexOf("GET / HTTP/1.1") >= 0) {
 88                 Serial.println("Main Web Page");
 89             }   
 90             else if(header.indexOf("GET /gpio5on HTTP/1.1") >= 0){
 91                 Serial.println("GPIO 5 On");
 92                 gpio5_state = "On";
 93                 digitalWrite(gpio5_pin, HIGH);
 94             }
 95             else if(header.indexOf("GET /gpio5off HTTP/1.1") >= 0){
 96                 Serial.println("GPIO 5 Off");
 97                 gpio5_state = "Off";
 98                 digitalWrite(gpio5_pin, LOW);
 99             }
100             else if(header.indexOf("GET /gpio4on HTTP/1.1") >= 0){
101                 Serial.println("GPIO 4 On");
102                 gpio4_state = "On";
103                 digitalWrite(gpio4_pin, HIGH);
104             }
105             else if(header.indexOf("GET /gpio4off HTTP/1.1") >= 0){
106                 Serial.println("GPIO 4 Off");
107                 gpio4_state = "Off";
108                 digitalWrite(gpio4_pin, LOW);
109             }
110             // your web page
111             client.println("<!DOCTYPE HTML>");
112             client.println("<html>");
113             client.println("<head>");
114             client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
115             client.println("<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css\">");                                                                    
116             client.println("</head><div class=\"container\">");
117             client.println("<h1>Web Server</h1>");
118             client.println("<h2>GPIO 5 - Current State: " + gpio5_state);          
119             client.println("<div class=\"row\">");
120             client.println("<div class=\"col-md-2\"><a href=\"/gpio5on\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>");
121             client.println("<div class=\"col-md-2\"><a href=\"/gpio5off\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>");                                                                    
122             client.println("</div>");
123             client.println("<h2>GPIO 4 - Current State: " + gpio4_state);      
124             client.println("<div class=\"row\">");
125             client.println("<div class=\"col-md-2\"><a href=\"/gpio4on\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>");                                                                    
126             client.println("<div class=\"col-md-2\"><a href=\"/gpio4off\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>");
127             client.println("</div></div></html>");    
128           } 
129        // wrong user or passm, so http request fails...   
130         else {            
131            client.println("HTTP/1.1 401 Unauthorized");
132            client.println("WWW-Authenticate: Basic realm=\"Secure\"");
133            client.println("Content-Type: text/html");
134            client.println();
135            client.println("<html>Authentication failed</html>");
136         }   
137         header = "";
138         break;
139         }
140         if (c == '\n') {
141           // when starts reading a new line
142           blank_line = true;
143         }
144         else if (c != '\r') {
145           // when finds a character on the current line
146           blank_line = false;
147         }
148       }
149     }  
150     // closing the client connection
151     delay(1);
152     client.stop();
153     Serial.println("Client disconnected.");
154   }
155 }

См.также

Внешние ссылки