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

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

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Черновик


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

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

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

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

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

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

// Подключаем библиотеку «ESP8266WiFi»:
#include <ESP8266WiFi.h>

// Пишем SSID и пароль для своей WiFi-сети:
const char* ssid = "YOUR_NETWORK_NAME";
const char* password = "YOUR_NETWORK_PASSWORD";

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

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

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

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

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

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

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

// Этот код будет запущен только один раз:
void setup() {
  // Инициализируем последовательную коммуникацию (для отладки): 
  Serial.begin(115200);
  delay(10);

  // Подготавливаем GPIO-контакты:
  pinMode(gpio5_pin, OUTPUT);
  digitalWrite(gpio5_pin, LOW);
  pinMode(gpio4_pin, OUTPUT);
  digitalWrite(gpio4_pin, LOW);

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

// Подключаемся к WiFi-сети:
  Serial.println();
  Serial.print("Connecting to ");
           //  "Подключаемся к "

  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
             //  "Подключились к WiFi"

  // Запускаем веб-сервер:
  server.begin();
  Serial.println("Web server running. Waiting for the ESP IP...");
             //  "Веб-сервер запущен. 
             //  "Ждем получения IP-адреса ESP8266..."
  delay(1000);

  // Печатаем IP-адрес ESP8266:
  Serial.println(WiFi.localIP());
}

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

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

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

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

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

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

// Проверяем, правильные ли введены имя пользователя и пароль:
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».

          // Проверяем, правильные ли введены
          // имя пользователя и пароль:
          if(header.indexOf("dXNlcjpwYXNz") >= 0) {
            // Залогинивание прошло успешно:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close");
                       //  "Соединение: отключено"
            client.println();
            // Включаем/выключаем GPIO-контакты:
            if(header.indexOf("GET / HTTP/1.1") >= 0) {
                Serial.println("Main Web Page");
                           //  "Главная веб-страница"
            }   
            else if(header.indexOf("GET /gpio5on HTTP/1.1") >= 0){
                Serial.println("GPIO 5 On");
                           //  "Контакт GPIO5 включен"
                gpio5_state = "On";
                digitalWrite(gpio5_pin, HIGH);
            }
            else if(header.indexOf("GET /gpio5off HTTP/1.1") >= 0){
                Serial.println("GPIO 5 Off");
                           //  "Контакт GPIO5 выключен"
                gpio5_state = "Off";
                digitalWrite(gpio5_pin, LOW);
            }
            else if(header.indexOf("GET /gpio4on HTTP/1.1") >= 0){
                Serial.println("GPIO 4 On");
                           //  "Контакт GPIO4 включен"
                gpio4_state = "On";
                digitalWrite(gpio4_pin, HIGH);
            }
            else if(header.indexOf("GET /gpio4off HTTP/1.1") >= 0){
                Serial.println("GPIO 4 Off");
                           //  "Контакт GPIO4 выключен"
                gpio4_state = "Off";
                digitalWrite(gpio4_pin, LOW);
            }

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

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

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

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

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

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

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

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

        header = "";
        break;
        }
        if (c == '\n') {
          // Когда нужно перейти к следующей строчке:
          blank_line = true;
        }
        else if (c != '\r') {
          // Когда в текущей строке найден символ: 
          blank_line = false;
        }
      }
    }  
    // Завершаем подключение к клиенту:
    delay(1);
    client.stop();
    Serial.println("Client disconnected.");
               //  "Клиент отключился."
  }
}

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

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

your_username:your_password
Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Важно

Выставьте скорость монитора порта на 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.

Схема

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

Код

/*********
  Rui Santos
  Complete project details at http://randomnerdtutorials.com  
*********/

// Including the ESP8266 WiFi library
#include <ESP8266WiFi.h>

// Replace with your network details
const char* ssid = "YOUR_NETWORK_NAME";
const char* password = "YOUR_NETWORK_PASSWORD";

// Web Server on port 8888
WiFiServer server(8888);

