mirror of
				https://github.com/Djeeberjr/fw-anwesenheit.git
				synced 2025-11-03 23:24:10 +00:00 
			
		
		
		
	fixed webserver not working
- shared store in main - multiple tasks for webserver
This commit is contained in:
		
							parent
							
								
									8cbdf834a1
								
							
						
					
					
						commit
						082f1faba9
					
				
							
								
								
									
										37
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								src/main.rs
									
									
									
									
									
								
							@ -3,11 +3,12 @@
 | 
			
		||||
#![feature(type_alias_impl_trait)]
 | 
			
		||||
#![feature(impl_trait_in_assoc_type)]
 | 
			
		||||
 | 
			
		||||
use alloc::rc::Rc;
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
use embassy_net::Stack;
 | 
			
		||||
use embassy_sync::{
 | 
			
		||||
    blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex},
 | 
			
		||||
    channel::Channel,
 | 
			
		||||
    mutex::Mutex,
 | 
			
		||||
    pubsub::{
 | 
			
		||||
        PubSubChannel, Publisher,
 | 
			
		||||
        WaitResult::{Lagged, Message},
 | 
			
		||||
@ -20,7 +21,11 @@ use esp_hal::{gpio::InputConfig, peripherals};
 | 
			
		||||
use log::{debug, info};
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@ -28,30 +33,34 @@ mod drivers;
 | 
			
		||||
mod feedback;
 | 
			
		||||
mod init;
 | 
			
		||||
mod store;
 | 
			
		||||
//mod webserver;
 | 
			
		||||
mod webserver;
 | 
			
		||||
 | 
			
		||||
static FEEDBACK_STATE: Signal<CriticalSectionRawMutex, feedback::FeedbackState> = Signal::new();
 | 
			
		||||
 | 
			
		||||
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
 | 
			
		||||
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
 | 
			
		||||
type UsedStore = IDStore<SDCardPersistence>;
 | 
			
		||||
 | 
			
		||||
#[esp_hal_embassy::main]
 | 
			
		||||
async fn main(mut spawner: Spawner) {
 | 
			
		||||
    let (uart_device, stack, _i2c, _led, buzzer_gpio, sd_det_gpio, persistence_layer) =
 | 
			
		||||
        init::hardware::hardware_init(&mut spawner).await;
 | 
			
		||||
 | 
			
		||||
    wait_for_stack_up(stack).await;
 | 
			
		||||
 | 
			
		||||
    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 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 ***********************************/
 | 
			
		||||
    debug!("spawing NFC 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));
 | 
			
		||||
    /******************************************************************************/
 | 
			
		||||
 | 
			
		||||
    let mut sub = chan.subscriber().unwrap();
 | 
			
		||||
 | 
			
		||||
    debug!("everything spawned");
 | 
			
		||||
    FEEDBACK_STATE.signal(feedback::FeedbackState::Startup);
 | 
			
		||||
 | 
			
		||||
    let mut store = IDStore::new_from_storage(persistence_layer).await;
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        let wait_result = sub.next_message().await;
 | 
			
		||||
        match wait_result {
 | 
			
		||||
@ -81,7 +86,7 @@ async fn main(mut spawner: Spawner) {
 | 
			
		||||
                debug!("Got message: {msg:?}");
 | 
			
		||||
 | 
			
		||||
                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 {
 | 
			
		||||
                    FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										66
									
								
								src/webserver/api.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/webserver/api.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										31
									
								
								src/webserver/app.rs
									
									
									
									
									
										Normal 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))),
 | 
			
		||||
        // )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,54 +1,57 @@
 | 
			
		||||
use alloc::rc::Rc;
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
use embassy_net::Stack;
 | 
			
		||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
 | 
			
		||||
use embassy_time::Duration;
 | 
			
		||||
use picoserve::{AppBuilder, AppRouter, routing::get};
 | 
			
		||||
use picoserve::{AppRouter, AppWithStateBuilder};
 | 
			
		||||
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 state = make_static!(AppState { store });
 | 
			
		||||
 | 
			
		||||
    let config = make_static!(picoserve::Config::new(picoserve::Timeouts {
 | 
			
		||||
        start_read_request: Some(Duration::from_secs(5)),
 | 
			
		||||
        persistent_start_read_request: Some(Duration::from_secs(1)),
 | 
			
		||||
        read_request: Some(Duration::from_secs(1)),
 | 
			
		||||
        write: Some(Duration::from_secs(1)),
 | 
			
		||||
        persistent_start_read_request: Some(Duration::from_secs(5)),
 | 
			
		||||
        read_request: Some(Duration::from_secs(5)),
 | 
			
		||||
        write: Some(Duration::from_secs(5)),
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    let _ = spawner.spawn(webserver_task(0, stack, app, config));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"))
 | 
			
		||||
    for task_id in 0..NETWORK_STACK_SIZE {
 | 
			
		||||
        spawner.must_spawn(webserver_task(task_id, stack, app, config, state));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[embassy_executor::task]
 | 
			
		||||
#[embassy_executor::task(pool_size = NETWORK_STACK_SIZE)]
 | 
			
		||||
async fn webserver_task(
 | 
			
		||||
    id: usize,
 | 
			
		||||
    task_id: usize,
 | 
			
		||||
    stack: embassy_net::Stack<'static>,
 | 
			
		||||
    app: &'static AppRouter<AppProps>,
 | 
			
		||||
    config: &'static picoserve::Config<Duration>,
 | 
			
		||||
    state: &'static AppState,
 | 
			
		||||
) -> ! {
 | 
			
		||||
    let mut tcp_rx_buffer = [0u8; 1024];
 | 
			
		||||
    let mut tcp_tx_buffer = [0u8; 1024];
 | 
			
		||||
    let mut http_buffer = [0u8; 2048];
 | 
			
		||||
 | 
			
		||||
    picoserve::listen_and_serve(
 | 
			
		||||
        id,
 | 
			
		||||
        app,
 | 
			
		||||
        config,
 | 
			
		||||
        stack,
 | 
			
		||||
        80,
 | 
			
		||||
        &mut tcp_rx_buffer,
 | 
			
		||||
        &mut tcp_tx_buffer,
 | 
			
		||||
        &mut http_buffer,
 | 
			
		||||
    )
 | 
			
		||||
    .await
 | 
			
		||||
    picoserve::Server::new(&app.shared().with_state(state), config, &mut http_buffer)
 | 
			
		||||
        .listen_and_serve(task_id, stack, 80, &mut tcp_rx_buffer, &mut tcp_tx_buffer)
 | 
			
		||||
        .await
 | 
			
		||||
        .into_never()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/webserver/sse.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/webserver/sse.rs
									
									
									
									
									
										Normal 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?,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user