От переводчика. С каждой новой статьёй уроки руководство становится всё более и более практичным и вот мы уже добрались до 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.
farafonoff
Вставлять responseText, полученный по plain http прямо в innerHTML - худшая из возможных практик. Не зря говорят, что в аббревиатуре IOT буква S означает безопасность.
Добавлять символы в строку без всякого подсчета - верный путь к DOS атаке.
Парсить tcp поток через indexOf - открывает богатый простор для всяких reflect и XSS атак.
smart_alex Автор
Это же пример для начинающих, основная задача которого - донести ПРИНЦИП подобного взаимодействия.
Всё остальное будет потом.
noRoman
Мне кажется о безопасности надо говорить сразу. Иначе получим - "мы так привыкли". Джуниоров сразу учим, что ваш код всегда кто-то хочет взломать. Поэтому безопасность прежде всего.
smart_alex Автор
О безопасности работы сетевых протоколов и безопасных методах обработки данных на микроконтроллерах можно говорить с теми, кто понимает о чём идёт речь.
Здесь же задача состоит в том, чтобы показать принцип сетевого взаимодействия тем, кто об этом не имеет никакого представления.
Для новичков это и так сложная для понимания тема, а если сюда добавить ещё и рассуждения о безопасности (что само по себе может потребовать отдельного курса), то мало кто из новичков вообще что-либо поймёт.
segment
Для новичков и веб-сервер сложная тема.