Starting Electronics: руководство по веб-серверам на Arduino. Часть 5. AJAX взаимодействие с веб-сервером

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


От переводчика. С каждой новой статьёй уроки руководство становится всё более и более практичным и вот мы уже добрались до AJAX взаимодействия между клиентом (браузером) и сервером. Отсюда всего один шаг до практического применения кода и знаний, описанных в этом руководстве.

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


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

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

В этом видео показано, как Arduino веб-сервер отображает состояние кнопки с помощью AJAX.


Что такое AJAX?


AJAX — это асинхронный JavaScript и XML.

AJAX использует JavaScript функции для обмена информацией с веб-сервером (в данном случае, на Arduino). Это позволяет обновляться данным на веб-странице, не перезагружая каждый раз саму страницу.

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

Что такое JavaScript?


JavaScript — это язык сценариев, исполняющихся на стороне клиента. То есть JavaScript код будет работать в браузере.

В нашем случае JavaScript код включается непосредственно в HTML страницу. Когда вы загружаете и просматриваете веб-страницу, вместе с ней в ваш браузер загружается и JavaScript код. После загрузки страницы браузер запускает полученный JavaScript код на исполнение (при условии, что вы не отключили JavaScript в своем браузере).

Оборудование веб-сервера


Оборудование для этого урока:

  • Контроллер Arduino Uno
  • Плата Ethernet Shield
  • Кнопка
  • Резистор 10 кОм
  • Соединительные провода

Кнопка подключается к плате Arduino/Ethernet Shield так, как показано на принципиальной схеме ниже. В изначальном состоянии вывод D3 контроллера подтянут к земле при помощи резистора 10 кОм (низкий потенциал, LOW или «0»), а после нажатия кнопки на вывод D3 подаётся высокий потенциал (HIGH или «1»).



Скетч Arduino с использованием AJAX


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

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

  Описание:  Arduino веб-сервер отображающий состояние переключателя на веб-странице при помощи AJAX. Состояние переключателя может быть получено по клику на кнопку на веб-странице.

  Оборудование: контроллер Arduino Uno, плата Ethernet Shield, кнопка.
                
  Программное обеспечение: среда разработки 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

  Дата создания:         15 January 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(3, INPUT); // кнопка подключена к плате Arduino на пин D3
}

void loop() {
    EthernetClient client = server.available();

    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 запрос состояния кнопки
                        GetSwitchState(client); // определение и посылка клиенту состояния кнопки
                    } else {
                        // Посылка веб-страницы, содержащей JavaScript код и AJAX вызовы
                        client.println("<!DOCTYPE html>");
                        client.println("<html>");

                        // Заголовок веб-страницы и встроенный в неё код JavaScript
                        client.println("<head>");
                        client.println("<title>Arduino Web Page</title>");
                        client.println("<script>");
                        client.println("function GetSwitchState() {");
                        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(\"switch_txt\")\
.innerHTML = this.responseText;");
                        client.println("}}}}");
                        client.println("request.open(\"GET\", \"ajax_switch\" + nocache, true);");
                      //client.println("request.open(\"GET\", \"ajax_switch\", true);");
                        client.println("request.send(null);");
                        client.println("}");
                        client.println("</script>");
                        client.println("</head>");

                        // Тело веб-страницы
                        client.println("<body>");
                        client.println("<h1>Arduino AJAX Switch Status</h1>");
                        client.println(
                        "<p id=\"switch_txt\">Switch state: Not requested...</p>");
                        client.println("<button type=\"button\"\
                            onclick=\"GetSwitchState()\">Get Switch State</button>");
                        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)
} // loop

// Посылка данных о состоянии кнопки браузеру

void GetSwitchState(EthernetClient cl) {
    if (digitalRead(3)) {
        cl.println("Switch state: ON");
    } else {
        cl.println("Switch state: OFF");
    }
}

HTML и JavaScript


Ниже показано содержимое веб-страницы с HTML и JavaScript кодом, которую наш скетч формирует и отправляет в браузер.



Примечание переводчика. Автор одинаково назвал как функцию скетча Arduino, так и JavaScript функцию (GetSwitchState). Это совершенно разные функции, нужно это помнить и не путать их.

Структура страницы


JavaScript код помещается между открывающим и закрывающим тегами <script> в разделе head веб-страницы.

Каждый раз при нажатии кнопки на веб-странице вызывается JavaScript функция GetSwitchState().

Работа JavaScript


Когда нажимается кнопка на странице и вызывается функция GetSwitchState(), она отправляет HTTP GET запрос на сервер, содержащий текст «ajax_switch». Этот запрос выглядит следующим образом:

GET /ajax_switch&nocache=29860.903564600583 HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://10.0.0.20/
Connection: keep-alive

