Starting Electronics: руководство по веб-серверам на Arduino. Часть 12. Отображение DI и AI входов при помощи Ajax и XML

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


От переводчика. В этой части руководства объясняется как работают в связке Ajax и ХML для передачи данных от Arduino сервера браузеру и даются примеры кода для реализации этого взаимодействия на практике.

Также объясняется как можно просто извлекать нужные значения из XML файлов при помощи Javascript и почему это лучше и удобнее, чем предавать данные в простом текстовом формате.


Arduino сервер содержит веб-страницу (хранящуюся на SD-карте), на которой отображается состояние двух кнопок и аналогового входа.

Состояние кнопок и аналогового входа обновляется на веб-странице с помощью Ajax. XML файл, содержащий информацию о состоянии кнопок и аналоговое значение, отправляется из Arduino в веб-браузер.

Этот пример использует тот же вывод на веб-странице (только с измененным текстом), что и в 7-й части этого руководства — «Отображение DI и AI входов при помощи AJAX», со следующими изменениями:
  • Статусы кнопок и аналоговое значение предаются в XML файле, а не в виде блока HTML.
  • Для получения значений из XML-файла вместо responseText используется JavaScript responseXML.
  • Значения из XML файла (данные от Arduino) вставляются в HTML абзацы на веб-странице вместо замены всего абзаца целиком.
  • Веб-страница хранится на microSD карте памяти, подключённой к плате Ethernet Shield.

Зачем использовать Ajax с XML?

Преимущество использования Ajax с XML заключается в том, что отдельные значения могут быть легко получены (извлечены) с помощью JavaScript, вместо того чтобы писать специальный JavaScript код для извлечения этих значений из текстового файла.

Структура XML файла


XML файл использует теги, такие как в HTML или XHTML. Файл содержит основной тег, который идентифицирует его как XML файл. Далее следует определяемый пользователем тег, который содержит все остальные теги передаваемых значений.

Ниже показана структура XML файла, используемого в этом примере:

<?xml version = "1.0" ?>
<inputs>
    <button1></button1>
    <button2></button2>
    <analog1></analog1>
</inputs>

Тег inputs и все названия (имена) других тегов, содержащиеся в нем, определяются пользователем. Этот XML код может быть несколько другим:

<?xml version = "1.0" ?>
<inputs>
    <button></button>
    <button></button>
    <analog></analog>
</inputs>

Здесь теги определяют типы «кнопка» и «аналоговое значение», которые могут использоваться для хранения любого состояния кнопки или аналогового входа. Добавляя больше тегов button или analog, можно передавать состояние большего количества кнопок или аналоговых входов.

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

XML файл Arduino


В этом примере Arduino создает файл XML и добавляет в него между тегами информацию о состоянии кнопок и аналогового входа. Этот XML файл отправляется браузеру в ответ на его Ajax запрос.

Ниже показан пример XML файла, отправленного с Arduino срвера.



Как работает Ajax с XML


Если вы внимательно знакомились с предыдущими частями этого руководства, то многое из рассматриваемого на этом уроке покажется вам знакомым.

Чтобы обновить значения Arduino входов на веб-странице, должно произойти следующее:

1. Запрос веб-страницы


Как обычно, браузер используется для доступа к веб-серверу Arduino по IP-адресу, указанному в скетче Arduino.


Подключение к веб-серверу Arduino

Это приводит к отправке веб-браузером HTTP запроса:

GET / HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.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
Connection: keep-alive

2. Отправка веб-страницы


Веб-сервер Arduino получает вышеуказанный запрос и отвечает HTTP стандартным заголовком, за которым следует сама веб-страница:

HTTP/1.1 200 OK
Content-Type: text/html
Connection: keep-alive



Arduino считывает веб-страницу с SD карты памяти и отправляет её в веб-браузер. После её получения, она будет отображаться в веб-браузере (пользователь её увидит на экране).

Эта веб-страница содержит JavaScript код, который используется для работы Ajax.

