Arduino+Ethernet shield+SD карта: Не могу подгрузить внешние .js файлы для веб страницы.

Vadim111
Offline
Зарегистрирован: 14.01.2015

Если коротко, то суть вопроса в том, что если на Ардуине запущен Веб сервер и файлы Веб приложения находятся на SD карте, то файлы .htm, .css, .jpg загружаются без проблем, а файлы .js (javascript) – никак. В конце поста привожу примеры простейшей страницы и скетча с подробными комментариями. Если кто-нибудь сможет указать причину проблем с загрузкой .js, буду очень признателен.

На всякий случай опишу свой вопрос более детально:

Простое Веб приложение (в том числе и по управлению роботизированными устройствами через И-нет) состоит, как правило, из html кода, который наполняет страницу информацией; css стилей, которые говорят что и как располагать и разукрашивать на странице; и javaScript скрипта, который описывает функциональность страницы. Все эти части можно описать в одном файле, но гораздо удобней разделять на отдельные html, css, js файлы, в каждом из которых находится своя часть. К тому же, jQuery библиотека в принципе идет, как отдельный .js файл.

Если подготовить такой комплект файлов некоего Веб приложения, то его можно размещать где угодно: и на Веб сервере локальной машины Appache, и на сервере И-нета, и на сервере, запущенном на Raspberry или intel Galileo платах. При этом ничего не нужно менять, а только копировать папку с файлами.

Что касается Ардуино плат, то в большинстве примеров работы Веб сервера, html страница выводится прямо из скетча. Но это неудобно, ненаглядно и трудоемко. Гораздо удобней записать на SD карточку файлы веб приложения, подготовленные для любых платформ, и работать с ними. К тому же, при таком подходе, в Ардуину может быть залит один универсальный скетч, а информация с Веб сервера будет выводится в зависимости от того, какую карточку (с каким набором файлов) воткнуть.

При работе с внешними файлами (с файлами с sd карты) процесс выглядит примерно так: На сервер, запущенный на Ардуине, приходит запрос на открытие некой заглавной страницы. Ардуина вытаскивает нужный файл с карточки и шлет его клиенту, приславшему запрос. Когда браузер начинает открывать этот файл и натыкается на ссылку на любой другой файл, то он формирует и шлет новый запрос серверу (Ардуине), та вытаскивает новый файл и т.д., пока вся страница не будет открыта. Т.е. если в html странице указаны ссылки на .js, .scc файлы и на пару картинок, то на каждый из этих файлов Ардуина получит запрос от клиента и должна будет вытащить необходимый файл и отправить его по назначению.

Так вот, суть моей проблемы заключается в том, что Ардуина отлично справляется с отсылкой htm, css и jpg файлов. А файлы скрипта не отсылает. Но я не могу утверждать, что она их не отсылает, поскольку не знаю, как это проверить. Может быть она не досылает какого-то заголовка и браузер не может открыть эти файлы? Но js скрипт на загружаемой странице не работает.

В сети нашел 2 поста с аналогичной проблемой.

http://stackoverflow.com/questions/26856223/how-to-retrieve-css-js-files-on-arduino

http://forum.arduino.cc/index.php?topic=252757.0

Судя по всему, проблема решена на была. Даваемые там советы мне не помогли.

Ниже привожу все части элементарного Веб приложения. По нажатии на кнопку должно появляться сообщение «ОК». Если кто-то сможет подсказать, где ошибка, и как загрузить необходимую функциональность на страницу, в том числе и .js файл библиотеки jQuery, буду очень признателен.

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

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino Web Page</title>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <script src="scripts/myscript.jsp" type="text/javascript"> </script>
    </head>
    <body>
        <h1>Hello from Arduino!</h1>
        <p>A web page from the Arduino server</p>
        <img src="img03.jpg" />
        <button id="myButton">Test</button>
    </body>
</html>

css файл:

/*Цвет шрифта и фона для заголовка H1*/
h1 {
  color:#374896;
  padding: 0 0 0 25px;
  background: rgb(218, 245, 196);
}

js файл:

//После загрузки страницы запускаем функцию initPage.
window.onload = initPage;

function initPage() {
    //Записываем элемент с определенным ID (кнопку) в переменную.
    var myDomElement = document.getElementById("myButton");

    //При событии клика по кнопке запускаем функцию...
    myDomElement.onclick = function () {
        //Выводим сообщение ОК.
        alert("OK");
    }
}

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

//Подключение библиотек.
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

// размер буфера для записи HTTP запроса.
#define REQ_BUF_SZ   20

// Переметры для Веб сервера (MAC, IP, порт)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 177);   // IP address.
EthernetServer server(80);       // port 80
//Переменная, типа File для работы с файлами на SD карте.
File webFile;
char HTTP_req[REQ_BUF_SZ] = {0}; // HTTP запрос записывается в эту строку (массив символов)
char req_index = 0;              // index строки запроса.

