use std::{net::Ipv4Addr, sync::Arc, time::Duration}; use axum::{extract::State, http::StatusCode, response::Html, routing::{get, post}, serve, Form, Router}; use common::{Name, Request}; use ping_rs::send_ping_async; use postcard::to_allocvec; use tokio::{fs, io::{AsyncReadExt, AsyncWriteExt}, join, net::TcpStream, sync::{RwLock, Semaphore}, time::{self, sleep, Instant}}; use anyhow::{Ok, Result}; use tracing::{error, info, trace, warn, Level}; use tracing_subscriber::{filter, layer::{Filter, SubscriberExt}, util::SubscriberInitExt, Layer}; const FISH: &'static str = "192.168.0.12:1234"; const DOOR: &'static str = "192.168.0.10:1234"; #[derive(Default, Debug)] struct NetStatus { fish_reachable: bool, fish_ping: Option, door_reachable: bool, door_ping: Option, router_reachable: bool, router_ping: Option, } #[derive(Clone)] struct AppState { fish: Arc, last_badge: Arc>, net_status: Arc>, } #[tokio::main] async fn main() -> Result<()>{ let log = fs::OpenOptions::new().append(true).create(true).open("john.log").await?; let (non_blocking, _guard) = tracing_appender::non_blocking(log.into_std().await); let stdout_filter = filter::Targets::new() .with_target("server", Level::TRACE) .with_target("mio::poll", Level::WARN); let log_filter = stdout_filter.clone() .with_target("server", Level::INFO); let stdout = tracing_subscriber::fmt::layer() .compact() .with_file(true) .with_target(true) .with_filter(stdout_filter); let log = tracing_subscriber::fmt::layer() .compact() .with_file(false) .with_target(true) .with_writer(non_blocking) .with_filter(log_filter); let reg = tracing_subscriber::registry() .with(stdout) .with(log); reg.try_init()?; let fish = Arc::new(Semaphore::new(1)); let last_badge = Arc::new(RwLock::new((0u64,Instant::now()))); let network_status = Arc::new(RwLock::new(NetStatus::default())); info!("starting"); let app: Router = Router::new() .route("/names", get(names)) .route("/", get(root)) .route("/admin", get(control)) .route("/phil", get(phil)) .route("/recent", get(recent_badge)) .route("/setLast", post(set_last)) .route("/openDoor", post(open_door)) .route("/setVolume", post(set_volume)) //.route("/map", post(set_badge)) .with_state(AppState { fish: fish.clone(), last_badge: last_badge.clone(), net_status: network_status.clone(), }); let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); tokio::spawn(async {serve(listener, app).await.unwrap()}); let fish_ = fish.clone(); tokio::spawn(async move { loop { match get_recent_badge(fish_.clone()).await { Result::Ok(badge) => { *last_badge.write().await = (badge, Instant::now()); }, Err(e) => { trace!("badge read fail: {e}"); } } sleep(Duration::from_secs(1)).await; } }); tokio::spawn(async move { loop { let fish = Ipv4Addr::new(192,168,0,12).into(); let door = Ipv4Addr::new(192,168,0,10).into(); let router = Ipv4Addr::new(192,168,0,50).into(); let (fish,door,router) = join!( send_ping_async(&fish, Duration::from_millis(1500), Arc::new(&[255,127]), None), send_ping_async(&door, Duration::from_millis(1500), Arc::new(&[255,127]), None), send_ping_async(&router, Duration::from_millis(1500), Arc::new(&[255,127]), None) ); let status = NetStatus { fish_reachable: fish.is_ok(), door_reachable: door.is_ok(), router_reachable: router.is_ok(), fish_ping: fish.ok().map(|p| p.rtt), door_ping: door.ok().map(|p| p.rtt), router_ping: router.ok().map(|p| p.rtt), }; let mut last = network_status.write().await; if last.fish_reachable != status.fish_reachable { warn!("fish connected: {}", status.fish_reachable); } if last.door_reachable != status.door_reachable { warn!("door connected: {}", status.door_reachable); } if last.router_reachable != status.router_reachable { warn!("router connected: {}", status.router_reachable); } *last = status; drop(last); sleep(Duration::from_secs(1)).await; } }); /*{ let _permit = fish.acquire().await?; let mut conn = TcpStream::connect(FISH).await?; let req = Request::RecentBadge; conn.write_all(&to_allocvec(&req)?).await?; conn.flush().await?; let badge = conn.read_u64().await?; info!("read: {badge}"); if badge != 0 { let req = Request::SetBadge(common::Name::Evan, badge); conn.write_all(&to_allocvec(&req)?).await?; conn.flush().await?; } let req = Request::SetVolume(30); conn.write_all(&to_allocvec(&req)?).await?; }*/ loop { sleep(Duration::from_secs(2)).await; } } async fn get_recent_badge(fish: Arc) -> Result { let _permit = fish.acquire().await?; let mut conn = TcpStream::connect(FISH).await?; let req = Request::RecentBadge; conn.write_all(&to_allocvec(&req)?).await?; conn.flush().await?; let badge = conn.read_u64().await?; if badge != 0 { info!("new badge: {badge:#0x}"); Ok(badge) } else { Err(anyhow::anyhow!("no badge present")) } } async fn root() -> Html { let page = format!(r#"

