204 lines
6.5 KiB
Rust
204 lines
6.5 KiB
Rust
use iced::time::{self, Duration, Instant, milliseconds};
|
|
use std::collections::HashMap;
|
|
use std::fmt::{Formatter, Error as fmtError};
|
|
use serde::{Deserialize, Serialize};
|
|
use iced::widget::{center, column, row, text};
|
|
use iced::{Element, Subscription, Theme, Center, Task};
|
|
use tokio::fs::File;
|
|
use tokio::io::AsyncWriteExt;
|
|
use std::fs::read_to_string;
|
|
use reqwest::Client;
|
|
use chrono::{DateTime, Local};
|
|
use iced::futures::join;
|
|
|
|
|
|
mod card_reader;
|
|
mod approx_time;
|
|
mod config;
|
|
mod db_update;
|
|
use config::CONFIG;
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct RacerTime{
|
|
#[serde(with = "approx_time")]
|
|
time: Instant,
|
|
racer: Racer,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct State{
|
|
racers: Vec<RacerTime>,
|
|
db: HashMap<usize, Racer>,
|
|
#[serde(with = "approx_time")]
|
|
time: Instant,
|
|
}
|
|
|
|
impl Default for State {
|
|
fn default() -> Self {
|
|
Self {
|
|
racers: vec![],
|
|
db: Default::default(),
|
|
time: Instant::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn dump_state(json: String) -> Result<(), tokio::io::Error> {
|
|
let mut file = File::create("state.json").await?;
|
|
file.write_all(json.as_bytes()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct RegisterRacer{
|
|
card_id: usize,
|
|
time: String,
|
|
station_id: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
|
|
struct Racer{
|
|
starting_number: usize,
|
|
card_id: usize,
|
|
}
|
|
|
|
enum Message{
|
|
CardReader(card_reader::Event),
|
|
RacerRecieved(db_update::Event),
|
|
Tick(Instant),
|
|
Nothing,
|
|
}
|
|
|
|
impl std::fmt::Debug for Message {
|
|
// Required method
|
|
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), fmtError>{
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
async fn station_register(card_id: usize, time: DateTime::<Local>) -> Result<(), reqwest::Error>{
|
|
let register_racer = RegisterRacer{
|
|
card_id: card_id,
|
|
station_id: CONFIG.STATION_ID,
|
|
time: format!("{}", time.format("%d.%m.%Y %H:%M:%S.%6f")),
|
|
};
|
|
Client::new()
|
|
.post(CONFIG.HOST.clone() + "/api/station/register")
|
|
.json(®ister_racer)
|
|
.send()
|
|
.await?;
|
|
// Result can be error (400 and so on), whatever for now
|
|
Ok(())
|
|
}
|
|
|
|
async fn register_and_dump(card_id: usize, datetime: DateTime<Local>, json: String) -> (Result<(), tokio::io::Error>, Result<(), reqwest::Error>){
|
|
return join!(dump_state(json), station_register(card_id, datetime))
|
|
}
|
|
|
|
impl State{
|
|
fn update(&mut self, message: Message) -> Task<Message>{
|
|
match message {
|
|
Message::RacerRecieved(event) => {
|
|
match event {
|
|
db_update::Event::MessageReceived(s) => {
|
|
let racer: Racer = serde_json::from_str(s.as_str()).unwrap();
|
|
let old_racer = self.db.insert(racer.card_id, racer.clone());
|
|
if old_racer != Some(racer){
|
|
let json = serde_json::to_string(self).unwrap();
|
|
Task::perform(dump_state(json), |_| Message::Nothing)
|
|
}else{
|
|
Task::none()
|
|
}
|
|
},
|
|
}
|
|
}
|
|
Message::Tick(time) => {
|
|
self.time = time;
|
|
Task::none()
|
|
},
|
|
Message::Nothing => Task::none(),
|
|
Message::CardReader(card_reader::Event::ReadedCardId(card_id)) => {
|
|
let time = Instant::now();
|
|
let datetime = Local::now();
|
|
if let Some(racer) = self.db.get(&card_id){
|
|
if !self.racers.iter().any(|rt| rt.racer.starting_number == racer.starting_number){
|
|
let racer_time = RacerTime{time, racer: racer.clone()};
|
|
self.racers.push(racer_time);
|
|
}
|
|
}else{
|
|
println!("Racer {} not found in db!", card_id)
|
|
}
|
|
let json = serde_json::to_string(self).unwrap();
|
|
Task::perform(register_and_dump(card_id, datetime, json), |_| Message::Nothing)
|
|
},
|
|
Message::CardReader(card_reader::Event::Connected) => {
|
|
println!("Card Reader Connected!");
|
|
Task::none()
|
|
},
|
|
Message::CardReader(card_reader::Event::Disconnected) => {
|
|
println!("Card Reader Disconnected!");
|
|
Task::none()
|
|
},
|
|
}
|
|
}
|
|
fn view(&self) -> Element<Message> {
|
|
let mut content = column![
|
|
row![
|
|
text("Číslo závodníka").width(120).align_x(Center).align_y(Center), text("čas").size(20).width(120).align_x(Center).align_y(Center)
|
|
],
|
|
];
|
|
for racer_time in &self.racers {
|
|
const MINUTE: u64 = 60;
|
|
|
|
|
|
let time = self.time - racer_time.time;
|
|
let duration = Duration::new(CONFIG.DURATION, 0);
|
|
let (duration, negative) = if duration < time {
|
|
(time - duration, true)
|
|
} else {
|
|
(duration - time, false)
|
|
};
|
|
let seconds = duration.as_secs();
|
|
|
|
if negative && seconds >= 10 {
|
|
continue
|
|
}
|
|
|
|
let duration = text!(
|
|
"{}{:0>2}:{:0>2}.{:0>2}",
|
|
if negative {"-"} else {""},
|
|
seconds / MINUTE,
|
|
seconds % MINUTE,
|
|
duration.subsec_millis() / 10,
|
|
).size(20).width(120).align_x(Center).align_y(Center);
|
|
content = content.push(
|
|
row![
|
|
text(racer_time.racer.starting_number.to_string()).size(20).width(120).align_x(Center).align_y(Center), duration
|
|
]
|
|
);
|
|
}
|
|
center(content).into()
|
|
}
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
Subscription::batch(vec![
|
|
time::every(milliseconds(10)).map(Message::Tick),
|
|
Subscription::run(card_reader::connect).map(Message::CardReader),
|
|
Subscription::run(db_update::connect).map(Message::RacerRecieved)
|
|
])
|
|
}
|
|
fn theme(&self) -> Theme {
|
|
Theme::Dark
|
|
}
|
|
}
|
|
|
|
pub fn main() -> iced::Result {
|
|
let init_state = || -> State {read_to_string("state.json").ok().map(|s| serde_json::from_str(&s).ok()).flatten().unwrap_or_else(||Default::default())} ;
|
|
env_logger::init();
|
|
rustls::crypto::aws_lc_rs::default_provider().install_default().unwrap();
|
|
iced::application(init_state, State::update, State::view)
|
|
.subscription(State::subscription)
|
|
.theme(State::theme)
|
|
.run()
|
|
}
|