void setup()
{
  // Инициализация порта для SD карты на пине 4.
  SPI.begin(4);
  // Инициализация порта для Веб сервера на пине 10
  SPI.begin(10);
    
    Serial.begin(9600);       // Сериал порт для вывода сообщений.
    
    // Инициализация SD карты
    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
        Serial.println("ERROR - SD card initialization failed!");
        return;
    }
    Serial.println("SUCCESS - SD card initialized.");
    
    // Проверка существования index.htm файла в указанном месте на карте.
    // И вывод соответствующего сообщения
    if (!SD.exists("proba/index.htm")) {
        Serial.println("ERROR - Can't find proba/index.htm file!");
        return;
    }
    Serial.println("SUCCESS - Found proba/index.htm file.");
    
    // Проверка существования img03.jpg файла в указанном месте на карте.
    // И вывод соответствующего сообщения
    if (!SD.exists("proba/img03.jpg")) {
        Serial.println("ERROR - Can't find proba/img03.jpg file!");
        return;
    }
    Serial.println("SUCCESS - Found proba/img03.jpg file.");
    
    // Проверка существования style.css файла в указанном месте на карте.
    // И вывод соответствующего сообщения
    if (!SD.exists("proba/css/style.css")) {
        Serial.println("ERROR - Can't find proba/css/style.css file!");
        return;
    }
    Serial.println("SUCCESS - Found proba/scripts/myscript.js file.");
    
    // Проверка существования img03.jpg файла в указанном месте на карте.
    // И вывод соответствующего сообщения
    if (!SD.exists("proba/scripts/myscript.js")) {
        Serial.println("ERROR - Can't find proba/scripts/myscript.js file!");
        return;  // can't find index file
    }
    Serial.println("SUCCESS - Found proba/scripts/myscript.js file.");

    Ethernet.begin(mac, ip);  // Инициализация Ethernet шилда.
    server.begin();           // Запуск Веб сервера.
}

void loop()
{
    EthernetClient client = server.available();  // Ожидаем запроса от клиента, пока заботает сервер.

    if (client) {  // Если запрос пришел...
        boolean currentLineIsBlank = true;
        while (client.connected()) { //Пока клиент на связи...
            if (client.available()) {  //Если в запросе от клиента еще есть, что прочесть...
                char c = client.read(); // читаем 1 байт из клиентского запроса и записываем его в переменную.
                //Записываем этот прочитанный байт в общую строку запроса от клиента.
                //Т.е. записываем одну букву в последний индекс строчного массива.
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;
                    req_index++;
                }
                Serial.print(c);    // выводим прочитанный символ в сериал монитор
                
                //Если достигнут конец запроса...
                // (последняя строка пустая и заканчивается на \n)
                if (c == '\n' && currentLineIsBlank) {
                    // Если в запросе содержатся подстроки: "GET / " или "GET /proba/index.htm" или "GET /index.htm"
                    // Т.е. если пришел запрос на открытие заглавной страницы...
                    if (StrContains(HTTP_req, "GET / ")
                                 || StrContains(HTTP_req, "GET /proba/index.htm")
                                 || StrContains(HTTP_req, "GET /index.htm")) {
                        client.println("HTTP/1.1 200 OK");
                        client.println("Content-Type: text/html");
                        client.println("Connnection: close");
                        client.println();
                        webFile = SD.open("proba/index.htm");        // Открываем файл с заглавной страницей  с SD карты.
                    }
                    // Если в запросе содержатся подстроки: "GET /img03.jpg"
                    // Т.е. если пришел запрос на открытие картинки...
                    else if (StrContains(HTTP_req, "GET /img03.jpg")) {
                        webFile = SD.open("proba/img03.jpg");       // открываем файл с картинкой с SD карты.
                        if (webFile) {
                            client.println("HTTP/1.1 200 OK");
                            client.println();
                        }
                    }
                    // Если в запросе содержатся подстрока: "GET /css/style.css"
                    // Т.е. если пришел запрос на открытие css файла...
                    else if (StrContains(HTTP_req, "GET /css/style.css")) {
                        client.println("HTTP/1.1 200 OK");
                        client.println("Content-Type: text/css");
                        client.println("Connnection: close");
                        client.println();
                        webFile = SD.open("proba/css/style.css");    // открываем файл css с SD карты.
                    }
                    // Если в запросе содержатся подстрока: "GET /scripts/myscript.js"
                    // Т.е. если пришел запрос на открытие js файла...
                    else if (StrContains(HTTP_req, "GET /scripts/myscript.js")) {
                        client.println("HTTP/1.1 200 OK");
                        //client.println("Content-Type: application/x-javascript");
                       client.println("Content-Type: text/javascript");
                        client.println("Connnection: close");
                        client.println();
                        webFile = SD.open("proba/scripts/myscript.js"); 
                    }

                    // Если нужный файл открылся, отсылаем страницу клиенту.
                    if (webFile) {
                        while(webFile.available()) {
                            client.write(webFile.read());
                        }
                        webFile.close();
                    }
                    // устанавливаем индекс строки запроса в 0 и очищаем саму строку.
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    break;
                }
                // every line of text received from the client ends with \r\n
                if (c == '\n') {
                    // last character on line of received text
                    // starting new line with next character read
                    currentLineIsBlank = true;
                } 
                else if (c != '\r') {
                    // a text character was received from client
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);      // give the web browser time to receive the data
        client.stop(); // close the connection
    } // end if (client)
}

// Функция очищает строку, содержащую запрос.
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;
}

 

