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

Пишем код на Rust для запуска веб-сайта на ESP32

В предыдущих упражнениях мы обращались к сайтам и выводили ответ в консоль. В этом упражнении сделаем наоборот: запустим веб-сервер на ESP32. Этот сервер будет доступен в локальной сети Wi-Fi. Чтобы открыть его из интернета, потребуется дополнительная настройка. Сначала сосредоточимся на доступе к сайту в локальной сети Wi-Fi. По-прежнему будем работать только в режиме STA (то есть подключаться к существующей сети Wi-Fi).

Что мы будем делать

Настроим простой веб-сервер, который отдаёт одну страницу index.html. В этом примере будем считать, что ESP32 назначен IP-адрес «192.168.0.101» (он выводится в консоль во время работы). Когда сервер запущен, страницу можно открыть в браузере компьютера по адресу «http://192.168.0.101/». Можно использовать свою HTML-страницу или index.html, который я создал для этого упражнения и который можно найти здесь.

Note

Можно задать статический IP-адрес вместо того, чтобы его назначал DHCP-сервер. Так адрес будет постоянным, но добавятся дополнительные шаги. Чтобы не усложнять, в этом упражнении мы этого делать не будем. Настройку статического IP покажем в одном из следующих упражнений.

Хватит ждать, начнём прямо сейчас.

Создание проекта с помощью esp-generate

Чтобы создать проект, используйте команду esp-generate. Выполните:

esp-generate --chip esp32 webserver-html

Откроется экран с вопросом о выборе опций. Чтобы включить Wi-Fi, сначала нужно включить функции «unstable» и «alloc». Как вы могли заметить, пока не выбраны эти две опции, пункт Wi-Fi недоступен. Поэтому выбирайте их по одной:

  • Сначала выберите опцию «Enable unstable HAL features».
  • Выберите опцию «Enable allocations via the esp-alloc crate».
  • Теперь можно включить «Enable Wi-Fi via esp-radio crate».
  • Выберите опцию «Adds embassy framework support».

Также включите логирование.

  • Прокрутите до пункта «Flashing, logging and debugging (espflash)» и нажмите Enter.
  • Затем выберите «Use defmt to print messages».

Сохраните настройки, нажав на клавиатуре «s».

Обновляем зависимости

Крейт picoserve

picoserve — это крейт, который предоставляет асинхронный HTTP-сервер для bare-metal окружений и сильно вдохновлён Axum. Как можно догадаться по названию, изначально он создавался с расчётом на «Raspberry Pi Pico W» и Embassy. Но он прекрасно работает и с другими встраиваемыми рантаймами и железом, включая ESP32. Этот крейт сильно упрощает работу. Без него пришлось бы писать ядро веб-сервера с нуля, а это трудоёмкая задача, выходящая за рамки этой книги.

picoserve = { version = "0.17.1", features = ["embassy"] }

Структура проекта

Разделим логику на модули. В lib создадим два подмодуля: web.rs и wifi.rs.

├── build.rs
├── Cargo.toml
├── rust-toolchain.toml
├── src
│   ├── bin
│   │   └── main.rs
│   ├── index.html
│   ├── lib.rs
│   ├── web.rs
│   └── wifi.rs

Модуль lib

Перенесём макрос mk_static, который позволяет создавать статические переменные, инициализируемые во время выполнения, в модуль lib. Кроме того, включим нестабильную возможность языка (feature gate) impl_trait_in_assoc_type, которую требует крейт picoserve.

#![allow(unused)]
#![no_std]
#![feature(impl_trait_in_assoc_type)]

fn main() {
pub mod web;
pub mod wifi;

#[macro_export]
macro_rules! mk_static {
    ($t:ty,$val:expr) => {{
        static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
        #[deny(unused_attributes)]
        let x = STATIC_CELL.uninit().write(($val));
        x
    }};
}
}

Функция main (файл main.rs)

Чтобы использовать модуль lib, обычно пришлось бы обращаться к нему по полному имени проекта (например, webserver::web). Однако чтобы ссылки оставались одинаковыми в разных упражнениях, мы дадим импорту псевдоним «lib». Так к нему можно обращаться как к «lib::web».

В функции main начинаем с типового кода: настраиваем глобальный аллокатор кучи и инициализируем Embassy.

Далее создаём контроллер Wi-Fi, который передаём функции start_wifi; её мы вскоре определим в модуле wifi. Эта функция вернёт экземпляр сетевого стека.

Создадим экземпляр веб-приложения и настроим маршрутизацию и параметры с помощью крейта picoserve. Затем запустим несколько задач для обработки входящих запросов в соответствии с заданным размером пула. Каждая задача получает идентификатор, экземпляр приложения, сетевой стек и настройки сервера.

use webserver_html as lib;

#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {
    // generator version: 1.0.0

    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 98767);

    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_rtos::start(timg0.timer0);

    info!("Embassy initialized!");

    let radio_init = &*lib::mk_static!(
        esp_radio::Controller<'static>,
        esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller")
    );
    let rng = Rng::new();

    let stack = lib::wifi::start_wifi(radio_init, peripherals.WIFI, rng, &spawner).await;

    let web_app = lib::web::WebApp::default();
    for id in 0..lib::web::WEB_TASK_POOL_SIZE {
        spawner.must_spawn(lib::web::web_task(
            id,
            stack,
            web_app.router,
            web_app.config,
        ));
    }

    loop {
        Timer::after(Duration::from_secs(1)).await;
    }
}