Initial commit
This commit is contained in:
58
src/httpd.rs
Normal file
58
src/httpd.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use picoserve::{make_static, routing::get, AppBuilder, AppRouter, Router};
|
||||
use picoserve::routing::PathRouter;
|
||||
use embassy_time::Duration;
|
||||
use embassy_net::Stack;
|
||||
|
||||
use static_cell::StaticCell;
|
||||
|
||||
static PICO_CONFIG : 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)),
|
||||
}).keep_connection_alive();
|
||||
|
||||
/**
|
||||
struct AppProps;
|
||||
|
||||
impl AppBuilder for AppProps {
|
||||
type PathRouter = impl picoserve::routing::PathRouter;
|
||||
|
||||
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
|
||||
picoserve::Router::new().route("/", get(|| async move { "Hello World" }))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn httpd_spawn(spawner: embassy_executor::Spawner, size: usize, stack: Stack<'static>) -> Result<(), super::Error> {
|
||||
for i in 0..size {
|
||||
spawner.must_spawn(web_task(i, stack));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = super::MAX_CONCURRENT_SOCKETS)]
|
||||
async fn web_task(
|
||||
id: usize,
|
||||
stack: Stack<'static>,
|
||||
) -> ! {
|
||||
let app =
|
||||
picoserve::Router::new().route("/", get(|| async { "Hello World" })).route("/test", get(|| async { "Test" }));
|
||||
|
||||
let port = 80;
|
||||
let mut tcp_rx_buffer = [0; 1024];
|
||||
let mut tcp_tx_buffer = [0; 1024];
|
||||
let mut http_buffer = [0; 2048];
|
||||
|
||||
picoserve::listen_and_serve(
|
||||
id,
|
||||
&app,
|
||||
&PICO_CONFIG,
|
||||
stack,
|
||||
port,
|
||||
&mut tcp_rx_buffer,
|
||||
&mut tcp_tx_buffer,
|
||||
&mut http_buffer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
111
src/main.rs
Normal file
111
src/main.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::prelude::*;
|
||||
use log::{info, error};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use heapless::String;
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::timer::systimer::SystemTimer;
|
||||
|
||||
/**
|
||||
* Embassy includes
|
||||
*/
|
||||
use esp_hal_embassy::init as initialize_embassy;
|
||||
use esp_hal::timer::systimer::Target;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
|
||||
/**
|
||||
* Rng functionality
|
||||
*/
|
||||
mod random;
|
||||
use self::random::RngWrapper;
|
||||
|
||||
/**
|
||||
* Wifi functionality
|
||||
*/
|
||||
mod wifi;
|
||||
use self::wifi::connect as connect_to_wifi;
|
||||
use self::wifi::Error as WifiError;
|
||||
|
||||
/**
|
||||
* httpd
|
||||
*/
|
||||
mod httpd;
|
||||
use httpd::httpd_spawn;
|
||||
|
||||
/**
|
||||
* serial
|
||||
*/
|
||||
mod serial;
|
||||
use serial::serial_spawn;
|
||||
|
||||
/// SSID for WiFi network
|
||||
const WIFI_SSID: &str = env!("WIFI_SSID");
|
||||
|
||||
/// Password for WiFi network
|
||||
const WIFI_PASSWORD: &str = env!("WIFI_PASSWORD");
|
||||
|
||||
/// Size of heap for dynamically-allocated memory
|
||||
const HEAP_MEMORY_SIZE: usize = 72 * 1024;
|
||||
|
||||
const MAX_CONCURRENT_SOCKETS: usize = 5;
|
||||
|
||||
#[main]
|
||||
async fn main(spawner: Spawner) {
|
||||
|
||||
info!("booting firmware");
|
||||
|
||||
if let Err(error) = main_fallible(spawner).await {
|
||||
error!("Error while running firmware: {error:?}");
|
||||
}
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/v0.22.0/examples/src/bin
|
||||
}
|
||||
|
||||
async fn main_fallible(
|
||||
spawner: Spawner,
|
||||
) -> Result<(), Error> {
|
||||
let peripherals = esp_hal::init({
|
||||
let mut config = esp_hal::Config::default();
|
||||
config.cpu_clock = CpuClock::max();
|
||||
config
|
||||
});
|
||||
|
||||
esp_alloc::heap_allocator!(HEAP_MEMORY_SIZE);
|
||||
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let systimer = SystemTimer::new(peripherals.SYSTIMER).split::<Target>();
|
||||
initialize_embassy(systimer.alarm0);
|
||||
|
||||
let rng = Rng::new(peripherals.RNG);
|
||||
|
||||
let ssid = String::<32>::try_from(WIFI_SSID).map_err(|()| Error::ParseCredentials)?;
|
||||
let password =
|
||||
String::<64>::try_from(WIFI_PASSWORD).map_err(|()| Error::ParseCredentials)?;
|
||||
|
||||
let stack = connect_to_wifi(spawner, TimerGroup::new(peripherals.TIMG0), rng, peripherals.WIFI, peripherals.RADIO_CLK, (ssid, password)).await.unwrap();
|
||||
|
||||
httpd_spawn(spawner, MAX_CONCURRENT_SOCKETS-1, stack);
|
||||
|
||||
serial_spawn(spawner, peripherals.UART0.into(), peripherals.GPIO20.into(), peripherals.GPIO21.into());
|
||||
|
||||
info!("firmware done booting");
|
||||
|
||||
// we got here - all is fine
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An error
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Error while parsing SSID or password
|
||||
ParseCredentials,
|
||||
}
|
||||
|
||||
60
src/random.rs
Normal file
60
src/random.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright Claudio Mattera 2024.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Random numbers generator
|
||||
|
||||
use rand_core::CryptoRng;
|
||||
use rand_core::Error;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use esp_hal::rng::Rng;
|
||||
|
||||
/// A wrapper for ESP random number generator that implement traits form
|
||||
/// `rand_core`
|
||||
#[derive(Clone)]
|
||||
pub struct RngWrapper(Rng);
|
||||
|
||||
impl From<Rng> for RngWrapper {
|
||||
fn from(rng: Rng) -> Self {
|
||||
Self(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for RngWrapper {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.random()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
u32_pair_to_u64(self.next_u32(), self.next_u32())
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
for value in dest.iter_mut() {
|
||||
let [random_value, _, _, _] = self.next_u32().to_ne_bytes();
|
||||
*value = random_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptoRng for RngWrapper {}
|
||||
|
||||
/// Join a pair of `u32` into a `u64`
|
||||
#[allow(
|
||||
clippy::many_single_char_names,
|
||||
clippy::min_ident_chars,
|
||||
reason = "This is still readable"
|
||||
)]
|
||||
fn u32_pair_to_u64(first: u32, second: u32) -> u64 {
|
||||
let [a, b, c, d] = first.to_ne_bytes();
|
||||
let [e, f, g, h] = second.to_ne_bytes();
|
||||
u64::from_ne_bytes([a, b, c, d, e, f, g, h])
|
||||
}
|
||||
36
src/serial.rs
Normal file
36
src/serial.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use esp_hal::{
|
||||
// clock::ClockControl,
|
||||
peripherals::{Peripherals},
|
||||
prelude::*,
|
||||
uart::{AtCmdConfig, AnyUart, Uart, UartRx, UartTx, Config},
|
||||
gpio::AnyPin,
|
||||
Async,
|
||||
};
|
||||
use log::{info, error};
|
||||
|
||||
const BUFFER_SIZE: usize = 64;
|
||||
|
||||
pub fn serial_spawn(spawner: embassy_executor::Spawner, peri_uart: AnyUart, rx_pin: AnyPin, tx_pin: AnyPin) {
|
||||
// Initialize and configure UART0
|
||||
let config = Config::default().rx_fifo_full_threshold(BUFFER_SIZE as u16);
|
||||
let my_uart = Uart::new_with_config(peri_uart, config, rx_pin, tx_pin).unwrap().into_async();
|
||||
// Split UART0 to create seperate Tx and Rx handles
|
||||
let (rx, _tx) = my_uart.split();
|
||||
|
||||
spawner.spawn(reader(rx)).ok();
|
||||
//spawner.spawn(writer(tx)).ok();
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn reader(mut rx: UartRx<'static, Async>) {
|
||||
let mut rbuf: [u8; BUFFER_SIZE] = [0u8; BUFFER_SIZE];
|
||||
loop {
|
||||
let r = embedded_io_async::Read::read(&mut rx, &mut rbuf[0..]).await;
|
||||
match r {
|
||||
Ok(len) => {
|
||||
info!("Read: {len}, data: {:?}", &rbuf[..len]);
|
||||
}
|
||||
Err(e) => error!("RX Error: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/wifi.rs
Normal file
203
src/wifi.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright Claudio Mattera 2024.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Functions and task for WiFi connection
|
||||
|
||||
use log::debug;
|
||||
use log::error;
|
||||
use log::info;
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
|
||||
use embassy_net::new as new_network_stack;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::signal::Signal;
|
||||
|
||||
use esp_wifi::init as initialize_wifi;
|
||||
use esp_wifi::wifi::new_with_mode as new_wifi_with_mode;
|
||||
use esp_wifi::wifi::wifi_state;
|
||||
use esp_wifi::wifi::ClientConfiguration;
|
||||
use esp_wifi::wifi::Configuration;
|
||||
use esp_wifi::wifi::WifiController;
|
||||
use esp_wifi::wifi::WifiDevice;
|
||||
use esp_wifi::wifi::WifiError as EspWifiError;
|
||||
use esp_wifi::wifi::WifiEvent;
|
||||
use esp_wifi::wifi::WifiStaDevice;
|
||||
use esp_wifi::wifi::WifiState;
|
||||
use esp_wifi::EspWifiController;
|
||||
use esp_wifi::InitializationError as WifiInitializationError;
|
||||
|
||||
use embassy_net::Config;
|
||||
use embassy_net::DhcpConfig;
|
||||
use embassy_net::Runner;
|
||||
use embassy_net::Stack;
|
||||
use embassy_net::StackResources;
|
||||
|
||||
use embassy_time::Duration;
|
||||
use embassy_time::Timer;
|
||||
|
||||
use esp_hal::peripherals::RADIO_CLK;
|
||||
use esp_hal::peripherals::TIMG0;
|
||||
use esp_hal::peripherals::WIFI;
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal::Blocking;
|
||||
|
||||
use heapless::String;
|
||||
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use rand_core::RngCore as _;
|
||||
|
||||
use crate::RngWrapper;
|
||||
|
||||
/// max number of concurrent connections
|
||||
static STACK_COUNT : usize = super::MAX_CONCURRENT_SOCKETS;
|
||||
|
||||
/// Static cell for network stack resources
|
||||
static STACK_RESOURCES: StaticCell<StackResources<STACK_COUNT>> = StaticCell::new();
|
||||
|
||||
/// Static cell for WiFi controller
|
||||
static WIFI_CONTROLLER: StaticCell<EspWifiController<'static>> = StaticCell::new();
|
||||
|
||||
/// Signal to request to stop WiFi
|
||||
pub static STOP_WIFI_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
|
||||
/// Connect to WiFi
|
||||
pub async fn connect(
|
||||
spawner: Spawner,
|
||||
timg0: TimerGroup<'static, TIMG0, Blocking>,
|
||||
rng: Rng,
|
||||
wifi: WIFI,
|
||||
radio_clock_control: RADIO_CLK,
|
||||
(ssid, password): (String<32>, String<64>),
|
||||
) -> Result<Stack<'static>, Error> {
|
||||
let mut rng_wrapper = RngWrapper::from(rng);
|
||||
let seed = rng_wrapper.next_u64();
|
||||
debug!("Use random seed 0x{seed:016x}");
|
||||
|
||||
let wifi_controller = initialize_wifi(timg0.timer0, rng, radio_clock_control)?;
|
||||
let wifi_controller: &'static mut _ = WIFI_CONTROLLER.init(wifi_controller);
|
||||
|
||||
let (wifi_interface, controller) = new_wifi_with_mode(wifi_controller, wifi, WifiStaDevice)?;
|
||||
|
||||
let config = Config::dhcpv4(DhcpConfig::default());
|
||||
|
||||
debug!("Initialize network stack");
|
||||
let stack_resources: &'static mut _ = STACK_RESOURCES.init(StackResources::new());
|
||||
let (stack, runner) = new_network_stack(wifi_interface, config, stack_resources, seed);
|
||||
|
||||
spawner.must_spawn(connection(controller, ssid, password));
|
||||
spawner.must_spawn(net_task(runner));
|
||||
|
||||
debug!("Wait for network link");
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
debug!("Wait for IP address");
|
||||
loop {
|
||||
if let Some(config) = stack.config_v4() {
|
||||
info!("Connected to WiFi with IP address {}", config.address);
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Ok(stack)
|
||||
}
|
||||
|
||||
/// Task for ongoing network processing
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static, WifiStaDevice>>) {
|
||||
runner.run().await;
|
||||
}
|
||||
|
||||
/// Task for WiFi connection
|
||||
///
|
||||
/// This will wrap [`connection_fallible()`] and trap any error.
|
||||
#[embassy_executor::task]
|
||||
async fn connection(controller: WifiController<'static>, ssid: String<32>, password: String<64>) {
|
||||
if let Err(error) = connection_fallible(controller, ssid, password).await {
|
||||
error!("Cannot connect to WiFi: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallible task for WiFi connection
|
||||
async fn connection_fallible(
|
||||
mut controller: WifiController<'static>,
|
||||
ssid: String<32>,
|
||||
password: String<64>,
|
||||
) -> Result<(), Error> {
|
||||
debug!("Start connection");
|
||||
debug!("Device capabilities: {:?}", controller.capabilities());
|
||||
loop {
|
||||
if 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.clone(),
|
||||
password: password.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_config)?;
|
||||
debug!("Starting WiFi controller");
|
||||
controller.start_async().await?;
|
||||
debug!("WiFi controller started");
|
||||
}
|
||||
|
||||
debug!("Connect to WiFi network");
|
||||
|
||||
match controller.connect_async().await {
|
||||
Ok(()) => {
|
||||
debug!("Connected to WiFi network");
|
||||
|
||||
debug!("Wait for request to stop wifi");
|
||||
STOP_WIFI_SIGNAL.wait().await;
|
||||
info!("Received signal to stop wifi");
|
||||
controller.stop_async().await?;
|
||||
break;
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to connect to WiFi network: {error:?}");
|
||||
Timer::after(Duration::from_millis(5000)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Leave connection task");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error within WiFi connection
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error during WiFi initialization
|
||||
WifiInitialization(#[expect(unused, reason = "Never read directly")] WifiInitializationError),
|
||||
|
||||
/// Error during WiFi operation
|
||||
Wifi(#[expect(unused, reason = "Never read directly")] EspWifiError),
|
||||
}
|
||||
|
||||
impl From<WifiInitializationError> for Error {
|
||||
fn from(error: WifiInitializationError) -> Self {
|
||||
Self::WifiInitialization(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EspWifiError> for Error {
|
||||
fn from(error: EspWifiError) -> Self {
|
||||
Self::Wifi(error)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user