От переводчика. С каждой новой статьёй уроки руководство становится всё более и более практичным и вот мы уже добрались до 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.


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


  1. farafonoff
    00.00.0000 00:00
    +2

    Вставлять responseText, полученный по plain http прямо в innerHTML - худшая из возможных практик. Не зря говорят, что в аббревиатуре IOT буква S означает безопасность.

    Добавлять символы в строку без всякого подсчета - верный путь к DOS атаке.

    Парсить tcp поток через indexOf - открывает богатый простор для всяких reflect и XSS атак.


    1. smart_alex Автор
      00.00.0000 00:00

      Это же пример для начинающих, основная задача которого - донести ПРИНЦИП подобного взаимодействия.

      Всё остальное будет потом.


      1. noRoman
        00.00.0000 00:00
        +2

        Мне кажется о безопасности надо говорить сразу. Иначе получим - "мы так привыкли". Джуниоров сразу учим, что ваш код всегда кто-то хочет взломать. Поэтому безопасность прежде всего.


        1. smart_alex Автор
          00.00.0000 00:00

          О безопасности работы сетевых протоколов и безопасных методах обработки данных на микроконтроллерах можно говорить с теми, кто понимает о чём идёт речь.

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

          Для новичков это и так сложная для понимания тема, а если сюда добавить ещё и рассуждения о безопасности (что само по себе может потребовать отдельного курса), то мало кто из новичков вообще что-либо поймёт.


          1. segment
            00.00.0000 00:00
            +1

            Для новичков и веб-сервер сложная тема.