290 lines
8.8 KiB
Rust
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
|
|
}
|
|
}
|