Skip to content

Commit

Permalink
Merge pull request #76 from delft-hyperloop/bounds
Browse files Browse the repository at this point in the history
datatypes can have checks for their values being within some range: added in config
  • Loading branch information
andtsa authored Jul 7, 2024
2 parents e27a61c + 1d95797 commit 7afa681
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 17 deletions.
17 changes: 17 additions & 0 deletions config/datatypes.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
## -
# Format for new datatypes:
# ```
# [[Datatype]]
# name = "YourNewDatatypeName"
# id = 42 # must be unique. code will not compile if there is a collision. Values can be from 0-2047, or 0x000 to 0x7FF.
# upper = 100 # an upper bound for the values of this datatype.
# lower = 12 # a lower bound for the values of this datatype.
# ```
# when a datapoint is received there's a check if
# lower <= value <= upper
# If this is false, then an emergency brake is triggered.
# Keep in mind that lower, value, and upper are all of type u64.
# This means that:
# - everything has a lower bound of 0
# - careful when setting bounds for floating point numbers.

[[Datatype]]
name = "DefaultDatatype"
id = 0x00
upper = 42001

[[Datatype]]
name = "PropulsionVoltage"
Expand Down
18 changes: 18 additions & 0 deletions config/procedures/example.procedure
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This is an example procedure

Id:
DH08.PROC.SC.x

People:
Kiko
Kiril

Equipment:
Andreas

Steps:
1. I refuse to elaborate.
2. :)
3. if in trouble just call me
just text is also fine in a procedure.

89 changes: 87 additions & 2 deletions gs/station/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::path::PathBuf;
use crate::api::Message;
use crate::Command;
use anyhow::anyhow;
use std::path::PathBuf;
use regex::Regex;
use tokio::task::AbortHandle;

// /// Any frontend that interfaces with this backend needs to comply to this trait
Expand Down Expand Up @@ -52,7 +54,10 @@ impl Backend {
message_receiver,
command_transmitter,
command_receiver,
log: Log { messages: vec![], commands: vec![] },
log: Log {
messages: vec![],
commands: vec![],
},
}
}

Expand Down Expand Up @@ -146,4 +151,84 @@ impl Backend {
pub fn log_cmd(&mut self, cmd: &Command) {
self.log.commands.push(*cmd);
}

pub fn load_procedures(folder: PathBuf) -> anyhow::Result<Vec<[String; 6]>> {
let mut r = vec![];

for entry in std::fs::read_dir(folder)? {
let f = entry?;

let f_name = f
.file_name()
.to_str()
.ok_or_else(|| anyhow!("Invalid procedure file name: {:?}", f))?
.to_string();
if f_name.ends_with(".procedure") {
let contents = std::fs::read_to_string(f.path())?;
let mut lines = contents.lines();
let title = lines
.next()
.ok_or_else(|| anyhow!("Missing procedure title"))?
.trim_start_matches([' ', '#'])
.to_string();

let (id, people, equipment, content) =
Self::parse_procedure_content(lines.fold(String::new(),|acc, x| {
acc + x + "\n"
}))?;

r.push([f_name.trim_end_matches(".procedure").to_string(), title, id, people, equipment, content]);
}
}

Ok(r)
}

fn parse_procedure_content(
content: String,
) -> anyhow::Result<(String, String, String, String)> {
let mut x = (String::new(), String::new(), String::new(), String::new());

x.0 = Regex::new("\n[iI][dD][: ]*\n([a-zA-Z0-9\n .]*)\n\n")
.unwrap()
.captures(&content)
.ok_or_else(|| anyhow!("\nMissing \"ID\" field for procedure:\n{:?}", content))?
.get(1)
.unwrap()
.as_str()
.to_string();

x.1 = Regex::new("\nPeople[: ]*\n([a-zA-Z0-9\n .]*)\n\n")
.unwrap()
.captures(&content)
.ok_or_else(|| anyhow!("\nMissing \"People\" field for procedure:\n{:?}", content))?
.get(1)
.unwrap()
.as_str()
.to_string();

x.2 = Regex::new("\nEquipment[: ]*\n([a-zA-Z0-9\n .]*)\n\n")
.unwrap()
.captures(&content)
.ok_or_else(|| anyhow!("\nMissing \"Equipment\" field for procedure:\n{:?}", content))?
.get(1)
.unwrap()
.as_str()
.to_string();

x.3 = Regex::new("\nSteps[: ]*\n([a-zA-Z0-9\n .;!,:(){}]*)\n\n")
.unwrap()
.captures(&content)
.ok_or_else(|| anyhow!("\nMissing \"Steps\" field for procedure:\n{:?}", content))?
.get(1)
.unwrap()
.as_str()
.to_string();

Ok(x)
}
}