Обратите внимание, что тип содержимого в HTTP заголовке веб-страницы — HTML (text/html).

3. Ajax запрос


JavaScript код, содержащийся на веб-странице, инициирует отправку Ajax запроса на сервер Arduino (и продолжает отправлять такие запросы каждую секунду).

GET /ajax_inputs&nocache=299105.2747379479 HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.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

4. Arduino отвечает на запрос Ajax


Получив запрос XML файла, Arduino отвечает стандартным HTTP заголовком, за которым следует сам XML файл, содержащий нужные значения состояния входов Arduino.

HTTP/1.1 200 OK
Content-Type: text/xml
Connection: keep-alive



Обратите внимание, что тип содержимого в HTTP заголовке теперь text/xml.

5. Отображение данных


И наконец, JavaScript на веб-странице извлекает три значения данных из XML файла и отображает их на веб-странице.

Arduino скетч и веб-страница


Веб-страница


Arduino сервер хранит на SD карте следующую веб-страницу:



По сути, это та же веб-страница как и в 7-й части этого руководства, но со следующими изменениями (помимо изменений текста):

Функция


JavaScript функция на веб-странице была переименована в GetArduinoInputs().

Эта функция по-прежнему отправляет Ajax запрос каждую секунду. Теперь вместе с GET запросом дополнительно отправляется текст (идентификатор) «ajax_inputs».

Поскольку в ответ с Arduino сервера отправляется XML файл, то функция проверяет, содержит ли данные responseXML, вместо responseText, как в предыдущей версии:

if (this.responseXML != null) {

Далее данные извлекаются из полученного XML, как это будет описано ниже:

HTML


HTML код изменен для отображения трех абзацев текста, по одному для каждого значения, отправленного с Arduino. Каждый из этих абзацев содержит тег span c уникальным идентификатором.

JavaScript функция вставляет извлеченные из XML файла значения в каждый отдельный тег span. Это заменит текст по умолчанию «...» в каждом абзаце на текущие значения состояния входов Arduino.

Для вставки каждого значения на веб-страницу используется следующий код (здесь показан код для вставки input1):

document.getElementById("input1").innerHTML =

Извлечение XML данных


Данные извлекаются из полученного XML файла с помощью следующей строки:

this.responseXML.getElementsByTagName('button1')[0].childNodes[0].nodeValue;

В этом коде используется this.responseXML вместо this.responseText, как в предыдущих примерах.

Теперь к каждому тегу в XML файле можно получить доступ с помощью следующей конструкции this.responseXML.getElementsByTagName('button1') и соответствующего идентификатора (в данном случае button1).

Если вы вернетесь к началу этой статьи в раздел «Структура XML-файла», то увидите, что могут быть теги с одинаковыми именами. Если бы мы использовали это, например, для тегов кнопок, то доступ к каждому значению можно было бы получить следующим образом:

this.responseXML.getElementsByTagName('button')[0].childNodes[0].nodeValue;
this.responseXML.getElementsByTagName('button')[1].childNodes[0].nodeValue;

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

Информацию об общем количестве кнопок в файле XML можно получить с помощью конструкции:

this.responseXML.getElementsByTagName('button').length

Скетч Ардуино


Arduino скетч для этого примера показан ниже.

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

  Описание:  Arduino веб-сервер с SD картой, динамически (раз в секунду) отображающий текущее состояние двух кнопок и аналогового входа на веб-странице при помощи Ajax и XML.
  
  Оборудование: контроллер Arduino Uno, плата Ethernet Shield, microSD карта памяти 2 ГБ, две кнопки, подключённые на пины D7 и D8 Arduino, потенциометр, подключённый на пин A2.
                
  Программное обеспечение: среда разработки Arduino IDE, microSD карта с файлом index.htm
  
  Ссылки:
    - 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
    - SD Card library documentation: http://arduino.cc/en/Reference/SD

  Дата создания: 27 марта 2013
  Изменения: 17 июня 2013
 
  Автор: W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 20); // IP-адрес (нужно изменить на актуальный для вашей сети)
