Ajax web-server (w5500)

Udjin
Offline
Зарегистрирован: 10.06.2017

Здравствуйте.

В перспективе создать web-сервер с выводом показаний датчиков и возможностью вкл/выкл нагрузок.

В статике вывод аналоговых и цифровых показателей работает, кноки управления нагрузки тоже. Изучаю Ajax. Победить его на ENC28J60 без SD не смог. Приобрел w5500.

Скетч из примера в сети с подставленным PIR-сенсором:

[spoiler]

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

// MAC address from Ethernet shield sticker under board
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 177); // IP address, may need to change depending on network
EthernetServer server(80);  // create a server at port 80

String HTTP_req;            // stores the HTTP request

int pirPin = 2;  // инициализируем пин для получения сигнала от пироэлектрического датчика движения

void setup()
{
    Ethernet.begin(mac, ip);  // initialize Ethernet device
    server.begin();           // start to listen for clients
    Serial.begin(9600);       // for diagnostics
    pinMode(2, INPUT);        // switch is attached to Arduino pin 3
}

void loop()
{
    EthernetClient client = server.available();    // опрашиваем канал в поисках клиента

    if (client) {  // got client?
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {   // client data available to read
                char c = client.read(); // read 1 byte (character) from client
                HTTP_req += c;  // save the HTTP request 1 char at a time
                // last line of client request is blank and ends with \n
                // respond to client only after last line received
                if (c == '\n' && currentLineIsBlank) {
                    // send a standard http response header
                    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 request for switch state
                        // read switch state and send appropriate paragraph text
                        GetSwitchState(client);
                    }
                    else {  // HTTP request for web page
                        // send web page - contains JavaScript with AJAX calls
                        client.println("<!DOCTYPE html>");
                        client.println("<html>");
                        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(\"data_to_update\").innerHTML = this.responseText;");
                        client.println("}}}}");
                        client.println("request.open(\"GET\", \"ajax_switch\" + nocache, true);");
                        client.println("request.send(null);");
                        client.println("setTimeout('GetSwitchState()', 1000);"); //  интервал запросов данных (вызова функции "GetSwitchState()") (мсек)
                        client.println("}");
                        client.println("</script>");
                        client.println("</head>");
                        client.println("<body>");
                        client.println("<body onload=\"GetSwitchState()\">");
                        client.println("<h1>Arduino AJAX Switch Status</h1>");
                        client.println("<div id=\"data_to_update\">");
                        client.print("<p id=\"switch_txt\">Switch state: Not requested...</p>");
                        // switch state and button code will be inserted here by the JavaScript after AJAX call
                        client.println("</div>");
                        client.println("</body>");
                        client.println("</html>");
                    }
                    Serial.print(HTTP_req);   // display received HTTP request on serial port
                    HTTP_req = "";            // finished with request, empty string
                    break;
                }
                //каждая строка полученная от клиента заканчивается  \r\n
                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)
}

// send the state of the switch to the web browser
void GetSwitchState(EthernetClient cl)
{
    if (digitalRead(2)) {
        cl.println("<p>Switch state: ON</p><button type=\"button\" style=\"color: green;\" onclick=\"GetSwitchState()\">On</button>");
    }
    else {
        cl.println("<p>Switch state: OFF</p><button type=\"button\" style=\"color: red;\" onclick=\"GetSwitchState()\">Off</button>");
    }
}

[/spoiler]

Вопрос: После загрузки скетча страница отображает/обновляет статус датчика, xml ходят. Но стоит только перезагрузить страницу в браузере, статус показаний меняется на "Not requested...". Тоже самое и в других браузерах. Грубо говоря всё работает только при первой загрузке страницы сразу после заливки скетча. Выглядит это примерно так:

Gif-ка

 

 

 

 

 

 

 

Подскажите как мне получать статус с датчиков после перезагрузки страницы?

vitalikost
Offline
Зарегистрирован: 28.11.2014

А что терминал прилетает? есть там строка "ajax_switch"?

vitalikost
Offline
Зарегистрирован: 28.11.2014

Похоже не весь ответ считывает.

 и замени в строке 28, if  на  while.

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Можно в консоль браузера статусы хттп-реквестов повыводить - мошт ошибка возникает при загрузке.  А весь html вынести в PGM и выплёвывать одной строкой, не ломая мозг...

Voodoo Doll
Voodoo Doll аватар
Offline
Зарегистрирован: 18.09.2016

1. Гонять по TCP/IP HTML код тупо и небезопасно. Конечно, в домашней сети его не подменить, но привыкать к этому не следует. Не всяк тот хакер. Хак - код дебила, индусский и т. п., существуют правила. Любой хак однажды аукается. Код должен предусматривать все состояния, и не быть полиморфным. И нельзя писать код который работает, надо писать код который понятен. Иначе есть риск стать индусом, спать на улице, начать поклоняться Шиве и подцепить паразитов. Прекратите в ответах выдавать HTML. Для состояний есть JSON. И короче, BTW.

2. Раз уж такой праздник (Wiznet), можно же править заголовки. То есть ещё раз тыкаю носом: перепишите JS так чтобы ему был нужен только JSON: "[0]" выключено, "[1]" включено, без кавычек (в строке), через запятую если несколько. На ENC28J60 заголовки тоже можно изменять, держу в курсе (правда, треба уже лезть в библиотеку, там это спрятано).

