mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2025-07-01 16:54:17 +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