Когда веб-сервер Arduino получает запрос (содержащий текст «ajax_switch»), он сначала посылает в ответ стандартный HTTP заголовок, а затем текст, содержащий состояние переключателя.

В скетче функция GetSwitchState() считывает состояние кнопки на выводе D3 контроллера и, в зависимости от него, отправляет текст «Switch state: ON» или «Switch state: OFF» браузеру.

Когда JavaScript в браузере получает этот ответ, он выполняет код безымянной функции request.onreadystatechange = function(). Эта функция выполняется каждый раз, когда сервер Arduino отправляет ответ и заменяет текст «Switch state: x» на веб-странице (или текст «Switch state: Not requested...») новым текстом, полученным от Arduino.

Этот запрос JavaScript из браузера и ответ на него от сервера Arduino и есть AJAX в действии.

Случайное число


Браузер может кешировать GET запросы. Это означает, что первый запрос будет работать корректно, но последующие завершатся ошибкой, так как браузер будет брать ответ из кеша, а не получать от сервера.

Добавление случайного числа в запрос решает проблему кеширования браузером GET запросов к серверу.

Работа AJAX


Работу AJAX, представленную в нашем примере, можно детализировать следующим образом:

1. AJAX запрос из браузера


При нажатии кнопки на веб-странице запускается JavaScript функция GetSwitchState(). Эта функция делает следующее:

1. Генерирует случайное число для отправки с GET запросом: nocache = "&nocache=" + Math.random() * 1000000;
2. Создает объект XMLHttpRequest() и присваивает его request: var request = new XMLHttpRequest();
3. Назначает функцию для обработки ответа веб-сервера: request.onreadystatechange = function() (и код, находящийся между фигурными скобками {}).
4. Формирует HTTP GET запрос для отправки на сервер: request.open(«GET», «ajax_switch» + nocache, true);
5. Отправляет HTTP запрос: request.send(null);

2. Ответ от веб-сервера Arduino


Когда веб-сервер Arduino получает HTTP GET запрос, он отправляет стандартный HTTP ответ, за которым следует текст, содержащий информацию о состоянии кнопки. Состояние кнопки определяется функцией Arduino скетча GetSwitchState().

3. JavaScript в браузере обрабатывает ответ


HTTP ответ от веб-сервера Arduino обрабатывается кодом JavaScript. Функция обработчика событий JavaScript запускается, когда получен ответ от Arduino (функция обработчика событий — это безымянная функция, присвоенная request.onreadystatechange).

Если полученный ответ корректный и не пустой, то выполняется следующая строка JavaScript:

document.getElementById("switch_txt").innerHTML = this.responseText;

Этот JavaScript код находит в HTML абзац, помеченный идентификатором switch_txt, и заменяет его текущий текст текстом, полученным от Arduino. HTML код этого абзаца выглядит так:

<p id="switch_txt">Switch state: Not requested...</p>

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

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


Первое замечание по поводу дублирования сущностей автором руководства. На мой взгляд, это вносит путаницу и усложняет понимание материала начинающими. Он почему-то одинаково назвал и Arduino и JavaScript функции (GetSwitchState) и в примере использует как физическую кнопку (подключённую на пин D3 Arduino), так «виртуальную» кнопку на веб-странице. Эти кнопки здесь имеют совершенно разный смысл и функции — физическая кнопка используется в качестве «подопытной» для изменения её состояния и последующего контроля кодом скетча, а кнопка на веб-странице используется только для посылки GET запроса серверу и обновления информации о состоянии физической кнопки.

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

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

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


Источник: https://habr.com/ru/company/timeweb/blog/716362/


Интересные статьи

Интересные статьи

Данный перевод является второй частью перевода протокола безопасности транспортного уровня (TLS) версии 1.2 (RFC 5246). Первая часть перевода находится  здесь. Вторая часть перевода содержит опис...
«Дерево не влияет на звук струнного инструмента с электромагнитным датчиком и последующей аналоговой и/или цифровой обработкой сигнала.» Проще говоря, электрогитары. Динамическая головка (громкогово...
Это заключительная часть расследования о Scala-движении в России. В первой части я узнал от Романа Гребенникова о воронежском бомонде, C++ и Erlang, а от Романа Тимушева о первой Akka и рождении ...
В нашем сегодняшнем материале мы расскажем о том, как DPI-системы помогают интернет-провайдерам экономить, защищать данные клиентов, и поговорим о способах подключения.
Пенни, все становится лучше, когда есть bluetooth © Шелдон, TBBT Домик хотя и "тестовый", но все старались делать максимально правильно — хорошее утепление, приличные стеклопакеты и т.п. Зим...