}
}
void loop()
{
}
Настройка веб-сервера
Проект «Физический веб-сервер», описанный далее в этой главе, иллюстрирует организацию скетча, реализующего веб-сервер. А в этом разделе мы рассмотрим функции, помогающие реализовать веб-сервер.
Большая часть функций, необходимых для реализации веб-сервера, содержится в классе EthernetServer. Для запуска веб-сервера после установки соединения с сетью требуется пройти еще два этапа. Во-первых, нужно создать новый объект сервера, указав номер порта, который должен использоваться для приема входящих запросов. Это объявление находится в скетче перед функцией setup:
EthernetServer server = EthernetServer(80);
Обычно для приема запросов веб-серверы используют порт 80. То есть если вы настроили свой веб-сервер на обслуживание порта 80, вам не придется добавлять этот номер в адреса URL, чтобы связаться с сервером.
Во-вторых, чтобы фактически запустить сервер, в функции setup нужно выполнить следующую команду:
server.begin();
Эта функция запустит сервер, который будет ждать, пока кто-то не запросит страницу, которую обслуживает данный сервер. Фактическое обслуживание осуществляется в функции loop с применением функции available. Эта функция возвращает null (если нет запросов для обслуживания) или объект EthernetClient. Данный объект, как это ни странно, используется также для отправки исходящих запросов из Arduino к внешним веб-серверам. В данном случае EthernetClient представляет соединение между веб-сервером и браузером клиента.
Получив этот объект, можно прочитать входящий запрос с помощью read и вернуть HTML-ответ с помощью функций write, print и println. Закончив отправку HTML-ответа клиенту, нужно завершить сеанс вызовом функции stop объекта клиента. Я расскажу, как это сделать, в разделе «Физический веб-сервер» далее в этой главе.
Выполнение запросов
Плата Arduino может действовать не только как веб-сервер, но и как веб-браузер, посылая запросы удаленным веб-серверам, которые могут находиться в вашей сети или в Интернете.
Чтобы выполнить запрос, сначала нужно установить соединение с сетью, как в случае с веб-сервером, описанном в предыдущем разделе, но вместо объекта EthernetServer создать объект EthernetClient:
EthernetClient client;
Больше с объектом клиента ничего не требуется делать до отправки веб-запроса. Чтобы отправить веб-запрос, следует выполнить следующие действия:
if (client.connect("api.openweathermap.org", 80))
{
client.println("GET /data/2.5/weather?q=Manchester,uk HTTP/1.0");
client.println();
while (client.connected())
{
while (client.available())
{
Serial.write(client.read());
}
}
client.stop();
}
Функция connect вернет true, если соединение с веб-сервером было успешно установлено. Две команды client.println посылают веб-серверу запрос на получение желаемой страницы. Затем два вложенных цикла while читают данные, пока клиент остается подключенным к веб-серверу и продолжают поступать данные.
Может показаться заманчивым объединить два цикла while в один с условием client.available() && client.connected(), но такое объединение — далеко не то же самое, что два отдельных цикла, так как данные могут поступать от веб-сервера фрагментами из-за низкой скорости сети или по другим причинам. Внешний цикл поддерживает соединение открытым, а внутренний извлекает данные.
Это блокирующее решение (скетч не сможет производить никаких других действий, пока выполнение запроса не завершится). Если для вашего проекта такое решение неприемлемо, включите во внутренний цикл while код, выполняющий проверку других условий.
Примеры использования Ethernet
Далее демонстрируются два примера практического использования библиотеки Ethernet. Вместе они охватывают большинство вариантов сетевых взаимодействий, которые могут вам пригодиться при создании своих проектов на Arduino.
Физический веб-сервер
Первый пример иллюстрирует наиболее частый случай использования сетевых возможностей Arduino. Здесь плата выступает в роли веб-сервера. Подключившись к веб-серверу Arduino, посетители смогут не только читать аналоговые входы, но и изменять уровни на цифровых выходах, щелкая на кнопках в веб-странице (рис. 12.5).
Рис. 12.5. Интерфейс физического веб-сервера
Этот пример демонстрирует отличный способ связать плату Arduino со смартфоном или планшетным компьютером, так как для отправки запросов Arduino достаточно самого простого браузера. Скетч, реализующий этот пример (sketch_12_02_server), содержит 172 строки кода, поэтому он не будет приводиться здесь целиком, но я рекомендую открыть его в Arduino IDE и заглядывать в него в процессе чтения моих пояснений.
Первая часть скетча содержит код, стандартный для любого скетча, реализующего сетевые взаимодействия. Здесь выполняется импортирование библиотек и определение объектов EthernetServer и EthernetClient.
Далее определяются переменные, служащие разным целям:
const int numPins = 5;
int pins[] = {3, 4, 5, 6, 7};
int pinState[] = {0, 0, 0, 0, 0};
char line1[100];
char buffer[100];
Константа numPins определяет размер массивов pins и pinState. Массив pinState предназначен для хранения состояний цифровых выходов, HIGH или LOW. Функция setup настраивает все контакты, перечисленные в массиве pins, на работу в режиме цифровых выходов. Она также устанавливает соединение с сетью, как было показано в примерах ранее. Наконец, массивы символов line1 и buffer предназначены для хранения первой и последующих строк HTTP-запроса соответственно.
Далее приводится функция loop:
void loop()
{
client = server.available();
if (client)
{
if (client.connected())
{
readHeader();
if (! pageNameIs("/"))
{
client.stop();
return;
}
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println();
sendBody();
client.stop();
}
}
}
Функция проверяет наличие любых запросов от браузеров, ожидающих обработки. Если был получен запрос и соединение с клиентом еще не разорвано, вызывается функция readHeader. Эту функцию вы найдете ближе к концу скетча. Функция readHeader читает содержимое заголовка запроса в буфер (line1) и пропускает остальные строки запроса. Это необходимо, чтобы получить имя страницы, запрошенной браузером, и любые дополнительные параметры запроса, если они имеются.
Обратите внимание: из-за большого объема текста, который посылается скетчем в монитор последовательного порта и сеть, я использовал функцию F, сохраняющую массивы символов во флеш-памяти (см. главу 6).
После чтения заголовка вызывается функция pageNameIs (также находится ближе к концу скетча), чтобы проверить совпадение имени запрошенной страницы с именем корневой страницы (/). Если была запрошена не корневая страница, такой запрос игнорируется. Это важно, потому что многие браузеры посылают веб-серверу дополнительные запросы с целью получить значок для веб-сайта. Эти запросы не следует путать с другими запросами к серверу.
Теперь нужно сгенерировать ответ с заголовком и некоторой разметкой HTML, которую смог бы отобразить браузер. Функция sendHeader генерирует ответ «OK», чтобы показать, что запрос браузера признан допустимым. Функция sendBody, представленная далее, организована намного сложнее:
void sendBody()
{
client.println(F("<html><body>"));
sendAnalogReadings();
client.println(F("<h1>Output Pins</h1>"));
client.println(F("<form method='GET'>"));
setValuesFromParams();
setPinStates();
sendHTMLforPins();
client.println(F("<input type='submit' value='Update'/>"));
client.println(F("</form>"));
client.println(F("</body></html>"));
}
Она выводит