esp32-website/src/main.rs

290 lines
8.8 KiB
Rust

//! Example for an HTTP server using [edge-http](https://github.com/ivmarkov/edge-net) as the
//! HTTP server implementation.
//!
//! Note: If you run out of heap memory, you need to increase `heap_size` in cfg.toml
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)]
extern crate alloc;
use alloc::format;
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::sync::atomic::{AtomicI32, Ordering};
#[doc(hidden)]
pub use esp_hal as hal;
use edge_http::io::server::{Connection, Handler, Server};
use edge_http::io::Error;
use edge_http::Method;
use edge_nal_embassy::{Tcp, TcpBuffers};
use embedded_io_async::{Read, Write};
use embassy_net::{Config, Runner, StackResources};
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::tsens::{self, TemperatureSensor};
use esp_println::logger::init_logger;
use esp_println::println;
use esp_wifi::wifi::{
ClientConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiStaDevice,
WifiState,
};
use esp_wifi::{init, EspWifiController};
use hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup};
// Patch until https://github.com/embassy-rs/static-cell/issues/16 is fixed
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
}};
}
static TEMP: AtomicI32 = AtomicI32::new(0);
const SSID: &str = "ABakery"; // env!("SSID");
const PASSWORD: &str = env!("PASSWORD");
const HTML: &str = include_str!("index.html");
const FONT: &[u8] = include_bytes!("Figtree[wght].woff2");
/// Number of sockets used for the HTTPS server
#[cfg(feature = "esp32")]
const SERVER_SOCKETS: usize = 10;
#[cfg(not(feature = "esp32"))]
const SERVER_SOCKETS: usize = 10;
/// Total number of sockets used for the application
const SOCKET_COUNT: usize = 1 + 1 + SERVER_SOCKETS; // DHCP + DNS + Server
const RX_SIZE: usize = 4096;
const TX_SIZE: usize = 2048;
/// HTTPS server evaluated at compile time with socket count and buffer size.
pub type HttpServer = Server<SERVER_SOCKETS, RX_SIZE, 32>;
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) -> ! {
init_logger(log::LevelFilter::Info);
let peripherals = esp_hal::init({
let mut config = esp_hal::Config::default();
config.cpu_clock = CpuClock::max();
config
});
esp_alloc::heap_allocator!(130 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
let temperature_sensor =
TemperatureSensor::new(peripherals.TSENS, tsens::Config::default()).unwrap();
let init = &*mk_static!(
EspWifiController<'_>,
init(
timg0.timer0,
Rng::new(peripherals.RNG),
peripherals.RADIO_CLK,
)
.unwrap()
);
let wifi = peripherals.WIFI;
let (wifi_interface, controller) =
esp_wifi::wifi::new_with_mode(init, wifi, WifiStaDevice).unwrap();
cfg_if::cfg_if! {
if #[cfg(feature = "esp32")] {
let timg1 = TimerGroup::new(peripherals.TIMG1);
esp_hal_embassy::init(timg1.timer0);
} else {
use esp_hal::timer::systimer::SystemTimer;
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(systimer.alarm0);
}
}
let config = Config::dhcpv4(Default::default());
let seed = 1234; // very random, very secure seed
// Init network stack
let (stack, runner) = embassy_net::new(
wifi_interface,
config,
mk_static!(
StackResources<SOCKET_COUNT>,
StackResources::<SOCKET_COUNT>::new()
),
seed,
);
spawner.spawn(connection(controller)).ok();
spawner.spawn(net_task(runner)).ok();
spawner.spawn(update_temperature(temperature_sensor)).ok();
loop {
if stack.is_link_up() {
break;
}
Timer::after(Duration::from_millis(500)).await;
}
println!("Waiting to get IP address...");
loop {
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
println!("Point your browser to http://{}/", config.address.address());
break;
}
Timer::after(Duration::from_millis(500)).await;
}
let mut server = HttpServer::new();
let buffers = TcpBuffers::<SERVER_SOCKETS, TX_SIZE, RX_SIZE>::new();
let tcp = Tcp::new(stack, &buffers);
use edge_nal::TcpBind;
let acceptor = tcp
.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 80))
.await
.unwrap();
loop {
match server
.run(
Some(5 * 1000),
edge_nal::WithTimeout::new(15_000, &acceptor),
HttpHandler,
)
.await
{
Ok(_) => {}
Err(error) => {
// panic!("{:?}", error);
log::error!("{:?}", error);
}
}
}
}
struct HttpHandler;
impl Handler for HttpHandler {
type Error<E>
= Error<E>
where
E: core::fmt::Debug;
async fn handle<T, const N: usize>(
&self,
_task_id: impl core::fmt::Display + Copy,
connection: &mut Connection<'_, T, N>,
) -> Result<(), Self::Error<T::Error>>
where
T: Read + Write,
{
println!("Got new connection");
let headers = connection.headers()?;
if headers.method != Method::Get {
connection
.initiate_response(405, Some("Method Not Allowed"), &[("Connection", "close")])
.await?;
} else {
match headers.path {
"/" => {
connection
.initiate_response(
200,
Some("OK"),
&[("Content-Type", "text/html"), ("Connection", "close")],
)
.await?;
let t = TEMP.load(Ordering::Relaxed);
let t = format!("{:.1}", t as f32 / 10.);
let mut pos = HTML.find("{temp}").unwrap();
connection.write_all(&HTML.as_bytes()[..pos]).await?;
connection.write_all(t.as_bytes()).await?;
pos += 6; // len of {temp}
connection.write_all(&HTML.as_bytes()[pos..]).await?;
}
"/Figtree[wght].woff2" => {
connection
.initiate_response(
200,
Some("OK"),
&[("Content-Type", "font/woff2"), ("Connection", "close")],
)
.await?;
connection.write_all(FONT).await?;
}
_ => {
connection
.initiate_response(404, Some("Not Found"), &[("Connection", "close")])
.await?;
}
}
}
Ok(())
}
}
#[embassy_executor::task]
async fn connection(mut controller: WifiController<'static>) {
println!("start connection task");
println!("Device capabilities: {:?}", controller.capabilities());
loop {
if matches!(esp_wifi::wifi::wifi_state(), WifiState::StaConnected) {
// wait until we're no longer connected
controller.wait_for_event(WifiEvent::StaDisconnected).await;
Timer::after(Duration::from_millis(5000)).await
}
if !matches!(controller.is_started(), Ok(true)) {
let client_config = Configuration::Client(ClientConfiguration {
ssid: SSID.try_into().unwrap(),
password: PASSWORD.try_into().unwrap(),
..Default::default()
});
controller.set_configuration(&client_config).unwrap();
println!("Starting wifi");
controller.start_async().await.unwrap();
println!("Wifi started!");
}
println!("About to connect...");
match controller.connect_async().await {
Ok(_) => println!("Wifi connected!"),
Err(e) => {
println!("Failed to connect to wifi: {e:?}");
Timer::after(Duration::from_millis(5000)).await
}
}
}
}
#[embassy_executor::task]
async fn net_task(mut runner: Runner<'static, WifiDevice<'static, WifiStaDevice>>) {
runner.run().await
}
#[embassy_executor::task]
async fn update_temperature(temperature_sensor: TemperatureSensor<'static>) {
loop {
let t = temperature_sensor.get_temperature();
let c = (t.to_celsius() * 10.0) as i32;
TEMP.store(c, Ordering::Relaxed);
Timer::after(Duration::from_millis(5000)).await
}
}