EthernetServer server(80);
File webFile;

// Буфер для HTTP запросов
#define REQ_BUF_SZ   50
char HTTP_req[REQ_BUF_SZ] = {0}; // Хранится как null terminated string
char req_index = 0;  // индекс буфера

void setup() {
    // отключение Ethernet
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);
    
    Serial.begin(115200);
    
    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
        Serial.println("ERROR - SD card initialization failed!");
        return;
    }
    Serial.println("SUCCESS - SD card initialized.");

    if (!SD.exists("index.htm")) {
        Serial.println("ERROR - Can't find index.htm file!");
        return;
    }
    Serial.println("SUCCESS - Found index.htm file.");

    pinMode(7, INPUT);        // Кнопка на D7
    pinMode(8, INPUT);        // Кнопка на D8
    
    Ethernet.begin(mac, ip);
    server.begin();
}

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

    if (client) {
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {
                char c = client.read(); // получаем очередной байт (символ) от клиента
                // сохраняем последний элемент массива 0 (null terminate string)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c; // сохраняем символ HTTP запроса
                    req_index++;
                }

                if (c == '\n' && currentLineIsBlank) {
                    // Посылаем http заголовок
                    client.println("HTTP/1.1 200 OK");
                    // HTML или XML запрос
                    if (StrContains(HTTP_req, "ajax_inputs")) {
                        client.println("Content-Type: text/xml");
                        client.println("Connection: keep-alive");
                        client.println();
                        XML_response(client);
                    } else {
                        client.println("Content-Type: text/html");
                        client.println("Connection: keep-alive");
                        client.println();
                        webFile = SD.open("index.htm");
                        if (webFile) {
                            while(webFile.available()) {
                                client.write(webFile.read());
                            }
                            webFile.close();
                        }
                    }
                    Serial.print(HTTP_req);
                    // Обнуляем массив (буфер)
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    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)
}

// Посылаем XML файл
void XML_response(EthernetClient cl) {
    int analog_val;
    
    cl.print("<?xml version = \"1.0\" ?>");
    cl.print("<inputs>");
    cl.print("<button1>");
    if (digitalRead(7)) {
        cl.print("ON");
    } else {
        cl.print("OFF");
    }
    cl.print("</button1>");
    cl.print("<button2>");
    if (digitalRead(8)) {
        cl.print("ON");
    } else {
        cl.print("OFF");
    }
    cl.print("</button2>");
    // read analog pin A2
    analog_val = analogRead(2);
    cl.print("<analog1>");
    cl.print(analog_val);
    cl.print("</analog1>");
    cl.print("</inputs>");
}

