От переводчика. Автор руководства медленно, но неуклонно ведёт заинтересованного читателя к пониманию работы веб-серверов на 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.
solderman
Под 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";