От переводчика. Сегодня у нас лёгкий урок, мы можем расслабиться и просто следовать за объяснениями автора. Если вы внимательно ознакомились с предыдущей статьёй и уяснили принцип работы веб-сервера с файлами на SD карте памяти, то на этом занятии вам всё должно быть понятно — мы просто добавим одну строку в код страницы и немного модернизируем скетч из прошлого урока.

Несмотря на лёгкость сегодняшнего занятия, оно даёт нам важное умение — добавлять изображения на веб-страницы сервера и делать эти страницы красивыми и информативными.


Работа с изображениями


Веб-страницы, находящиеся SD карте Arduino сервера, могут содержать изображения. В этом уроке рассказывается как вставить JPEG картинку в HTML страницу и как отправить её браузеру, когда веб-сервер получает соответствующий HTTP запрос.

В качестве оборудования в этом уроке используются Arduino Uno с платой Ethernet Shield и microSD картой памяти.

Это видео демонстрирует в действии реализованный пример из этого урока:



HTML код для отображения картинок


HTML тег <img> используется для вставки изображения на веб-страницу. Возьмём файл index.htm из предыдущей части этого руководства и немного изменим его для добавления изображения.

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino SD Card Web Page</title>
    </head>
    <body>
        <h1>Arduino SD Card Page with Image and Link</h1>
        <img src="pic.jpg" />
        <p>Go to <a href="page2.htm">page 2</a>.</p>
    </body>
</html>

В вышеприведенном HTML коде изображение, содержащиеся в файле pic.jpg, добавляется на веб-страницу при помощи строки:

<img src="pic.jpg" />

Атрибут src используется для указания имени отображаемого файла картинки.

Исходный код


Для работы с этим примером нужно скопировать три файла на microSD карту памяти, которая затем будет вставлена Ethernet Shield.

SD_card_image.zip (8,2 КБ) — этот архив содержит HTML фалы веб-страниц index.htm, page2.htm и файл картинки pic.jpg, используемые в этом уроке.

HTTP запросы


При подключении к Arduino веб-серверу, браузер сначала отправляет HTTP запрос веб-страницы. После того, как браузер получает запрошенную страницу, он обнаружит, что она содержит изображение. Для загрузки этого изображения браузер отправит серверу ещё один HTTP запрос.

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


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

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

  Описание:  Arduino веб-сервер с SD картой памяти и возможностью работы с изображениями
  
  Оборудование: контроллер Arduino Uno, плата Ethernet Shield, microSD карта памяти 2 Гб, отформатированная в FAT16
                
  Программное обеспечение: среда разработки Arduino IDE (требуется наличие файлов index.htm, page2.htm и pic.jpg на microSD карте памяти)
  
  Ссылки:
    - WebServer example by David A. Mellis and modified by Tom Igoe
    - SD card examples by David A. Mellis and Tom Igoe
    - Ethernet library documentation: http://arduino.cc/en/Reference/Ethernet
    - SD Card library documentation: http://arduino.cc/en/Reference/SD

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

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

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

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

void setup() {
    // Выключение Ethernet чипа
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);
    
    Serial.begin(115200);
    
    // Инициализация 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("index.htm")) {
        Serial.println("ERROR - Can't find index.htm file!");
        return;
    }
    Serial.println("SUCCESS - Found index.htm file.");
    
    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 строка)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c; // сохраняем символ HTTP запроса
                    req_index++;
                }
                Serial.print(c); // печатаем HTTP запрос в Serial
                if (c == '\n' && currentLineIsBlank) {
                    // Открываем запрошенный(ые) файл(ы)
                    if (StrContains(HTTP_req, "GET / ") || 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("index.htm");
                    }
                    else if (StrContains(HTTP_req, "GET /page2.htm")) {
                        client.println("HTTP/1.1 200 OK");
                        client.println("Content-Type: text/html");
                        client.println("Connnection: close");
                        client.println();
                        webFile = SD.open("page2.htm");
                    }
                    else if (StrContains(HTTP_req, "GET /pic.jpg")) {
                        webFile = SD.open("pic.jpg");
                        if (webFile) {
                            client.println("HTTP/1.1 200 OK");
                            client.println();
                        }
                    }
                    // Посылаем веб-страницу клиенту
                    if (webFile) {
                        while(webFile.available()) {
                            client.write(webFile.read());
                        }
                        webFile.close();
                    }
                    // Обнуляем буфер
                    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)
}

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

Этот скетч работает так же, как и скетч из предыдущего урока, за исключением следующего участка кода, который обрабатывает запросы изображений в формате JPEG:

 else if (StrContains(HTTP_req, "GET /pic.jpg")) {
    webFile = SD.open("pic.jpg");
    if (webFile) {
        client.println("HTTP/1.1 200 OK");
        client.println();
    }
}

Этот код проверяет, запрашивает ли браузером JPEG изображение (файл) pic.jpg.

Если запрос изображения получен и файл этой картинки присутствует SD карте, то браузеру посылается стандартный HTTP ответ OK, а затем и сама JPEG картинка.

Как и в предыдущей части, код примера специально максимально упрощён для лучшего понимания принципа работы. Этот пример не обрабатывает случаи, когда ресурс (HTML файл или файл изображения) не может быть найден на SD карте. Он также рассчитан на работу только с файлом «pic.jpg».

Для практического применения можно выделять запрошенное имя HTML страницы или файла изображения из HTTP запроса, а затем пытаться найти его на SD карте памяти сервера. Также должна быть предусмотрена обработка случаев, когда запрашиваемый файл не может быть найден на SD карте.

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


Говоря простыми словами, здесь мы «настраиваем» скетч на выдачу (в ответ на запрос браузера) картинки, находящейся в файле pic.jpg. То есть в этом варианте наш веб-сервер может выдавать только одну картинку из определённого заранее файла (pic.jpg).

Такой вариант веб-сервера неприменим на практике и подходит только для целей обучения. Но это легко исправить — достаточно добавить обработку любых названий файлов и расширений популярных графических форматов файлов (PNG, GIF и т. д.) — и наш сервер станет универсальным и начнёт выдавать красивые веб-страницы с картинками, точно так же, как это делают «взрослые» сервера в интернете.


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

Комментарии (4)


  1. Rubilnik
    00.00.0000 00:00

    А можно просто использовать base64 inline код


    1. pavel_raskin
      00.00.0000 00:00

      Можно. Для статики отличный вариант. Но внешняя память будет поудобнее, если есть нужда хоть в каком-то относительно частом обновлении или дополнении контента.


    1. smart_alex Автор
      00.00.0000 00:00

      Использовать base64 можно, но это особый случай. Тут же фишка в том, чтобы научиться работать со стандартными графическими файлами — тогда можно будет создавать веб-страницы в привычном и наиболее используемом виде.


      1. Rubilnik
        00.00.0000 00:00

        Да, это я понял, конечно. Просто предложил в качестве альтернативного варианта)