// Обнуление массива
void StrClear(char *str, char length) {
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

// Поиск подстроки
// 1, если найдена
// 0, если не найдена
char StrContains(char *str, char *sfind) {
    char found = 0;
    char index = 0;
    char len;

    len = strlen(str);
    
    if (strlen(sfind) > len) {
        return 0;
    }
    while (index < len) {
        if (str[index] == sfind[found]) {
            found++;
            if (strlen(sfind) == found) {
                return 1;
            }
        }
        else {
            found = 0;
        }
        index++;
    }

    return 0;
}

Этот скетч является модифицированной версией скетча из предыдущей части этого руководства.

Создание XML файла


Функция XML_response() обеспечивает создание и отправку XML файла в уже объясненном ранее в этой статье формате.

Значения состояния кнопок и аналогового входа вставляются в XML файл и отправляются в веб-браузер.

HTTP ответ


Поскольку HTTP ответ должен содержать контент разных типов для HTML страницы и XML файла (text/html или text/xml), то формирующий его код был разделен в скетче для отправки соответствующих данных в каждом HTTP заголовке.

Как и в предыдущей части этого руководства, веб-страница сохраняется на SD карте памяти в виде файла index.htm и отправляется браузеру, когда он обращается к веб-серверу Arduino.

Запуск скетча


Подключите к Arduino кнопки и потенциометр, как было показано на принципиальной схеме в 7-й части этого руководства.

Скопируйте файл index.htm (см. содержимое файла ниже) на microSD карту памяти и вставьте её в разъем на плате Ethernet Shield.

Загрузите вышеприведенный скетч в Arduino и подключитесь к Arduino серверу с помощью веб-браузера.

Визуально вы не увидите существенной разницы между работой системы из этого урока и из 7-й части этого руководства, но теперь у вас есть простой (и более правильный) способ извлечения значений, отправляемых с Arduino.

Исходный код веб-страницы


Содержимое веб-страницы можно скопировать отсюда и вставить в файл с именем index.htm:

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino SD Card Web Page using Ajax with XML</title>
        <script>
        function GetArduinoInputs() {
            nocache = "&nocache=" + Math.random() * 1000000;
            var request = new XMLHttpRequest();
            request.onreadystatechange = function() {
                if (this.readyState == 4) {
                    if (this.status == 200) {
                        if (this.responseXML != null) {
                            // extract XML data from XML file (containing switch states and analog value)
                            document.getElementById("input1").innerHTML =
                                this.responseXML.getElementsByTagName('button1')[0].childNodes[0].nodeValue;
                            document.getElementById("input2").innerHTML =
                                this.responseXML.getElementsByTagName('button2')[0].childNodes[0].nodeValue;
                            document.getElementById("input3").innerHTML =
                                this.responseXML.getElementsByTagName('analog1')[0].childNodes[0].nodeValue;
                        }
                    }
                }
            }
            request.open("GET", "ajax_inputs" + nocache, true);
            request.send(null);
            setTimeout('GetArduinoInputs()', 1000);
        }
    </script>
    </head>
    <body onload="GetArduinoInputs()">
        <h1>Arduino Inputs from SD Card Web Page using Ajax with XML</h1>
        <p>Button 1 (pin 7): <span id="input1">...</span></p>
        <p>Button 2 (pin 8): <span id="input2">...</span></p>
        <p>Analog (A2): <span id="input3">...</span></p>
    </body>
</html>

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


В этой части автор подробно объясняет технику взаимодействия Arduino сервера и веб-браузера при помощи Ajax и XML, но оставляет в умолчаниях объяснение некоторых общих вещей.

Здесь файл формата XML выступает неким «контейнером», который несёт определённым образом размеченные и наименованные данные, для (удобного) извлечения которых уже имеются соответствующие функции Javascript.

Но в качестве такого контейнера могут выступать, например, простой текст, файлы формата JSON, или вообще такое взаимодействие при помощи Ajax может быть заменено на обмен данными при помощи Websockets и т. п.

Часть 1, часть 2, часть 3, часть 4, часть 5, часть 6, часть 7, часть 8, часть 9, часть 10, часть 11.


Источник: https://habr.com/ru/companies/timeweb/articles/736488/


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

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

В прошлый раз мы с вами разобрали Алгоритм Кнута — Морриса — Пратта, сегодня мы разберем не менее интересный, а на мой личный взгляд, даже наиболее любопытный и изящный алгоритм, который подкупает сво...
Об этом телефоне мы писали несколько раз. Сначала опубликовали информацию о спецификациях и возможностях девайса, а потом — перевод с описанием впечатлений одного из зарубежных журналистов. Но все ж...
Продолжение статьи про комплексный подход реализации DevSecOps. В первой части были рассмотрены индустриальные вызовы, цели и задачи инструментов класса ASOC, Оркестрация и Корреляция.Первая часть: ht...
Меня часто спрашивают, почему я в своем игровом движке PointJS, построенном на технологии HTML5, перерисовываю весь CAVAS заново, а не обновляю только ту часть, на которо...
Привет, Хабр! Это продолжение туториала по библиотеке opencv в python. Для тех кто не читал первую и вторую части, сюда: Часть 1 и Часть 2, а всем остальным — приятного чтения! ...