mirror of
				https://github.com/Djeeberjr/fw-anwesenheit.git
				synced 2025-11-04 07:34:10 +00:00 
			
		
		
		
	refactored main function
- moved logger into its own file - moved code from main into its own function
This commit is contained in:
		
							parent
							
								
									3fe2f3f376
								
							
						
					
					
						commit
						7a438d1a9f
					
				
							
								
								
									
										70
									
								
								src/feedback.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/feedback.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
use log::error;
 | 
			
		||||
use rppal::pwm::Channel;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use tokio::join;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    buzzer::{Buzzer, GPIOBuzzer},
 | 
			
		||||
    led::{SpiLed, StatusLed},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "mock_pi")]
 | 
			
		||||
use crate::mock::{MockBuzzer, MockLed};
 | 
			
		||||
 | 
			
		||||
const PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12
 | 
			
		||||
 | 
			
		||||
pub struct Feedback<B: Buzzer, L: StatusLed> {
 | 
			
		||||
    buzzer: B,
 | 
			
		||||
    led: L,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
 | 
			
		||||
    pub async fn success(&mut self) {
 | 
			
		||||
        let (buzzer_result, led_result) =
 | 
			
		||||
            join!(self.buzzer.beep_ack(), self.led.turn_green_on_1s());
 | 
			
		||||
 | 
			
		||||
        buzzer_result.unwrap_or_else(|err| {
 | 
			
		||||
            error!("Failed to buzz: {err}");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        led_result.unwrap_or_else(|err| {
 | 
			
		||||
            error!("Failed to set LED: {err}");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn failure(&mut self) {
 | 
			
		||||
        let (buzzer_result, led_result) = join!(self.buzzer.beep_nak(), self.led.turn_red_on_1s());
 | 
			
		||||
 | 
			
		||||
        buzzer_result.unwrap_or_else(|err| {
 | 
			
		||||
            error!("Failed to buzz: {err}");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        led_result.unwrap_or_else(|err| {
 | 
			
		||||
            error!("Failed to set LED: {err}");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "mock_pi")]
 | 
			
		||||
pub type FeedbackImpl = Feedback<MockBuzzer, MockLed>;
 | 
			
		||||
#[cfg(not(feature = "mock_pi"))]
 | 
			
		||||
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
 | 
			
		||||
 | 
			
		||||
impl FeedbackImpl {
 | 
			
		||||
    pub fn new() -> Result<Self, Box<dyn Error>> {
 | 
			
		||||
        #[cfg(feature = "mock_pi")]
 | 
			
		||||
        {
 | 
			
		||||
            Ok(Feedback {
 | 
			
		||||
                buzzer: MockBuzzer {},
 | 
			
		||||
                led: MockLed {},
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        #[cfg(not(feature = "mock_pi"))]
 | 
			
		||||
        {
 | 
			
		||||
            Ok(Feedback {
 | 
			
		||||
                buzzer: GPIOBuzzer::new(PWM_CHANNEL_BUZZER)?,
 | 
			
		||||
                led: SpiLed::new()?,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/logger.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/logger.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
use std::env;
 | 
			
		||||
 | 
			
		||||
use log::LevelFilter;
 | 
			
		||||
use simplelog::{ConfigBuilder, SimpleLogger};
 | 
			
		||||
 | 
			
		||||
pub fn setup_logger() {
 | 
			
		||||
    let log_level = env::var("LOG_LEVEL")
 | 
			
		||||
        .ok()
 | 
			
		||||
        .and_then(|level| level.parse::<LevelFilter>().ok())
 | 
			
		||||
        .unwrap_or({
 | 
			
		||||
            if cfg!(debug_assertions) {
 | 
			
		||||
                LevelFilter::Debug
 | 
			
		||||
            } else {
 | 
			
		||||
                LevelFilter::Warn
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    let config = ConfigBuilder::new()
 | 
			
		||||
        .set_target_level(LevelFilter::Off)
 | 
			
		||||
        .set_location_level(LevelFilter::Off)
 | 
			
		||||
        .set_thread_level(LevelFilter::Off)
 | 
			
		||||
        .build();
 | 
			
		||||
 | 
			
		||||
    let _ = SimpleLogger::init(log_level, config);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										251
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										251
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,33 +1,33 @@
 | 
			
		||||
use activity_fairing::{ActivityNotifier, spawn_idle_watcher};
 | 
			
		||||
use buzzer::{Buzzer, GPIOBuzzer};
 | 
			
		||||
use feedback::{Feedback, FeedbackImpl};
 | 
			
		||||
use hotspot::{Hotspot, HotspotError, NMHotspot};
 | 
			
		||||
use id_store::IDStore;
 | 
			
		||||
use led::{SpiLed, StatusLed};
 | 
			
		||||
use log::{LevelFilter, debug, error, info, warn};
 | 
			
		||||
use log::{error, info, warn};
 | 
			
		||||
use pm3::run_pm3;
 | 
			
		||||
use rppal::pwm::Channel;
 | 
			
		||||
use simplelog::{ConfigBuilder, SimpleLogger};
 | 
			
		||||
use std::{env, error::Error, sync::Arc, time::Duration};
 | 
			
		||||
use tally_id::TallyID;
 | 
			
		||||
use tokio::{
 | 
			
		||||
    fs, join,
 | 
			
		||||
    fs,
 | 
			
		||||
    sync::{
 | 
			
		||||
        Mutex,
 | 
			
		||||
        broadcast::{self, Sender},
 | 
			
		||||
        broadcast::{self, Receiver, Sender},
 | 
			
		||||
    },
 | 
			
		||||
    try_join,
 | 
			
		||||
};
 | 
			
		||||
use webserver::start_webserver;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "mock_pi")]
 | 
			
		||||
use mock::{MockBuzzer, MockHotspot, MockLed};
 | 
			
		||||
use mock::MockHotspot;
 | 
			
		||||
 | 
			
		||||
mod activity_fairing;
 | 
			
		||||
mod buzzer;
 | 
			
		||||
mod color;
 | 
			
		||||
mod feedback;
 | 
			
		||||
mod hotspot;
 | 
			
		||||
mod id_mapping;
 | 
			
		||||
mod id_store;
 | 
			
		||||
mod led;
 | 
			
		||||
mod logger;
 | 
			
		||||
mod mock;
 | 
			
		||||
mod parser;
 | 
			
		||||
mod pm3;
 | 
			
		||||
@ -35,94 +35,6 @@ mod tally_id;
 | 
			
		||||
mod webserver;
 | 
			
		||||
 | 
			
		||||
const STORE_PATH: &str = "./data.json";
 | 
			
		||||
const PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12
 | 
			
		||||
 | 
			
		||||
fn setup_logger() {
 | 
			
		||||
    let log_level = env::var("LOG_LEVEL")
 | 
			
		||||
        .ok()
 | 
			
		||||
        .and_then(|level| level.parse::<LevelFilter>().ok())
 | 
			
		||||
        .unwrap_or({
 | 
			
		||||
            if cfg!(debug_assertions) {
 | 
			
		||||
                LevelFilter::Debug
 | 
			
		||||
            } else {
 | 
			
		||||
                LevelFilter::Warn
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    let config = ConfigBuilder::new()
 | 
			
		||||
        .set_target_level(LevelFilter::Off)
 | 
			
		||||
        .set_location_level(LevelFilter::Off)
 | 
			
		||||
        .set_thread_level(LevelFilter::Off)
 | 
			
		||||
        .build();
 | 
			
		||||
 | 
			
		||||
    let _ = SimpleLogger::init(log_level, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Signal the user success via buzzer and led
 | 
			
		||||
async fn feedback_success<T: Buzzer, I: StatusLed>(
 | 
			
		||||
    gpio_buzzer: &Arc<Mutex<T>>,
 | 
			
		||||
    status_led: &Arc<Mutex<I>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut buzzer_guard = gpio_buzzer.lock().await;
 | 
			
		||||
    let mut led_guard = status_led.lock().await;
 | 
			
		||||
 | 
			
		||||
    let (buzz, led) = join!(buzzer_guard.beep_ack(), led_guard.turn_green_on_1s());
 | 
			
		||||
 | 
			
		||||
    buzz.unwrap_or_else(|err| {
 | 
			
		||||
        error!("Failed to buzz: {err}");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    led.unwrap_or_else(|err| {
 | 
			
		||||
        error!("Failed to set LED: {err}");
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Signal the user failure via buzzer and led
 | 
			
		||||
async fn feedback_failure<T: Buzzer, I: StatusLed>(
 | 
			
		||||
    gpio_buzzer: &Arc<Mutex<T>>,
 | 
			
		||||
    status_led: &Arc<Mutex<I>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut buzzer_guard = gpio_buzzer.lock().await;
 | 
			
		||||
    let mut led_guard = status_led.lock().await;
 | 
			
		||||
 | 
			
		||||
    let (buzz, led) = join!(buzzer_guard.beep_nak(), led_guard.turn_red_on_1s());
 | 
			
		||||
 | 
			
		||||
    buzz.unwrap_or_else(|err| {
 | 
			
		||||
        error!("Failed to buzz: {err}");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    led.unwrap_or_else(|err| {
 | 
			
		||||
        error!("Failed to set LED: {err}");
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a buzzer
 | 
			
		||||
/// Respects the `mock_pi` flag.
 | 
			
		||||
fn create_buzzer() -> Result<Arc<Mutex<impl Buzzer>>, rppal::pwm::Error> {
 | 
			
		||||
    #[cfg(feature = "mock_pi")]
 | 
			
		||||
    {
 | 
			
		||||
        Ok(Arc::new(Mutex::new(MockBuzzer {})))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(feature = "mock_pi"))]
 | 
			
		||||
    {
 | 
			
		||||
        Ok(Arc::new(Mutex::new(GPIOBuzzer::new(PWM_CHANNEL_BUZZER)?)))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates a status led.
 | 
			
		||||
/// Respects the `mock_pi` flag.
 | 
			
		||||
fn create_status_led() -> Result<Arc<Mutex<impl StatusLed>>, rppal::spi::Error> {
 | 
			
		||||
    #[cfg(feature = "mock_pi")]
 | 
			
		||||
    {
 | 
			
		||||
        Ok(Arc::new(Mutex::new(MockLed {})))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(feature = "mock_pi"))]
 | 
			
		||||
    {
 | 
			
		||||
        Ok(Arc::new(Mutex::new(SpiLed::new()?)))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a struct to manage the hotspot
 | 
			
		||||
/// Respects the `mock_pi` flag.
 | 
			
		||||
@ -142,7 +54,8 @@ async fn run_webserver<H>(
 | 
			
		||||
    store: Arc<Mutex<IDStore>>,
 | 
			
		||||
    id_channel: Sender<String>,
 | 
			
		||||
    hotspot: Arc<Mutex<H>>,
 | 
			
		||||
) where
 | 
			
		||||
) -> Result<(), Box<dyn Error>>
 | 
			
		||||
where
 | 
			
		||||
    H: Hotspot + Send + Sync + 'static,
 | 
			
		||||
{
 | 
			
		||||
    let activity_channel = spawn_idle_watcher(Duration::from_secs(60 * 30), move || {
 | 
			
		||||
@ -157,46 +70,21 @@ async fn run_webserver<H>(
 | 
			
		||||
        sender: activity_channel,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = start_webserver(store, id_channel, notifier).await {
 | 
			
		||||
        error!("Failed to start webserver: {e}");
 | 
			
		||||
    start_webserver(store, id_channel, notifier).await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn load_or_create_store() -> Result<IDStore, Box<dyn Error>> {
 | 
			
		||||
    if fs::try_exists(STORE_PATH).await? {
 | 
			
		||||
        info!("Loading data from file");
 | 
			
		||||
        IDStore::new_from_json(STORE_PATH).await
 | 
			
		||||
    } else {
 | 
			
		||||
        info!("No data file found. Creating empty one.");
 | 
			
		||||
        Ok(IDStore::new())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    setup_logger();
 | 
			
		||||
 | 
			
		||||
    info!("Starting application");
 | 
			
		||||
 | 
			
		||||
    let (tx, mut rx) = broadcast::channel::<String>(32);
 | 
			
		||||
    let sse_tx = tx.clone();
 | 
			
		||||
 | 
			
		||||
    tokio::spawn(async move {
 | 
			
		||||
        match run_pm3(tx).await {
 | 
			
		||||
            Ok(()) => {
 | 
			
		||||
                warn!("PM3 exited with a zero exit code");
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to run PM3: {e}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let raw_store = if fs::try_exists(STORE_PATH).await? {
 | 
			
		||||
        info!("Loading data from file");
 | 
			
		||||
        IDStore::new_from_json(STORE_PATH).await?
 | 
			
		||||
    } else {
 | 
			
		||||
        info!("No data file found. Creating empty one.");
 | 
			
		||||
        IDStore::new()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    debug!("created store sucessfully");
 | 
			
		||||
 | 
			
		||||
    let store: Arc<Mutex<IDStore>> = Arc::new(Mutex::new(raw_store));
 | 
			
		||||
    let gpio_buzzer = create_buzzer()?;
 | 
			
		||||
    let status_led = create_status_led()?;
 | 
			
		||||
    let hotspot = Arc::new(Mutex::new(create_hotspot()?));
 | 
			
		||||
 | 
			
		||||
fn get_hotspot_enable_ids() -> Vec<TallyID> {
 | 
			
		||||
    let hotspot_ids: Vec<TallyID> = env::var("HOTSPOT_IDS")
 | 
			
		||||
        .map(|ids| ids.split(";").map(|id| TallyID(id.to_owned())).collect())
 | 
			
		||||
        .unwrap_or_default();
 | 
			
		||||
@ -207,40 +95,79 @@ async fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let channel_store = store.clone();
 | 
			
		||||
    let channel_hotspot = hotspot.clone();
 | 
			
		||||
    tokio::spawn(async move {
 | 
			
		||||
        while let Ok(tally_id_string) = rx.recv().await {
 | 
			
		||||
            let tally_id = TallyID(tally_id_string);
 | 
			
		||||
    hotspot_ids
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            if hotspot_ids.contains(&tally_id) {
 | 
			
		||||
                info!("Enableing hotspot");
 | 
			
		||||
                channel_hotspot
 | 
			
		||||
                    .lock()
 | 
			
		||||
                    .await
 | 
			
		||||
                    .enable_hotspot()
 | 
			
		||||
                    .await
 | 
			
		||||
                    .unwrap_or_else(|err| {
 | 
			
		||||
                        error!("Hotspot: {err}");
 | 
			
		||||
                    });
 | 
			
		||||
                // TODO: Should the ID be added anyway or ignored ?
 | 
			
		||||
            }
 | 
			
		||||
async fn handle_ids_loop(
 | 
			
		||||
    mut id_channel: Receiver<String>,
 | 
			
		||||
    hotspot_enable_ids: Vec<TallyID>,
 | 
			
		||||
    id_store: Arc<Mutex<IDStore>>,
 | 
			
		||||
    hotspot: Arc<Mutex<impl Hotspot>>,
 | 
			
		||||
    mut user_feedback: FeedbackImpl,
 | 
			
		||||
) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    while let Ok(tally_id_string) = id_channel.recv().await {
 | 
			
		||||
        let tally_id = TallyID(tally_id_string);
 | 
			
		||||
 | 
			
		||||
            if channel_store.lock().await.add_id(tally_id) {
 | 
			
		||||
                info!("Added new id to current day");
 | 
			
		||||
        if hotspot_enable_ids.contains(&tally_id) {
 | 
			
		||||
            info!("Enableing hotspot");
 | 
			
		||||
            hotspot
 | 
			
		||||
                .lock()
 | 
			
		||||
                .await
 | 
			
		||||
                .enable_hotspot()
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap_or_else(|err| {
 | 
			
		||||
                    error!("Hotspot: {err}");
 | 
			
		||||
                });
 | 
			
		||||
            // TODO: Should the ID be added anyway or ignored ?
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                feedback_success(&gpio_buzzer, &status_led).await;
 | 
			
		||||
        if id_store.lock().await.add_id(tally_id) {
 | 
			
		||||
            info!("Added new id to current day");
 | 
			
		||||
 | 
			
		||||
                if let Err(e) = channel_store.lock().await.export_json(STORE_PATH).await {
 | 
			
		||||
                    error!("Failed to save id store to file: {e}");
 | 
			
		||||
                    feedback_failure(&gpio_buzzer, &status_led).await;
 | 
			
		||||
                    // TODO: How to handle a failure to save ?
 | 
			
		||||
                }
 | 
			
		||||
            user_feedback.success().await;
 | 
			
		||||
 | 
			
		||||
            if let Err(e) = id_store.lock().await.export_json(STORE_PATH).await {
 | 
			
		||||
                error!("Failed to save id store to file: {e}");
 | 
			
		||||
                user_feedback.failure().await;
 | 
			
		||||
                // TODO: How to handle a failure to save ?
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    run_webserver(store.clone(), sse_tx, hotspot.clone()).await;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    logger::setup_logger();
 | 
			
		||||
 | 
			
		||||
    info!("Starting application");
 | 
			
		||||
 | 
			
		||||
    let (tx, rx) = broadcast::channel::<String>(32);
 | 
			
		||||
    let sse_tx = tx.clone();
 | 
			
		||||
 | 
			
		||||
    let store: Arc<Mutex<IDStore>> = Arc::new(Mutex::new(load_or_create_store().await?));
 | 
			
		||||
    let user_feedback = Feedback::new()?;
 | 
			
		||||
    let hotspot = Arc::new(Mutex::new(create_hotspot()?));
 | 
			
		||||
    let hotspot_enable_ids = get_hotspot_enable_ids();
 | 
			
		||||
 | 
			
		||||
    let pm3_handle = run_pm3(tx);
 | 
			
		||||
 | 
			
		||||
    let loop_handle = handle_ids_loop(
 | 
			
		||||
        rx,
 | 
			
		||||
        hotspot_enable_ids,
 | 
			
		||||
        store.clone(),
 | 
			
		||||
        hotspot.clone(),
 | 
			
		||||
        user_feedback,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let webserver_handle = run_webserver(store.clone(), sse_tx, hotspot.clone());
 | 
			
		||||
 | 
			
		||||
    let run_result = try_join!(pm3_handle, loop_handle, webserver_handle);
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = run_result {
 | 
			
		||||
        error!("Failed to run application: {e}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user