Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Веб-модуль: отдача веб-страницы

Типовой код для подключения Wi-Fi готов. Далее с помощью крейта picoserve настроим маршрут для корневого URL («/»), который будет отдавать нашу HTML-страницу.

Нестабильная возможность impl_trait_in_assoc_type

Крейту picoserve нужна возможность языка impl_trait_in_assoc_type, которая в Rust пока нестабильна. Чтобы её включить, добавьте следующую строку в начало файла lib.rs (мы уже сделали это):

#![allow(unused)]
#![feature(impl_trait_in_assoc_type)]
fn main() {
}

Приложение и маршрутизация

Крейт picoserve предоставляет различные трейты для настройки маршрутизации и других функций, нужных веб-приложению. Трейт AppBuilder используется для создания статического роутера без состояния, а трейт AppWithStateBuilder позволяет создать статический роутер с состоянием приложения. Поскольку наше приложение отдаёт только одну HTML-страницу и не требует состояния, реализуем трейт AppBuilder. Больше примеров использования picoserve можно найти здесь.

#![allow(unused)]
fn main() {
pub struct Application;

impl AppBuilder for Application {
    type PathRouter = impl routing::PathRouter;

    fn build_app(self) -> picoserve::Router<Self::PathRouter> {
        picoserve::Router::new().route(
            "/",
            routing::get_service(File::html(include_str!("index.html"))),
        )
    }
}
}

Мы создали простую структуру, которая реализует трейт AppBuilder. Нужно указать тип PathRouter, и мы определяем его как любой тип, реализующий трейт routing::PathRouter.

Затем нужно реализовать функцию build_app, которая возвращает экземпляр Router. Мы настраиваем единственный маршрут для «/», который отдаёт статическую HTML-страницу. Содержимое HTML-страницы встраивается в приложение на этапе компиляции с помощью макроса include_str!(“index.html”). Поместите файл «index.html» в папку «src/».

Размер пула

Чтобы обрабатывать входящие запросы, нужно запустить несколько задач. Вскоре мы создадим функцию web_task (задачу Embassy) с размером пула, который задаётся константой, объявленной прямо сейчас. Затем запустим задачи в цикле на основе этого значения, которое определяет, сколько задач может работать одновременно.

#![allow(unused)]
fn main() {
pub const WEB_TASK_POOL_SIZE: usize = 2;
}

Мы задали размер пула 2. В примерах picoserve размер пула равен 8. Его можно увеличить, но имейте в виду, что тогда придётся соответствующим образом изменить и такие ресурсы, как сокеты и область памяти.

Веб-приложение

Определим структуру WebApp, которая содержит экземпляр роутера picoserve и конфигурацию.

#![allow(unused)]
fn main() {
pub struct WebApp {
    pub router: &'static Router<<Application as AppBuilder>::PathRouter>,
    pub config: &'static picoserve::Config<Duration>,
}
}

Далее реализуем трейт Default для WebApp и инициализируем роутер picoserve, вызвав build_app. Также настроим таймауты сервера, которые ограничивают время выполнения операций: чтения запроса, ожидания чтения или ожидания записи ответа. Если какая-либо операция превышает таймаут, соединение будет закрыто.

#![allow(unused)]
fn main() {
impl Default for WebApp {
    fn default() -> Self {
        let router = picoserve::make_static!(AppRouter<Application>, Application.build_app());

        let config = picoserve::make_static!(
            picoserve::Config<Duration>,
            picoserve::Config::new(picoserve::Timeouts {
                start_read_request: Some(Duration::from_secs(5)),
                read_request: Some(Duration::from_secs(1)),
                write: Some(Duration::from_secs(1)),
                persistent_start_read_request: Some(Duration::from_secs(1)),
            })
            .keep_connection_alive()
        );

        Self { router, config }
    }
}
}

Функция веб-задачи

Мы создали задачу Embassy и указали размер пула в атрибуте. Веб-сервер будет слушать порт 80. Для каждой задачи мы определяем буферы чтения и записи TCP, а также HTTP-буфер. Наконец, вызываем функцию listen_and_serve из picoserve для обработки входящих запросов.

#![allow(unused)]
fn main() {
#[embassy_executor::task(pool_size = WEB_TASK_POOL_SIZE)]
pub async fn web_task(
    task_id: usize,
    stack: Stack<'static>,
    router: &'static AppRouter<Application>,
    config: &'static picoserve::Config<Duration>,
) -> ! {
    let port = 80;
    let mut tcp_rx_buffer = [0; 1024];
    let mut tcp_tx_buffer = [0; 1024];
    let mut http_buffer = [0; 2048];

    picoserve::Server::new(router, config, &mut http_buffer)
        .listen_and_serve(task_id, stack, port, &mut tcp_rx_buffer, &mut tcp_tx_buffer)
        .await
        .into_never()
}
}

Клонирование существующего проекта

Можно клонировать проект, который я создал (или просто посмотреть его), и перейти в папку webserver-html.

git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/webserver-html

Как запустить?

В этом примере тоже нужно передать переменные окружения для подключения к Wi-Fi. Можно создать файл .env или передать их напрямую, как делаю я здесь.

SSID=YOUR_WIFI_NAME PASSWORD=YOUR_WIFI_PASSWORD  cargo run --release

Когда прошивка будет загружена в ESP32, в консоли должен появиться такой вывод с IP-адресом, который назначил ваш Wi-Fi-роутер.

Убедитесь, что ваш компьютер подключён к той же сети Wi-Fi. Затем откройте веб-страницу в браузере по адресу «http://192.168.0.101/» (замените на полученный IP-адрес).