#[cfg(test)]
#[path = "tests/backend.rs"]
mod tests;
26 changes: 26 additions & 0 deletions gs/station/src/frontend/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::frontend::{BackendState, BACKEND};
use crate::Datatype;
use crate::{Command, ERROR_CHANNEL, INFO_CHANNEL, STATUS_CHANNEL, WARNING_CHANNEL};
use rand::Rng;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Mutex;
use tauri::{Manager, State};

Expand Down Expand Up @@ -182,3 +184,27 @@ pub fn quit_server() {
backend_mutex.get_mut().unwrap().quit_server();
}
}

#[allow(unused)]
#[tauri::command]
pub fn procedures() -> Vec<[String; 6]> {
let res =
Backend::load_procedures(PathBuf::from_str("../../../../config/procedures/").unwrap());
if let Some(backend_mutex) = unsafe { BACKEND.as_mut() } {
if let Ok(x) = res {
backend_mutex
.get_mut()
.unwrap()
.log_msg(Message::Info("Loading procedures".into()));
x
} else {
backend_mutex
.get_mut()
.unwrap()
.log_msg(Message::Error("Failed to load procedures".into()));
Vec::new()
}
} else {
res.unwrap()
}
}
18 changes: 18 additions & 0 deletions gs/station/src/tests/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::backend::Backend;
use std::path::PathBuf;
use std::str::FromStr;

#[test]
fn import_procedures() {
let procedures =
Backend::load_procedures(PathBuf::from_str("../../config/procedures/").unwrap())
.unwrap();
assert!(procedures.len() > 0);
// panic!("{:?}", procedures);
let example = procedures.iter().find(|x| x[0] == "example").unwrap();
assert_eq!(example[1], "This is an example procedure");
assert_eq!(example[2], "DH08.PROC.SC.x");
assert_eq!(example[3], "Kiko\nKiril");
assert_eq!(example[4], "Andreas");
assert_eq!(example[5], "1. I refuse to elaborate.\n2. :)\n3. if in trouble just call me\njust text is also fine in a procedure.");
}
5 changes: 5 additions & 0 deletions util/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# imports are single line only to better work with line-based diffs like git
imports_granularity = "Item"

# imports are sorted to better work with line-based diffs like git
group_imports = "StdExternalCrate"
3 changes: 2 additions & 1 deletion util/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#![allow(non_snake_case)]

use serde::Deserialize;
use std::fs;