3. Мне лень предусматривать различные запросы на дуине, я просто отдаю полезный JSON, добавив заголовок "Access-Control-Allow-Origin: *" (без кавычек), а сам вебинтерфейс (HTML+JS) держу на клиенте. От флешки смартфона, если записать туда полуторакилобайтный HTML файл, не убавится. А писанины меньше.

4. Недоступность ресурса после перезагрузки значит только одно: цикл обслуживания Eth - кривой. Сверяйте с примером.

5. Убедиться в наличии заголовка "Connection: close" (без кавычек). Соединения keep-alive чреваты вывертами.

Logik
Offline
Зарегистрирован: 05.08.2014

Верно. Но чем боротся с багами от применения старого громоздкого барахла из кладовки вебдизайна просто применить протокол, адекватный для реалтайм данных - https://ru.wikipedia.org/wiki/WebSocket

sadman41
Offline
Зарегистрирован: 19.10.2016

Правильно ли я понимаю, что Voodoo предлагает сам код страницы с интерфейсом помещать вручную на каждый смартфон или планшет (предварительно произведя Jailbreak для определённых), JSON применять в виде "...{"states":"0,1,1,1,1,0"}..." для дополнительно написанного JS-парсера, бояться keep-alive соединений и править некие тайные заголовки, зачем-то спрятанные внутри библиотеки?  В противном случае же Шива наградит пейсателя вшами, а Ганеш заставит петь и танцевать до изнеможения.

По поводу кривизны обслуживания запросов - согласен.

Voodoo Doll
Voodoo Doll аватар
Offline
Зарегистрирован: 18.09.2016

Не, неправильно. Не надо никаких парсеров, всё есть в V8.

Насчёт вставить HTML в progmem и отдавать вместе со статусом: слушаю и записываю. А то любят такие "ой да ты нуб, вот есть такая то магия, я ваще фаерболы суммоню". А как кастануть эту магию попросишь - так все врассыпную, как боты от перса, когда на паузу поставишь, читы подрубишь и левел апнешь.

sadman41
Offline
Зарегистрирован: 19.10.2016

Я на дваче и пр. не сижу, поэтому жаргон мне этот непонятен. А ролики с кликбейтными названиям вообще не смотрю.

Но, если действительно интересует, как в PGM держать исходник страницы, не размазывая его по куче print() то изволь:

const char html_page[] PROGMEM="<html><head><meta charset=\"utf-8\"><title>...</title>\n\
<style>\n\
body{font-family:Geneva,Arial,Helvetica,sans-serif;-webkit-text-size-adjust:100%}\n\
@media (min-width:1024px){.sctw{width:24.69%}.scts{width:49%}.sm{display:none}#box{max-width:1024px}}\n\
@media (min-width:426px) and (max-width:1023px){.sctw{width:49.5%}.scts{width:100%}.sm{display:none}}\n\
...
</style>\n\
</head><body><div id=\"box\"class=\"rb\">\n\
...
<div>IP Address</div><div><input id=\"na\" name=\"na\" type=\"text\" size=\"15\"/></div></div>\n\
<div><div>Subnet</div><div><input id=\"nm\" name=\"nm\" type=\"text\" size=\"15\"/></div></div>\n\
<div><div>Gateway</div><div><input id=\"ng\" name=\"ng\" type=\"text\" size=\"15\"/></div></div>\n\
<div><div>DHCP</div><div><input id=\"nd\" name=\"nd\" type=\"checkbox\"/></div></div>\n\
</div>\n\
...
<script>\n\
var h=new XMLHttpRequest(),oUpd=document.getElementById('upds'),oUpw=document.getElementById('updw')\n\
var cUpw,tUpd,e,j;\n\
function sQry(){h.open('GET',dUrl+'?t='+Math.random(),true);h.onreadystatechange=oRSC;h.send()}\n\
function oRSC(){\n\
if(this.readyState!=4){return;}\n\
try{\n\
...
</script></body></html>";

#define FSH_P(p) (reinterpret_cast<const __FlashStringHelper *>(p))

...
EthernetClient ethClient;
...
ethClient.print(FSH_P(html_page));

После этого достаточно просто на ПК всё отладить и копипастнуть исходник страницы прямо в .ino файл. 

Что же касается V8... Тебе, конечно, это может показаться странным, но я из Safari смотрю интерфейс, а то и из Firefox. Поэтому предпочитаю использовать функционал объекта JSON, который есть там и сям и кормить его вполне легальным (хотя и избыточным) JSON-форматом без извращений с псевдокомпрессией. И при этом мне не приходится ломать голову над тем, как на iPad засунуть html-файл, потом его загрузить из локального хранилища в единственный браузер, который имеет механику, чтобы раскрутить нечто, заточенное под V8.

 

 

Udjin
Offline
Зарегистрирован: 10.06.2017

Спасибо за ответы.

vitalikost пишет:
и замени в строке 28, if  на  while.

После этого действия web-страница в браузерах не загружается.

Стало намного стабильнее после замены библиотеки с Ethernet на Ethernet2 при том же скетче.

Logik Спасибо за наводку, начал изучать web сокет.

Voodoo Doll пишет:
перепишите JS так чтобы ему был нужен только JSON: "[0]" выключено, "[1]" включено, без кавычек (в строке)
Тут только экономия трафика или есть ещё какая-то выгода?

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

А давайте сначала по RS232 погоняем, а уж ПОТОМ...