
От переводчика. С каждой новой статьёй уроки руководство становится всё более и более практичным и вот мы уже добрались до 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.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩

