phoenix_rec/
lib.rs

1// This module contains various data types, client and server related code.
2pub mod client;
3pub mod data_types;
4pub mod server;
5
6// Importing necessary modules and libraries.
7use crate::server::add_data;
8use crate::Data::{RecordData, RecordDataOption};
9use data_types::DataType;
10use serde::{Deserialize, Serialize};
11use std::fmt::{Debug, Display};
12use std::fs::OpenOptions;
13use std::io::Write;
14use std::sync::atomic::AtomicBool;
15use std::sync::atomic::Ordering::SeqCst;
16use std::sync::Mutex;
17use std::time::{SystemTime, UNIX_EPOCH};
18use strum_macros::Display;
19use whoami::fallible;
20
21/// Enum representing the direction of movement.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
23pub enum Direction {
24    Left,
25    Right,
26}
27
28/// Enum representing various types of commands.
29#[derive(Debug, Clone, Copy)]
30pub enum Command {
31    Turn(i16),
32    // radius, angle
33    TurnRadius(i16, i16),
34    DriveDist(i16),
35    DriveLine(i16),
36    AlignDist(i16),
37    AlignLine(i16),
38    TurnOneWheel(i16, Direction),
39}
40
41// Implementing Display trait for Command enum.
42impl Display for Command {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Command::Turn(a) => write!(f, "Turn({})", a),
46            Command::TurnRadius(r, a) => write!(f, "TurnRadius({}, {})", r, a),
47            Command::DriveDist(d) => write!(f, "DriveDist({})", d),
48            Command::DriveLine(d) => write!(f, "DriveLine({})", d),
49            Command::AlignDist(d) => write!(f, "AlignDist({})", d),
50            Command::AlignLine(d) => write!(f, "AlignLine({})", d),
51            Command::TurnOneWheel(a, d) => write!(f, "TurnOneWheel({}, {})", a, d),
52        }
53    }
54}
55
56// Enum representing different types of data.
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display)]
58pub enum Data {
59    Command(String),
60    RecordData(u128, Vec<DataType>),
61    RecordDataOption(u128, Vec<Option<DataType>>),
62}
63
64// Struct representing recorded data.
65#[derive(Debug, Clone)]
66pub struct RecData {
67    /// first the format of the current data
68    pub data: Vec<Data>,
69    commands: Vec<Command>,
70    start_time: u128,
71    right_total_d: f32,
72    left_total_d: f32,
73    first_time: bool,
74}
75
76// Implementing Default trait for RecData struct.
77impl Default for RecData {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83// Implementing methods for RecData struct.
84impl RecData {
85    const fn new() -> Self {
86        Self {
87            data: vec![],
88            commands: vec![],
89            start_time: 0,
90            right_total_d: 0.0,
91            left_total_d: 0.0,
92            first_time: true,
93        }
94    }
95}
96
97// Mutex for RecData and AtomicBool for server.
98static REC_DATA: Mutex<RecData> = Mutex::new(RecData::new());
99static SERVER: AtomicBool = AtomicBool::new(false);
100
101// Function to set the server status.
102pub fn set_server(b: bool) {
103    SERVER.store(b, SeqCst);
104}
105
106// Macro to lock the mutex.
107macro_rules! lm {
108    ($mutex:expr) => {
109        $mutex.lock().expect("Mutex poisoned")
110    };
111}
112
113// Various functions to get and manipulate RecData.
114pub fn get_rec_data() -> RecData {
115    lm!(REC_DATA).clone()
116}
117
118pub fn get_rec_len() -> usize {
119    lm!(REC_DATA).data.len()
120}
121
122pub fn get_rec_index(index: usize) -> Data {
123    lm!(REC_DATA).data[index].clone()
124}
125
126pub fn get_rec_start_time() -> u128 {
127    lm!(REC_DATA).start_time
128}
129
130pub fn add_command(command: Command) {
131    lm!(REC_DATA).commands.push(command);
132    // todo: show also the params
133    lm!(REC_DATA).data.push(Data::Command(command.to_string()));
134}
135
136pub fn add_comment(comment: String) {
137    lm!(REC_DATA).data.push(Data::Command(comment));
138}
139
140pub fn get_data_name() -> String {
141    println!("COMMANDS: {:?}", lm!(REC_DATA).commands);
142    "data_".to_string()
143        + &*lm!(REC_DATA)
144            .commands
145            .iter()
146            .map(|c| c.to_string())
147            .collect::<Vec<String>>()
148            .join("_")
149            .replace(' ', "")
150            .to_string()
151}
152
153pub fn get_time() -> Option<u128> {
154    if lm!(REC_DATA).start_time == 0 {
155        None
156    } else {
157        Some(
158            SystemTime::now()
159                .duration_since(UNIX_EPOCH)
160                .unwrap()
161                .as_millis()
162                - lm!(REC_DATA).start_time,
163        )
164    }
165}
166
167pub fn save_data(mut data: Vec<DataType>) {
168    if lm!(REC_DATA).start_time == 0 {
169        lm!(REC_DATA).start_time = SystemTime::now()
170            .duration_since(UNIX_EPOCH)
171            .unwrap()
172            .as_millis();
173    }
174    // check if the data contains the same DataType multiple times
175    let mut had = vec![];
176    for d in &data {
177        if had.contains(d) {
178            panic!("data contains the same DataType multiple times");
179        }
180        had.push(*d);
181    }
182    // cache the total distance, because i cant look the mutex twice
183    let right_total_d = lm!(REC_DATA).right_total_d;
184    let left_total_d = lm!(REC_DATA).left_total_d;
185    // add RIGHT_TOTAL_D and LEFT_TOTAL_D to the DataType::DrivenDistance element
186    for d in data.iter_mut() {
187        if let DataType::DrivenDistance(r, l) = d {
188            *d = DataType::DrivenDistance(right_total_d + *r, left_total_d + *l);
189        }
190    }
191    let start_time = lm!(REC_DATA).start_time;
192    let rec_data = RecordData(
193        SystemTime::now()
194            .duration_since(UNIX_EPOCH)
195            .unwrap()
196            .as_millis()
197            - start_time,
198        data.clone(),
199    );
200    if SERVER.load(SeqCst) {
201        add_data(rec_data.clone());
202    }
203    lm!(REC_DATA).data.push(rec_data);
204}
205
206pub fn save_record_data(data: Data) {
207    lm!(REC_DATA).data.push(data);
208}
209
210pub fn update_total_distance(right: f32, left: f32) {
211    if !lm!(REC_DATA).first_time {
212        lm!(REC_DATA).right_total_d += right;
213        lm!(REC_DATA).left_total_d += left;
214    } else {
215        lm!(REC_DATA).first_time = false;
216    }
217}
218
219pub fn get_right_total_distance() -> f32 {
220    lm!(REC_DATA).right_total_d
221}
222
223pub fn get_left_total_distance() -> f32 {
224    lm!(REC_DATA).left_total_d
225}
226
227pub fn write_data(file_name: String) {
228    get_data_name();
229    println!(
230        "Writing data to {}: len: {}",
231        file_name,
232        lm!(REC_DATA).data.len()
233    );
234    let mut file = OpenOptions::new()
235        .write(true)
236        .create(true)
237        .open(file_name)
238        .unwrap();
239    file.set_len(0).unwrap();
240    // println!("Data: {:?}", DATA);
241    let mut data = vec![];
242    let mut used = vec![];
243    for dat in &*lm!(REC_DATA).data {
244        match dat {
245            RecordData(t, d) => {
246                // todo: maybe index compression
247                let mut temp = vec![];
248                for i in d {
249                    // convert to u8
250                    let index = i.to_u8();
251                    while temp.len() <= index as usize {
252                        temp.push(None);
253                    }
254                    if !used.contains(&index) {
255                        used.push(index);
256                    }
257                    temp[index as usize] = Some(*i);
258                }
259                data.push(RecordDataOption(*t, temp));
260            }
261            Data::Command(s) => {
262                data.push(Data::Command(s.clone()));
263            }
264            _ => {
265                panic!("Data is not RecordData or Command");
266            }
267        }
268    }
269    used.sort();
270    file.write_all(
271        format!(
272            "time, {}\n",
273            used.iter()
274                .map(|i| DataType::from_repr(*i)
275                    .expect("DataType not found")
276                    .write_description())
277                .filter(|x| x != &"".to_string())
278                .collect::<Vec<String>>()
279                .join(", ")
280        )
281        .as_bytes(),
282    )
283    .unwrap();
284    file.write_all(b"# Phoenix data\n").unwrap();
285    // todo: write time and date in this format: hh:mm:ss dd.mm.yyyy
286    // Get current time, date and the name of the current user and format it in a nice way
287    let now = chrono::Local::now();
288    let user = whoami::username();
289    let machine_name = fallible::hostname().unwrap_or_else(|_| "unknown".to_string());
290    file.write_all(format!(
291        "# Created on {} at {} by {} on {}\n",
292        now.format("%d-%m-%Y"),
293        now.format("%H:%M:%S"),
294        user,
295        machine_name
296    ).as_bytes()).unwrap();
297    file.write_all(format!("# {}\n", get_data_name()).as_bytes())
298        .unwrap();
299    // todo reimplement this
300    // file.write_all(
301    //     format!(
302    //         "# k_p_drive: {}, k_i_drive: {}, k_d_drive: {}\n",
303    //         KPDRIVE.load(SeqCst),
304    //         KIDRIVE.load(SeqCst),
305    //         KDDRIVE.load(SeqCst)
306    //     )
307    //     .as_bytes(),
308    // )
309    // .unwrap();
310    // println!("Data: {:?}", data);
311    for data in data {
312        match data {
313            RecordDataOption(t, d) => {
314                file.write_all(
315                    format!(
316                        "{}, {}\n",
317                        t,
318                        d.iter()
319                            .enumerate()
320                            .map(|(i, x)| {
321                                if let Some(x) = x {
322                                    x.write()
323                                } else if used.contains(&(i as u8)) {
324                                    DataType::None(i as u8).write()
325                                } else {
326                                    // dont print null, because its not needed and we want to save disc space
327                                    "".to_string()
328                                }
329                            })
330                            .filter(|x| x != &"".to_string())
331                            .collect::<Vec<String>>()
332                            .join(", ")
333                    )
334                    .as_bytes(),
335                )
336                .unwrap();
337            }
338            Data::Command(s) => {
339                file.write_all(format!("# {}\n", s).as_bytes()).unwrap();
340            }
341            _ => {
342                panic!("Data::RecordDataOption or Data::Command expected");
343            }
344        }
345    }
346}
347
348pub fn clear_data() {
349    *lm!(REC_DATA) = RecData::default();
350}
351/// This Function deletes the first n entries from the data, but keeps the rest.
352pub fn delete_data(n: usize) {
353    if n < lm!(REC_DATA).data.len() {
354        lm!(REC_DATA).data.drain(0..n);
355    }
356}