PHP для Arduino — часть 1

Плата шилдПосле знакомства с Arduino, в голове каждого, рано или поздно, появится идея для проекта, который потребует подключения платы Ардуино к сети. И не важно, что это будет:  домашняя автоматика, доступная по сети, или набор датчиков, передающих показания в базу данных, но Ардуино все равно нужно как-то подключить к Интернету. Здесь нам пригодится  плата Ethernet Shield.

Сначала немного истории. Изначально плата Ethernet Shield была совместима с небольшим Arduino. Почему не с Мега? Ну, потому, что для связи с чипом W5100, который является сердцем шилда, используется протокол SPI — на цифровых входах: 10, 11, 12 и 13. В Arduino Mega SPI находится на других входах. Обойти это можно было, согнув ножки, и подключив их кабелем к нужным цифровым входам.

Но такие манипуляции проводились со старыми шилдами. В настоящее время, платы Ethernet Shield совместимы как с небольшими Arduino (UNO), так и с Mega (с процессорами ATmega1280 и ATmega2560 ). Как распознать обновленный шилд? Все очень просто, если у шилда есть слот для карты microSD — значит, это более новая версия Ethernet Shield.

Ethernet Shield microSD
Ethernet Shield со слотом для карты microSD

Мы уже говорили, что W5100 — интегральная схема, управляющая Ethernet Shield. Данная схема отличается от многих других Ethernet-контроллеров тем, что стек TCP/IP реализован непосредственно на кристалле. Но что это значит для пользователя? Что библиотека, которую вам нужно использовать для связи, требует меньше оперативной памяти и занимает меньше флэш-памяти по сравнению с чипами, на которых нет стека TCP/IP.

Как использовать Ethernet Shield?

В Интернете есть много примеров того, как создавать веб-сайты, которые отображают данные из Arduino. Впрочем, если у вас уже есть опыт программирования на Arduino, вы наверняка знаете, что все строки, даже определенные в коде, занимают оперативную память, которой всегда не хватает.

Возьмем официальный пример кода из руководства по Arduino:

Потребуется 40 байт оперативной памяти (15 символов в HTTP… + конечный ноль и 23 в Conte… + конечный ноль). Легко представить, что это значит, когда в нашем распоряжении вообще 2 КБ. HTML-страница не может быть слишком сложной.

Есть решение, которое может нам помочь — хранение строк во флэш памяти. Это может уменьшить использование оперативной памяти, но часто за счет дополнительного кода. Доступ к строкам, определенным таким образом, требует использования специального макроса, и компилятор не позволит нам использовать этот макрос при вызове функции ожидания.

Кроме того, любое изменение HTML-кода, которое мы хотим отправить, означает, что мы должны изменить скетч и загрузить его в Arduino.

Подождите, скажите вы, но у Ethernet Shield был слот для карты microSD — разве мы не можем как-то использовать пространство, предоставленное SD-картой? Можем, но нужно немного постараться.

Первое — Ethernet Shield необходимо настроить для работы в сети — это означает, что нужно произвести настройку MAC и IP адресов. Это можно сделать следующим образом:

MAC-адрес лучше всего прочитать самостоятельно с наклейки внизу шилда. IP-адрес зависит от конфигурации сети.

Приведенная выше последовательность строк будет работать только в локальной сети — т.е. когда все IP-адреса находятся в одной подсети. Если шилд должен подключаться к хостам в других сетях (как в качестве клиента, так и в качестве сервера), вы должны указать еще один аргумент — таблицы с 4 числами — IP-адрес шлюза по умолчанию. Подробнее в описании на сайте Ардуино «Интернет библиотеки».

Большая часть скетча почти готова. Интернет библиотека, входящая в состав Arduino IDE, — это не то, что подойдет нам лучше всего. Это облегчает создание, в том числе TCP-сервера, но еще нужна часть для HTTP-сервера. Вот почему нам нужен Webduino — на основе Интернет библиотеки (кто-то проделал за нас большую работу по созданию HTTP-сервера).

Загружаем библиотеку со страницы «Загрузки» и распаковываем ее.

Мы немного модифицируем Webduino для наших нужд, он будет доступен для скачивания в конце этой статьи, так что пока ничего устанавливать не нужно.

Вебдуино — как начать?

Для начала нам нужно знать, что использование этой строки:

даст нам возможность зарегистрировать любую функцию, вызываемую при совпадении URL:

Пример:

Если URL-адрес начинается (то есть часть после адреса Arduino) с blob.htm , то функция будет вызываться:

который должен принимать аргументы в соответствии с определенным типом:

server объект WebServer, для которого был вызван метод type; тип подключения INVALID, GET, HEAD, POST; url_tail — это то, что осталось в URL-адресе после сопоставления blob.htm . Если URL-адрес был усечен из-за небольшого буфера, используемого Webduino (память), последний параметр tail_complete будет иметь значение false.

Хорошо, но не очень удобно прописывать каждую функцию, тем более, что мы хотим обслуживать данные с SD-карты, содержимое которой нам неизвестно. Нам сейчас пригодится:

Эта строка позволит нам зарегистрировать функцию в нашем коде, вызываемом, когда не было другого совпадения с зарегистрированными функциями:

Поэтому, если URL-адрес не соответствует какой-либо ранее сообщенной функции, будет вызвана функция, указанная для:

Теперь нужно проверить, так ли это:

Это имя файла на SD-карте (поскольку совпадения не было, url_tail содержит полный URL-адрес, включая первый символ / после адреса Arduino). Когда файла нет — выводим HTTP 404, если есть — просто отправляем клиенту.