Datak
Offline
Зарегистрирован: 09.10.2014

Vadim111 пишет:
Ардуина отлично справляется с отсылкой htm, css и jpg файлов. А файлы скрипта не отсылает. Но я не могу утверждать, что она их не отсылает, поскольку не знаю, как это проверить.

Так трудно разве, расставить Serial.print в сомнительных местах - и сразу всё будет понятно.

Сам файл, который js, открывается нормально? Строчка "SUCCESS - Found proba/scripts/myscript.js file"  в serial monitor'е появляется?

Vadim111
Offline
Зарегистрирован: 14.01.2015

Datak пишет:

Так трудно разве, расставить Serial.print в сомнительных местах - и сразу всё будет понятно.

Сам файл, который js, открывается нормально? Строчка "SUCCESS - Found proba/scripts/myscript.js file"  в serial monitor'е появляется?

Все элементарные вещи я проверил. Например, в setup-е делается проверка наличия всех необходимых файлов на карточке. Конечно же, при запуске скетча выводится инфа, что все файлы присутствуют.

Затем, в сериале выводится полный текст запроса, который приходит от сервера. В нем тоже все в порядке - приходит именно та подстрочка (на js файл), которая и проверяется в условии.

А вот что можно сделать еще? Serial.print какого параметра мне надо указать, чтобы в сериал мониторе увидеть построчно, какой файл (с каким содержимым) отправляется клиенту?

Но и это не все. Я не могу найти никакой инфы на счет того, а как js файлы отправляются клиенту с обычного Веб сервера на стационарном компе. Может быть в заголовках запроса какие-то отличия и Ардуиновский ответ не понимается браузерами?

В догонку: Пробовал работу на 3-х браузерах: IE, Фаерфокс и Хром. Файлы на карточке пробовал размещать все в корень. js файлу пробовал давать расширение из 3 букв. Различий нет.

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

там народ и не видит ни какой проблемы... обычный сервер из примера выдает любые файлы с карточки... вопрос в том как и в какой последовательности у вас запрашивается.. и как у вас построена передача всей этой ботвы на ардуино... берете пример сервера и запрашиваете с негог любые файлы с карточки...

Datak
Offline
Зарегистрирован: 09.10.2014

Vadim111 пишет:

А вот что можно сделать еще? Serial.print какого параметра мне надо указать, чтобы в сериал мониторе увидеть построчно, какой файл (с каким содержимым) отправляется клиенту?

Не люблю программировать "в уме", а попробовать сейчас не на чем.
Но, наверно, приблизительно так должно выглядеть:

// Если нужный файл открылся, отсылаем страницу клиенту.
if (webFile) {
   Serial.println( "---------- start of file ----------" );
   while(webFile.available()) 
   {
      char Ch = webFile.read( );
      client.write( Ch );

      Serial.print( Ch );
   }
   Serial.println( "----------- end of file -----------" );
   webFile.close();
}

Vadim111 пишет:

Но и это не все. Я не могу найти никакой инфы на счет того, а как js файлы отправляются клиенту с обычного Веб сервера на стационарном компе. Может быть в заголовках запроса какие-то отличия и Ардуиновский ответ не понимается браузерами?

А для этого советую установить на стороне клиента какой-нибудь http-сниффер.
У меня, например, установлен Lucid Edge Illumination. Все http-запросы и ответы, вместе с заголовками, видны как на ладони. Остаётся только сравнить ответ ардуины с ответом какого-нибудь другого сервера.

Программа платная, но по-моему работает и "за так" - правда, жалуется на это, при каждом запуске. :)

glory24
Offline
Зарегистрирован: 29.11.2013

Если тема все еще актуальна, попробуйте этот скетч, но у мен я отлично читает SD карту, только джаву исполнять не хочет (видимо у него нет такой задачи).

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,1,100);
char rootFileName[] = "index.htm";
EthernetServer server(80);
File myFile;
 