use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct Config {
pub(crate) Command: Vec<Command>,
Expand Down
57 changes: 54 additions & 3 deletions util/src/datatypes.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#![allow(non_snake_case)]
#![allow(non_snake_case, non_camel_case_types)]

use serde::Deserialize;
use std::fs;

use serde::Deserialize;

#[derive(Deserialize)]
pub struct Config {
pub(crate) Datatype: Vec<Datatype>,
Expand All @@ -12,6 +13,8 @@ pub struct Config {
pub struct Datatype {
pub name: String,
pub id: u16,
pub lower: Option<u64>,
pub upper: Option<u64>,
}

pub fn get_data_config(path: &str) -> Config {
Expand All @@ -35,6 +38,8 @@ pub fn generate_datatypes(path: &str, drv: bool) -> String {
let mut match_from_id = String::new();
let mut data_ids = vec![];
let mut from_str = String::new();
let mut bounds = String::new();

for dtype in config.Datatype {
data_ids.push(dtype.id);
enum_definitions.push_str(&format!(" {},\n", dtype.name));
Expand All @@ -50,6 +55,42 @@ pub fn generate_datatypes(path: &str, drv: bool) -> String {
" {:?} => Datatype::{},\n",
dtype.name, dtype.name
));
bounds.push_str(&format!(
" Datatype::{} => {} {} {},\n",
dtype.name,
dtype
.lower
.map(|x| format!("other >= {}u64", x))
.unwrap_or("".to_string()),
if dtype.lower.is_some() && dtype.upper.is_some() {
"&&".to_string()
} else {
"".to_string()
},
dtype
.upper
.map(|x| format!("other <= {}u64", x))
.unwrap_or_else(|| {
if dtype.lower.is_none() && dtype.upper.is_none() {
"true".to_string()
} else {
"".to_string()
}
}),
));
if let Some(l) = dtype.lower {
if l == 0 {
panic!(
"
You set a lower bound of 0 for {}. \
Since all values are treated as u64, \
values less than 0 are impossible.
Please ommit specifying this to keep the config clean :)
",
dtype.name
);
}
}
}

format!(
Expand Down Expand Up @@ -82,8 +123,15 @@ impl Datatype {{
_ => Datatype::DefaultDatatype,
}}
}}
pub fn check_bounds(&self, other: u64) -> bool {{
match *self {{
{}
}}
}}
}}
pub static DATA_IDS : [u16;{}] = [{}];\n",
",
if drv {
"#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, PartialOrd, Ord)]"
} else {
Expand All @@ -93,6 +141,9 @@ pub static DATA_IDS : [u16;{}] = [{}];\n",
match_to_id,
match_from_id,
from_str,
bounds,
) + &format!(
"pub static DATA_IDS : [u16;{}] = [{}];\n",
data_ids.len(),
data_ids
.iter()
Expand Down
3 changes: 2 additions & 1 deletion util/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#![allow(non_snake_case)]

use serde::Deserialize;
use std::fs;

use serde::Deserialize;

#[derive(Deserialize)]
pub struct Config {
Event: Vec<Event>,
Expand Down
3 changes: 2 additions & 1 deletion util/src/ip.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use local_ip_address::local_ip;
use std::net::IpAddr;

use local_ip_address::local_ip;

pub fn configure_gs_ip(ip: [u8; 4], port: u16, force: bool) -> String {
let mut ip = (ip[0], ip[1], ip[2], ip[3], port);
if !force {
Expand Down
12 changes: 6 additions & 6 deletions util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ pub fn check_ids(dp: &str, cp: &str, ep: &str) -> Vec<u16> {
if !seen.insert(id) {
panic!(
"\nDuplicate id found: {} ({}). Closest available id is: {}\n",
format!("{:#05x}", id),
format!("{:#05x}", id),
format!("{:#05x}", nearest_id(*id, &ids)),
format_args!("{:#05x}", id),
format_args!("{:#05x}", id),
format_args!("{:#05x}", nearest_id(*id, &ids)),
);
} else if *id > 2u16.pow(11) {
panic!(
"\nId out of range: {}. Allowed ids are 0x000-0x7ff. Closest available id is: {}\n",
format!("{:#05x}", id),
format!("{:#05x}", nearest_id(*id, &ids))
format_args!("{:#05x}", id),
format_args!("{:#05x}", nearest_id(*id, &ids))
);
}
}
ids
}

fn nearest_id(id: u16, ids: &[u16]) -> u16 {
for i in min(id,2u16.pow(11))..2u16.pow(11) {
for i in min(id, 2u16.pow(11))..2u16.pow(11) {
if !ids.contains(&i) {
return i;
}
Expand Down
7 changes: 4 additions & 3 deletions util/src/shared/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ mod tests {
#[test]
fn printing_speed_values() {
let route = Route {
positions: LocationSequence { // 0 210 0 180 0
positions: LocationSequence {
// 0 210 0 180 0
0: [
Location::StopAndWait,
Location::StraightStart,
Expand Down Expand Up @@ -477,11 +478,11 @@ mod tests {
(Location::StopAndWait, 0),
(Location::BrakeHere, 0),
]
.into(),
.into(),
),
};
let s_bytes: u64 = route.speeds.clone().into();
let r_bytes: u64 = route.positions.clone().into();
panic!("Speeds: {}\nPositions: {}", s_bytes, r_bytes);
// panic!("Speeds: {}\nPositions: {}", s_bytes, r_bytes);
}
}

0 comments on commit 7afa681

Please sign in to comment.