Как прочитать файл с SD-карты?

Как и в случае с HTTP-сервером, нам не нужно делать все самостоятельно. Мы будем использовать библиотеку SdFatLib , которая имеет поддержку файловых систем FAT16 и FAT32 (что обычно и поддерживается картами SD и SDHC).

Код для инициализации и обработки файлов:

который был зарегистрирован:

Его задача — найти файл во вкладке и отправить его в браузер:

В самом начале мы зарегистрировали несколько символьных констант, хранящихся во флеш-памяти: 

макрос:

Этот макрос является частью Webduino, который используется для записи строк во флэш-память, а не в ОЗУ. И эти константы являются именами различных форматов данных, так называемого MIME Type, который мы хотим обрабатывать. О чем это? Браузер не знает, какие данные будут отправлены сервером. Будет это HTML или изображение, она узнает из заголовка Content-Type, о котором поговорим чуть позже.

Затем мы «избавляемся» от слэша:

Затем пытаемся открыть файл на SD-карте — если не получается — выводим ошибку HTTP 404 (Not Found):

Если файл успешно открылся, то в:

То мы постараемся прочитать его и отправить клиенту.

Теперь несколько комментариев. Во-первых, SdFatLib поддерживает только короткие имена в формате 8.3. Если вы попытаетесь использовать более длинные имена (что позволяет FAT32), помните, что имя, видимое SdFatLib, будет отличаться от того, которое вы увидите при сопряжении карты на своем компьютере. А если сделать:

index.html
(четыре символа в расширении) то имя будет
ind~1.htm
для SdFatLib.Ну и даже если сейчас переименовать комп в
index.htm
, то запись в каталоге будет в расширенном виде. Вам нужно удалить файл и создать его заново с именем в формате 8.3.

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

Ну, хорошо, вернемся к коду:

Так как нам удалось открыть файл, то ищем точку в имени файла и если находим, проверяем, будет ли остальное (расширение) соответствовать известным нам расширениям. Потому что нам недостаточно отправить данные HTTP-клиенту — мы должны отправить заголовок с информацией о правильном Content-Type (о нем мы говорили ранее), иначе данные могут быть неправильно интерпретированы браузером. </p>

Несколько слов о том, как выглядит ответ веб-сервера. Он разделен на две части. Первая, это так называемые заголовки. Браузер воспринимает все в начале как заголовок, пока не встретит пустую строку текста (строки разделяются символами CR LF). В остальном правильный ответ. То, что интерпретируется, будет зависеть от заголовков. Сервер может помочь браузеру, установив заголовок, определяющий тип данных:

До первого двоеточия находится имя заголовка ( здесь Content-Type ), за которым следует значение заголовка. Здесь используются так называемые MIME — типы (можете найти в Википедии). Например:

для изображения PNG:

для JPG или:

для HTML-файла.

Обязательным заголовком является статус — то есть был ли обработан запрос клиента, была ли ошибка или перенаправление:

Мы имею в виду, что пока все идет хорошо, будет контент. Во-первых, это протокол:

и его версия (версия 1.0) а потом сам код 200 — в HTTP — это означает, что все в порядке. Другими распространенными кодами являются 404 — ресурс не найден (знаменитый Not Found), 301 и 302 — редиректы.

Зная это, мы пытаемся распознать расширение файла и отправить соответствующий заголовок:

Итак, у нас уже есть отправленный HTTP-заголовок (заканчивается пустой строкой):

поэтому мы будем отправлять только данные:

Считываем 30 байт, отправляем клиенту через функцию буфера отправленных данных. Зачем? Ну а если использовать самое простое решение и отправлять данные посимвольно, то каждый символ будет в отдельном TCP-пакете. Это будет очень неэффективное решение. Потому, что:

отправляет данные медленно.

Вот почему мы написали функцию:

которая принимает в качестве аргументов объект веб-сервера, указатель на буфер данных и размер буфера. Почему бы нам не использовать функции размера буфера символов, такие как:

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

В WC и C++ концом строки является символ 0 (не цифра, а только байт со значением 0). Если в нашем потоке данных могут появляться нули, то все функции, связанные со строками и предлагаемые стандартной библиотекой, нам не пригодятся.

По этой причине мы должны явно указать количество данных, отправляемых в буфер.

И в основном это все. У нас есть веб-сервер на Arduino, который отправляет данные с SD-карты.

Имеет ли это смысл?

Всего несколько тестов с более сложным веб-сайтом (не один файл HTML, а несколько CSS и изображений), чтобы увидеть, что у этого решения есть свои ограничения. Arduino однопоточный, поэтому каждый элемент с нашего веб-сервера загружается по очереди. Это означает, что сайт загружается медленно с точки зрения пользователя.

Так для чего это нужно? Arduino может представить данные, собранные с датчиков, в более удобной форме, если нет ограничения объема оперативной памяти, необходимой для более сложного веб-сайта. Сохраняя HTML-код на SD-карте, мы избавляемся от этого ограничения. Но как поместить данные, собранные Arduino с датчиков, в HTML, хранящиеся на SD-карте?

Нам нужно что-то, что позволит нам вводить данные в HTML между «чтением» и «отправкой». Итак, что-то вроде PHP для Ардуино…

Конечно же, это преувеличение! Нам нужно что-то, что больше похоже на шаблоны, чем на полноценный PHP, но в начале PHP тоже не был полноценным

Как сделать такой парсер (и полный код скетча) — в нашей следующей статье «PHP для Arduino — часть 2».

С Уважением, МониторБанк

Добавить комментарий