diff --git a/app/Cargo.toml b/app/Cargo.toml index ebd8b0b02..21c22d739 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -55,6 +55,7 @@ features = ["defmt"] # Only for build.rs [build-dependencies] +anyhow = "1.0.86" regex = "1.10.3" serde = { version = "1.0.197", features = ["derive"] } toml = "0.8.11" diff --git a/app/build.rs b/app/build.rs index f3e841c89..19bc5ce11 100644 --- a/app/build.rs +++ b/app/build.rs @@ -5,7 +5,7 @@ extern crate serde; use std::env; use std::fs; use std::path::Path; - +use anyhow::Result; use goose_utils::check_ids; use goose_utils::ip::configure_gs_ip; use serde::Deserialize; @@ -68,31 +68,31 @@ pub const DATATYPES_PATH: &str = "../config/datatypes.toml"; pub const COMMANDS_PATH: &str = "../config/commands.toml"; pub const EVENTS_PATH: &str = "../config/events.toml"; -fn main() { +fn main() -> Result<()> { // if cfg!(debug_assertions) { // env::set_var("DEFMT_LOG", "trace"); // } else { // env::set_var("DEFMT_LOG", "off"); // } - let out_dir = env::var("OUT_DIR").unwrap(); + let out_dir = env::var("OUT_DIR")?; let dest_path = Path::new(&out_dir).join("config.rs"); - let ip_file = fs::read_to_string(CONFIG_PATH).unwrap(); - let config: Config = toml::from_str(&ip_file).unwrap(); + let ip_file = fs::read_to_string(CONFIG_PATH)?; + let config: Config = toml::from_str(&ip_file)?; let mut content = String::from("//@generated\n"); let _ = check_ids(DATATYPES_PATH, COMMANDS_PATH, EVENTS_PATH); content.push_str(&configure_ip(&config)); - content.push_str(&configure_gs_ip(config.gs.ip, config.gs.port, config.gs.force)); + content.push_str(&configure_gs_ip(config.gs.ip, config.gs.port, config.gs.force)?); content.push_str(&configure_pod(&config)); content.push_str(&configure_internal(&config)); - content.push_str(&goose_utils::commands::generate_commands(COMMANDS_PATH, true)); - content.push_str(&goose_utils::datatypes::generate_datatypes(DATATYPES_PATH, false)); - content.push_str(&goose_utils::events::generate_events(EVENTS_PATH, true)); - content.push_str(&goose_utils::info::generate_info(CONFIG_PATH, false)); + content.push_str(&goose_utils::commands::generate_commands(COMMANDS_PATH, true)?); + content.push_str(&goose_utils::datatypes::generate_datatypes(DATATYPES_PATH, false)?); + content.push_str(&goose_utils::events::generate_events(EVENTS_PATH, true)?); + content.push_str(&goose_utils::info::generate_info(CONFIG_PATH, false)?); // content.push_str(&*can::main(&id_list)); fs::write(dest_path.clone(), content).unwrap_or_else(|e| { @@ -107,6 +107,8 @@ fn main() { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) } fn configure_ip(config: &Config) -> String { diff --git a/app/src/core/communication/can.rs b/app/src/core/communication/can.rs index 3af6751a8..4620f0beb 100644 --- a/app/src/core/communication/can.rs +++ b/app/src/core/communication/can.rs @@ -61,7 +61,13 @@ pub async fn can_receiving_handler( // frame.header().format(); let id = id_as_value(frame.id()); #[cfg(debug_assertions)] - data_sender.send(Datapoint::new(Datatype::ReceivedCan, id as u64, bytes_to_u64(frame.data()))).await; + data_sender + .send(Datapoint::new( + Datatype::ReceivedCan, + id as u64, + bytes_to_u64(frame.data()), + )) + .await; #[cfg(debug_assertions)] info!("[CAN ({})] received frame: id={:?} data={:?}", bus_nr, id, frame.data()); if DATA_IDS.contains(&id) { @@ -90,7 +96,7 @@ pub async fn can_receiving_handler( timestamp.as_ticks(), ) .await; - } else if gfd_counter > 2 { + } else if gfd_counter > 2 && frame.data()[2] == 0xFE { gfd_counter = 0; can_sender .send( diff --git a/app/src/core/communication/tcp.rs b/app/src/core/communication/tcp.rs index 94fa17ca9..d3b938193 100644 --- a/app/src/core/communication/tcp.rs +++ b/app/src/core/communication/tcp.rs @@ -6,6 +6,7 @@ use embassy_net::Stack; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::Ethernet; use embassy_stm32::peripherals::ETH; +use embassy_time::Instant; use embassy_time::Timer; use embedded_io_async::Write; use heapless::Deque; @@ -15,9 +16,13 @@ use crate::core::communication::Datapoint; use crate::pconfig::embassy_socket_from_config; use crate::Command; use crate::DataReceiver; +use crate::DataSender; use crate::Datatype; use crate::Event; use crate::EventSender; +use crate::COMMAND_HASH; +use crate::DATA_HASH; +use crate::EVENTS_HASH; use crate::GS_IP_ADDRESS; use crate::IP_TIMEOUT; use crate::NETWORK_BUFFER_SIZE; @@ -32,6 +37,7 @@ pub async fn tcp_connection_handler( stack: &'static Stack>, event_sender: EventSender, data_receiver: DataReceiver, + data_sender: DataSender, ) -> ! { let mut last_valid_timestamp = embassy_time::Instant::now().as_millis(); // info!("------------------------------------------------ TCP Connection Handler Started! ------------------------------------------"); @@ -69,6 +75,15 @@ pub async fn tcp_connection_handler( }, } event_sender.send(Event::ConnectionEstablishedEvent).await; + data_sender + .send(Datapoint::new(Datatype::CommandHash, COMMAND_HASH, Instant::now().as_ticks())) + .await; + data_sender + .send(Datapoint::new(Datatype::DataHash, DATA_HASH, Instant::now().as_ticks())) + .await; + data_sender + .send(Datapoint::new(Datatype::EventsHash, EVENTS_HASH, Instant::now().as_ticks())) + .await; // let mut connection = client.connect(gs_addr).await.unwrap(); // info!("----------------------------------------------------------------Connected to ground station=========================="); @@ -203,6 +218,11 @@ pub async fn tcp_connection_handler( info!("[tcp] Start Run command received"); event_sender.send(Event::RunStarting).await; }, + Command::ContinueRun(_) => { + #[cfg(debug_assertions)] + info!("[tcp] Start Run command received"); + event_sender.send(Event::ContinueRunEvent).await; + }, Command::Shutdown(_) => { #[cfg(debug_assertions)] info!("[tcp] Shutdown command received"); @@ -219,6 +239,16 @@ pub async fn tcp_connection_handler( event_sender.send(Event::TurnOffHVCommand).await; // TODO: no turn off HV exists?? }, + Command::DcOn(_) => { + #[cfg(debug_assertions)] + info!("[tcp] DcOn command received"); + event_sender.send(Event::DcOn).await; + }, + Command::DcOff(_) => { + #[cfg(debug_assertions)] + info!("[tcp] DcOff command received"); + event_sender.send(Event::DcOff).await; + }, Command::EmitEvent(e) => { #[cfg(debug_assertions)] info!("[tcp] EmitEvent command received"); diff --git a/app/src/core/controllers/breaking_controller.rs b/app/src/core/controllers/breaking_controller.rs index e9f83770a..c82bfa78b 100644 --- a/app/src/core/controllers/breaking_controller.rs +++ b/app/src/core/controllers/breaking_controller.rs @@ -152,7 +152,7 @@ impl BrakingController { pf12: peripherals::PF12, adc: Adc<'static, ADC1>, _pb0: peripherals::PB0, - _pd5: peripherals::PD5, + pd5: peripherals::PD5, _ptime: TIM16, ) -> Self { info!("breaking controller started"); @@ -181,6 +181,9 @@ impl BrakingController { let braking_signal = Output::new(pb8, Level::High, Speed::Low); // pwm.enable(Channel::Ch1); + // VGA ground + let _ = Output::new(pd5, Level::Low, Speed::Low); + try_spawn!( braking_sender, x.spawn(control_braking_heartbeat(braking_sender, data_sender, braking_signal,)) diff --git a/app/src/core/controllers/ethernet_controller.rs b/app/src/core/controllers/ethernet_controller.rs index 6699102ed..e53e18cba 100644 --- a/app/src/core/controllers/ethernet_controller.rs +++ b/app/src/core/controllers/ethernet_controller.rs @@ -17,6 +17,7 @@ use static_cell::StaticCell; use crate::core::communication::tcp::tcp_connection_handler; use crate::try_spawn; use crate::DataReceiver; +use crate::DataSender; use crate::EventSender; use crate::Irqs; use crate::POD_MAC_ADDRESS; @@ -47,6 +48,7 @@ impl EthernetController { x: Spawner, sender: EventSender, receiver: DataReceiver, + data_sender: DataSender, pins: EthernetPins, ) -> Self { let mut rng = Rng::new(pins.p_rng, Irqs); @@ -100,7 +102,10 @@ impl EthernetController { try_spawn!(sender, x.spawn(net_task(stack))); - try_spawn!(sender, x.spawn(tcp_connection_handler(x, stack, sender, receiver))); + try_spawn!( + sender, + x.spawn(tcp_connection_handler(x, stack, sender, receiver, data_sender)) + ); // unwrap!(x.spawn(udp_connection_handler(stack))); ethernet_controller diff --git a/app/src/core/controllers/finite_state_machine_peripherals.rs b/app/src/core/controllers/finite_state_machine_peripherals.rs index e9e4c6fa8..0846dbdaf 100644 --- a/app/src/core/controllers/finite_state_machine_peripherals.rs +++ b/app/src/core/controllers/finite_state_machine_peripherals.rs @@ -1,8 +1,10 @@ use defmt::debug; use embassy_executor::Spawner; use embassy_stm32::adc::Adc; +use embassy_stm32::gpio::Input; use embassy_stm32::gpio::Level; use embassy_stm32::gpio::Output; +use embassy_stm32::gpio::Pull; use embassy_stm32::gpio::Speed; use embassy_stm32::Peripherals; @@ -33,8 +35,8 @@ pub struct FSMPeripherals { impl FSMPeripherals { // pub fn new(p : Peripherals, x: &Spawner, q : &PriorityChannel) -> Self { pub async fn new(p: Peripherals, x: &Spawner, i: InternalMessaging) -> Self { - // let mut init = PInit{p,x,q}; - // let (braking_controller, init) = BrakingController::new(init); + // set to high impedance, since there's a 24V signal being given and this would fry the PCB + let _ = Input::new(p.PD4, Pull::None); // The braking controller is responsible for rearming the braked let braking_controller = BrakingController::new( @@ -65,6 +67,7 @@ impl FSMPeripherals { *x, i.event_sender, i.data_receiver, + i.data_sender, EthernetPins { p_rng: p.RNG, eth_pin: p.ETH, @@ -130,6 +133,7 @@ impl FSMPeripherals { pin_4: Output::new(p.PD3, Level::Low, Speed::Low), pin_6: Output::new(p.PG9, Level::Low, Speed::Low), pin_7: Output::new(p.PG10, Level::Low, Speed::Low), + dc_dc: Output::new(p.PD2, Level::Low, Speed::Low), }, red_led: Output::new(p.PB14, Level::Low, Speed::High), propulsion_controller: PropulsionController::new( diff --git a/app/src/core/controllers/hv_controller.rs b/app/src/core/controllers/hv_controller.rs index 346ea791e..6cf2f6db5 100644 --- a/app/src/core/controllers/hv_controller.rs +++ b/app/src/core/controllers/hv_controller.rs @@ -6,6 +6,7 @@ pub struct HVPeripherals { pub pin_4: Output<'static>, pub pin_6: Output<'static>, pub pin_7: Output<'static>, + pub dc_dc: Output<'static>, } impl HVPeripherals { diff --git a/app/src/core/data/mod.rs b/app/src/core/data/mod.rs index 52c225841..c5a3e75cf 100644 --- a/app/src/core/data/mod.rs +++ b/app/src/core/data/mod.rs @@ -4,12 +4,14 @@ //! This module is the middle man between the data producers and the ground station. mod batteries; - +mod sources; +use crate::Datatype; +use crate::ValueCheckResult; use crate::DataReceiver; use crate::DataSender; use crate::Event; use crate::EventSender; - +use crate::Datapoint; /// ## Individual handling of datapoints /// A lot of the subsystems on the pod use their own "encoding" for data. /// In order to make a reasonable matching between semantic meaning of @@ -24,11 +26,33 @@ pub async fn data_middle_step( let data = incoming.receive().await; // 1. check thresholds - if data.datatype.check_bounds(data.value) { - event_sender.send(Event::ValueOutOfBounds).await; + match data.datatype.check_bounds(data.value) { + ValueCheckResult::Fine => {}, + ValueCheckResult::Warn => { + outgoing.send(value_warning(data.datatype, data.value)).await; + } + ValueCheckResult::Error => { + outgoing.send(value_error(data.datatype, data.value)).await; + } + ValueCheckResult::BrakeNow => { + event_sender.send(Event::ValueOutOfBounds).await; + outgoing.send(value_critical(data.datatype, data.value)).await; + } } // 2. check specific data types outgoing.send(data).await; } } + +fn value_warning(dt: Datatype, v: u64) -> Datapoint { + Datapoint::new(Datatype::ValueWarning, dt.to_id() as u64, v) +} + +fn value_error(dt: Datatype, v: u64) -> Datapoint { + Datapoint::new(Datatype::ValueError, dt.to_id() as u64, v) +} + +fn value_critical(dt: Datatype, v: u64) -> Datapoint { + Datapoint::new(Datatype::ValueCausedBraking, dt.to_id() as u64, v) +} diff --git a/app/src/core/data/sources.rs b/app/src/core/data/sources.rs new file mode 100644 index 000000000..abe097455 --- /dev/null +++ b/app/src/core/data/sources.rs @@ -0,0 +1,43 @@ +#![allow(unused)] + +use crate::Datatype; + +pub enum Subsystems { + GroundStation, + SensorHub, + Propulsion, + Batteries, + Levitation, +} + +pub const GROUND_STATION_DATA: [Datatype; 1] = [Datatype::ResponseHeartbeat]; + +pub const SENSOR_HUB_DATA: [Datatype; 9] = [ + Datatype::Acceleration, + Datatype::Velocity, + Datatype::Localisation, + Datatype::AccelerationX, + Datatype::AccelerationY, + Datatype::AccelerationZ, + Datatype::GyroscopeX, + Datatype::GyroscopeY, + Datatype::GyroscopeZ, +]; + +pub const PROPULSION_DATA: [Datatype; 3] = + [Datatype::PropulsionSpeed, Datatype::PropulsionVoltage, Datatype::PropulsionCurrent]; + +pub const HV_BMS_DATA: [Datatype; 3] = [ + Datatype::BatteryBalanceHigh, + Datatype::BatteryCurrentHigh, + Datatype::BatteryEstimatedChargeHigh, +]; + +pub const LV_BMS_DATA: [Datatype; 6] = [ + Datatype::BatteryBalanceLow, + Datatype::BatteryCurrentLow, + Datatype::BatteryEstimatedChargeLow, + Datatype::BatteryVoltageLow, + Datatype::BatteryTemperatureLow, + Datatype::ChargeStateLow, +]; diff --git a/app/src/core/finite_state_machine.rs b/app/src/core/finite_state_machine.rs index ab7315455..082c880f7 100644 --- a/app/src/core/finite_state_machine.rs +++ b/app/src/core/finite_state_machine.rs @@ -5,7 +5,9 @@ use embassy_time::Instant; use crate::core::communication::Datapoint; use crate::core::controllers::finite_state_machine_peripherals::FSMPeripherals; +use crate::core::fsm_status::Location; use crate::core::fsm_status::Route; +use crate::core::fsm_status::RouteUse; use crate::core::fsm_status::Status; use crate::DataSender; use crate::Datatype; @@ -115,11 +117,56 @@ impl Fsm { State::RunConfig => self.entry_run_config(), State::HVOn => self.entry_hv_on(), State::Levitating => self.entry_levitating(), - State::MovingST => self.entry_mv_st(), - State::MovingLSST => self.entry_ls_st(), - State::MovingLSCV => self.entry_ls_cv(), - State::EndST => self.entry_end_st(), - State::EndLS => self.entry_end_ls(), + State::MovingST => { + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + self.entry_mv_st() + }, + State::MovingLSST => { + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + self.entry_ls_st() + }, + State::MovingLSCV => { + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + self.entry_ls_cv() + }, + State::EndST => { + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + self.entry_end_st() + }, + State::EndLS => { + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + self.entry_end_ls() + }, State::EmergencyBraking => { self.entry_emergency_braking(); self.pod_safe().await; @@ -167,10 +214,52 @@ impl Fsm { return; }, + Event::DcOn => { + self.peripherals.hv_peripherals.dc_dc.set_high(); + }, + + Event::DcOff => { + self.peripherals.hv_peripherals.dc_dc.set_low(); + }, + /////////////////// // Debugging events Event::SetOverrides(overrides) => self.status.overrides.set(overrides), + Event::ContinueRunEvent => { + match self.route.next_position() { + Location::ForwardA | Location::BackwardsA => { + transit!(self, State::MovingST); + }, + Location::ForwardB | Location::BackwardsB => { + transit!(self, State::EndST); + }, + Location::ForwardC | Location::BackwardsC => { + transit!(self, State::EndLS); + }, + Location::LaneSwitchStraight => { + transit!(self, State::MovingLSST); + }, + Location::LaneSwitchCurved => { + transit!(self, State::MovingLSCV); + }, + Location::StopAndWait => { + transit!(self, State::Levitating); + }, + Location::BrakeHere => { + transit!(self, State::Exit); + }, + } + self.data_queue + .send(Datapoint::new( + Datatype::RoutePlan, + self.route.positions.into(), + self.route.current_position as u64, + )) + .await; + return; + }, + // Override enabling or disabling propulsion GPIO Event::DisablePropulsionCommand => { self.peripherals.propulsion_controller.disable(); diff --git a/app/src/core/fsm_status.rs b/app/src/core/fsm_status.rs index 14109c14a..af0b385bc 100644 --- a/app/src/core/fsm_status.rs +++ b/app/src/core/fsm_status.rs @@ -36,7 +36,7 @@ impl Overrides { /// Enable or disable braking from a falling edge of the braking communication signal pub fn prevent_braking_communication(&self) -> bool { self.values & 0b10 != 0 } - /// Allow HV ON without brakes armed + /// Allow HV ON without brakes armed pub fn hv_without_brakes_armed(&self) -> bool { self.values & 0b100 != 0 } } diff --git a/config/commands.toml b/config/commands.toml index 0e1f05011..40a2b9c19 100644 --- a/config/commands.toml +++ b/config/commands.toml @@ -121,6 +121,10 @@ id = 0xff name = "StartRun" id = 0x5d +[[Command]] +name = "ContinueRun" +id = 0x5e + [[Command]] name = "StartHV" id = 0x60 diff --git a/config/datatypes.toml b/config/datatypes.toml index c856d0da2..60a422bee 100644 --- a/config/datatypes.toml +++ b/config/datatypes.toml @@ -1,25 +1,62 @@ -## - +##### ----- # 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. +# id = 42 # must be unique. +# # the code will not compile if there is a collision. +# # Values can be from 0-2047, or 0x000 to 0x7FF. # ``` -# 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. +# +# Limits: +# --------- +# Every time a datapoint is received by the main pcb, +# it checks the limits of that datatype, and produces +# an error if any of the limits are surpassed. +# +# There are 3 severities for surpassing bounds: +# - warn: the associated field on the ground station will turn yellow +# - err: the associated field on the ground station will turn red and blink +# - brake: critical limit, the main pcb will immediately deploy emergency brakes +# +# Not specifying a specific limit means nothing will happen. +# +# ``` +# [[Datatype]] +# name = "Example1" +# id = 50 +# lower = 5 +# upper = { warn = 50, err = 60, brake = 100 } +# ``` +# as you can see, you can either specify *one* limit (e.g. `lower = 5`), +# which if surpassed will function equivalent to `warn`, just informing the +# ground station operator, +# or you can specify multiple limits of different severities (as in `upper = { ... }`). +# +# If you don't want a certain severity to ever happen, +# just don't include the field: +# ``` +# [[Datatype]] +# name = "Example2" +# id = 51 +# lower = { warn = 10, err = 4 } +# ``` +# For this one, there will be +# - no upper limit, +# - a warning when it's below 10 +# - a (ground station) error when it's below 4, +# - and no emergency braking at any point. + [[Datatype]] name = "DefaultDatatype" id = 0x00 upper = 42001 +[[Datatype]] +name = "ConnectionStatus" +id = 0x7fe + [[Datatype]] name = "PropulsionVoltage" id = 0x1b2 @@ -250,6 +287,8 @@ id = 0x1a [[Datatype]] name = "Localisation" id = 0x500 +lower = 500 +upper = 12000 [[Datatype]] name = "Velocity" @@ -266,10 +305,13 @@ id = 0x1d [[Datatype]] name = "BrakePressure" id = 0x15 +upper = 250 +lower = 1 [[Datatype]] name = "UnknownCanId" id = 0x7F0 + [[Datatype]] name = "Info" id = 0x7FA @@ -278,67 +320,79 @@ id = 0x7FA [[Datatype]] name = "Presure_VB" id = 0x210 +# no limit necessary [[Datatype]] name = "Average_Temp_VB_Bottom" id = 0x211 - +upper = 80000 # (e-2) degrees [[Datatype]] name = "Average_Temp_VB_top" id = 0x200 - +upper = 80000 # (e-2) degrees [[Datatype]] name = "Temp_HEMS_1" id = 0x20a +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_HEMS_2" id = 0x20b +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_HEMS_3" id = 0x20c +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_HEMS_4" id = 0x20d +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_Motor_1" id = 0x20e +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_Motor_2" id = 0x20f +upper = 10000 # (e-2) degrees [[Datatype]] name = "Ambient_presure" id = 0x220 +# no limit necessary [[Datatype]] name = "Ambient_temp" id = 0x224 +# no limit necessary [[Datatype]] name = "Temp_EMS_1" id = 0x222 +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_EMS_2" id = 0x223 +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_EMS_3" id = 0x225 +upper = 10000 # (e-2) degrees [[Datatype]] name = "Temp_EMS_4" id = 0x226 - +upper = 10000 # (e-2) degrees [[Datatype]] name = "SingleCellVoltageHigh_1" @@ -655,10 +709,14 @@ id = 0x777 [[Datatype]] name = "LowPressureSensor" id = 0x127 +# lower = 40 #bar +# upper = 52 #bar [[Datatype]] name = "HighPressureSensor" id = 0x128 +# lower = 80 #bar +# upper = 180 #bar # SJOERD LEVITATION DATATYPES @@ -803,3 +861,35 @@ id = 0x163 name = "SendingCANEvent" id = 0x164 +[[Datatype]] +name = "RoutePlan" +id = 0x165 + +[[Datatype]] +name = "CurrentPosition" +id = 0x166 + +[[Datatype]] +name = "CommandHash" +id = 0x167 + +[[Datatype]] +name = "EventsHash" +id = 0x168 + +[[Datatype]] +name = "DataHash" +id = 0x169 + +[[Datatype]] +name = "ValueError" +id = 0x16a + +[[Datatype]] +name = "ValueWarning" +id = 0x16b + +[[Datatype]] +name = "ValueCausedBraking" +id = 0x16c + diff --git a/config/events.toml b/config/events.toml index 7af502fe5..305862cc3 100644 --- a/config/events.toml +++ b/config/events.toml @@ -22,6 +22,11 @@ name = "ValueOutOfBounds" id = 0x7ff priority = 1 +[[Event]] +name = "EndOfTrackTriggered" +id = 0x121 +priority = 0 + [[Event]] name = "BootingCompleteEvent" id = 0xa1 @@ -70,6 +75,11 @@ name = "RunStarting" id = 0xb9 priority = 1 +[[Event]] +name = "ContinueRunEvent" +id = 0xbd +priority = 1 + [[Event]] name = "SystemResetCommand" id = 0xbE @@ -105,6 +115,16 @@ id = 0x427 name = "ReConfigureCommand" priority = 0 +[[Event]] +id = 0x429 +name = "DcOn" +priority = 1 + +[[Event]] +id = 0x42a +name = "DcOff" +priority = 1 + # ########################################### # Events that carry configuration information diff --git a/config/procedures/Charging of the high voltage batteries.procedure b/config/procedures/Charging of the high voltage batteries.procedure index ad380576e..9a1897626 100644 --- a/config/procedures/Charging of the high voltage batteries.procedure +++ b/config/procedures/Charging of the high voltage batteries.procedure @@ -14,7 +14,6 @@ EMUS G1 BMS Control Unit + appropriate cables Insulating gloves Safety Glasses - Procedures: [If off pod] Batteries are dismounted and transported to designated charging place following HVBATTERY_DISMOUNTING and HVBATTERY_TRANSPORT procedures 2 battery packs/enclosures are taken out of the battery box, for both packs it is checked if the MID is off diff --git a/config/procedures/State Machine States.procedure b/config/procedures/State Machine States.procedure new file mode 100644 index 000000000..8ccca50a4 --- /dev/null +++ b/config/procedures/State Machine States.procedure @@ -0,0 +1,21 @@ +# Full Pushed-Run State Machine Demonstration + +Id: +DH08.PROC.SC.SM + +People: +1 Embedded Software Engineer as Ground Station Operator and Procedure lead +1 Safety Officer at the end of track +1 Person in the track to push the pod + +Items: +Ground Station setup + +Procedures: + + +Ensure localisation is enabled and sends reasonable readings +Bring the pod to ForwardA section of the track +... + + diff --git a/ehw/EHW24_Full_Scrutineering_Checklist.pdf b/ehw/EHW24_Full_Scrutineering_Checklist.pdf new file mode 100644 index 000000000..693562aca Binary files /dev/null and b/ehw/EHW24_Full_Scrutineering_Checklist.pdf differ diff --git a/ehw/EHW24_Powertrain_Scrutineering.docx b/ehw/EHW24_Powertrain_Scrutineering.docx new file mode 100644 index 000000000..e99b1283a Binary files /dev/null and b/ehw/EHW24_Powertrain_Scrutineering.docx differ diff --git a/ehw/SenseCon_Scrutineering_Checklist.pdf b/ehw/SenseCon_Scrutineering_Checklist.pdf new file mode 100644 index 000000000..3a39246a9 Binary files /dev/null and b/ehw/SenseCon_Scrutineering_Checklist.pdf differ diff --git a/gs/SCRUTINEERING.md b/ehw/gs-scrutineering.md similarity index 94% rename from gs/SCRUTINEERING.md rename to ehw/gs-scrutineering.md index 09c961d23..bde721a59 100644 --- a/gs/SCRUTINEERING.md +++ b/ehw/gs-scrutineering.md @@ -43,7 +43,7 @@ - [x] PLEASE increase the hitbox for dragging the logs panel up and down - [ ] change the description under the title - [x] none of the keyboard shortcuts work -- [ ] add a note that `Quit Server` doesn't kill an active connection +- [x] add a note that `Quit Server` doesn't kill an active connection - [ ] add a button and a text field to `Save logs to file` (backend already implemented) ### Run tab @@ -57,8 +57,8 @@ ### Location tab - [x] rename tab to `Location` instead of `Location & IMU` -- [ ] fix the graph (kiko is working on this) -- [ ] fix the lighting up of the SVG with the new graph +- [x] fix the graph (kiko is working on this) +- [x] fix the lighting up of the SVG with the new graph - [ ] we need buttons to actually control levitation. ask sjoerd what buttons are necessary for manual use and what should instead be automated by the FSM ### Batteries tab @@ -93,9 +93,9 @@ 5. content, one long string (`

