fixed webserver not working

- shared store in main
- multiple tasks for webserver
This commit is contained in:
Djeeberjr 2025-10-07 17:29:14 +02:00
parent 8cbdf834a1
commit 082f1faba9
5 changed files with 179 additions and 45 deletions

View File

@ -3,11 +3,12 @@
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
use alloc::rc::Rc;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::Stack; use embassy_net::Stack;
use embassy_sync::{ use embassy_sync::{
blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}, blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex},
channel::Channel, mutex::Mutex,
pubsub::{ pubsub::{
PubSubChannel, Publisher, PubSubChannel, Publisher,
WaitResult::{Lagged, Message}, WaitResult::{Lagged, Message},
@ -20,7 +21,11 @@ use esp_hal::{gpio::InputConfig, peripherals};
use log::{debug, info}; use log::{debug, info};
use static_cell::make_static; use static_cell::make_static;
use crate::store::{Date, IDStore, TallyID}; use crate::{
init::sd_card::SDCardPersistence,
store::{Date, IDStore, TallyID},
webserver::start_webserver,
};
extern crate alloc; extern crate alloc;
@ -28,30 +33,34 @@ mod drivers;
mod feedback; mod feedback;
mod init; mod init;
mod store; mod store;
//mod webserver; mod webserver;
static FEEDBACK_STATE: Signal<CriticalSectionRawMutex, feedback::FeedbackState> = Signal::new(); static FEEDBACK_STATE: Signal<CriticalSectionRawMutex, feedback::FeedbackState> = Signal::new();
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>; type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>; type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
type UsedStore = IDStore<SDCardPersistence>;
#[esp_hal_embassy::main] #[esp_hal_embassy::main]
async fn main(mut spawner: Spawner) { async fn main(mut spawner: Spawner) {
let (uart_device, stack, _i2c, _led, buzzer_gpio, sd_det_gpio, persistence_layer) = let (uart_device, stack, _i2c, _led, buzzer_gpio, sd_det_gpio, persistence_layer) =
init::hardware::hardware_init(&mut spawner).await; init::hardware::hardware_init(&mut spawner).await;
wait_for_stack_up(stack).await;
info!("Starting up..."); info!("Starting up...");
let chan: &'static mut TallyChannel = make_static!(PubSubChannel::new());
//start_webserver(&mut spawner, stack);
let publisher = chan.publisher().unwrap();
let mut rtc = drivers::rtc::RTCClock::new(_i2c).await; let mut rtc = drivers::rtc::RTCClock::new(_i2c).await;
let store: UsedStore = IDStore::new_from_storage(persistence_layer).await;
let shared_store = Rc::new(Mutex::new(store));
let chan: &'static mut TallyChannel = make_static!(PubSubChannel::new());
let publisher = chan.publisher().unwrap();
let mut sub = chan.subscriber().unwrap();
wait_for_stack_up(stack).await;
start_webserver(&mut spawner, stack, shared_store.clone());
/****************************** Spawning tasks ***********************************/ /****************************** Spawning tasks ***********************************/
debug!("spawing NFC reader task..."); debug!("spawing NFC reader task...");
spawner.must_spawn(drivers::nfc_reader::rfid_reader_task( spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(
@ -66,13 +75,9 @@ async fn main(mut spawner: Spawner) {
spawner.must_spawn(sd_detect_task(sd_det_gpio)); spawner.must_spawn(sd_detect_task(sd_det_gpio));
/******************************************************************************/ /******************************************************************************/
let mut sub = chan.subscriber().unwrap();
debug!("everything spawned"); debug!("everything spawned");
FEEDBACK_STATE.signal(feedback::FeedbackState::Startup); FEEDBACK_STATE.signal(feedback::FeedbackState::Startup);
let mut store = IDStore::new_from_storage(persistence_layer).await;
loop { loop {
let wait_result = sub.next_message().await; let wait_result = sub.next_message().await;
match wait_result { match wait_result {
@ -81,7 +86,7 @@ async fn main(mut spawner: Spawner) {
debug!("Got message: {msg:?}"); debug!("Got message: {msg:?}");
let day: Date = rtc.get_date().await; let day: Date = rtc.get_date().await;
let added = store.add_id(msg, day).await; let added = shared_store.lock().await.add_id(msg, day).await;
if added { if added {
FEEDBACK_STATE.signal(feedback::FeedbackState::Ack); FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);

66
src/webserver/api.rs Normal file
View File

@ -0,0 +1,66 @@
use alloc::string::String;
use picoserve::{
extract::{Json, State},
response::{self, IntoResponse},
};
use serde::Deserialize;
use crate::{
store::{Name, TallyID},
webserver::app::AppState,
};
#[derive(Deserialize)]
pub struct NewMapping {
id: String,
name: Name,
}
pub fn hex_string_to_tally_id(s: &str) -> Option<TallyID> {
let bytes = s.as_bytes();
if bytes.len() != 24 {
return None;
}
let mut out = [0u8; 12];
for i in 0..12 {
let hi = hex_val(bytes[2 * i])?;
let lo = hex_val(bytes[2 * i + 1])?;
out[i] = (hi << 4) | lo;
}
Some(out)
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
/*
* #[get("/api/idevent")]
* #[get("/api/csv")]
* #[get("/api/mapping")]
* #[post("/api/mapping", format = "json", data = "<new_mapping>")]
* struct NewMapping {
* id: String,
* name: Name,
* }
*/
pub async fn get_mapping(State(state): State<AppState>) -> impl IntoResponse {
let store = state.store.lock().await;
response::Json(store.mapping.clone())
}
pub async fn add_mapping(
State(state): State<AppState>,
Json(data): Json<NewMapping>,
) -> impl IntoResponse {
let mut store = state.store.lock().await;
let tally_id = hex_string_to_tally_id(&data.id).unwrap();
store.mapping.add_mapping(tally_id, data.name);
}

31
src/webserver/app.rs Normal file
View File

@ -0,0 +1,31 @@
use alloc::rc::Rc;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use picoserve::{AppWithStateBuilder, routing::get};
use crate::{
webserver::{
api::{add_mapping, get_mapping},
assets::Assets,
}, UsedStore,
};
#[derive(Clone)]
pub struct AppState {
pub store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
}
pub struct AppProps;
impl AppWithStateBuilder for AppProps {
type State = AppState;
type PathRouter = impl picoserve::routing::PathRouter<AppState>;
fn build_app(self) -> picoserve::Router<Self::PathRouter, AppState> {
picoserve::Router::from_service(Assets)
.route("/api/mapping", get(get_mapping).post(add_mapping))
// .route(
// "/api/idevent",
// get(move || response::EventStream(Events(self.chan))),
// )
}
}

View File

@ -1,54 +1,57 @@
use alloc::rc::Rc;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::Stack; use embassy_net::Stack;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use embassy_time::Duration; use embassy_time::Duration;
use picoserve::{AppBuilder, AppRouter, routing::get}; use picoserve::{AppRouter, AppWithStateBuilder};
use static_cell::make_static; use static_cell::make_static;
mod assets; use crate::{
UsedStore,
init::network::NETWORK_STACK_SIZE,
webserver::app::{AppProps, AppState},
};
pub fn start_webserver(spawner: &mut Spawner, stack: Stack<'static>) { mod assets;
// mod sse;
mod api;
mod app;
pub fn start_webserver(
spawner: &mut Spawner,
stack: Stack<'static>,
store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
) {
let app = make_static!(AppProps.build_app()); let app = make_static!(AppProps.build_app());
let state = make_static!(AppState { store });
let config = make_static!(picoserve::Config::new(picoserve::Timeouts { let config = make_static!(picoserve::Config::new(picoserve::Timeouts {
start_read_request: Some(Duration::from_secs(5)), start_read_request: Some(Duration::from_secs(5)),
persistent_start_read_request: Some(Duration::from_secs(1)), persistent_start_read_request: Some(Duration::from_secs(5)),
read_request: Some(Duration::from_secs(1)), read_request: Some(Duration::from_secs(5)),
write: Some(Duration::from_secs(1)), write: Some(Duration::from_secs(5)),
})); }));
let _ = spawner.spawn(webserver_task(0, stack, app, config)); for task_id in 0..NETWORK_STACK_SIZE {
} spawner.must_spawn(webserver_task(task_id, stack, app, config, state));
struct AppProps;
impl AppBuilder for AppProps {
type PathRouter = impl picoserve::routing::PathRouter;
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
picoserve::Router::from_service(assets::Assets).route("/api/a", get(async move || "Hello"))
} }
} }
#[embassy_executor::task] #[embassy_executor::task(pool_size = NETWORK_STACK_SIZE)]
async fn webserver_task( async fn webserver_task(
id: usize, task_id: usize,
stack: embassy_net::Stack<'static>, stack: embassy_net::Stack<'static>,
app: &'static AppRouter<AppProps>, app: &'static AppRouter<AppProps>,
config: &'static picoserve::Config<Duration>, config: &'static picoserve::Config<Duration>,
state: &'static AppState,
) -> ! { ) -> ! {
let mut tcp_rx_buffer = [0u8; 1024]; let mut tcp_rx_buffer = [0u8; 1024];
let mut tcp_tx_buffer = [0u8; 1024]; let mut tcp_tx_buffer = [0u8; 1024];
let mut http_buffer = [0u8; 2048]; let mut http_buffer = [0u8; 2048];
picoserve::listen_and_serve( picoserve::Server::new(&app.shared().with_state(state), config, &mut http_buffer)
id, .listen_and_serve(task_id, stack, 80, &mut tcp_rx_buffer, &mut tcp_tx_buffer)
app, .await
config, .into_never()
stack,
80,
&mut tcp_rx_buffer,
&mut tcp_tx_buffer,
&mut http_buffer,
)
.await
} }

29
src/webserver/sse.rs Normal file
View File

@ -0,0 +1,29 @@
use embassy_time::{Duration, Timer};
use log::warn;
use picoserve::response;
pub struct Events(pub TallySubscriber);
impl response::sse::EventSource for Events {
async fn write_events<W: picoserve::io::Write>(
mut self,
mut writer: response::sse::EventWriter<W>,
) -> Result<(), W::Error> {
loop {
let timeout = Timer::after(Duration::from_secs(15));
let sel = embassy_futures::select::select(self.0.next_message(), timeout);
match sel.await {
embassy_futures::select::Either::First(msg) => match msg {
embassy_sync::pubsub::WaitResult::Message(id) => {
writer.write_event("msg", id.to_string().as_str()).await?
}
embassy_sync::pubsub::WaitResult::Lagged(_) => {
warn!("SSE subscriber got lagged");
}
},
embassy_futures::select::Either::Second(_) => writer.write_keepalive().await?,
}
}
}
}