От переводчика. Автор руководства медленно, но неуклонно ведёт заинтересованного читателя к пониманию работы веб-серверов на Arduino и всему из этого вытекающему: поняв как это работает, можно самостоятельно создавать очень интересные IoT проекты, функционал которых ограничен только вашей фантазией.

На этом уроке рассматривается динамическое отображение на веб-странице состояния кнопок и аналогового входа при помощи AJAX. Скетч из этого урока можно легко модифицировать для отображения любого (в разумных пределах, конечно) количества кнопок и аналоговых входов, а также доработать код для отображения других параметров контроллера Arduino.


На этом уроке мы рассмотрим отображение на веб-странице текущего состояния двух кнопок и одного аналогового входа контроллера Arduino.

JavaScript используется для выполнения периодических AJAX запросов к веб-серверу о состоянии кнопок и аналогового входа.

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


Принципиальная электрическая схема


На нижеприведенной схеме показано подключение кнопок (на пины D7 и D8) к контроллеру Arduino с платой Ethernet Shield. Потенциометр подключен к аналоговому входу A2 и при его помощи можно изменять напряжение на этом входе и отображать его текущее значение на веб-странице.

Принципиальная схема подключений кнопок и потенциометра к плате Arduino:



Скетч


Скетч для этого урока — это модифицированная версия скетча из предыдущей статьи.