` probably) ### Vitals -- [ ] make fsm a bit smaller. -- [ ] the location diagram should be visible here. -- [ ] we really need buttons for other important actions: +- [x] make fsm a bit smaller. +- [x] the location diagram should be visible here. +- [x] we really need buttons for other important actions: - turn off high voltage - arm brakes - continue run (`send_command StartRun`) diff --git a/ehw/sc-scrutineering.md b/ehw/sc-scrutineering.md new file mode 100644 index 000000000..d8cf769bb --- /dev/null +++ b/ehw/sc-scrutineering.md @@ -0,0 +1,39 @@ +# Sense and Control Scrutineering +- for "procedures", write a short document explaining the steps that need to be taken in order to demonstrate this item to the Scrutineering team. +- rest of the todo's are due 9th of July. +- sensecon testing to happen after lunch on 9th July + +## Necessary Procedures +1. [ ] [G] Power On procedure +2. [ ] [G] Power Off procedure +3. [ ] [A] All State Transition procedure +4. [ ] [A+Gz] All safe actuations brakes, contactors, relays, valves) +5. [ ] [A] Out Of Range values cause emergency brake +6. [ ] [A+*] System is moved by hand - is navigation handled properly and state machine correct for this manual run +7. [ ] [G?] Demonstrate end of track +8. [ ] [G+Gz] (brief) Gonzalo->Goose explains principles of braking pcb + +## Still Missing +- [x] [F] a chart/diagram/reference for LEDs interpretation +- [ ] [K+F] Safe range for monitored variables is clearly indicated. +- [ ] [A] Demonstrator’s health must be visible to an external viewer using colored visual indicators. +- [ ] [F] confirmation on validity of state-of-charge +- [ ] [F+A] Sensor failures properly handled +- [ ] [A] Proper explanation of how system indicates end of run +- [ ] [A] System left stationary for longer time in initial state - is navigation and possible drift handled properly? +- [ ] [F] Sensor hub send Datapoint of who caused an emergency brake +- [ ] [A+K] Demonstrator health should be easily visible by external viewer +- + +## Other todo's +- [ ] [K] +2 rows on cell LV voltages (similar to HV) +- [ ] [K] last procedure on list is not visible +- [ ] [K] procedure content not entirely visible +- [ ] [K] newlines in procedure content + +## Powertrain Scrutineering +- we need to test insulation for 100V, 250V and possibly 400V +- fix measuring resistance between HV+ and LV ground +- [ ] [F] "fix IMD" + + diff --git a/gs/src/lib/components/FSM.svelte b/gs/src/lib/components/FSM.svelte index bb628abd7..7f36cba00 100644 --- a/gs/src/lib/components/FSM.svelte +++ b/gs/src/lib/components/FSM.svelte @@ -42,7 +42,21 @@ let interval: NodeJS.Timeout; onMount(() => { - all_states = [boot_state, est_con_state, idle_state, run_conf_state, exit_state, hv_on_state, emerg_brake_state, levi_state, moving_st, moving_ls_cv, moving_ls_end, moving_st_end, moving_ls_st]; + all_states = [ + boot_state, + est_con_state, + run_conf_state, + idle_state, + hv_on_state, + levi_state, + moving_st, + moving_ls_st, + moving_ls_cv, + moving_st_end, + moving_ls_end, + emerg_brake_state, + exit_state + ]; interval = setInterval(() => { turn_off_all(all_states); diff --git a/gs/src/lib/components/Localiser.svelte b/gs/src/lib/components/Localiser.svelte index b996f7770..ee93aeb97 100644 --- a/gs/src/lib/components/Localiser.svelte +++ b/gs/src/lib/components/Localiser.svelte @@ -21,7 +21,6 @@ let point_start:SVGCircleElement; let point_divergence:SVGCircleElement; let point_choice_straight:SVGCircleElement; - let point_final:SVGCircleElement; let point_end_straight:SVGCircleElement; let point_choice_turn:SVGCircleElement; let point_end_turn:SVGCircleElement; @@ -60,7 +59,7 @@ onMount(() => { if (turning) { - disable(progress_straight, point_choice_straight, point_final, point_end_straight) + disable(progress_straight, point_choice_straight, point_end_straight) progress_container.insertBefore(path_straight, path_turn); pathLength = setupProgressPathing(progress_turn); } else { @@ -89,7 +88,6 @@ progress_straight.style.strokeDashoffset = offset.toString(); point_divergence.style.fill = normalized_loc > 38 ? color_active : color_off; point_choice_straight.style.fill = normalized_loc > 62 ? color_active : color_off; - point_final.style.fill = normalized_loc > 74 ? color_active : color_off; point_end_straight.style.fill = normalized_loc >= 100 ? color_active : color_off; emergYPosition = progress_straight.getPointAtLength(emergPosition).y; } @@ -117,7 +115,6 @@ - @@ -125,20 +122,57 @@ - Straight Start + Forward A + Backward A Lane-switch Straight - Straight Backwards - Straight End Track + Forward B + Backward B Lane-switch curve - Lane-switch End Track + Backward C + + Forward C - Location: {loc} - Speed: {$velocity} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gs/src/lib/components/SpeedsInput.svelte b/gs/src/lib/components/SpeedsInput.svelte index 104e6b97f..b51fd3075 100644 --- a/gs/src/lib/components/SpeedsInput.svelte +++ b/gs/src/lib/components/SpeedsInput.svelte @@ -27,11 +27,11 @@ const speedForm = { ForwardA: 0, - BackwardsA: 0, + BackwardA: 0, ForwardB: 0, - BackwardsB: 0, + BackwardB: 0, ForwardC: 0, - BackwardsC: 0, + BackwardC: 0, LaneSwitchStraight: 0, LaneSwitchCurved: 0, }; @@ -104,44 +104,71 @@ - - - Straight Start + Forward A + Backward A + Lane-switch Straight + Forward B + Backward B + Lane-switch curve - - - Lane-switch Straight + Backward C - - - Straight Backwards - - - - Straight End Track - - - - Lane-switch curve - - - - Lane-switch End Track + Forward C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

diff --git a/gs/src/lib/components/generic/Chart.svelte b/gs/src/lib/components/generic/Chart.svelte index 31c97e284..9c739763b 100644 --- a/gs/src/lib/components/generic/Chart.svelte +++ b/gs/src/lib/components/generic/Chart.svelte @@ -5,37 +5,40 @@ import {chartStore} from "$lib/stores/state"; export let title: string; - export let refreshRate: number = 1000; - export let span: number = 5*60*1000; export let background: string = "bg-surface-800"; export let height: number = 200; - export let chart: PlotBuffer = $chartStore.get(title) || new PlotBuffer(refreshRate, span, [0, 100], false); + export let chart: PlotBuffer|undefined = $chartStore.get(title); let width: number; let resize = (width:number) => { - chart.setSize(width-15, height); + chart?.setSize(plotContainer, width-15, height); } $: resize(width); let plotContainer: HTMLDivElement; onMount(async () => { - chart.draw(plotContainer); + chart?.draw(plotContainer); resize(width) }) onDestroy(() => { - chart!.destroy(); + chart?.destroy(plotContainer); }); -
-
-

{title}

+{#if chart} +
+
+

{title}

+
+
+
+
-
-
-
-
+{:else} +

CHART {title} NOT FOUND

+{/if} + diff --git a/gs/src/lib/panels/VitalsPanel.svelte b/gs/src/lib/panels/VitalsPanel.svelte index e04ca65cc..080dfaf14 100644 --- a/gs/src/lib/panels/VitalsPanel.svelte +++ b/gs/src/lib/panels/VitalsPanel.svelte @@ -122,7 +122,7 @@
- + {#if width > 550} {:else} @@ -153,7 +153,15 @@ Total: {$totalHVV} V
+
+ + + +
+ + + @@ -165,16 +173,22 @@
- - - - - - - - - + + + + + + + + + + + + + + + + {/if} diff --git a/gs/src/lib/panels/tabs/ProceduresTab.svelte b/gs/src/lib/panels/tabs/ProceduresTab.svelte index 0cccd5d18..138d13d0c 100644 --- a/gs/src/lib/panels/tabs/ProceduresTab.svelte +++ b/gs/src/lib/panels/tabs/ProceduresTab.svelte @@ -9,26 +9,26 @@
{#each $procedures as procedure, i} - -

{procedure.name}

+ +

{procedure?.name || 'NONE LOADED'}

{/each}
-

{$procedures[currentTile].title}

+

{$procedures[currentTile]?.title || 'NONE LOADED'}

Needed personnel:
    - {#each $procedures[currentTile].people as person} + {#each $procedures[currentTile]?.people || [] as person}
  • {person}
  • {/each}
Needed equipment:
    - {#each $procedures[currentTile].equipment as equipment} + {#each $procedures[currentTile]?.equipment || [] as equipment}
  • {equipment}
  • {/each}
-

{$procedures[currentTile].content}

+
{@html $procedures[currentTile]?.content || 'NONE LOADED'}
diff --git a/gs/src/lib/panels/tabs/RunInitTab.svelte b/gs/src/lib/panels/tabs/RunInitTab.svelte index 4ecddfd45..e3278f896 100644 --- a/gs/src/lib/panels/tabs/RunInitTab.svelte +++ b/gs/src/lib/panels/tabs/RunInitTab.svelte @@ -20,6 +20,13 @@ const state = storeManager.getStore("FSMState"); + // const mainpcb_connected = storeManager.getStore(""); + // const propulsion_connected = storeManager.getStore("PropulsionVRefInt"); + // const levitation_connected = storeManager.getStore(""); + // const mainpcb_connected = storeManager.getStore(""); + // const mainpcb_connected = storeManager.getStore(""); + + let tableArr2:any[][]; $: tableArr2 = [ ["Acceleration X", $accelX], @@ -51,30 +58,30 @@ - -

Helios III

- -

Propulsion:

+

Main PCB

+ +

Propulsion

+ +

Levitation

+ +

Sensor Hub

-

Levitation:

- -

PTC:

- -

Localization:

+

Batteries

-

Braking PCB:

- +

Braking PCB

+
- + diff --git a/gs/src/lib/stores/state.ts b/gs/src/lib/stores/state.ts index ab115e261..a046311a5 100644 --- a/gs/src/lib/stores/state.ts +++ b/gs/src/lib/stores/state.ts @@ -4,7 +4,6 @@ import {PlotBuffer} from "$lib"; export const detailTabSet: Writable = writable(1); export const inputSpeed: Writable = writable(50); -export const inputPosit: Writable = writable(-1); export const inputEmerg: Writable = writable(-1); export const inputTurn: Writable = writable(RunMode.ShortRun); diff --git a/gs/src/lib/util/PlotBuffer.ts b/gs/src/lib/util/PlotBuffer.ts index bab6fc477..46509e725 100644 --- a/gs/src/lib/util/PlotBuffer.ts +++ b/gs/src/lib/util/PlotBuffer.ts @@ -6,7 +6,7 @@ import uPlot from 'uplot'; * It also takes care of buffering the data and updating the graph. */ export class PlotBuffer { - private _plot: uPlot | undefined; + private _plots: Map = new Map(); private _intervalId: number | undefined; private readonly _data: uPlot.AlignedData; private readonly _opts: uPlot.Options; @@ -67,7 +67,7 @@ export class PlotBuffer { const index: number = this._data.length; this._data.push(new Int32Array(this._data[0].length)); this._opts.series.push(series); - this._plot?.addSeries(series); + this._plots?.forEach(plot => plot.addSeries(series)); this.buffer[index] = []; } @@ -97,16 +97,6 @@ export class PlotBuffer { */ public addEntry(seriesIndex:number, value:number) { this.buffer[seriesIndex].push(value); - // let datum = this._data[seriesIndex]; - // - // for (let i = 0; i < datum.length-1; i++) { - // datum[i] = datum[i+1]; - // } - // - // datum[datum.length-1] = value; - // - // // Redraw the chart with the updated data - // console.log("") } /** @@ -117,16 +107,18 @@ export class PlotBuffer { * Documentation is severely lacking, but the examples should be helpful. */ public draw(plotContainer: HTMLDivElement) { - this._plot = new uPlot(this._opts, this._data, plotContainer); + this._plots.set(plotContainer, new uPlot(this._opts, this._data, plotContainer)); if (this.updateInterval === 0) { this.redraw(); return; } - this._intervalId = window.setInterval(() => { - this.updateData(); - }, this.updateInterval); + if (!this._intervalId) { + this._intervalId = window.setInterval(() => { + this.updateData(); + }, this.updateInterval); + } } private updateData() { @@ -135,15 +127,12 @@ export class PlotBuffer { this.buffer[i].reduce((a, b) => a + b, 0) / this.buffer[i].length : this.lastEntryOf(i); - // Update the _data array let datum = this._data[i]; for (let j = 0; j < datum.length - 1; j++) { datum[j] = datum[j + 1]; } datum[datum.length - 1] = value; - - this.buffer[i] = []; } @@ -159,23 +148,22 @@ export class PlotBuffer { * This method should be called when the data in the graph has to be drawn. */ public redraw() { - if (this._plot) { - this._plot.batch(() => { - this._plot!.setData(this._data, true); - this._plot!.redraw(false, false); - }); - } + this._plots.forEach(plot => + plot.batch(() => { + plot.setData(this._data, true); + plot.redraw(false, false); + }) + ); } /** * Set the size of the graph. + * @param plotContainer The HTML element in which the graph is drawn. * @param width width in pixels * @param height height in pixels */ - public setSize(width:number, height:number) { - if (this._plot) { - this._plot.setSize({width: width, height: height}); - } + public setSize(plotContainer:HTMLDivElement, width:number, height:number) { + this._plots.get(plotContainer)?.setSize({width, height}); } /** @@ -183,10 +171,17 @@ export class PlotBuffer { * This method should be called when the graph is no longer needed. * It will stop the update interval and destroy the uPlot object. */ - public destroy() { - this._plot?.destroy(); - if (this._intervalId !== undefined) { + public destroy(plotContainer:HTMLDivElement) { + const plot = this._plots.get(plotContainer); + + if (plot) { + plot.destroy(); + this._plots.delete(plotContainer); + } + + if (this._plots.size === 0 && this._intervalId !== undefined) { window.clearInterval(this._intervalId); + this._intervalId = undefined; } } } diff --git a/gs/src/routes/+layout.svelte b/gs/src/routes/+layout.svelte index af18c1868..47abf41bf 100644 --- a/gs/src/routes/+layout.svelte +++ b/gs/src/routes/+layout.svelte @@ -56,6 +56,9 @@ let voffChart = new PlotBuffer(500, 60000, [8, 25], false) $chartStore.set('Offset Vertical', voffChart); + let accelChart = new PlotBuffer(500, 60000, [0, 25], false) + $chartStore.set('Acceleration', accelChart); + let rolPitchChart = new PlotBuffer(500, 60000, [-0.8, 0.8], true, "roll") rolPitchChart.addSeries(StrokePresets.theoretical("pitch")) $chartStore.set("Roll Pitch", rolPitchChart) @@ -64,11 +67,11 @@ hoffChart.addSeries(StrokePresets.theoretical("cd")) $chartStore.set('Offset Horizontal', hoffChart); - let velChart = new PlotBuffer(500, 5*60*1000, [0, 100], false) + let velChart = new PlotBuffer(500, 60000, [0, 100], false) $chartStore.set('Velocity', velChart); - let leviChart = new PlotBuffer(500, 60000, [0, 13000], false); - $chartStore.set('Localisation', leviChart); + let localisationChart = new PlotBuffer(500, 60000, [0, 13000], false); + $chartStore.set('Localisation', localisationChart); let trr = new PlotBuffer(500, 60000, [0, 50], false) trr.addSeries(StrokePresets.theoretical()) diff --git a/gs/station/Cargo.toml b/gs/station/Cargo.toml index f08ebf199..449263054 100644 --- a/gs/station/Cargo.toml +++ b/gs/station/Cargo.toml @@ -13,6 +13,7 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] +anyhow = "1.0.86" tauri-build = { version = "1.5.1", features = [] } serde = { version = "1.0.197", features = ["derive"] } regex = "1.10.3" diff --git a/gs/station/build.rs b/gs/station/build.rs index a65c4a33f..eda6bc425 100644 --- a/gs/station/build.rs +++ b/gs/station/build.rs @@ -5,7 +5,7 @@ use std::env; use std::fs; use std::path::Path; use std::path::PathBuf; - +use anyhow::Result; use goose_utils::check_ids; use goose_utils::commands::generate_commands; use goose_utils::datatypes::generate_datatypes; @@ -51,34 +51,36 @@ pub const DATATYPES_PATH: &str = "../../config/datatypes.toml"; pub const COMMANDS_PATH: &str = "../../config/commands.toml"; pub const EVENTS_PATH: &str = "../../config/events.toml"; -fn main() { +fn main() -> Result<()> { tauri_build::build(); - let out_dir = env::var("OUT_DIR").unwrap(); + let out_dir = env::var("OUT_DIR")?; let dest_path = Path::new(&out_dir).join("config.rs"); - let gs_file = fs::read_to_string(CONFIG_PATH).unwrap(); + let gs_file = fs::read_to_string(CONFIG_PATH)?; let _ = check_ids(DATATYPES_PATH, COMMANDS_PATH, EVENTS_PATH); - let config: Config = toml::from_str(&gs_file).unwrap(); + let config: Config = toml::from_str(&gs_file)?; let mut content = String::from("//@generated\n"); content.push_str(&configure_gs(&config)); - content.push_str(&configure_gs_ip(config.gs.ip, config.gs.port, config.gs.force)); - content.push_str(&generate_datatypes(DATATYPES_PATH, true)); - content.push_str(&generate_commands(COMMANDS_PATH, false)); - content.push_str(&generate_events(EVENTS_PATH, false)); + content.push_str(&configure_gs_ip(config.gs.ip, config.gs.port, config.gs.force)?); + content.push_str(&generate_datatypes(DATATYPES_PATH, true)?); + content.push_str(&generate_commands(COMMANDS_PATH, false)?); + content.push_str(&generate_events(EVENTS_PATH, false)?); content.push_str(&configure_channels(&config)); - content.push_str(&goose_utils::info::generate_info(CONFIG_PATH, true)); + content.push_str(&goose_utils::info::generate_info(CONFIG_PATH, true)?); fs::write(dest_path.clone(), content).unwrap_or_else(|_| { - panic!("Couldn't write to {}! Build failed.", dest_path.to_str().unwrap()) + panic!("Couldn't write to {}! Build failed.", dest_path.to_str().unwrap()); }); println!("cargo:rerun-if-changed={}", CONFIG_PATH); println!("cargo:rerun-if-changed={}", COMMANDS_PATH); println!("cargo:rerun-if-changed={}", DATATYPES_PATH); println!("cargo:rerun-if-changed={}", EVENTS_PATH); + + Ok(()) } fn configure_gs(config: &Config) -> String { diff --git a/gs/station/src/backend.rs b/gs/station/src/backend.rs index 8aa5072c9..874c593ca 100644 --- a/gs/station/src/backend.rs +++ b/gs/station/src/backend.rs @@ -200,7 +200,7 @@ impl Backend { ) -> anyhow::Result<(String, String, String, String)> { let mut x = (String::new(), String::new(), String::new(), String::new()); - let all = r#"[a-zA-Z0-9\n .;!,:(){}\"'_/+-=\[\]\t%]"#; + let all = r#"[a-zA-Z0-9\n .;!,:(){}\"'_/+-=\[\]\t%<>]"#; x.0 = Regex::new("\n[iI][dD][: ]*\n([a-zA-Z0-9\n ._]*)\n\n") .unwrap() @@ -236,7 +236,10 @@ impl Backend { .get(1) .unwrap() .as_str() - .to_string(); + .lines() + .map(|x| format!("

{x}


")) + .collect::>() + .join("\n"); Ok(x) } diff --git a/gs/station/src/connect/handle_incoming_data.rs b/gs/station/src/connect/handle_incoming_data.rs index 6019df326..9f6448ba3 100644 --- a/gs/station/src/connect/handle_incoming_data.rs +++ b/gs/station/src/connect/handle_incoming_data.rs @@ -5,6 +5,9 @@ use crate::api::Datapoint; use crate::api::Message; use crate::Command; use crate::Datatype; +use crate::COMMAND_HASH; +use crate::DATA_HASH; +use crate::EVENTS_HASH; pub async fn handle_incoming_data( data: Datapoint, @@ -17,6 +20,21 @@ pub async fn handle_incoming_data( Datatype::LeviInstruction => { cmd_sender.send(Command::from_id(data.value as u16, 0))?; }, + Datatype::CommandHash => { + if data.value != COMMAND_HASH { + msg_sender.send(Message::Error("Command hash mismatch".to_string()))?; + } + }, + Datatype::DataHash => { + if data.value != DATA_HASH { + msg_sender.send(Message::Error("Data hash mismatch".to_string()))?; + } + }, + Datatype::EventsHash => { + if data.value != EVENTS_HASH { + msg_sender.send(Message::Error("Event hash mismatch".to_string()))?; + } + }, _ => {}, } diff --git a/gs/station/src/frontend/app.rs b/gs/station/src/frontend/app.rs index 455f6bf90..b41093587 100644 --- a/gs/station/src/frontend/app.rs +++ b/gs/station/src/frontend/app.rs @@ -1,6 +1,9 @@ use std::sync::Mutex; -use tauri::{GlobalShortcutManager, Manager, WindowEvent}; +use tauri::GlobalShortcutManager; +use tauri::Manager; +use tauri::WindowEvent; + use crate::api::Message; use crate::backend::Backend; use crate::frontend::commands::*; @@ -8,9 +11,9 @@ use crate::frontend::BackendState; use crate::frontend::BACKEND; use crate::ERROR_CHANNEL; use crate::INFO_CHANNEL; +use crate::SHORTCUT_CHANNEL; use crate::STATUS_CHANNEL; use crate::WARNING_CHANNEL; -use crate::SHORTCUT_CHANNEL; pub fn tauri_main(backend: Backend) { println!("Starting tauri application"); @@ -43,39 +46,48 @@ pub fn tauri_main(backend: Backend) { let s = app_handle.clone(); let shortcuts = app_handle.global_shortcut_manager(); - window.on_window_event(move |event| { let mut sh = shortcuts.clone(); match event { WindowEvent::Focused(true) => { // Register shortcuts when window is focused let ss = s.clone(); - sh.register("Esc", move || { + sh.register("Space", move || { send_command("EmergencyBrake".into(), 0); ss.emit_all(SHORTCUT_CHANNEL, "emergency_brake").unwrap(); - }).expect("Could not register shortcut"); + }) + .expect("Could not register shortcut"); + let ss = s.clone(); - (0..10).into_iter().for_each(|i| { + sh.register("Esc", move || { + send_command("EmergencyBrake".into(), 0); + ss.emit_all(SHORTCUT_CHANNEL, "emergency_brake").unwrap(); + }) + .expect("Could not register shortcut"); + + (0..10).for_each(|i| { let sss = s.clone(); sh.register(&format!("{i}"), move || { sss.emit_all(SHORTCUT_CHANNEL, &format!("tab_{i}")).unwrap(); - }).expect(&format!("Could not register shortcut tab_{i}")); + }) + .unwrap_or_else(|_| panic!("Could not register shortcut tab_{i}")); }); - } + }, WindowEvent::Focused(false) => { // Unregister shortcuts when window loses focus sh.unregister("Esc").expect("Could not unregister shortcut"); + sh.unregister("Space").expect("Could not unregister shortcut"); let mut shh = sh.clone(); - (0..10).into_iter().for_each(|i| { - shh.unregister(&format!("{i}")).expect(&format!("Could not unregister shortcut tab_{i}")); + (0..10).for_each(|i| { + shh.unregister(&format!("{i}")).unwrap_or_else(|_| { + panic!("Could not unregister shortcut tab_{i}") + }); }); - } - _ => {} + }, + _ => {}, } }); - - // -- tokio::spawn(async move { diff --git a/gs/station/src/frontend/commands.rs b/gs/station/src/frontend/commands.rs index 60850d2d0..ba48ddb99 100644 --- a/gs/station/src/frontend/commands.rs +++ b/gs/station/src/frontend/commands.rs @@ -109,12 +109,27 @@ pub fn quit_server() { } } +#[macro_export] +#[allow(unused)] +#[tauri::command] +pub fn save_to_file(path: &str) -> bool { + if let Some(backend_mutex) = unsafe { BACKEND.as_ref() } { + let log = &backend_mutex.lock().unwrap().log; + if let Ok(x) = PathBuf::from_str(path) { + Backend::save_to_path(log, x).is_ok() + } else { + false + } + } else { + false + } +} + #[macro_export] #[allow(unused)] #[tauri::command] pub fn procedures() -> Vec<[String; 6]> { - let res = - Backend::load_procedures(PathBuf::from_str("../../config/procedures/").unwrap()); + let res = Backend::load_procedures(PathBuf::from("../../config/procedures/")); 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())); @@ -124,25 +139,16 @@ pub fn procedures() -> Vec<[String; 6]> { .get_mut() .unwrap() .log_msg(&Message::Error("Failed to load procedures".into())); - Vec::new() + vec![[ + "Failed".into(), + "Failed to parse some procedures".into(), + "".into(), + "".into(), + "".into(), + format!("{:?}", res), + ]] } } else { res.unwrap() } } - -#[macro_export] -#[allow(unused)] -#[tauri::command] -pub fn save_to_file(path: &str) -> bool { - if let Some(backend_mutex) = unsafe { BACKEND.as_ref() } { - let log = &backend_mutex.lock().unwrap().log; - if let Ok(x) = PathBuf::from_str(path) { - Backend::save_to_path(log, x).is_ok() - } else { - false - } - } else { - false - } -} diff --git a/gs/station/src/frontend/mod.rs b/gs/station/src/frontend/mod.rs index 226691eb3..736dfc616 100644 --- a/gs/station/src/frontend/mod.rs +++ b/gs/station/src/frontend/mod.rs @@ -1,5 +1,5 @@ -pub mod commands; pub mod app; +pub mod commands; use std::fmt::Debug; use std::sync::Mutex; diff --git a/gs/station/src/tests/backend.rs b/gs/station/src/tests/backend.rs index f6b5b885e..dd3b6e4a9 100644 --- a/gs/station/src/tests/backend.rs +++ b/gs/station/src/tests/backend.rs @@ -14,5 +14,5 @@ fn import_procedures() { assert_eq!(example[2], "DH08.PROC.SC.x"); assert_eq!(example[3], "Kiko\nKiril\n"); assert_eq!(example[4], "Andreas\n"); - 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.\n"); + assert_eq!(example[5], "

1. I refuse to elaborate.


\n

2. :)


\n

3. if in trouble just call me


\n

just text is also fine in a procedure.


"); } diff --git a/gs/station/src/tui/app.rs b/gs/station/src/tui/app.rs index 02d181a52..cb9f72db0 100644 --- a/gs/station/src/tui/app.rs +++ b/gs/station/src/tui/app.rs @@ -107,6 +107,16 @@ impl App { }, _ => {}, }, + Datatype::RoutePlan => { + self.logs.push(( + Message::Info(format!( + "Route: \n{:?}\ncurrently at {}", + datapoint.value.to_be_bytes(), + datapoint.timestamp, + )), + timestamp(), + )); + }, Datatype::FSMState => { self.cur_state = state_to_string(datapoint.value).to_string(); self.logs.push(( diff --git a/gs/station/src/tui/interactions.rs b/gs/station/src/tui/interactions.rs index 860cd1d49..e9001b303 100644 --- a/gs/station/src/tui/interactions.rs +++ b/gs/station/src/tui/interactions.rs @@ -98,8 +98,8 @@ impl App { self.cmds[self.selected_row].value /= 10; }, KeyCode::Char('p') => { - self.backend.send_command(Command::SetRoute(8356402650779983807)); - self.backend.send_command(Command::SetSpeeds(215199336366080)); + self.backend.send_command(Command::SetRoute(1822648539875311616)); + self.backend.send_command(Command::SetSpeeds(14106055789030410752)); }, KeyCode::Char('o') => { self.backend.send_command(Command::SetRoute(8328165916070586159)); diff --git a/util/Cargo.lock b/util/Cargo.lock index 26982e910..99a930ea4 100644 --- a/util/Cargo.lock +++ b/util/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "byteorder" version = "1.5.0" @@ -24,6 +30,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" name = "goose_utils" version = "0.1.0" dependencies = [ + "anyhow", "local-ip-address", "serde", "toml", diff --git a/util/Cargo.toml b/util/Cargo.toml index 0539af1ef..e3cedd743 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.86" local-ip-address = "=0.6.1" serde = { version = "1.0.201", features = ["derive"] } toml = "0.8.12" + diff --git a/util/rustfmt.toml b/util/rustfmt.toml index ef7df81a5..d05c816b4 100644 --- a/util/rustfmt.toml +++ b/util/rustfmt.toml @@ -18,5 +18,5 @@ reorder_impl_items = true newline_style = "Unix" match_block_trailing_comma = true -format_macro_bodies = false -format_strings = false \ No newline at end of file +# format_macro_bodies = false +format_strings = false diff --git a/util/src/commands.rs b/util/src/commands.rs index 4dbc66823..c9e8cd375 100644 --- a/util/src/commands.rs +++ b/util/src/commands.rs @@ -1,35 +1,42 @@ #![allow(non_snake_case)] use std::fs; - +use std::hash::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; +use anyhow::Result; use serde::Deserialize; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Hash)] pub struct Config { pub(crate) Command: Vec, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Hash)] pub struct Command { pub name: String, pub id: u16, } -pub fn get_command_ids(path: &str) -> Vec { - let config_str = fs::read_to_string(path).unwrap(); - let config: Config = toml::from_str(&config_str).unwrap(); +pub fn get_command_ids(path: &str) -> Result> { + let config_str = fs::read_to_string(path)?; + let config: Config = toml::from_str(&config_str)?; let mut ids = Vec::new(); for command in config.Command { ids.push(command.id); } - ids + Ok(ids) } -pub fn generate_commands(path: &str, drv: bool) -> String { - let config_str = fs::read_to_string(path).unwrap(); - let config: Config = toml::from_str(&config_str).unwrap(); +pub fn generate_commands(path: &str, drv: bool) -> Result { + let config_str = fs::read_to_string(path)?; + let config: Config = toml::from_str(&config_str)?; // println!("{:?}", config); + let mut hasher = DefaultHasher::new(); + config.hash(&mut hasher); + let hash = hasher.finish(); + let mut enum_definitions = String::new(); let mut match_to_id = String::new(); let mut match_from_id = String::new(); @@ -57,7 +64,7 @@ pub fn generate_commands(path: &str, drv: bool) -> String { to_idx.push_str(&format!(" Command::{}(_) => {i},\n", &command.name)); } - format!( + Ok(format!( "\n #[allow(non_camel_case_types)] #[allow(non_snake_case)] @@ -117,4 +124,5 @@ pub const COMMANDS_LIST: [&str; {}] = [{}]; to_idx, ids.len(), ids.join(", "), name_list.len(), name_list.join(", ") ) + + &format!("\npub const COMMAND_HASH: u64 = {hash};")) } diff --git a/util/src/datatypes.rs b/util/src/datatypes.rs index 2e9126815..5afefc94c 100644 --- a/util/src/datatypes.rs +++ b/util/src/datatypes.rs @@ -1,33 +1,77 @@ #![allow(non_snake_case, non_camel_case_types)] - +use std::fmt::Display; use std::fs; - +use std::hash::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; use serde::Deserialize; +use std::fmt::Formatter; +use anyhow::Result; -#[derive(Deserialize)] +const NONE: fn() -> Limit = || Limit::No; + +#[derive(Deserialize, Hash)] pub struct Config { pub(crate) Datatype: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Hash)] pub struct Datatype { pub name: String, pub id: u16, - pub lower: Option, - pub upper: Option, + #[serde(default = "NONE")] + pub lower: Limit, + #[serde(default = "NONE")] + pub upper: Limit, +} + +#[derive(Hash, Clone, Copy)] +pub enum Limit { + No, + Single(u64), + Multiple(Severities) +} + +impl Display for Limit { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match *self { + Limit::No => write!(f, "Limit::No"), + Limit::Single(x) => write!(f, "Limit::Single({x})"), + Limit::Multiple(y) => { + write!(f, + "Limit::Multiple(Severities {{ warn: {}, err: {}, brake: {} }})", + y.warn.map(|x| format!("Some({x})")).unwrap_or("None".into()), + y.err.map(|x| format!("Some({x})")).unwrap_or("None".into()), + y.brake.map(|x| format!("Some({x})")).unwrap_or("None".into()), + ) + } + } + } +} + + +#[derive(Deserialize, Hash, Clone, Copy)] +pub struct Severities { + pub warn: Option, + pub err: Option, + pub brake: Option, } -pub fn get_data_config(path: &str) -> Config { - let config_str = fs::read_to_string(path).unwrap(); - toml::from_str(&config_str).unwrap() +pub fn get_data_config(path: &str) -> Result { + let config_str = fs::read_to_string(path)?; + Ok(toml::from_str(&config_str)?) } -pub fn get_data_ids(path: &str) -> Vec { - get_data_config(path).Datatype.iter().map(|x| x.id).collect() +pub fn get_data_ids(path: &str) -> Result> { + Ok(get_data_config(path)?.Datatype.iter().map(|x| x.id).collect()) } -pub fn generate_datatypes(path: &str, drv: bool) -> String { - let config: Config = get_data_config(path); +pub fn generate_datatypes(path: &str, drv: bool) -> Result { + let config: Config = get_data_config(path)?; + + let mut hasher = DefaultHasher::new(); + config.hash(&mut hasher); + let hash = hasher.finish(); let mut enum_definitions = String::new(); let mut match_to_id = String::new(); @@ -43,39 +87,64 @@ pub fn generate_datatypes(path: &str, drv: bool) -> String { match_from_id.push_str(&format!(" {} => Datatype::{},\n", dtype.id, dtype.name)); from_str.push_str(&format!(" {:?} => Datatype::{},\n", dtype.name, dtype.name)); bounds.push_str(&format!( - " Datatype::{} => {} {} {},\n", + " 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() - } - }), + dtype.upper, + dtype.lower, )); - 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 - ); - } - } +// 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!( + Ok(format!( "\n +pub enum Limit {{ + No, + Single(u64), + Multiple(Severities) +}} + +pub struct Severities {{ + pub warn: Option, + pub err: Option, + pub brake: Option, +}} + +pub enum ValueCheckResult {{ + Fine, + Warn, + Error, + BrakeNow, +}} + #[allow(non_camel_case_types)] #[allow(non_snake_case)] {} @@ -106,11 +175,55 @@ impl Datatype {{ }} - pub fn check_bounds(&self, other: u64) -> bool {{ + pub fn bounds(&self) -> (Limit, Limit) {{ match *self {{ {} }} }} + + pub fn check_bounds(&self, value: u64) -> ValueCheckResult {{ + let (up, low) = self.bounds(); + let ok_up = match up {{ + Limit::No => 0, + Limit::Single(x) => if value > x {{ 1 }} else {{ 0 }}, + Limit::Multiple(Severities {{ + warn: a, + err: b, + brake: c, + }}) => {{ + if let Some(cc) = c {{ + if value > cc {{ 100 }} else {{ 0 }} + }} else if let Some(bb) = b {{ + if value > bb {{ 10 }} else {{ 0 }} + }} else if let Some(aa) = a {{ + if value > aa {{ 1 }} else {{ 0 }} + }} else {{ 0 }} + }} + }}; + let ok_low = match low {{ + Limit::No => 0, + Limit::Single(x) => if value < x {{ 1 }} else {{ 0 }}, + Limit::Multiple(Severities {{ + warn: a, + err: b, + brake: c, + }}) => {{ + if let Some(cc) = c {{ + if value < cc {{ 100 }} else {{ 0 }} + }} else if let Some(bb) = b {{ + if value < bb {{ 10 }} else {{ 0 }} + }} else if let Some(aa) = a {{ + if value < aa {{ 1 }} else {{ 0 }} + }} else {{ 0 }} + }} + }}; + match ok_up + ok_low {{ + 0 => ValueCheckResult::Fine, + 1..=9 => ValueCheckResult::Warn, + 10..=99 => ValueCheckResult::Error, + _ => ValueCheckResult::BrakeNow, + }} + }} }} ", if drv { @@ -127,5 +240,5 @@ impl Datatype {{ "pub static DATA_IDS : [u16;{}] = [{}];\n", data_ids.len(), data_ids.iter().map(|x| x.to_string()).collect::>().join(", ") - ) + ) + &format!("\npub const DATA_HASH: u64 = {hash};")) } diff --git a/util/src/events.rs b/util/src/events.rs index d56808e4c..b253baa7b 100644 --- a/util/src/events.rs +++ b/util/src/events.rs @@ -1,15 +1,18 @@ #![allow(non_snake_case)] - +use anyhow::Result; use std::fs; +use std::hash::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Hash)] pub struct Config { Event: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Hash)] pub struct Event { name: String, id: u16, @@ -17,17 +20,21 @@ pub struct Event { params: Option, } -pub fn get_events_config(path: &str) -> Config { - let config_str = fs::read_to_string(path).unwrap(); - toml::from_str(&config_str).unwrap() +pub fn get_events_config(path: &str) -> Result { + let config_str = fs::read_to_string(path)?; + Ok(toml::from_str(&config_str)?) } -pub fn get_event_ids(path: &str) -> Vec { - get_events_config(path).Event.iter().map(|x| x.id).collect() +pub fn get_event_ids(path: &str) -> Result> { + Ok(get_events_config(path)?.Event.iter().map(|x| x.id).collect()) } -pub fn generate_events(path: &str, drv: bool) -> String { - let config: Config = get_events_config(path); +pub fn generate_events(path: &str, drv: bool) -> Result { + let config: Config = get_events_config(path)?; + + let mut hasher = DefaultHasher::new(); + config.hash(&mut hasher); + let hash = hasher.finish(); let mut enum_definitions = String::new(); let mut match_to_id = String::new(); @@ -74,7 +81,7 @@ pub fn generate_events(path: &str, drv: bool) -> String { to_str.push_str(&format!("\"{}\",", event.name)); } - format!( + Ok(format!( "\n\npub const EVENTS_DISPLAY: [&str; {}] = [{}\"Unknown\"];\n", event_count + 1, to_str @@ -131,5 +138,5 @@ impl Event {{ "\n\npub static EVENT_IDS : [u16;{}] = [{}];\n", event_ids.len(), event_ids.iter().map(|x| x.to_string()).collect::>().join(", ") - ) + ) + &format!("\npub const EVENTS_HASH: u64 = {hash};")) } diff --git a/util/src/info.rs b/util/src/info.rs index 32487cbaf..708ca1fde 100644 --- a/util/src/info.rs +++ b/util/src/info.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use serde::Deserialize; +use serde::Deserialize;use anyhow::Result; #[derive(Debug, Deserialize)] pub struct Config { @@ -13,9 +13,9 @@ pub struct Info { pub colour: Option, } -pub fn generate_info(path: &str, drv: bool) -> String { - let config_str = std::fs::read_to_string(path).unwrap(); - let config: Config = toml::from_str(&config_str).unwrap(); +pub fn generate_info(path: &str, drv: bool) -> Result { + let config_str = std::fs::read_to_string(path)?; + let config: Config = toml::from_str(&config_str)?; let mut enum_definitions = String::new(); let mut match_to_id = String::new(); @@ -32,7 +32,7 @@ pub fn generate_info(path: &str, drv: bool) -> String { } } - format!( + Ok(format!( " pub const INFO_COLOURS: [&str; {}] = [{}\"yellow\"]; @@ -71,5 +71,5 @@ impl Info {{ enum_definitions, match_to_id, match_from_id - ) + )) } diff --git a/util/src/ip.rs b/util/src/ip.rs index 1c5d841db..163e146bf 100644 --- a/util/src/ip.rs +++ b/util/src/ip.rs @@ -1,11 +1,11 @@ use std::net::IpAddr; - +use anyhow::Result; use local_ip_address::local_ip; -pub fn configure_gs_ip(ip: [u8; 4], port: u16, force: bool) -> String { +pub fn configure_gs_ip(ip: [u8; 4], port: u16, force: bool) -> Result { let mut ip = (ip[0], ip[1], ip[2], ip[3], port); if !force { - match local_ip().unwrap() { + match local_ip()? { IpAddr::V4(ipv4) => { ip = (ipv4.octets()[0], ipv4.octets()[1], ipv4.octets()[2], ipv4.octets()[3], port); }, @@ -13,8 +13,9 @@ pub fn configure_gs_ip(ip: [u8; 4], port: u16, force: bool) -> String { } } - format!( + Ok(format!( "\npub static mut GS_IP_ADDRESS: ([u8;4],u16) = ([{},{},{},{}],{});\n", ip.0, ip.1, ip.2, ip.3, ip.4 - ) + )) } + diff --git a/util/src/lib.rs b/util/src/lib.rs index 678c5f13d..bc9521156 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -7,12 +7,14 @@ pub mod events; pub mod info; pub mod ip; mod shared; +pub mod limits; +use anyhow::Result; -pub fn check_ids(dp: &str, cp: &str, ep: &str) -> Vec { +pub fn check_ids(dp: &str, cp: &str, ep: &str) -> Result> { let mut ids = vec![]; - ids.extend(datatypes::get_data_ids(dp)); - ids.extend(commands::get_command_ids(cp)); - ids.extend(events::get_event_ids(ep)); + ids.extend(datatypes::get_data_ids(dp)?); + ids.extend(commands::get_command_ids(cp)?); + ids.extend(events::get_event_ids(ep)?); let mut seen = HashSet::new(); for id in &ids { if !seen.insert(id) { @@ -30,7 +32,7 @@ pub fn check_ids(dp: &str, cp: &str, ep: &str) -> Vec { ); } } - ids + Ok(ids) } fn nearest_id(id: u16, ids: &[u16]) -> u16 { @@ -46,3 +48,4 @@ fn nearest_id(id: u16, ids: &[u16]) -> u16 { } panic!("There are no more available ids!") } + diff --git a/util/src/limits.rs b/util/src/limits.rs new file mode 100644 index 000000000..e7692a8fe --- /dev/null +++ b/util/src/limits.rs @@ -0,0 +1,57 @@ +use serde::de::{self, Deserializer, Visitor, MapAccess}; +use serde::{Deserialize, }; +use std::fmt; +use crate::datatypes::Limit; +use crate::datatypes::Severities; + +impl<'de> Deserialize<'de> for Limit { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct LimitVisitor; + + impl<'de> Visitor<'de> for LimitVisitor { + type Value = Limit; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer, a table with severities, or the string 'no'") + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Limit::Single(value)) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(Limit::Single(value as u64)) + } + + fn visit_map(self, map: M) -> Result + where + M: MapAccess<'de>, + { + let severities = Severities::deserialize(de::value::MapAccessDeserializer::new(map))?; + Ok(Limit::Multiple(severities)) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match v { + "no" => Ok(Limit::No), + _ => Err(de::Error::unknown_variant(v, &["no"])), + } + } + } + + deserializer.deserialize_any(LimitVisitor) + } +} + diff --git a/util/src/shared/routes.rs b/util/src/shared/routes.rs index bf09ce0e4..d9b86bcf6 100644 --- a/util/src/shared/routes.rs +++ b/util/src/shared/routes.rs @@ -385,7 +385,8 @@ mod tests { positions: LocationSequence::default(), current_position: 0, speeds: LocationSpeedMap( - [ (Location::ForwardA,195), + [ + (Location::ForwardA, 195), (Location::BackwardsA, 194), (Location::ForwardB, 0), (Location::BackwardsB, 0), @@ -413,31 +414,31 @@ mod tests { positions: LocationSequence([ Location::ForwardA, Location::LaneSwitchStraight, - Location::LaneSwitchCurved, - Location::BackwardsC, - Location::BackwardsB, + Location::ForwardB, Location::StopAndWait, - Location::BackwardsA, + Location::BackwardsB, Location::LaneSwitchStraight, - Location::LaneSwitchCurved, - Location::ForwardC, - Location::BackwardsC, - Location::ForwardB, Location::BackwardsA, - Location::LaneSwitchStraight, + Location::BrakeHere, + Location::StopAndWait, + Location::ForwardA, Location::LaneSwitchCurved, - Location::BackwardsC, + Location::ForwardC, + Location::BrakeHere, + Location::BrakeHere, + Location::BrakeHere, + Location::BrakeHere, ]), current_position: 0, speeds: LocationSpeedMap( [ - (Location::ForwardA,195), + (Location::ForwardA, 195), (Location::BackwardsA, 194), - (Location::ForwardB, 0), + (Location::ForwardB, 199), (Location::BackwardsB, 0), - (Location::ForwardC, 0), + (Location::ForwardC, 199), (Location::BackwardsC, 0), - (Location::LaneSwitchStraight, 0), + (Location::LaneSwitchStraight, 10), (Location::LaneSwitchCurved, 0), (Location::StopAndWait, 0), (Location::BrakeHere, 0), @@ -447,7 +448,7 @@ mod tests { }; let s_bytes: u64 = route.speeds.clone().into(); let r_bytes: u64 = route.positions.into(); - // panic!("Speeds: {}\nPositions: {}", s_bytes, r_bytes); + panic!("Speeds: {}\nPositions: {}", s_bytes, r_bytes); assert!(s_bytes > 0); assert!(r_bytes > 0); }