JOHN

"#); Html::from(page) } async fn names(state: State) -> Html { let last_badge_view = last_badge_view(&state.last_badge).await; let mut page = format!(r#" {last_badge_view}

set last badge

"#); for name in [ "Tess", "Amaia", "Prueky", "David", "Nathaniel", "Thia", "Michael", "Zoey", ] { page.push_str(&format!(r#"
"#)); } page.push_str(&format!(r#"
"#)); Html::from(page) } async fn control(state: State) -> Html { let last_badge_view = last_badge_view(&state.last_badge).await; let netstat = state.net_status.read().await; let door_color = if netstat.door_reachable {"green"} else {"red"}; let fish_color = if netstat.fish_reachable {"green"} else {"red"}; let router_color = if netstat.router_reachable {"green"} else {"red"}; let fish_ping = if let Some(ping) = netstat.fish_ping {&format!("ping: {ping}ms")} else {"disconnected"}; let door_ping = if let Some(ping) = netstat.door_ping {&format!("ping: {ping}ms")} else {"disconnected"}; let router_ping = if let Some(ping) = netstat.router_ping {&format!("ping: {ping}ms")} else {"offline"}; let page = format!(r#" JOHNADMIN

JOHN ADMIN PANEL

lovesense {router_ping}
door {door_ping}
fish {fish_ping}

{last_badge_view} "#); Html::from(page) } async fn last_badge_view(last_badge: &Arc>) -> String { let (last_badge, time) = *last_badge.read().await; let time = time.elapsed().as_secs(); if last_badge == 0 { format!(r#"

no badges scanned, up {time}s

"#) } else { format!(r#"

most recent badge {last_badge:#0x}
{time}s ago

"#) } } #[derive(serde::Deserialize)] struct Volume { volume: u8, } async fn open_door() -> Html<&'static str> { info!("opening door"); let mut conn = TcpStream::connect(DOOR).await.unwrap(); conn.write_all(b"B f7c31d1603000000 \r\n").await.unwrap(); conn.flush().await.unwrap(); Html::from("

opening the noor

") } async fn set_volume( state: State, Form(input): Form) -> Html { info!("setting volume to {}", input.volume); let _fish = state.fish.acquire().await.unwrap(); let mut conn = TcpStream::connect(FISH).await.unwrap(); let req = Request::SetVolume(input.volume); conn.write_all(&to_allocvec(&req).unwrap()).await.unwrap(); conn.flush().await.unwrap(); Html::from(format!("

volume is now {}


back", input.volume)) } #[derive(serde::Deserialize)] struct NameForm { name: String, } async fn set_last( state: State, Form(input): Form) -> Html { let name_e = match input.name.as_str() { "Tess" => Name::Tess, "Amaia" => Name::Amaia, "Prueky" => Name::Prueky, "David" => Name::David, "Nathaniel" => Name::Nathaniel, "Thia" => Name::Thia, "Michael" => Name::Michael, "Zoey" => Name::Zoey, _ => Name::Unknown, }; let (last_badge, _) = *state.last_badge.read().await; info!("setting {} ({name_e:?}) to {last_badge:#0x}", input.name); let _fish = state.fish.acquire().await.unwrap(); let mut conn = TcpStream::connect(FISH).await.unwrap(); if last_badge == 0 { return Html::from("

failed, no badge scanned

".to_string()); } let req = Request::SetBadge(name_e, last_badge); conn.write_all(&to_allocvec(&req).unwrap()).await.unwrap(); conn.flush().await.unwrap(); Html::from(format!("

{last_badge:#0x} is now {}


back", input.name)) } async fn recent_badge(state: State) -> String { let (last_badge, time) = *state.last_badge.read().await; format!("id: {last_badge:#0x}, scanned {:?} ago", time.elapsed()) } async fn phil(state: State) -> String { let _fish = state.fish.acquire().await.unwrap(); let (last_badge, time) = *state.last_badge.read().await; info!("id: {last_badge:#0x}, scanned {:?} ago", time.elapsed()); let mut conn = TcpStream::connect(FISH).await.unwrap(); if last_badge != 0 { let req = Request::SetBadge(common::Name::Phil, last_badge); conn.write_all(&to_allocvec(&req).unwrap()).await.unwrap(); conn.flush().await.unwrap(); } String::from(format!("{last_badge:#0x} is now phil")) }