Ethernet + SD card

Тема в разделе "Arduino & Shields", создана пользователем xRinatx, 11 дек 2011.

  1. xRinatx

    xRinatx Нуб

    Приветствую всех. У меня есть такой небольшой вопрос. Есть в наличии ардуина и Ethernet Shield v2.0 (нормальный, со встроенным слотом microSD). Все работает, но возник вопрос - а каким образом можно отдавать файлы с SD карточки в браузер пользователям? Или еще например каким образом можно делать html странички с изображениями, которые также будут подгружаться с sd карты?
     
  2. nailxx

    nailxx Официальный Нерд Администратор

    Вам необходимо реализовать обработку входящих HTTP запросов. Из URL запроса вы можете понять какой файл интересует клиента на другом конце. На основе этой информации необходимо создать правильный заголовок HTTP-ответа, отправить его, и затем не закрывая соединения байт-за-байтом застримить выбранный файл.

    Собственно, есть туториал.
     
  3. .c8r

    .c8r Нуб

    По-моемому вопрос какраз в том как застримить файл с карты? Как отдать клиенту строку из текстового файла с sd'шки ясно, а как прилепить изображение или что-то вроде того?
     
  4. nailxx

    nailxx Официальный Нерд Администратор

    Если речь именно об этом, то отдача бинарного файла вроде картинки по большому счёту не отличается от отдачи текстового файла. Разница лишь в HTTP-заголовках:

    Код (Text):

    Content-Type: application/octet-stream
     
    Скажет браузеру клиента о том, что ответ — это произвольный бинарный файл. Если по URL браузер не может определить конкретный тип файла (например URL имеет вид http://192.168.0.100/path/to/some/image/), то следует уточнить MIME-type. Например:

    Код (Text):

    Content-Type: image/png
     
    Если кроме того хочется заставить клиентский браузер скачивать файл, а не показывать его в окне, следует добавить заголовок вида:

    Код (Text):

    Content-Disposition: attachment; filename=default-download-filename.png;
     
     
  5. xRinatx

    xRinatx Нуб

    Угумс, все круто. Только теперь вот еще какой вопрос.
    Если отдавать файл с флешки пользователю, то можно наблюдать просто дико черепашью скорость, порядка 5 килобайт в секунду, что естественно немножко напрягает. Можно-ли с этим как-то бороться?
     
  6. Hokama

    Hokama Нерд

    присоединяюсь к вопросу скорости загрузки. нереально долго грузит css и js для страницы.
    три файла: html, css и js, в сумме 255 кб. страница грузится 40 секунд!
    кто как решал данный вопрос. дунька Mega R3 с Ethernet R3
     
  7. nailxx

    nailxx Официальный Нерд Администратор

    Тема действительно любопытная. Прошлый раз хотел поэкспериментировать самостоятельно, но не хватило времени, потом забыл. Итак, промежуточные результаты. Разогнать в пару раз можно передавая не байт за байтом, а сразу пачками. При таком подходе у меня получилось отдать 120 кб картинку за 9 секунд.

    Selection_009.png

    Это конечно тоже не фонтан, но уже лучше. Далее выяснилось, что узкое место — это чтение SD. Из 9 секунд оно занимает 8. Что с этим делать пока не ясно, ковыряния продолжаются. Блоки для SD было бы лучше использовать по 512 байт ибо это родной размер блока FAT, но просто изменение размера буфера до 512 ничем хорошим на Arduino пока не закончилось. Ковыряем.

    Код прилагается:

    Код (Text):

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

    #define BUFFER_SIZE 192
    #define ETH_SS 10
    #define SD_SS 4

    byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
    byte ip[] = { 192, 168, 10, 10 };
    EthernetServer server(80);

    void setup()
    {
        pinMode(ETH_SS, OUTPUT);
        digitalWrite(ETH_SS, HIGH);

        SD.begin(SD_SS);

        digitalWrite(SD_SS, HIGH);
        digitalWrite(ETH_SS, LOW);
        Ethernet.begin(mac, ip);
        server.begin();

        Serial.begin(9600);
    }

    void loop()
    {
        EthernetClient client = server.available();
        byte buff[BUFFER_SIZE];

        if (client) {
            bool eol = false;
            while (client.connected()) {
                if (client.available()) {
                    char c = client.read();
                    Serial.write(c);
                    if (c == '\r')
                        continue;

                    if (c == '\n' && eol) {
                        File f = SD.open("robot.jpg");
                        if (f) {
                            client.println("HTTP/1.1 200 OK");
                            client.println("Content-Type: image/jpeg");
                            client.println();

                            while (true) {
                                byte n = f.readBytes((char*)buff, BUFFER_SIZE);
                                if (!n)
                                    break;
                                client.write(buff, n);
                            }

                            f.close();
                        } else {
                            client.println("HTTP/1.1 404 Not Found");
                            client.println("Content-Type: text/plain");
                            client.println();
                            client.println("Not found");
                            client.println();
                        }

                        break;
                    }

                    eol = (c == '\n');
                }
            }

            client.stop();
        }
    }
     
     
  8. Корней

    Корней Гик

  9. Hokama

    Hokama Нерд

    собственно, попытки поиграть с размером буфера к какимто ощутимым результатам не привели..
    использую библиотеку SdFat.
    Код (Text):

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

    /************ ETHERNET STUFF ************/
    byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
    byte ip[] = { 192, 168, 1, 177 };
    char rootFileName[] = "index.htm";
    EthernetServer server(80);

    /************ SDCARD STUFF ************/
    Sd2Card card;
    SdVolume volume;
    SdFile root;
    SdFile file;

    // store error strings in flash to save RAM
    #define error(s) error_P(PSTR(s))

    void error_P(const char* str) {
      PgmPrint("error: ");
      SerialPrintln_P(str);
      if (card.errorCode()) {
        PgmPrint("SD error: ");
        Serial.print(card.errorCode(), HEX);
        Serial.print(',');
        Serial.println(card.errorData(), HEX);
      }
      while(1);
    }

    void setup() {

      Serial.begin(115200);

      PgmPrint("Free RAM: ");
      Serial.println(FreeRam());

      // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
      // breadboards.  use SPI_FULL_SPEED for better performance.
      pinMode(10, OUTPUT);                       // set the SS pin as an output (necessary!)
      digitalWrite(10, HIGH);                    // but turn off the W5100 chip!

      if (!card.init(SPI_FULL_SPEED, 4)) error("card.init failed!");

      // initialize a FAT volume
      if (!volume.init(&card)) error("vol.init failed!");

      PgmPrint("Volume is FAT");
      Serial.println(volume.fatType(),DEC);
      Serial.println();

      if (!root.openRoot(&volume)) error("openRoot failed");

      // list file in root with date and size
      PgmPrintln("Files found in root:");
      root.ls(LS_DATE | LS_SIZE);
      Serial.println();

      // Recursive list of all directories
      PgmPrintln("Files found in all dirs:");
      root.ls(LS_R);

      Serial.println();
      PgmPrintln("Done");

      // Debugging complete, we start the server!
      Ethernet.begin(mac, ip);
      server.begin();
    }

    // How big our line buffer should be
    #define BUFSIZ 192

    void loop()
    {

      char clientline[BUFSIZ];
      char *filename;
      int index = 0;
      int image = 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();

            // If it isn't a new line, add the character to the buffer
            if (c != '\n' && c != '\r') {
              clientline[index] = c;
              index++;
              // are we too big for the buffer? start tossing out data
              if (index >= BUFSIZ)
                index = BUFSIZ -1;

              // continue to read more data!
              continue;
            }

            // got a \n or \r new line, which means the string is done
            clientline[index] = 0;
            filename = 0;

            // Print it out for debugging
            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);

              if (! file.open(&root, filename, O_READ)) {
                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();

              int16_t c;
              while ((c = file.read()) >= 0) {
                  // uncomment the serial to debug (slow!)
                  //Serial.print((char)c);
                  client.print((char)c);
              }
              file.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();
      }
    }
     
    буду пробовать дальше.
    Корней, спасибо за ссылки. попробую разобраться.
     
  10. nailxx

    nailxx Официальный Нерд Администратор

    Омайгадбл! Я ускорился до 2.5 секунды на ту же картику (121 килобайт). Фишка вот в чём. Если посмотреть на имплементацию File::readBytes в библиотеке SD мы увидим, что её там попросту нет! File унаследован от Stream и поэтому пользуется его имплементацией, которая работает в общем случае, но мягко говоря, не эффективна:

    Код (Text):
    size_t Stream::readBytes(char *buffer, size_t length)
    {
      size_t count = 0;
      while (count < length) {
        int c = timedRead();
        if (c < 0) break;
        *buffer++ = (char)c;
        count++;
      }
      return count;
    }

    int Stream::timedRead()
    {
      int c;
      _startMillis = millis();
      do {
        c = read();
        if (c >= 0) return c;
      } while(millis() - _startMillis < _timeout);
      return -1;     // -1 indicates timeout
    }
     
    То есть она тупо сводится обратно к побайтному чтению. Выход: использовать одну из перегрузок метода File::read, с той же сигнатурой и вообще той же семантикой, что и readBytes. Она то и осуществляет скоростное чтение. Странно, что метод readBytes не перекрыт в библиотеке SD изначально. Так или иначе, полный листинг далее:

    Код (Text):

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

    #define BUFFER_SIZE 512
    #define ETH_SS 10
    #define SD_SS 4

    byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
    byte ip[] = { 192, 168, 10, 10 };
    EthernetServer server(80);

    void setup()
    {
        pinMode(ETH_SS, OUTPUT);
        digitalWrite(ETH_SS, HIGH);

        SD.begin(SD_SS);

        digitalWrite(SD_SS, HIGH);
        digitalWrite(ETH_SS, LOW);
        Ethernet.begin(mac, ip);
        server.begin();
    }

    void loop()
    {
        EthernetClient client = server.available();
        byte buff[BUFFER_SIZE];

        if (client) {
            bool eol = false;
            while (client.connected()) {
                if (client.available()) {
                    char c = client.read();
                    if (c == '\r')
                        continue;

                    if (c == '\n' && eol) {
                        File f = SD.open("robot.jpg");
                        if (f) {
                            client.println("HTTP/1.1 200 OK");
                            client.println("Content-Type: image/jpeg");
                            client.println();

                            while (true) {
                                int n = f.read((char*)buff, BUFFER_SIZE);
                                if (!n)
                                    break;
                                client.write(buff, n);
                            }

                            f.close();
                        } else {
                            client.println("HTTP/1.1 404 Not Found");
                            client.println("Content-Type: text/plain");
                            client.println();
                            client.println("Not found");
                            client.println();
                        }

                        break;
                    }

                    eol = (c == '\n');
                }
            }

            client.stop();
        }
    }
     
    Достоверно известно, что открыть файл, полностью считать его, закрыть и отправить затраченное время по Serial занимает 490 мс, что уже вполне себе адекватно. Смысла что-то делать с этим в рамках разбирательства с SD дальше не вижу.
     
    fixedip, AlexVS и Hokama нравится это.
  11. Hokama

    Hokama Нерд

    nailxx! теперь страница открывается за 4,33 секунды! в 10 раз возросла скорость чтения.

    [​IMG]
     
  12. nailxx

    nailxx Официальный Нерд Администратор

    Чтобы ещё больше ускорить осталось только HTTP ETag прикрутить

    Fuck_yeah_kindle-337x450.png
     
  13. Nitro77rus

    Nitro77rus Гик

    Подскажите пожалуйста, что я не так делаю?
    Есть скетч со страничкой отображающей данные с датчиков, но при подключении библиотеки SD.h, страничка перестаёт грузиться.
     
  14. Nitro77rus

    Nitro77rus Гик

    Всё, победил :)
     
  15. 9ser

    9ser Нуб

    Добрый день.
    Дорабатываю проект. Изначально была библиотека SD.h не устраивал формат имени файлов 8.3. Перешел на sdFat.h
    Подскажите как прочесть подкаталоги?
    Код (C++):

    bool openWebFile() {
      char *fileName;
      fileName = strtok(HTTP_req, GET);
      webFile = SD.open(fileName);
      if (webFile) {return true;}
              else {return false;}
     
    Открывает файлы только в корне
     
    Последнее редактирование: 26 янв 2019