void setup() {
  SD.begin(4);
  Serial.begin(9600);
  Serial.println("Free RAM: ");
  Serial.println(FreeRam());
  pinMode(10, OUTPUT);                    // установить SS вывод как выходящий
  digitalWrite(10, HIGH);                    // Выключить чип w5100
  // Запускаем сервер. По умолчанию шлюз выбран 192.168.1.1, маска 255.255.255.0
  Ethernet.begin(mac, ip);
  server.begin();
}
// Выбираем размер буфера 100 символов, где находится имя файла.
#define BUFSIZ 100
void loop()
{
  char clientline[BUFSIZ];
  char *filename;
  int index = 0;
  EthernetClient client = server.available();
    if (client) {
    // an http request ends with a blank line
    boolean current_line_is_blank = true;
    // reset the input buffer
    index = 0;
      while (client.connected()) {
        if (client.available()) {
          char c = client.read();
        // Сбросить соединение, если пришел непонятный символ от клиента.
        //Например, наблюдались зависания от браузера
        // Safary (IPad 2), который посылал "непонятные" символы
        if ( c==0x0A || c==0x0D ) goto aa;
        if ( c<0x20 || c>0x7E ) break;
   aa:
      // Если символ от клиента правильный, записываем его в буфер
       // Если идет чтение не новой строки, то продолжаем ее символы записывать в буфер.
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          // Идем на продолжение считывать новый символ.
          continue;
        }
   // Заканчиваем строку символом 0, если следующая строка новая (получили  \n или \r )
        clientline[index] = 0;
        filename = 0;
        // Распечатываем прочитанную строку.
        Serial.println(clientline);
        // Look for substring such as a request to get the root file
        if (strstr(clientline, "GET / ") != 0) {
          filename = rootFileName;
        }
        if (strstr(clientline, "GET /") != 0) {
          // this time no space after the /, so a sub-file
          if (!filename) filename = clientline + 5; // look after the "GET /" (5 chars)
          // a little trick, look for the " HTTP/1.1" string and
          // turn the first character of the substring into a 0 to clear it out.
          (strstr(clientline, " HTTP"))[0] = 0;
          // print the file we want
          Serial.println(filename);
         myFile = SD.open(filename);
          if (!myFile ) {
            client.println("HTTP/1.1 404 Not Found");
            client.println("Content-Type: text/html");
            client.println();
            client.println("<h2>File Not Found!</h2>");
            break;
          }
          Serial.println("Opened!");
           client.println("HTTP/1.1 200 OK");
           if (strstr(filename, ".htm") != 0)
             client.println("Content-Type: text/html");
         else if (strstr(filename, ".css") != 0)
             client.println("Content-Type: text/css");
         else if (strstr(filename, ".png") != 0)
             client.println("Content-Type: image/png");
          else if (strstr(filename, ".jpg") != 0)
             client.println("Content-Type: image/jpeg");
         else if (strstr(filename, ".gif") != 0)
             client.println("Content-Type: image/gif");
         else if (strstr(filename, ".3gp") != 0)
             client.println("Content-Type: video/mpeg");
         else if (strstr(filename, ".pdf") != 0)
             client.println("Content-Type: application/pdf");
         else if (strstr(filename, ".js") != 0)
             client.println("Content-Type: application/x-javascript");
         else if (strstr(filename, ".xml") != 0)
             client.println("Content-Type: application/xml");
         else
             client.println("Content-Type: text");
          client.println();
  byte cB[64];
  int cC=0;
    while (myFile.available())
       {
         cB[cC]=myFile.read();
         cC++;
       if(cC > 63)
        {
         client.write(cB,64);
        cC=0;
        }
       }
      if(cC > 0) client.write(cB,cC);
       myFile.close();
        } else {
          // everything else is a 404
          client.println("HTTP/1.1 404 Not Found");
          client.println("Content-Type: text/html");
          client.println();
          client.println("<h2>File Not Found!</h2>");
           }
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

Ваш скетч у меня почему-то не проходит компиляцию по строке 023  SPI.begin(10);

9ser
Offline
Зарегистрирован: 18.11.2012

Попробуй перенести все файлы из папок в корень карты памяти.

И соответственно исправь обращения к файлам в коде. Так же в библиотеке SD.h действуют ограничения к имени файла 8.3 символов

пример

toc
Offline
Зарегистрирован: 09.02.2013

ну зачем поднимать старые темы?....

Vadim111,
1. html содержит ссылку на myscript.jsp - тут ошибка
2. файл со скриптом на карте как называется? js?
3. какой content type?
4. в строке 057 непроверенное заявление выводите.

>> ... http сниффер ...
в 2017 году в любом браузере нажать F12 -network

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Это прям совет из будущего :)