/*--------------------------------------------------------------
  Скетч:      eth_websrv_AJAX_IN

  Описание:  Arduino веб-сервер, отображающий состояние двух кнопок и аналогового входа на веб-странице при помощи AJAX.
  
  Оборудование: контроллер Arduino Uno, плата Ethernet Shield, 2 кнопки, потенциометр.
                
  Программное обеспечение: среда разработки Arduino IDE
  
  Ссылки:
    - WebServer example by David A. Mellis and modified by Tom Igoe
    - Ethernet library documentation: http://arduino.cc/en/Reference/Ethernet
    - Learning PHP, MySQL & JavaScript by Robin Nixon, O'Reilly publishers

  Дата создания:         20 февраля 2013
 
  Автор:       W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10, 0, 0, 20); // IP-адрес (нужно изменить на актуальный для вашей сети)
EthernetServer server(80);

String HTTP_req; // для хранения HTTP запроса

void setup() {
    Ethernet.begin(mac, ip);
    server.begin();
    Serial.begin(115200);
    pinMode(7, INPUT); // кнопка подключена к плате Arduino на пин D7
    pinMode(8, INPUT); // кнопка подключена к плате Arduino на пин D8
}

void loop() {
    EthernetClient client = server.available();  // try to get client

    if (client) {
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {
                char c = client.read(); // получаем очередной байт (символ) от клиента
                HTTP_req += c; // сохраняем символ HTTP запроса
                if (c == '\n' && currentLineIsBlank) {
                    // Посылаем http заголовок
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/html");
                    client.println("Connection: keep-alive");
                    client.println();

                    if (HTTP_req.indexOf("ajax_switch") > -1) {
                        // AJAX запрос
                        GetAjaxData(client);
                    } else {
                        // Посылка веб-страницы, содержащей JavaScript код и AJAX вызовы
                        client.println("<!DOCTYPE html>");
                        client.println("<html>");
                        client.println("<head>");
                        client.println("<title>Arduino Web Page</title>");
                        client.println("<script>");
                        client.println("function GetSwitchAnalogData() {");
                        client.println(
                            "nocache = \"&nocache=\" + Math.random() * 1000000;");
                        client.println("var request = new XMLHttpRequest();");
                        client.println("request.onreadystatechange = function() {");
                        client.println("if (this.readyState == 4) {");
                        client.println("if (this.status == 200) {");
                        client.println("if (this.responseText != null) {");
                        client.println("document.getElementById(\"sw_an_data\")\
.innerHTML = this.responseText;");
                        client.println("}}}}");
                        client.println(
                        "request.open(\"GET\", \"ajax_switch\" + nocache, true);");
                        client.println("request.send(null);");
                        client.println("setTimeout('GetSwitchAnalogData()', 1000);");
                        client.println("}");
                        client.println("</script>");
                        client.println("</head>");

                        // Тело веб-страницы
                        client.println("<body onload=\"GetSwitchAnalogData()\">");
                        client.println("<h1>Arduino AJAX Input</h1>");
                        client.println("<div id=\"sw_an_data\">");
                        client.println("</div>");
                        client.println("</body>");
                        client.println("</html>");
                    }
                    // Выводим принятый HTTP запрос в Serial
                    Serial.print(HTTP_req);
                    HTTP_req = "";
                    break;
                }
                if (c == '\n') {
                    currentLineIsBlank = true;
                } else if (c != '\r') {
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);
        client.stop();
    } // end if (client)
}

// Определение состояния цифровых и аналогового входов и посылка данных о них в браузер

void GetAjaxData(EthernetClient cl) {
    int analog_val;
    
    if (digitalRead(7)) {
        cl.println("<p>Switch 7 state: ON</p>");
    } else {
        cl.println("<p>Switch 7 state: OFF</p>");
    }

    if (digitalRead(8)) {
        cl.println("<p>Switch 8 state: ON</p>");
    } else {
        cl.println("<p>Switch 8 state: OFF</p>");
    }

    // Работа с аналоговым входом A2

    analog_val = analogRead(2);
    cl.print("<p>Analog A2: ");
    cl.print(analog_val);
    cl.println("</p>");
}

Код веб-страницы


Вышеприведенный Arduino скетч создает и отправляет в браузер следующий HTML код:



Изменения в скетче (относительно версии из предыдущего урока)


Пины D7 и D8 контроллера Arduino настраиваются как входы в разделе setup() скетча.

    pinMode(7, INPUT); // кнопка подключена к плате Arduino на пин D7
    pinMode(8, INPUT); // кнопка подключена к плате Arduino на пин D8

Была переименована JavaScript функция, которая обрабатывает AJAX вызовы.

GetSwitchAnalogData()

Также была переименована Arduino функция, отвечающая на AJAX запросы.

void GetAjaxData(EthernetClient cl) {

Под заголовком H1 в HTML коде создан элемент div с идентификатором «sw_an_data». Сам тег div не отображается на странице, но служит местом для размещения JavaScript информации (данных о состоянии кнопок и аналогового входа, получаемых от Arduino).

Отправка запроса из браузера


JavaScript функция GetSwitchAnalogData() вызывается каждую секунду и каждую секунду отправляет GET запросы на Arduino веб-сервер.

Получение и обработка AJAX запроса на Arduino


Когда Arduino сервер определяет, что получил AJAX запрос, то вызывает функцию GetAjaxData(). Эта функция считывает состояние двух кнопок и отправляет их состояние (ON или OFF) в ответ браузеру. Эта функция также считывает значение аналогового входа A2 и отправляет эти данные браузеру.

Отображение данных в веб-браузере


Когда веб-браузер получает данные, запрошенные им у Arduino сервера, он просто вставляет их в тег div с идентификатором sw_an_data.

От переводчика о 7-й части


Начинающим трудно понять работу этой системы потому, что она довольно «хитро» устроена — Javascript функция вызывает сама себя и при этом имеет участки кода, которые вступают в работу отложенно, только после после поступления ответа от сервера Arduino. А сервер Arduino, в свою очередь, не просто посылает HTML код веб-страницы, а «синтезирует» ещё и сам Javascript код, который потом будет взаимодействовать с самим Arduino сервером.

У начинающих, с непривычки, ум может зайти за разум от таких «наворотов». Для начала я бы посоветовал обратить внимание на следующий участок Arduino кода:

                    if (HTTP_req.indexOf("ajax_switch") > -1) {
                        // AJAX запрос
                        GetAjaxData(client);
                    } else {
                        // Посылка веб-страницы, содержащей JavaScript код и AJAX вызовы
                        client.println("<!DOCTYPE html>");

Это один из «узловых» моментов всей логики работы системы, который нужно хорошо понимать: здесь веб-сервер определяет какой запрос к нему пришёл — если это «простой» GET запрос, то он выдаёт браузеру веб-страницу, а если этот AJAX запрос, то он выдаёт только небольшой ответ с AJAX данными о состоянии кнопок и аналогового входа.

Важно понимать: веб-страницу сервер выдаёт один раз в начале «сеанса», а затем (массово и беспрерывно) посылает только AJAX ответы на запросы им же сгенерированного Javascript кода внутри этой веб-страницы.

Лучше понять логику сетевого взаимодействия можно, если нажать (в браузере Firefox) клавишу F12 и выбрать вкладку «Network».

Часть 1, часть 2, часть 3, часть 4, часть 5, часть 6.


Комментарии (1)


  1. solderman
    00.00.0000 00:00
    +2

    Под rawliteral html страницы выглядит как нормальный html. И места занимает меньше. Например так. Кстати еще и PROGMEM не помешает.

    const char html_page[] PROGMEM = R"rawliteral(

    <!DOCTYPE HTML>

    <html>

    <head>

    <meta name="viewport" content="width=device-width, initial-scale=1">

    </head>

    <body>

    </body>

    </html>

    )rawliteral";