// variables
String header;
String gpio5_state = "Off";
String gpio4_state = "Off";
int gpio5_pin = 5;
int gpio4_pin = 4;

// only runs once
void setup() {
  // Initializing serial port for debugging purposes
  Serial.begin(115200);
  delay(10);
  
  // preparing GPIOs
  pinMode(gpio5_pin, OUTPUT);
  digitalWrite(gpio5_pin, LOW);
  pinMode(gpio4_pin, OUTPUT);
  digitalWrite(gpio4_pin, LOW);
  
  // Connecting to WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  // Starting the web server
  server.begin();
  Serial.println("Web server running. Waiting for the ESP IP...");
  delay(1000);
  
  // Printing the ESP IP address
  Serial.println(WiFi.localIP());
}

// runs over and over again
void loop() {
  // Listenning for new clients
  WiFiClient client = server.available();
  
  if (client) {
    Serial.println("New client");
    // boolean to locate when the http request ends
    boolean blank_line = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        header += c;
  
        if (c == '\n' && blank_line) {

          // checking if header is valid
          // dXNlcjpwYXNz = 'user:pass' (user:pass) base64 encode
    
          Serial.print(header);
          
          // Finding the right credential string
          if(header.indexOf("dXNlcjpwYXNz") >= 0) {
            //successful login
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close");
            client.println();
            // turns the GPIOs on and off
            if(header.indexOf("GET / HTTP/1.1") >= 0) {
                Serial.println("Main Web Page");
            }   
            else if(header.indexOf("GET /gpio5on HTTP/1.1") >= 0){
                Serial.println("GPIO 5 On");
                gpio5_state = "On";
                digitalWrite(gpio5_pin, HIGH);
            }
            else if(header.indexOf("GET /gpio5off HTTP/1.1") >= 0){
                Serial.println("GPIO 5 Off");
                gpio5_state = "Off";
                digitalWrite(gpio5_pin, LOW);
            }
            else if(header.indexOf("GET /gpio4on HTTP/1.1") >= 0){
                Serial.println("GPIO 4 On");
                gpio4_state = "On";
                digitalWrite(gpio4_pin, HIGH);
            }
            else if(header.indexOf("GET /gpio4off HTTP/1.1") >= 0){
                Serial.println("GPIO 4 Off");
                gpio4_state = "Off";
                digitalWrite(gpio4_pin, LOW);
            }
            // your web page
            client.println("<!DOCTYPE HTML>");
            client.println("<html>");
            client.println("<head>");
            client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css\">");                                                                    
            client.println("</head><div class=\"container\">");
            client.println("<h1>Web Server</h1>");
            client.println("<h2>GPIO 5 - Current State: " + gpio5_state);          
            client.println("<div class=\"row\">");
            client.println("<div class=\"col-md-2\"><a href=\"/gpio5on\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>");
            client.println("<div class=\"col-md-2\"><a href=\"/gpio5off\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>");                                                                    
            client.println("</div>");
            client.println("<h2>GPIO 4 - Current State: " + gpio4_state);      
            client.println("<div class=\"row\">");
            client.println("<div class=\"col-md-2\"><a href=\"/gpio4on\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>");                                                                    
            client.println("<div class=\"col-md-2\"><a href=\"/gpio4off\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>");
            client.println("</div></div></html>");    
          } 
       // wrong user or passm, so http request fails...   
        else {            
           client.println("HTTP/1.1 401 Unauthorized");
           client.println("WWW-Authenticate: Basic realm=\"Secure\"");
           client.println("Content-Type: text/html");
           client.println();
           client.println("<html>Authentication failed</html>");
        }   
        header = "";
        break;
        }
        if (c == '\n') {
          // when starts reading a new line
          blank_line = true;
        }
        else if (c != '\r') {
          // when finds a character on the current line
          blank_line = false;
        }
      }
    }  
    // closing the client connection
    delay(1);
    client.stop();
    Serial.println("Client disconnected.");
  }
}

См.также

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