diff --git a/satrs-example/client/src/main.rs b/satrs-example/client/src/main.rs index f0c2eff..9a46051 100644 --- a/satrs-example/client/src/main.rs +++ b/satrs-example/client/src/main.rs @@ -3,9 +3,8 @@ use arbitrary_int::u11; use clap::Parser as _; use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT}; use satrs_minisim::{ - SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider, - SimReply, SimRequest, acs, acs::MgmRequestLis3Mdl, acs::MgmRequestLis3MdlMgm0, - acs::MgmRequestLis3MdlMgm1, acs::SpiFault, udp::SIM_CTRL_PORT, + SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, acs::mgm, + udp::SIM_CTRL_PORT, }; use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader}; use std::{ @@ -98,12 +97,12 @@ enum FaultMode { AllOnes, } -impl From for acs::SpiFaultMode { +impl From for mgm::SpiFaultMode { fn from(mode: FaultMode) -> Self { match mode { - FaultMode::None => acs::SpiFaultMode::None, - FaultMode::AllZeros => acs::SpiFaultMode::AllZeros, - FaultMode::AllOnes => acs::SpiFaultMode::AllOnes, + FaultMode::None => mgm::SpiFaultMode::None, + FaultMode::AllZeros => mgm::SpiFaultMode::AllZeros, + FaultMode::AllOnes => mgm::SpiFaultMode::AllOnes, } } } @@ -205,7 +204,7 @@ fn handle_mgm_command( if let Some(mode) = args.fault { inject_mgm_failure( target_id, - SpiFault { + mgm::SpiFault { mode: mode.into(), cleared_by_power_cycle: args.fault_kind == FaultKind::Transient, }, @@ -500,26 +499,20 @@ fn main() -> anyhow::Result<()> { /// Confirms the simulator is actually reachable first (same ping/pong check the OBSW's own /// internal sim client does, see `SimClientUdp::attempt_connection`), since a fire-and-forget /// UDP send would otherwise silently do nothing if minisim is not running. -fn inject_mgm_failure(target_id: types::ComponentId, fault: SpiFault) -> anyhow::Result<()> { +fn inject_mgm_failure(target_id: types::ComponentId, fault: mgm::SpiFault) -> anyhow::Result<()> { let sim_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), SIM_CTRL_PORT); let sim_socket = UdpSocket::bind("127.0.0.1:0")?; sim_socket.set_read_timeout(Some(Duration::from_millis(200)))?; let mut reply_buf = [0u8; 4096]; - let ping = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping); + let ping = SimRequestWithTime::new_with_epoch_time(SimCtrlRequest::Ping); sim_socket.send_to(&serde_json::to_vec(&ping)?, sim_addr)?; match sim_socket.recv(&mut reply_buf) { Ok(len) => { let reply: SimReply = serde_json::from_slice(&reply_buf[..len])?; - if reply.component() != SimComponent::SimCtrl { + if reply != SimReply::SimCtrl(SimCtrlReply::Pong) { bail!("unexpected reply while checking minisim connectivity: {reply:?}"); } - match SimCtrlReply::from_sim_message(&reply).expect("invalid SIM reply") { - SimCtrlReply::Pong => {} - SimCtrlReply::InvalidRequest(e) => { - bail!("minisim rejected connectivity ping: {e:?}") - } - } } Err(e) if matches!( @@ -532,16 +525,15 @@ fn inject_mgm_failure(target_id: types::ComponentId, fault: SpiFault) -> anyhow: Err(e) => return Err(e.into()), } - let fault_request = MgmRequestLis3Mdl::SetSpiFault(fault); - let request = match target_id { - types::ComponentId::AcsMgm0 => { - SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0(fault_request)) - } - types::ComponentId::AcsMgm1 => { - SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm1(fault_request)) - } + let id = match target_id { + types::ComponentId::AcsMgm0 => mgm::Id::Mgm0, + types::ComponentId::AcsMgm1 => mgm::Id::Mgm1, _ => bail!("SPI fault injection is not supported for {target_id:?}"), }; + let request = SimRequestWithTime::new_with_epoch_time(SimRequest::Mgm { + id, + request: mgm::Request::SetSpiFault(fault), + }); sim_socket.send_to(&serde_json::to_vec(&request)?, sim_addr)?; log::info!("injected SPI fault {fault:?} into minisim {target_id:?}"); Ok(()) diff --git a/satrs-example/minisim/src/acs/mgm.rs b/satrs-example/minisim/src/acs/mgm.rs index 94ab3c9..095c139 100644 --- a/satrs-example/minisim/src/acs/mgm.rs +++ b/satrs-example/minisim/src/acs/mgm.rs @@ -1,13 +1,7 @@ -use std::{f32::consts::PI, sync::mpsc, time::Duration}; +use std::{f32::consts::PI, sync::mpsc}; use nexosim::model::{Context, Model}; -use satrs_minisim::{ - acs::{ - mgm::{MgmId, MgmReply, MgmReplyWrapper}, - MgmSensorValuesMicroTesla, SpiFault, - }, - SimReply, -}; +use satrs_minisim::{acs::mgm, SimReply}; use types::pcdu::SwitchStateBinary; use crate::time::current_millis; @@ -25,24 +19,21 @@ const PHASE_Z: f32 = 0.2; /// An ideal sensor would sample the magnetic field at a high fixed rate. This might not be /// possible for a general purpose OS, but self self-sampling at a relatively high rate (20-40 ms) /// might still be possible and is probably sufficient for many OBSW needs. -pub struct MagnetometerModel { - pub id: MgmId, +pub struct MgmModel { + pub id: mgm::Id, pub switch_state: SwitchStateBinary, - #[allow(dead_code)] - pub periodicity: Duration, - pub external_mag_field: Option, - pub spi_fault: SpiFault, + pub external_mag_field: Option, + pub spi_fault: mgm::SpiFault, pub reply_sender: mpsc::Sender, } -impl MagnetometerModel { - pub fn new(mgm_id: MgmId, periodicity: Duration, reply_sender: mpsc::Sender) -> Self { +impl MgmModel { + pub fn new(mgm_id: mgm::Id, reply_sender: mpsc::Sender) -> Self { Self { id: mgm_id, switch_state: SwitchStateBinary::Off, - periodicity, external_mag_field: None, - spi_fault: SpiFault::default(), + spi_fault: mgm::SpiFault::default(), reply_sender, } } @@ -50,48 +41,48 @@ impl MagnetometerModel { pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) { self.switch_state = switch_state; if switch_state == SwitchStateBinary::Off && self.spi_fault.cleared_by_power_cycle { - self.spi_fault = SpiFault::default(); + self.spi_fault = mgm::SpiFault::default(); } } /// Force (or clear) a stuck-bus SPI fault, for FDIR testing purposes. - pub async fn set_spi_fault(&mut self, fault: SpiFault) { + pub async fn set_spi_fault(&mut self, fault: mgm::SpiFault) { self.spi_fault = fault; } pub async fn send_sensor_values(&mut self, _: (), scheduler: &mut Context) { - let reply = MgmReplyWrapper { - mgm_id: self.id, - reply: MgmReply::new( + let reply = SimReply::Mgm { + id: self.id, + reply: mgm::Reply::new( self.switch_state, self.calculate_current_mgm_tuple(current_millis(scheduler.time())), self.spi_fault.mode, ), }; self.reply_sender - .send(reply.to_sim_reply()) + .send(reply) .expect("sending MGM sensor values failed"); } // Devices like magnetorquers generate a strong magnetic field which overrides the default // model for the measured magnetic field. - pub async fn apply_external_magnetic_field(&mut self, field: MgmSensorValuesMicroTesla) { + pub async fn apply_external_magnetic_field(&mut self, field: mgm::SensorValuesMicroTesla) { self.external_mag_field = Some(field); } - fn calculate_current_mgm_tuple(&self, time_ms: u64) -> MgmSensorValuesMicroTesla { + fn calculate_current_mgm_tuple(&self, time_ms: u64) -> mgm::SensorValuesMicroTesla { if SwitchStateBinary::On == self.switch_state { if let Some(ext_field) = self.external_mag_field { return ext_field; } let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0); - return MgmSensorValuesMicroTesla { + return mgm::SensorValuesMicroTesla { x: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_X).sin(), y: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Y).sin(), z: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Z).sin(), }; } - MgmSensorValuesMicroTesla { + mgm::SensorValuesMicroTesla { x: 0.0, y: 0.0, z: 0.0, @@ -99,20 +90,13 @@ impl MagnetometerModel { } } -impl Model for MagnetometerModel {} +impl Model for MgmModel {} #[cfg(test)] mod tests { use std::time::Duration; - use satrs_minisim::{ - acs::{ - mgm::{self, MgmId, MgmReply, MgmReplyWrapper}, - MgmRequestLis3Mdl, MgmRequestLis3MdlMgm0, MgmRequestLis3MdlMgm1, SpiFault, - SpiFaultMode, - }, - SimComponent, SimMessageProvider, SimRequest, - }; + use satrs_minisim::{acs::mgm, SimReply, SimRequest}; use types::pcdu::{SwitchId, SwitchStateBinary}; use crate::{ @@ -120,72 +104,55 @@ mod tests { test_helpers::SimTestbench, }; - #[test] - fn test_basic_mgm_request() { - let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::RequestSensorData, - )); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply = sim_testbench.try_receive_next_reply(); - assert!(sim_reply.is_some()); - let sim_reply = sim_reply.unwrap(); - assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl); - let wrapper = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM sensor values"); - assert_eq!(wrapper.mgm_id, MgmId::Mgm0); - assert_eq!(wrapper.reply.switch_state, SwitchStateBinary::Off); - assert_eq!(wrapper.reply.sensor_values.x, 0.0); - assert_eq!(wrapper.reply.sensor_values.y, 0.0); - assert_eq!(wrapper.reply.sensor_values.z, 0.0); + fn request_sensor_data(sim_testbench: &mut SimTestbench, id: mgm::Id) -> mgm::Reply { + let sim_reply = sim_testbench + .request_reply(SimRequest::Mgm { + id, + request: mgm::Request::RequestSensorData, + }) + .expect("no MGM reply received"); + let SimReply::Mgm { + id: reply_id, + reply, + } = sim_reply + else { + panic!("unexpected reply {sim_reply:?}"); + }; + assert_eq!(reply_id, id); + reply } fn inject_spi_fault(sim_testbench: &mut SimTestbench, cleared_by_power_cycle: bool) { - let fault_request = SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::SetSpiFault(SpiFault { - mode: SpiFaultMode::AllOnes, + sim_testbench.send_and_step(SimRequest::Mgm { + id: mgm::Id::Mgm0, + request: mgm::Request::SetSpiFault(mgm::SpiFault { + mode: mgm::SpiFaultMode::AllOnes, cleared_by_power_cycle, }), - )); - sim_testbench - .send_request(fault_request) - .expect("sending MGM fault injection request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); + }); } - fn request_mgm_reply(sim_testbench: &mut SimTestbench) -> MgmReply { - let data_request = SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::RequestSensorData, - )); - sim_testbench - .send_request(data_request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply = sim_testbench - .try_receive_next_reply() - .expect("no MGM reply received"); - MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM sensor values") - .reply - } - - fn is_stuck_bus_reply(reply: &MgmReply) -> bool { + fn is_stuck_bus_reply(reply: &mgm::Reply) -> bool { reply.raw.x == -1 && reply.raw.y == -1 && reply.raw.z == -1 } + #[test] + fn test_basic_mgm_request() { + let mut sim_testbench = SimTestbench::new(); + let reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); + assert_eq!(reply.switch_state, SwitchStateBinary::Off); + assert_eq!(reply.sensor_values.x, 0.0); + assert_eq!(reply.sensor_values.y, 0.0); + assert_eq!(reply.sensor_values.z, 0.0); + } + #[test] fn test_mgm_spi_fault_injection_all_ones() { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgm0); inject_spi_fault(&mut sim_testbench, false); - let reply = request_mgm_reply(&mut sim_testbench); + let reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); // Even though the device is switched on, the injected fault forces a stuck-bus reply. assert_eq!(reply.switch_state, SwitchStateBinary::On); assert!(is_stuck_bus_reply(&reply)); @@ -196,12 +163,14 @@ mod tests { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgm0); inject_spi_fault(&mut sim_testbench, true); - assert!(is_stuck_bus_reply(&request_mgm_reply(&mut sim_testbench))); + let reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); + assert!(is_stuck_bus_reply(&reply)); switch_device_off(&mut sim_testbench, SwitchId::Mgm0); switch_device_on(&mut sim_testbench, SwitchId::Mgm0); sim_testbench.step_until(Duration::from_millis(50)).unwrap(); - assert!(!is_stuck_bus_reply(&request_mgm_reply(&mut sim_testbench))); + let reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); + assert!(!is_stuck_bus_reply(&reply)); } #[test] @@ -212,7 +181,7 @@ mod tests { switch_device_off(&mut sim_testbench, SwitchId::Mgm0); switch_device_on(&mut sim_testbench, SwitchId::Mgm0); - let reply = request_mgm_reply(&mut sim_testbench); + let reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); assert_eq!(reply.switch_state, SwitchStateBinary::On); assert!(is_stuck_bus_reply(&reply)); } @@ -222,56 +191,21 @@ mod tests { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgm0); - let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::RequestSensorData, - )); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let mut sim_reply_res = sim_testbench.try_receive_next_reply(); - assert!(sim_reply_res.is_some()); - let mut sim_reply = sim_reply_res.unwrap(); - assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl); - let first_reply = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM sensor values") - .reply; + let first_reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); sim_testbench.step_until(Duration::from_millis(50)).unwrap(); + let second_reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); - request = SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::RequestSensorData, - )); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - sim_reply_res = sim_testbench.try_receive_next_reply(); - assert!(sim_reply_res.is_some()); - sim_reply = sim_reply_res.unwrap(); - - let second_reply = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM sensor values") - .reply; - let x_conv_back = second_reply.raw.x as f32 - * mgm::FIELD_LSB_PER_GAUSS_4_SENS - * mgm::GAUSS_TO_MICROTESLA_FACTOR as f32; - let y_conv_back = second_reply.raw.y as f32 - * mgm::FIELD_LSB_PER_GAUSS_4_SENS - * mgm::GAUSS_TO_MICROTESLA_FACTOR as f32; - let z_conv_back = second_reply.raw.z as f32 - * mgm::FIELD_LSB_PER_GAUSS_4_SENS - * mgm::GAUSS_TO_MICROTESLA_FACTOR as f32; - let diff_x = (second_reply.sensor_values.x - x_conv_back).abs(); - assert!(diff_x < 0.01, "diff x too large: {}", diff_x); - let diff_y = (second_reply.sensor_values.y - y_conv_back).abs(); - assert!(diff_y < 0.01, "diff y too large: {}", diff_y); - let diff_z = (second_reply.sensor_values.z - z_conv_back).abs(); - assert!(diff_z < 0.01, "diff z too large: {}", diff_z); - // assert_eq!(second_reply.raw_reply, SwitchStateBinary::On); + let to_microtesla = |raw: i16| { + raw as f32 * mgm::FIELD_LSB_PER_GAUSS_4_SENS * mgm::GAUSS_TO_MICROTESLA_FACTOR as f32 + }; + let values = second_reply.sensor_values; + let raw = second_reply.raw; + for (value, raw) in [(values.x, raw.x), (values.y, raw.y), (values.z, raw.z)] { + let diff = (value - to_microtesla(raw)).abs(); + assert!(diff < 0.01, "raw value conversion diff too large: {diff}"); + } // Check that the values are changing. - assert!(first_reply != second_reply); + assert_ne!(first_reply, second_reply); } #[test] @@ -279,37 +213,9 @@ mod tests { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgm1); - for request in [ - SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0( - MgmRequestLis3Mdl::RequestSensorData, - )), - SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm1( - MgmRequestLis3Mdl::RequestSensorData, - )), - ] { - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - } - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - - let sim_reply = sim_testbench - .try_receive_next_reply() - .expect("no MGM0 reply received"); - assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl); - let mgm_0_reply = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM0 sensor values"); - assert_eq!(mgm_0_reply.mgm_id, MgmId::Mgm0); - assert_eq!(mgm_0_reply.reply.switch_state, SwitchStateBinary::Off); - - let sim_reply = sim_testbench - .try_receive_next_reply() - .expect("no MGM1 reply received"); - assert_eq!(sim_reply.component(), SimComponent::Mgm1Lis3Mdl); - let mgm_1_reply = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to deserialize MGM1 sensor values"); - assert_eq!(mgm_1_reply.mgm_id, MgmId::Mgm1); - assert_eq!(mgm_1_reply.reply.switch_state, SwitchStateBinary::On); + let mgm_0_reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm0); + assert_eq!(mgm_0_reply.switch_state, SwitchStateBinary::Off); + let mgm_1_reply = request_sensor_data(&mut sim_testbench, mgm::Id::Mgm1); + assert_eq!(mgm_1_reply.switch_state, SwitchStateBinary::On); } } diff --git a/satrs-example/minisim/src/acs/mgt.rs b/satrs-example/minisim/src/acs/mgt.rs index 661fe49..fe97297 100644 --- a/satrs-example/minisim/src/acs/mgt.rs +++ b/satrs-example/minisim/src/acs/mgt.rs @@ -3,26 +3,27 @@ use nexosim::{ ports::Output, }; use satrs_minisim::{ - acs::{MgmSensorValuesMicroTesla, MgtDipole, MgtHkSet, MgtReply, MGT_GEN_MAGNETIC_FIELD}, + acs::{mgm, mgt}, SimReply, }; use std::{sync::mpsc, time::Duration}; use types::pcdu::SwitchStateBinary; -pub struct MagnetorquerModel { +/// Simple magnetorquer simulation model. +pub struct MgtModel { switch_state: SwitchStateBinary, torquing: bool, - torque_dipole: MgtDipole, - pub gen_magnetic_field: Output, + torque_dipole: mgt::Dipole, + pub gen_magnetic_field: Output, reply_sender: mpsc::Sender, } -impl MagnetorquerModel { +impl MgtModel { pub fn new(reply_sender: mpsc::Sender) -> Self { Self { switch_state: SwitchStateBinary::Off, torquing: false, - torque_dipole: MgtDipole::default(), + torque_dipole: mgt::Dipole::default(), gen_magnetic_field: Output::new(), reply_sender, } @@ -30,7 +31,7 @@ impl MagnetorquerModel { pub async fn apply_torque( &mut self, - duration_and_dipole: (Duration, MgtDipole), + duration_and_dipole: (Duration, mgt::Dipole), cx: &mut Context, ) { self.torque_dipole = duration_and_dipole.1; @@ -45,7 +46,7 @@ impl MagnetorquerModel { } pub async fn clear_torque(&mut self, _: ()) { - self.torque_dipole = MgtDipole::default(); + self.torque_dipole = mgt::Dipole::default(); self.torquing = false; self.generate_magnetic_field(()).await; } @@ -65,17 +66,17 @@ impl MagnetorquerModel { pub fn send_housekeeping_data(&mut self) { self.reply_sender - .send(SimReply::new(&MgtReply::Hk(MgtHkSet { + .send(SimReply::from(mgt::Reply::Hk(mgt::HkSet { dipole: self.torque_dipole, torquing: self.torquing, }))) .unwrap(); } - fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValuesMicroTesla { + fn calc_magnetic_field(&self, _: mgt::Dipole) -> mgm::SensorValuesMicroTesla { // Simplified model: Just returns some fixed magnetic field for now. // Later, we could make this more fancy by incorporating the commanded dipole. - MGT_GEN_MAGNETIC_FIELD + mgm::MGT_GEN_MAGNETIC_FIELD } /// A torquing magnetorquer generates a magnetic field. This function can be used to apply @@ -90,113 +91,79 @@ impl MagnetorquerModel { } } -impl Model for MagnetorquerModel {} +impl Model for MgtModel {} #[cfg(test)] mod tests { use std::time::Duration; - use satrs_minisim::{ - acs::{MgtDipole, MgtHkSet, MgtReply, MgtRequest}, - SerializableSimMsgPayload, SimRequest, - }; + use satrs_minisim::{acs::mgt, SimReply, SimRequestWithTime}; use types::pcdu::SwitchId; use crate::{eps::tests::switch_device_on, test_helpers::SimTestbench}; + fn request_hk(sim_testbench: &mut SimTestbench) -> Option { + let sim_reply = sim_testbench.request_reply(mgt::Request::RequestHk)?; + let SimReply::Mgt(mgt::Reply::Hk(hk)) = sim_reply else { + panic!("unexpected reply {sim_reply:?}"); + }; + Some(hk) + } + #[test] fn test_basic_mgt_request_is_off() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply_res = sim_testbench.try_receive_next_reply(); - assert!(sim_reply_res.is_none()); + assert!(request_hk(&mut sim_testbench).is_none()); } #[test] fn test_basic_mgt_request_is_on() { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgt); - let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); - - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply_res = sim_testbench.try_receive_next_reply(); - assert!(sim_reply_res.is_some()); - let sim_reply = sim_reply_res.unwrap(); - let mgt_reply = MgtReply::from_sim_message(&sim_reply) - .expect("failed to deserialize MGM sensor values"); - match mgt_reply { - MgtReply::Hk(hk) => { - assert_eq!(hk.dipole, MgtDipole::default()); - assert!(!hk.torquing); - } - _ => panic!("unexpected reply"), - } - } - - fn check_mgt_hk(sim_testbench: &mut SimTestbench, expected_hk_set: MgtHkSet) { - let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply_res = sim_testbench.try_receive_next_reply(); - assert!(sim_reply_res.is_some()); - let sim_reply = sim_reply_res.unwrap(); - let mgt_reply = MgtReply::from_sim_message(&sim_reply) - .expect("failed to deserialize MGM sensor values"); - match mgt_reply { - MgtReply::Hk(hk) => { - assert_eq!(hk, expected_hk_set); - } - _ => panic!("unexpected reply"), - } + assert_eq!( + request_hk(&mut sim_testbench), + Some(mgt::HkSet { + dipole: mgt::Dipole::default(), + torquing: false, + }) + ); } #[test] fn test_basic_mgt_request_is_on_and_torquing() { let mut sim_testbench = SimTestbench::new(); switch_device_on(&mut sim_testbench, SwitchId::Mgt); - let commanded_dipole = MgtDipole { + let commanded_dipole = mgt::Dipole { x: -200, y: 200, z: 1000, }; - let request = SimRequest::new_with_epoch_time(MgtRequest::ApplyTorque { + let request = SimRequestWithTime::new_with_epoch_time(mgt::Request::ApplyTorque { duration: Duration::from_millis(100), dipole: commanded_dipole, }); sim_testbench .send_request(request) - .expect("sending MGM request failed"); + .expect("sending MGT request failed"); sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step_until(Duration::from_millis(5)).unwrap(); - check_mgt_hk( - &mut sim_testbench, - MgtHkSet { + assert_eq!( + request_hk(&mut sim_testbench), + Some(mgt::HkSet { dipole: commanded_dipole, torquing: true, - }, + }) ); sim_testbench .step_until(Duration::from_millis(100)) .unwrap(); - check_mgt_hk( - &mut sim_testbench, - MgtHkSet { - dipole: MgtDipole::default(), + assert_eq!( + request_hk(&mut sim_testbench), + Some(mgt::HkSet { + dipole: mgt::Dipole::default(), torquing: false, - }, + }) ); } } diff --git a/satrs-example/minisim/src/controller.rs b/satrs-example/minisim/src/controller.rs index 15b23dd..8c6eeb5 100644 --- a/satrs-example/minisim/src/controller.rs +++ b/satrs-example/minisim/src/controller.rs @@ -1,18 +1,20 @@ -use std::{sync::mpsc, time::Duration}; +use std::{ + sync::mpsc, + time::{Duration, SystemTime}, +}; use nexosim::{ - simulation::{Address, Scheduler, Simulation}, + simulation::{Address, Mailbox, SimInit, Simulation}, time::{Clock, MonotonicTime, SystemClock}, }; use satrs_minisim::{ - acs::{MgmRequestLis3Mdl, MgmRequestLis3MdlMgm0, MgmRequestLis3MdlMgm1, MgtRequest}, + acs::{mgm, mgt}, eps::PcduRequest, - SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider, - SimReply, SimRequest, SimRequestError, + SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, }; use crate::{ - acs::{mgm::MagnetometerModel, mgt::MagnetorquerModel}, + acs::{mgm::MgmModel, mgt::MgtModel}, eps::PcduModel, }; @@ -23,55 +25,85 @@ const MGM_REQ_WIRETAPPING: bool = false; const PCDU_REQ_WIRETAPPING: bool = false; const MGT_REQ_WIRETAPPING: bool = false; -pub struct ModelAddrWrapper { - mgm_0_addr: Address, - mgm_1_addr: Address, - pcdu_addr: Address, - mgt_addr: Address, +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ThreadingModel { + Default = 0, + Single = 1, +} + +struct ModelAddresses { + mgm_0: Address, + mgm_1: Address, + pcdu: Address, + mgt: Address, } // The simulation controller processes requests and drives the simulation. -#[allow(dead_code)] pub struct SimController { - pub sys_clock: SystemClock, - pub request_receiver: mpsc::Receiver, - pub reply_sender: mpsc::Sender, + sys_clock: SystemClock, + request_receiver: mpsc::Receiver, + reply_sender: mpsc::Sender, pub simulation: Simulation, - pub scheduler: Scheduler, - pub addr_wrapper: ModelAddrWrapper, + addrs: ModelAddresses, } -impl ModelAddrWrapper { - pub fn new( - mgm_0_addr: Address, - mgm_1_addr: Address, - pcdu_addr: Address, - mgt_addr: Address, - ) -> Self { - Self { - mgm_0_addr, - mgm_1_addr, - pcdu_addr, - mgt_addr, - } - } -} impl SimController { pub fn new( - sys_clock: SystemClock, - request_receiver: mpsc::Receiver, + threading_model: ThreadingModel, + start_time: MonotonicTime, reply_sender: mpsc::Sender, - simulation: Simulation, - scheduler: Scheduler, - addr_wrapper: ModelAddrWrapper, + request_receiver: mpsc::Receiver, ) -> Self { + let mgm_0_model = MgmModel::new(mgm::Id::Mgm0, reply_sender.clone()); + let mgm_1_model = MgmModel::new(mgm::Id::Mgm1, reply_sender.clone()); + let mut pcdu_model = PcduModel::new(reply_sender.clone()); + let mut mgt_model = MgtModel::new(reply_sender.clone()); + + let mgm_0_mailbox = Mailbox::new(); + let mgm_1_mailbox = Mailbox::new(); + let pcdu_mailbox = Mailbox::new(); + let mgt_mailbox = Mailbox::new(); + let addrs = ModelAddresses { + mgm_0: mgm_0_mailbox.address(), + mgm_1: mgm_1_mailbox.address(), + pcdu: pcdu_mailbox.address(), + mgt: mgt_mailbox.address(), + }; + + pcdu_model + .mgm_0_switch + .connect(MgmModel::switch_device, &addrs.mgm_0); + pcdu_model + .mgm_1_switch + .connect(MgmModel::switch_device, &addrs.mgm_1); + pcdu_model + .mgt_switch + .connect(MgtModel::switch_device, &addrs.mgt); + mgt_model + .gen_magnetic_field + .connect(MgmModel::apply_external_magnetic_field, &addrs.mgm_0); + mgt_model + .gen_magnetic_field + .connect(MgmModel::apply_external_magnetic_field, &addrs.mgm_1); + + let sim_init = if threading_model == ThreadingModel::Single { + SimInit::with_num_threads(1) + } else { + SimInit::new() + }; + let (simulation, _scheduler) = sim_init + .add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model") + .add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model") + .add_model(pcdu_model, pcdu_mailbox, "PCDU model") + .add_model(mgt_model, mgt_mailbox, "MGT model") + .init(start_time) + .unwrap(); Self { - sys_clock, + sys_clock: SystemClock::from_system_time(start_time, SystemTime::now()), request_receiver, reply_sender, simulation, - scheduler, - addr_wrapper, + addrs, } } @@ -97,14 +129,11 @@ impl SimController { if request.timestamp < old_timestamp && WARNING_FOR_STALE_DATA { log::warn!("stale data with timestamp {:?} received", request.timestamp); } - if let Err(e) = match request.component() { - SimComponent::SimCtrl => self.handle_ctrl_request(&request), - SimComponent::Mgm0Lis3Mdl => self.handle_mgm_request(0, &request), - SimComponent::Mgm1Lis3Mdl => self.handle_mgm_request(1, &request), - SimComponent::Mgt => self.handle_mgt_request(&request), - SimComponent::Pcdu => self.handle_pcdu_request(&request), - } { - self.handle_invalid_request_with_valid_target(e, &request) + match request.request { + SimRequest::SimCtrl(request) => self.handle_ctrl_request(request), + SimRequest::Mgm { id, request } => self.handle_mgm_request(id, request), + SimRequest::Mgt(request) => self.handle_mgt_request(request), + SimRequest::Pcdu(request) => self.handle_pcdu_request(request), } } Err(e) => match e { @@ -117,8 +146,7 @@ impl SimController { } } - fn handle_ctrl_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> { - let sim_ctrl_request = SimCtrlRequest::from_sim_message(request)?; + fn handle_ctrl_request(&mut self, sim_ctrl_request: SimCtrlRequest) { if SIM_CTRL_REQ_WIRETAPPING { log::info!("received sim ctrl request: {sim_ctrl_request:?}"); } @@ -126,115 +154,67 @@ impl SimController { SimCtrlRequest::Ping => { log::info!("received ping request, a client is connecting"); self.reply_sender - .send(SimReply::new(&SimCtrlReply::Pong)) + .send(SimReply::from(SimCtrlReply::Pong)) .expect("sending reply from sim controller failed"); } } - Ok(()) } - fn handle_mgm_request( - &mut self, - mgm_idx: usize, - request: &SimRequest, - ) -> Result<(), SimRequestError> { - let (mgm_request, addr) = match mgm_idx { - 0 => ( - MgmRequestLis3MdlMgm0::from_sim_message(request)?.0, - &self.addr_wrapper.mgm_0_addr, - ), - 1 => ( - MgmRequestLis3MdlMgm1::from_sim_message(request)?.0, - &self.addr_wrapper.mgm_1_addr, - ), - _ => panic!("invalid mgm index"), + fn handle_mgm_request(&mut self, mgm_id: mgm::Id, mgm_request: mgm::Request) { + let addr = match mgm_id { + mgm::Id::Mgm0 => &self.addrs.mgm_0, + mgm::Id::Mgm1 => &self.addrs.mgm_1, }; if MGM_REQ_WIRETAPPING { - log::info!("received MGM{mgm_idx} request: {mgm_request:?}"); + log::info!("received {mgm_id:?} request: {mgm_request:?}"); } match mgm_request { - MgmRequestLis3Mdl::RequestSensorData => { + mgm::Request::RequestSensorData => { self.simulation - .process_event(MagnetometerModel::send_sensor_values, (), addr) + .process_event(MgmModel::send_sensor_values, (), addr) .expect("event execution error for mgm"); } - MgmRequestLis3Mdl::SetSpiFault(fault_mode) => { - log::info!("MGM{mgm_idx}: setting SPI fault mode to {fault_mode:?}"); + mgm::Request::SetSpiFault(fault_mode) => { + log::info!("{mgm_id:?}: setting SPI fault mode to {fault_mode:?}"); self.simulation - .process_event(MagnetometerModel::set_spi_fault, fault_mode, addr) + .process_event(MgmModel::set_spi_fault, fault_mode, addr) .expect("event execution error for mgm"); } } - Ok(()) } - fn handle_pcdu_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> { - let pcdu_request = PcduRequest::from_sim_message(request)?; + fn handle_pcdu_request(&mut self, pcdu_request: PcduRequest) { if PCDU_REQ_WIRETAPPING { log::info!("received PCDU request: {pcdu_request:?}"); } match pcdu_request { PcduRequest::RequestSwitchInfo => { self.simulation - .process_event( - PcduModel::request_switch_info, - (), - &self.addr_wrapper.pcdu_addr, - ) + .process_event(PcduModel::request_switch_info, (), &self.addrs.pcdu) .unwrap(); } PcduRequest::SwitchDevice { switch, state } => { self.simulation - .process_event( - PcduModel::switch_device, - (switch, state), - &self.addr_wrapper.pcdu_addr, - ) + .process_event(PcduModel::switch_device, (switch, state), &self.addrs.pcdu) .unwrap(); } } - Ok(()) } - fn handle_mgt_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> { - let mgt_request = MgtRequest::from_sim_message(request)?; + fn handle_mgt_request(&mut self, mgt_request: mgt::Request) { if MGT_REQ_WIRETAPPING { log::info!("received MGT request: {mgt_request:?}"); } match mgt_request { - MgtRequest::ApplyTorque { duration, dipole } => self + mgt::Request::ApplyTorque { duration, dipole } => self .simulation - .process_event( - MagnetorquerModel::apply_torque, - (duration, dipole), - &self.addr_wrapper.mgt_addr, - ) + .process_event(MgtModel::apply_torque, (duration, dipole), &self.addrs.mgt) .unwrap(), - MgtRequest::RequestHk => self + mgt::Request::RequestHk => self .simulation - .process_event( - MagnetorquerModel::request_housekeeping_data, - (), - &self.addr_wrapper.mgt_addr, - ) + .process_event(MgtModel::request_housekeeping_data, (), &self.addrs.mgt) .unwrap(), }; - Ok(()) - } - - fn handle_invalid_request_with_valid_target( - &self, - error: SimRequestError, - request: &SimRequest, - ) { - log::warn!( - "received invalid {:?} request: {:?}", - request.component(), - error - ); - self.reply_sender - .send(SimReply::new(&SimCtrlReply::from(error))) - .expect("sending reply from sim controller failed"); } } @@ -247,18 +227,9 @@ mod tests { #[test] fn test_basic_ping() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping); - sim_testbench - .send_request(request) - .expect("sending sim ctrl request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply = sim_testbench.try_receive_next_reply(); - assert!(sim_reply.is_some()); - let sim_reply = sim_reply.unwrap(); - assert_eq!(sim_reply.component(), SimComponent::SimCtrl); - let reply = SimCtrlReply::from_sim_message(&sim_reply) - .expect("failed to deserialize MGM sensor values"); - assert_eq!(reply, SimCtrlReply::Pong); + assert_eq!( + sim_testbench.request_reply(SimCtrlRequest::Ping), + Some(SimReply::SimCtrl(SimCtrlReply::Pong)) + ); } } diff --git a/satrs-example/minisim/src/eps.rs b/satrs-example/minisim/src/eps.rs index d8360a2..97d2149 100644 --- a/satrs-example/minisim/src/eps.rs +++ b/satrs-example/minisim/src/eps.rs @@ -38,7 +38,7 @@ impl PcduModel { } pub fn send_switch_info(&mut self) { - let reply = SimReply::new(&PcduReply::SwitchInfo(self.switcher_map.0.clone())); + let reply = SimReply::from(PcduReply::SwitchInfo(self.switcher_map.0.clone())); self.reply_sender.send(reply).unwrap(); } @@ -75,9 +75,7 @@ pub(crate) mod tests { use super::*; use std::time::Duration; - use satrs_minisim::{ - eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, - }; + use satrs_minisim::{eps::PcduRequest, SimRequestWithTime}; use types::pcdu::SwitchMapBinary; use crate::test_helpers::SimTestbench; @@ -87,18 +85,12 @@ pub(crate) mod tests { switch: SwitchId, target: SwitchStateBinary, ) { - let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice { + sim_testbench.send_and_step(PcduRequest::SwitchDevice { switch, state: target, }); - sim_testbench - .send_request(request) - .expect("sending MGM switch request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); } - #[allow(dead_code)] pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: SwitchId) { switch_device(sim_testbench, switch, SwitchStateBinary::Off); } @@ -110,24 +102,18 @@ pub(crate) mod tests { SwitchMapBinaryWrapper::default().0 } + fn unwrap_switch_map(sim_reply: SimReply) -> SwitchMapBinary { + let SimReply::Pcdu(PcduReply::SwitchInfo(switch_map)) = sim_reply else { + panic!("unexpected reply {sim_reply:?}"); + }; + switch_map + } + fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMapBinary) { - let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); - sim_testbench - .send_request(request) - .expect("sending MGM request failed"); - sim_testbench.handle_sim_requests_time_agnostic(); - sim_testbench.step().unwrap(); - let sim_reply = sim_testbench.try_receive_next_reply(); - assert!(sim_reply.is_some()); - let sim_reply = sim_reply.unwrap(); - assert_eq!(sim_reply.component(), SimComponent::Pcdu); - let pcdu_reply = PcduReply::from_sim_message(&sim_reply) - .expect("failed to deserialize PCDU switch info"); - match pcdu_reply { - PcduReply::SwitchInfo(switch_map) => { - assert_eq!(switch_map, *expected_switch_map); - } - } + let sim_reply = sim_testbench + .request_reply(PcduRequest::RequestSwitchInfo) + .expect("no PCDU reply received"); + assert_eq!(unwrap_switch_map(sim_reply), *expected_switch_map); } fn test_pcdu_switching_single_switch(switch: SwitchId, target: SwitchStateBinary) { @@ -141,28 +127,20 @@ pub(crate) mod tests { #[test] fn test_pcdu_switcher_request() { let mut sim_testbench = SimTestbench::new(); - let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); + let request = SimRequestWithTime::new_with_epoch_time(PcduRequest::RequestSwitchInfo); sim_testbench .send_request(request) - .expect("sending MGM request failed"); + .expect("sending PCDU request failed"); sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.step_until(Duration::from_millis(1)).unwrap(); + assert!(sim_testbench.try_receive_next_reply().is_none()); - let sim_reply = sim_testbench.try_receive_next_reply(); - assert!(sim_reply.is_none()); - // Reply takes 20ms + // The reply is delayed by SWITCH_INFO_DELAY_MS. sim_testbench.step_until(Duration::from_millis(25)).unwrap(); - let sim_reply = sim_testbench.try_receive_next_reply(); - assert!(sim_reply.is_some()); - let sim_reply = sim_reply.unwrap(); - assert_eq!(sim_reply.component(), SimComponent::Pcdu); - let pcdu_reply = PcduReply::from_sim_message(&sim_reply) - .expect("failed to deserialize PCDU switch info"); - match pcdu_reply { - PcduReply::SwitchInfo(switch_map) => { - assert_eq!(switch_map, get_all_off_switch_map()); - } - } + let sim_reply = sim_testbench + .try_receive_next_reply() + .expect("no PCDU reply received"); + assert_eq!(unwrap_switch_map(sim_reply), get_all_off_switch_map()); } #[test] diff --git a/satrs-example/minisim/src/lib.rs b/satrs-example/minisim/src/lib.rs index 826329a..6007b68 100644 --- a/satrs-example/minisim/src/lib.rs +++ b/satrs-example/minisim/src/lib.rs @@ -1,8 +1,14 @@ use nexosim::time::MonotonicTime; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; +use crate::{ + acs::{mgm, mgt}, + eps::{PcduReply, PcduRequest}, +}; + +/// Used by clients to route replies to the component handling them. #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub enum SimComponent { +pub enum ComponentId { SimCtrl, Mgm0Lis3Mdl, Mgm1Lis3Mdl, @@ -10,122 +16,85 @@ pub enum SimComponent { Pcdu, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct SimMessage { - pub target: SimComponent, - pub payload: String, +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SimRequest { + SimCtrl(SimCtrlRequest), + Mgm { id: mgm::Id, request: mgm::Request }, + Mgt(mgt::Request), + Pcdu(PcduRequest), } -/// A generic simulation request type. Right now, the payload data is expected to be -/// JSON, which might be changed in the future. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct SimRequest { - inner: SimMessage, +impl From for SimRequest { + fn from(request: SimCtrlRequest) -> Self { + Self::SimCtrl(request) + } +} + +impl From for SimRequest { + fn from(request: mgt::Request) -> Self { + Self::Mgt(request) + } +} + +impl From for SimRequest { + fn from(request: PcduRequest) -> Self { + Self::Pcdu(request) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SimRequestWithTime { + pub request: SimRequest, pub timestamp: MonotonicTime, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SimMessageType { - Request, - Reply, -} - -/// Generic trait implemented by simulation request or reply payloads. It ties the request or -/// reply to a specific target and provides an API which does boilerplate tasks like checking the -/// validity of the target. -pub trait SerializableSimMsgPayload: - Serialize + DeserializeOwned + Sized -{ - const TARGET: SimComponent; - - fn from_sim_message(sim_message: &P) -> Result> { - if sim_message.component() == Self::TARGET { - return Ok(serde_json::from_str(sim_message.payload())?); - } - Err(SimMessageError::TargetRequestMissmatch(sim_message.clone())) - } -} - -pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized { - fn msg_type(&self) -> SimMessageType; - fn component(&self) -> SimComponent; - fn payload(&self) -> &String; - fn from_raw_data(data: &[u8]) -> serde_json::Result { - serde_json::from_slice(data) - } -} - -impl SimRequest { - pub fn new_with_epoch_time>( - serializable_request: T, - ) -> Self { - Self::new(serializable_request, MonotonicTime::EPOCH) - } - - pub fn new>( - serializable_request: T, - timestamp: MonotonicTime, - ) -> Self { +impl SimRequestWithTime { + pub fn new(request: impl Into, timestamp: MonotonicTime) -> Self { Self { - inner: SimMessage { - target: T::TARGET, - payload: serde_json::to_string(&serializable_request).unwrap(), - }, + request: request.into(), timestamp, } } -} -impl SimMessageProvider for SimRequest { - fn component(&self) -> SimComponent { - self.inner.target - } - fn payload(&self) -> &String { - &self.inner.payload - } - - fn msg_type(&self) -> SimMessageType { - SimMessageType::Request + pub fn new_with_epoch_time(request: impl Into) -> Self { + Self::new(request, MonotonicTime::EPOCH) } } -/// A generic simulation reply type. Right now, the payload data is expected to be -/// JSON, which might be changed in the future. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct SimReply { - inner: SimMessage, +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SimReply { + SimCtrl(SimCtrlReply), + Mgm { id: mgm::Id, reply: mgm::Reply }, + Mgt(mgt::Reply), + Pcdu(PcduReply), } impl SimReply { - pub fn new>(serializable_reply: &T) -> Self { - Self { - inner: SimMessage { - target: T::TARGET, - payload: serde_json::to_string(serializable_reply).unwrap(), - }, - } - } - - /// For payloads where the target is only known at runtime. - pub fn new_with_target(target: SimComponent, reply: &T) -> Self { - Self { - inner: SimMessage { - target, - payload: serde_json::to_string(reply).unwrap(), - }, + pub fn component(&self) -> ComponentId { + match self { + SimReply::SimCtrl(_) => ComponentId::SimCtrl, + SimReply::Mgm { id, .. } => id.sim_component(), + SimReply::Mgt(_) => ComponentId::Mgt, + SimReply::Pcdu(_) => ComponentId::Pcdu, } } } -impl SimMessageProvider for SimReply { - fn component(&self) -> SimComponent { - self.inner.target +impl From for SimReply { + fn from(reply: SimCtrlReply) -> Self { + Self::SimCtrl(reply) } - fn payload(&self) -> &String { - &self.inner.payload +} + +impl From for SimReply { + fn from(reply: mgt::Reply) -> Self { + Self::Mgt(reply) } - fn msg_type(&self) -> SimMessageType { - SimMessageType::Reply +} + +impl From for SimReply { + fn from(reply: PcduReply) -> Self { + Self::Pcdu(reply) } } @@ -134,52 +103,15 @@ pub enum SimCtrlRequest { Ping, } -impl SerializableSimMsgPayload for SimCtrlRequest { - const TARGET: SimComponent = SimComponent::SimCtrl; -} - -pub type SimReplyError = SimMessageError; -pub type SimRequestError = SimMessageError; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SimMessageError

{ - SerdeJson(String), - TargetRequestMissmatch(P), -} - -impl

From for SimMessageError

{ - fn from(error: serde_json::Error) -> SimMessageError

{ - SimMessageError::SerdeJson(error.to_string()) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SimCtrlReply { Pong, - InvalidRequest(SimRequestError), -} - -impl SerializableSimMsgPayload for SimCtrlReply { - const TARGET: SimComponent = SimComponent::SimCtrl; -} - -impl From for SimCtrlReply { - fn from(error: SimRequestError) -> Self { - SimCtrlReply::InvalidRequest(error) - } } pub mod eps { use super::*; use types::pcdu::{SwitchId, SwitchMapBinary, SwitchStateBinary}; - #[derive(Debug, Copy, Clone)] - #[repr(u8)] - pub enum PcduRequestId { - SwitchDevice = 0, - RequestSwitchInfo = 1, - } - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PcduRequest { SwitchDevice { @@ -189,102 +121,61 @@ pub mod eps { RequestSwitchInfo, } - impl SerializableSimMsgPayload for PcduRequest { - const TARGET: SimComponent = SimComponent::Pcdu; - } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum PcduReply { - // Ack, SwitchInfo(SwitchMapBinary), } - - impl SerializableSimMsgPayload for PcduReply { - const TARGET: SimComponent = SimComponent::Pcdu; - } } pub mod acs { - use std::time::Duration; - - use types::pcdu::SwitchStateBinary; - - use super::*; - - /// Fault mode injected on the simulated SPI bus, independent of the switch state. - /// - /// Models the classic symptom of a stuck SPI bus: an undriven MISO line commonly reads - /// back as all-1s, a shorted/grounded one as all-0s. - #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub enum SpiFaultMode { - #[default] - None, - AllZeros, - AllOnes, - } - - #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub struct SpiFault { - pub mode: SpiFaultMode, - /// The fault is cleared when the device is switched off, so a power cycle recovers - /// from it. - pub cleared_by_power_cycle: bool, - } - - #[derive(Debug, Copy, Clone, Serialize, Deserialize)] - pub enum MgmRequestLis3Mdl { - RequestSensorData, - /// Force the raw register reply into a stuck-bus pattern, regardless of switch state. - /// Used to test FDIR handling of SPI bus faults. - SetSpiFault(SpiFault), - } - - #[derive(Debug, Copy, Clone, Serialize, Deserialize)] - pub struct MgmRequestLis3MdlMgm0(pub MgmRequestLis3Mdl); - - impl SerializableSimMsgPayload for MgmRequestLis3MdlMgm0 { - const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl; - } - - #[derive(Debug, Copy, Clone, Serialize, Deserialize)] - pub struct MgmRequestLis3MdlMgm1(pub MgmRequestLis3Mdl); - - impl SerializableSimMsgPayload for MgmRequestLis3MdlMgm1 { - const TARGET: SimComponent = SimComponent::Mgm1Lis3Mdl; - } - - // Normally, small magnetometers generate their output as a signed 16 bit raw format or something - // similar which needs to be converted to a signed float value with physical units. We will - // simplify this now and generate the signed float values directly. The unit is micro tesla. - #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub struct MgmSensorValuesMicroTesla { - pub x: f32, - pub y: f32, - pub z: f32, - } - - #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub struct MgmReplyCommon {} - - pub const MGT_GEN_MAGNETIC_FIELD: MgmSensorValuesMicroTesla = MgmSensorValuesMicroTesla { - x: 30.0, - y: -30.0, - z: 30.0, - }; - pub const ALL_ONES_SENSOR_VAL: i16 = 0xffff_u16 as i16; - pub const ALL_ZEROS_SENSOR_VAL: i16 = 0; - /// MGM module strongly based on the LIS3MDL device. pub mod mgm { + use serde::{Deserialize, Serialize}; + use types::pcdu::SwitchStateBinary; - use super::*; + use crate::ComponentId; + + /// Fault mode injected on the simulated SPI bus, independent of the switch state. + /// + /// Models the classic symptom of a stuck SPI bus: an undriven MISO line commonly reads + /// back as all-1s, a shorted/grounded one as all-0s. + #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub enum SpiFaultMode { + #[default] + None, + AllZeros, + AllOnes, + } + + #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct SpiFault { + pub mode: SpiFaultMode, + /// The fault is cleared when the device is switched off, so a power cycle recovers + /// from it. + pub cleared_by_power_cycle: bool, + } + + // Normally, small magnetometers generate their output as a signed 16 bit raw format or something + // similar which needs to be converted to a signed float value with physical units. We will + // simplify this now and generate the signed float values directly. The unit is micro tesla. + #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] + pub struct SensorValuesMicroTesla { + pub x: f32, + pub y: f32, + pub z: f32, + } + + pub const MGT_GEN_MAGNETIC_FIELD: SensorValuesMicroTesla = SensorValuesMicroTesla { + x: 30.0, + y: -30.0, + z: 30.0, + }; + pub const ALL_ONES_SENSOR_VAL: i16 = 0xffff_u16 as i16; + pub const ALL_ZEROS_SENSOR_VAL: i16 = 0; // Field data register scaling pub const GAUSS_TO_MICROTESLA_FACTOR: u32 = 100; pub const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0; - pub const FIELD_LSB_PER_GAUSS_8_SENS: f32 = 1.0 / 3421.0; - pub const FIELD_LSB_PER_GAUSS_12_SENS: f32 = 1.0 / 2281.0; - pub const FIELD_LSB_PER_GAUSS_16_SENS: f32 = 1.0 / 1711.0; #[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct RawValues { @@ -292,165 +183,115 @@ pub mod acs { pub y: i16, pub z: i16, } + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub enum Request { + RequestSensorData, + /// Force the raw register reply into a stuck-bus pattern, regardless of switch state. + /// Used to test FDIR handling of SPI bus faults. + SetSpiFault(SpiFault), + } #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub struct MgmReply { + pub struct Reply { pub switch_state: SwitchStateBinary, - pub sensor_values: MgmSensorValuesMicroTesla, + pub sensor_values: SensorValuesMicroTesla, // Raw sensor values which are transmitted by the LIS3 device in little-endian // order. pub raw: RawValues, } #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub enum MgmId { + pub enum Id { Mgm0, Mgm1, } - impl MgmId { - pub const fn sim_component(&self) -> SimComponent { + impl Id { + pub const fn sim_component(&self) -> ComponentId { match self { - MgmId::Mgm0 => SimComponent::Mgm0Lis3Mdl, - MgmId::Mgm1 => SimComponent::Mgm1Lis3Mdl, + Id::Mgm0 => ComponentId::Mgm0Lis3Mdl, + Id::Mgm1 => ComponentId::Mgm1Lis3Mdl, } } } - /// Does not implement [SerializableSimMsgPayload] because the target depends on the - /// MGM ID, which is only known at runtime. - #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub struct MgmReplyWrapper { - pub mgm_id: MgmId, - pub reply: MgmReply, - } - - impl MgmReplyWrapper { - pub fn to_sim_reply(&self) -> SimReply { - SimReply::new_with_target(self.mgm_id.sim_component(), self) - } - - pub fn from_sim_reply(sim_reply: &SimReply) -> Result { - let wrapper: Self = serde_json::from_str(sim_reply.payload())?; - if wrapper.mgm_id.sim_component() != sim_reply.component() { - return Err(SimMessageError::TargetRequestMissmatch(sim_reply.clone())); + impl RawValues { + pub const fn splat(value: i16) -> Self { + Self { + x: value, + y: value, + z: value, + } + } + + pub fn from_microtesla(values: SensorValuesMicroTesla) -> Self { + let to_raw = |microtesla: f32| { + (microtesla / (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS)) + .round() as i16 + }; + Self { + x: to_raw(values.x), + y: to_raw(values.y), + z: to_raw(values.z), } - Ok(wrapper) } } - impl MgmReply { + impl Reply { pub fn new( switch_state: SwitchStateBinary, - sensor_values: MgmSensorValuesMicroTesla, + sensor_values: SensorValuesMicroTesla, fault_mode: SpiFaultMode, ) -> Self { - match fault_mode { - SpiFaultMode::AllZeros => { - return Self { - switch_state, - sensor_values, - raw: RawValues { - x: ALL_ZEROS_SENSOR_VAL, - y: ALL_ZEROS_SENSOR_VAL, - z: ALL_ZEROS_SENSOR_VAL, - }, - }; + // An injected fault always wins. A switched off device reads back like an + // undriven bus. + let raw = match (fault_mode, switch_state) { + (SpiFaultMode::AllZeros, _) => RawValues::splat(ALL_ZEROS_SENSOR_VAL), + (SpiFaultMode::AllOnes, _) | (SpiFaultMode::None, SwitchStateBinary::Off) => { + RawValues::splat(ALL_ONES_SENSOR_VAL) } - SpiFaultMode::AllOnes => { - return Self { - switch_state, - sensor_values, - raw: RawValues { - x: ALL_ONES_SENSOR_VAL, - y: ALL_ONES_SENSOR_VAL, - z: ALL_ONES_SENSOR_VAL, - }, - }; - } - SpiFaultMode::None => (), - } - match switch_state { - SwitchStateBinary::Off => Self { - switch_state, - sensor_values, - raw: RawValues { - x: ALL_ONES_SENSOR_VAL, - y: ALL_ONES_SENSOR_VAL, - z: ALL_ONES_SENSOR_VAL, - }, - }, - SwitchStateBinary::On => { - let mut raw_reply: [u8; 7] = [0; 7]; - let raw_x: i16 = (sensor_values.x - / (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS)) - .round() as i16; - let raw_y: i16 = (sensor_values.y - / (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS)) - .round() as i16; - let raw_z: i16 = (sensor_values.z - / (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS)) - .round() as i16; - // The first byte is a dummy byte. - raw_reply[1..3].copy_from_slice(&raw_x.to_be_bytes()); - raw_reply[3..5].copy_from_slice(&raw_y.to_be_bytes()); - raw_reply[5..7].copy_from_slice(&raw_z.to_be_bytes()); - Self { - switch_state, - sensor_values, - raw: RawValues { - x: raw_x, - y: raw_y, - z: raw_z, - }, - } + (SpiFaultMode::None, SwitchStateBinary::On) => { + RawValues::from_microtesla(sensor_values) } + }; + Self { + switch_state, + sensor_values, + raw, } } } } - // Simple model using i16 values. - #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub struct MgtDipole { - pub x: i16, - pub y: i16, - pub z: i16, - } + pub mod mgt { + use std::time::Duration; - #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] - pub enum MgtRequestType { - ApplyTorque, - } + use serde::{Deserialize, Serialize}; - #[derive(Debug, Copy, Clone, Serialize, Deserialize)] - pub enum MgtRequest { - ApplyTorque { - duration: Duration, - dipole: MgtDipole, - }, - RequestHk, - } + // Simple model using i16 values. + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct Dipole { + pub x: i16, + pub y: i16, + pub z: i16, + } - impl SerializableSimMsgPayload for MgtRequest { - const TARGET: SimComponent = SimComponent::Mgt; - } + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub enum Request { + ApplyTorque { duration: Duration, dipole: Dipole }, + RequestHk, + } - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub struct MgtHkSet { - pub dipole: MgtDipole, - pub torquing: bool, - } + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct HkSet { + pub dipole: Dipole, + pub torquing: bool, + } - #[derive(Debug, Copy, Clone, Serialize, Deserialize)] - pub enum MgtReply { - Ack(MgtRequestType), - Nak(MgtRequestType), - Hk(MgtHkSet), - } - - impl SerializableSimMsgPayload for MgtReply { - const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub enum Reply { + Hk(HkSet), + } } } @@ -462,41 +303,20 @@ pub mod udp { pub mod tests { use super::*; - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub enum DummyRequest { - Ping, - } - - impl SerializableSimMsgPayload for DummyRequest { - const TARGET: SimComponent = SimComponent::SimCtrl; - } - - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub enum DummyReply { - Pong, - } - - impl SerializableSimMsgPayload for DummyReply { - const TARGET: SimComponent = SimComponent::SimCtrl; + #[test] + fn test_request_serde_roundtrip() { + let sim_request = SimRequestWithTime::new_with_epoch_time(SimCtrlRequest::Ping); + let json = serde_json::to_string(&sim_request).unwrap(); + let deserialized: SimRequestWithTime = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, sim_request); } #[test] - fn test_basic_request() { - let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping); - assert_eq!(sim_request.component(), SimComponent::SimCtrl); - assert_eq!(sim_request.msg_type(), SimMessageType::Request); - let dummy_request = - DummyRequest::from_sim_message(&sim_request).expect("deserialization failed"); - assert_eq!(dummy_request, DummyRequest::Ping); - } - - #[test] - fn test_basic_reply() { - let sim_reply = SimReply::new(&DummyReply::Pong); - assert_eq!(sim_reply.component(), SimComponent::SimCtrl); - assert_eq!(sim_reply.msg_type(), SimMessageType::Reply); - let dummy_request = - DummyReply::from_sim_message(&sim_reply).expect("deserialization failed"); - assert_eq!(dummy_request, DummyReply::Pong); + fn test_reply_serde_roundtrip() { + let sim_reply = SimReply::from(SimCtrlReply::Pong); + assert_eq!(sim_reply.component(), ComponentId::SimCtrl); + let json = serde_json::to_string(&sim_reply).unwrap(); + let deserialized: SimReply = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, sim_reply); } } diff --git a/satrs-example/minisim/src/main.rs b/satrs-example/minisim/src/main.rs index 3c754b1..171fd9c 100644 --- a/satrs-example/minisim/src/main.rs +++ b/satrs-example/minisim/src/main.rs @@ -1,14 +1,8 @@ -use acs::{mgm::MagnetometerModel, mgt::MagnetorquerModel}; -use controller::{ModelAddrWrapper, SimController}; -use eps::PcduModel; -use nexosim::simulation::{Mailbox, SimInit}; -use nexosim::time::{MonotonicTime, SystemClock}; -use satrs_minisim::acs::mgm::MgmId; +use controller::{SimController, ThreadingModel}; +use nexosim::time::MonotonicTime; use satrs_minisim::udp::SIM_CTRL_PORT; -use satrs_minisim::{SimReply, SimRequest}; use std::sync::mpsc; use std::thread; -use std::time::{Duration, SystemTime}; use udp::SimUdpServer; mod acs; @@ -19,87 +13,12 @@ mod test_helpers; mod time; mod udp; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ThreadingModel { - Default = 0, - Single = 1, -} - -fn create_sim_controller( - threading_model: ThreadingModel, - start_time: MonotonicTime, - reply_sender: mpsc::Sender, - request_receiver: mpsc::Receiver, -) -> SimController { - // Instantiate models and their mailboxes. - let mgm_0_model = - MagnetometerModel::new(MgmId::Mgm0, Duration::from_millis(50), reply_sender.clone()); - let mgm_1_model = - MagnetometerModel::new(MgmId::Mgm1, Duration::from_millis(50), reply_sender.clone()); - - let mgm_0_mailbox = Mailbox::new(); - let mgm_0_addr = mgm_0_mailbox.address(); - let mgm_1_mailbox = Mailbox::new(); - let mgm_1_addr = mgm_1_mailbox.address(); - let pcdu_mailbox = Mailbox::new(); - let pcdu_addr = pcdu_mailbox.address(); - let mgt_mailbox = Mailbox::new(); - let mgt_addr = mgt_mailbox.address(); - - let mut pcdu_model = PcduModel::new(reply_sender.clone()); - pcdu_model - .mgm_0_switch - .connect(MagnetometerModel::switch_device, &mgm_0_addr); - pcdu_model - .mgm_1_switch - .connect(MagnetometerModel::switch_device, &mgm_1_addr); - - let mut mgt_model = MagnetorquerModel::new(reply_sender.clone()); - // Input connections. - pcdu_model - .mgt_switch - .connect(MagnetorquerModel::switch_device, &mgt_addr); - // Output connections. - mgt_model.gen_magnetic_field.connect( - MagnetometerModel::apply_external_magnetic_field, - &mgm_0_addr, - ); - mgt_model.gen_magnetic_field.connect( - MagnetometerModel::apply_external_magnetic_field, - &mgm_1_addr, - ); - - // Instantiate the simulator - let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now()); - let sim_init = if threading_model == ThreadingModel::Single { - SimInit::with_num_threads(1) - } else { - SimInit::new() - }; - let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr); - let (simulation, scheduler) = sim_init - .add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model") - .add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model") - .add_model(pcdu_model, pcdu_mailbox, "PCDU model") - .add_model(mgt_model, mgt_mailbox, "MGT model") - .init(start_time) - .unwrap(); - SimController::new( - sys_clock, - request_receiver, - reply_sender, - simulation, - scheduler, - addrs, - ) -} - fn main() { let (request_sender, request_receiver) = mpsc::channel(); let (reply_sender, reply_receiver) = mpsc::channel(); let t0 = MonotonicTime::EPOCH; let mut sim_ctrl = - create_sim_controller(ThreadingModel::Default, t0, reply_sender, request_receiver); + SimController::new(ThreadingModel::Default, t0, reply_sender, request_receiver); // Configure logger at runtime fern::Dispatch::new() // Perform allocation-free log formatting diff --git a/satrs-example/minisim/src/test_helpers.rs b/satrs-example/minisim/src/test_helpers.rs index 398d925..47a06bc 100644 --- a/satrs-example/minisim/src/test_helpers.rs +++ b/satrs-example/minisim/src/test_helpers.rs @@ -5,14 +5,14 @@ use nexosim::{ simulation::ExecutionError, time::{Deadline, MonotonicTime}, }; -use satrs_minisim::{SimReply, SimRequest}; +use satrs_minisim::{SimReply, SimRequest, SimRequestWithTime}; -use crate::{controller::SimController, create_sim_controller, ThreadingModel}; +use crate::controller::{SimController, ThreadingModel}; pub struct SimTestbench { pub sim_controller: SimController, pub reply_receiver: mpsc::Receiver, - pub request_sender: mpsc::Sender, + pub request_sender: mpsc::Sender, } impl SimTestbench { @@ -21,7 +21,7 @@ impl SimTestbench { let (reply_sender, reply_receiver) = mpsc::channel(); let t0 = MonotonicTime::EPOCH; let sim_ctrl = - create_sim_controller(ThreadingModel::Single, t0, reply_sender, request_receiver); + SimController::new(ThreadingModel::Single, t0, reply_sender, request_receiver); Self { sim_controller: sim_ctrl, @@ -43,10 +43,26 @@ impl SimTestbench { } } - pub fn send_request(&self, request: SimRequest) -> Result<(), mpsc::SendError> { + pub fn send_request( + &self, + request: SimRequestWithTime, + ) -> Result<(), mpsc::SendError> { self.request_sender.send(request) } + /// Sends the request and steps the simulation to the next scheduled event. + pub fn send_and_step(&mut self, request: impl Into) { + self.send_request(SimRequestWithTime::new_with_epoch_time(request)) + .expect("sending request failed"); + self.handle_sim_requests_time_agnostic(); + self.step().unwrap(); + } + + pub fn request_reply(&mut self, request: impl Into) -> Option { + self.send_and_step(request); + self.try_receive_next_reply() + } + pub fn try_receive_next_reply(&self) -> Option { match self.reply_receiver.try_recv() { Ok(reply) => Some(reply), diff --git a/satrs-example/minisim/src/udp.rs b/satrs-example/minisim/src/udp.rs index 6093a74..73f3aa1 100644 --- a/satrs-example/minisim/src/udp.rs +++ b/satrs-example/minisim/src/udp.rs @@ -6,13 +6,12 @@ use std::{ time::Duration, }; -use satrs_minisim::{SimMessageProvider, SimReply, SimRequest}; +use satrs_minisim::{SimReply, SimRequestWithTime}; // A UDP server which handles all TC received by a client application. pub struct SimUdpServer { socket: UdpSocket, - request_sender: mpsc::Sender, - // shared_last_sender: SharedSocketAddr, + request_sender: mpsc::Sender, reply_receiver: mpsc::Receiver, reply_queue: VecDeque, max_num_replies: usize, @@ -27,7 +26,7 @@ pub struct SimUdpServer { impl SimUdpServer { pub fn new( local_port: u16, - request_sender: mpsc::Sender, + request_sender: mpsc::Sender, reply_receiver: mpsc::Receiver, max_num_replies: usize, stop_signal: Option>, @@ -47,7 +46,7 @@ impl SimUdpServer { }) } - #[allow(dead_code)] + #[cfg(test)] pub fn server_addr(&self) -> std::io::Result { self.socket.local_addr() } @@ -90,7 +89,7 @@ impl SimUdpServer { self.sender_addr = Some(src); - let sim_req = SimRequest::from_raw_data(&self.req_buf[..bytes_read]); + let sim_req = serde_json::from_slice::(&self.req_buf[..bytes_read]); if let Err(e) = sim_req { log::warn!("received UDP request with invalid format: {}", e); return processed_requests; @@ -157,7 +156,7 @@ mod tests { use satrs_minisim::{ eps::{PcduReply, PcduRequest}, - SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, + SimCtrlReply, SimCtrlRequest, SimReply, SimRequestWithTime, }; use crate::eps::tests::get_all_off_switch_map; @@ -202,7 +201,7 @@ mod tests { }) } - pub fn send_request(&self, sim_request: &SimRequest) -> std::io::Result { + pub fn send_request(&self, sim_request: &SimRequestWithTime) -> std::io::Result { self.socket.send( &serde_json::to_vec(sim_request).expect("conversion of request to vector failed"), ) @@ -220,7 +219,7 @@ mod tests { struct UdpTestbench { client: SimUdpTestClient, stop_signal: Arc, - request_receiver: mpsc::Receiver, + request_receiver: mpsc::Receiver, reply_sender: mpsc::Sender, } @@ -256,7 +255,7 @@ mod tests { )) } - pub fn try_recv_request(&self) -> Result { + pub fn try_recv_request(&self) -> Result { self.request_receiver.try_recv() } @@ -272,7 +271,7 @@ mod tests { delegate! { to self.client { - pub fn send_request(&self, sim_request: &SimRequest) -> std::io::Result; + pub fn send_request(&self, sim_request: &SimRequestWithTime) -> std::io::Result; pub fn recv_sim_reply(&mut self) -> Result; } } @@ -316,7 +315,7 @@ mod tests { UdpTestbench::new(true, Some(SERVER_WAIT_TIME_MS), 10) .expect("could not create testbench"); let server_thread = std::thread::spawn(move || udp_server.run()); - let sim_request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); + let sim_request = SimRequestWithTime::new_with_epoch_time(PcduRequest::RequestSwitchInfo); udp_testbench .send_request(&sim_request) .expect("sending request failed"); @@ -338,10 +337,12 @@ mod tests { .expect("could not create testbench"); let server_thread = std::thread::spawn(move || udp_server.run()); udp_testbench - .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) + .send_request(&SimRequestWithTime::new_with_epoch_time( + SimCtrlRequest::Ping, + )) .expect("sending request failed"); - let sim_reply = SimReply::new(&PcduReply::SwitchInfo(get_all_off_switch_map())); + let sim_reply = SimReply::from(PcduReply::SwitchInfo(get_all_off_switch_map())); udp_testbench.send_reply(&sim_reply); udp_testbench.check_next_sim_reply(&sim_reply); @@ -362,11 +363,13 @@ mod tests { // Send a ping so that the server knows the address of the client. // Do not check that the request arrives on the receiver side, is done by other test. udp_testbench - .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) + .send_request(&SimRequestWithTime::new_with_epoch_time( + SimCtrlRequest::Ping, + )) .expect("sending request failed"); // Send a reply to the server, ensure it gets forwarded to the client. - let sim_reply = SimReply::new(&PcduReply::SwitchInfo(get_all_off_switch_map())); + let sim_reply = SimReply::from(PcduReply::SwitchInfo(get_all_off_switch_map())); udp_testbench.send_reply(&sim_reply); std::thread::sleep(Duration::from_millis(SERVER_WAIT_TIME_MS)); @@ -385,7 +388,7 @@ mod tests { let server_thread = std::thread::spawn(move || udp_server.run()); // Send a reply to the server. The client is not connected, so it won't get forwarded. - let sim_reply = SimReply::new(&PcduReply::SwitchInfo(get_all_off_switch_map())); + let sim_reply = SimReply::from(PcduReply::SwitchInfo(get_all_off_switch_map())); udp_testbench.send_reply(&sim_reply); std::thread::sleep(Duration::from_millis(10)); @@ -393,7 +396,9 @@ mod tests { // Connect by sending a ping. udp_testbench - .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) + .send_request(&SimRequestWithTime::new_with_epoch_time( + SimCtrlRequest::Ping, + )) .expect("sending request failed"); std::thread::sleep(Duration::from_millis(SERVER_WAIT_TIME_MS)); @@ -412,7 +417,7 @@ mod tests { let server_thread = std::thread::spawn(move || udp_server.run()); // The server only caches up to 3 replies. - let sim_reply = SimReply::new(&SimCtrlReply::Pong); + let sim_reply = SimReply::from(SimCtrlReply::Pong); for _ in 0..4 { udp_testbench.send_reply(&sim_reply); } @@ -422,7 +427,9 @@ mod tests { // Connect by sending a ping. udp_testbench - .send_request(&SimRequest::new_with_epoch_time(SimCtrlRequest::Ping)) + .send_request(&SimRequestWithTime::new_with_epoch_time( + SimCtrlRequest::Ping, + )) .expect("sending request failed"); std::thread::sleep(Duration::from_millis(SERVER_WAIT_TIME_MS)); diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index a059784..14cc6ae 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -2,11 +2,9 @@ use satrs::fdir::{FaultCounterStd, FaultResponse, RecoveryEvent, RecoveryFdir}; use satrs::health::HealthTableMapSync; use satrs::spacepackets::CcsdsPacketIdAndPsc; use satrs_example::{HkHelperSingleSet, TimestampHelper, TmtcQueues}; -use satrs_minisim::acs::mgm::{ - FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, MgmReplyWrapper, RawValues, -}; -use satrs_minisim::acs::{MgmRequestLis3Mdl, MgmRequestLis3MdlMgm0, MgmRequestLis3MdlMgm1}; -use satrs_minisim::{SimReply, SimRequest}; +use satrs_minisim::acs::mgm as sim_mgm; +use satrs_minisim::acs::mgm::{FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR}; +use satrs_minisim::{SimReply, SimRequest, SimRequestWithTime}; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -76,7 +74,7 @@ impl MgmId { #[derive(Default)] pub struct SpiDummyInterface { - pub dummy_values: RawValues, + pub dummy_values: sim_mgm::RawValues, } impl SpiDummyInterface { @@ -90,7 +88,7 @@ impl SpiDummyInterface { #[derive(Default)] pub struct TestSpiInterface { pub call_count: u32, - pub next_mgm_data: RawValues, + pub next_mgm_data: sim_mgm::RawValues, } impl TestSpiInterface { @@ -104,26 +102,33 @@ impl TestSpiInterface { pub struct SpiSimInterface { pub id: MgmId, - pub sim_request_tx: mpsc::Sender, + pub sim_request_tx: mpsc::Sender, pub sim_reply_rx: mpsc::Receiver, } impl SpiSimInterface { // Right now, we only support requesting sensor data and not configuration of the sensor. fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) { - let mgm_sensor_request = MgmRequestLis3Mdl::RequestSensorData; - let sim_request = match self.id { - MgmId::_0 => SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm0(mgm_sensor_request)), - MgmId::_1 => SimRequest::new_with_epoch_time(MgmRequestLis3MdlMgm1(mgm_sensor_request)), + let sim_id = match self.id { + MgmId::_0 => sim_mgm::Id::Mgm0, + MgmId::_1 => sim_mgm::Id::Mgm1, }; + let sim_request = SimRequestWithTime::new_with_epoch_time(SimRequest::Mgm { + id: sim_id, + request: sim_mgm::Request::RequestSensorData, + }); if let Err(e) = self.sim_request_tx.send(sim_request) { log::error!("failed to send MGM LIS3 request: {e}"); } match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) { Ok(sim_reply) => { - let sim_reply_lis3 = MgmReplyWrapper::from_sim_reply(&sim_reply) - .expect("failed to parse LIS3 reply") - .reply; + let sim_reply_lis3 = match sim_reply { + SimReply::Mgm { id, reply } if id == sim_id => reply, + _ => { + log::warn!("unexpected MGM LIS3 SIM reply: {sim_reply:?}"); + return; + } + }; rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2] .copy_from_slice(&sim_reply_lis3.raw.x.to_le_bytes()); rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2] @@ -647,7 +652,7 @@ mod tests { use arbitrary_int::u11; use satrs::health::{HealthState, HealthTableProvider}; use satrs::spacepackets::SpacePacketHeader; - use satrs_minisim::acs::mgm::RawValues; + use satrs_minisim::acs::mgm as sim_mgm; use types::{ Apid, ComponentId, TcHeader, acs::mgm::request::HkRequest, @@ -766,7 +771,7 @@ mod tests { } pub fn inject_stuck_bus(&mut self) { - self.test_spi_interface().next_mgm_data = RawValues { + self.test_spi_interface().next_mgm_data = sim_mgm::RawValues { x: -1, y: -1, z: -1, @@ -907,7 +912,7 @@ mod tests { #[test] fn test_normal_handler_mgm_set_conversion() { let mut testbench = MgmTestbench::new(); - let raw_values = RawValues { + let raw_values = sim_mgm::RawValues { x: 1000, y: -1000, z: 1000, @@ -1060,7 +1065,7 @@ mod tests { fn test_spi_fault_below_threshold_stays_healthy() { let mut testbench = MgmTestbench::new(); testbench.switch_to_normal(); - testbench.test_spi_interface().next_mgm_data = RawValues { + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues { x: -1, y: -1, z: -1, @@ -1101,7 +1106,7 @@ mod tests { testbench.drain_switch_requests(); testbench.mode_report_rx.try_iter().for_each(drop); testbench.exceed_spi_fault_threshold(); - testbench.test_spi_interface().next_mgm_data = RawValues::default(); + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues::default(); let call_count = testbench.test_spi_interface().call_count; testbench.complete_power_cycle(); @@ -1274,7 +1279,7 @@ mod tests { testbench.drain_events(); testbench.mode_report_rx.try_iter().for_each(drop); testbench.exceed_spi_fault_threshold(); - testbench.test_spi_interface().next_mgm_data = RawValues::default(); + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues::default(); // The switch never turns off. Every failed power cycle costs a recovery attempt. for _ in 0..RECOVERY_THRESHOLD { @@ -1312,7 +1317,7 @@ mod tests { let mut testbench = MgmTestbench::new(); testbench.switch_to_normal(); testbench.exceed_spi_fault_threshold(); - testbench.test_spi_interface().next_mgm_data = RawValues::default(); + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues::default(); testbench .tc_tx .send(create_request_tc( @@ -1377,7 +1382,7 @@ mod tests { testbench .health_table .set_health(ComponentId::AcsMgm0.into(), HealthState::ExternalControl); - testbench.test_spi_interface().next_mgm_data = RawValues { + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues { x: -1, y: -1, z: -1, @@ -1396,7 +1401,7 @@ mod tests { fn test_recovering_from_spi_fault_clears_invalid_data_flag() { let mut testbench = MgmTestbench::new(); testbench.switch_to_normal(); - testbench.test_spi_interface().next_mgm_data = RawValues { + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues { x: -1, y: -1, z: -1, @@ -1405,7 +1410,7 @@ mod tests { assert!(!testbench.handler.shared_mgm_set.lock().unwrap().valid); // Bus recovers before the threshold is exceeded. - testbench.test_spi_interface().next_mgm_data = RawValues::default(); + testbench.test_spi_interface().next_mgm_data = sim_mgm::RawValues::default(); testbench.handler.periodic_operation(); assert_eq!( testbench.health_table.health(ComponentId::AcsMgm0.into()), diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index d9e8842..135591b 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -9,7 +9,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use satrs::spacepackets::CcsdsPacketIdAndPsc; use satrs_example::TimestampHelper; use satrs_minisim::{ - SerializableSimMsgPayload, SimReply, SimRequest, + SimReply, SimRequestWithTime, eps::{PcduReply, PcduRequest}, }; use serde::{Deserialize, Serialize}; @@ -120,7 +120,7 @@ pub trait SerialInterface { #[derive(new)] pub struct SerialInterfaceToSim { - pub sim_request_tx: mpsc::Sender, + pub sim_request_tx: mpsc::Sender, pub sim_reply_rx: mpsc::Receiver, } @@ -136,7 +136,7 @@ impl SerialInterface for SerialInterfaceToSim { fn send(&self, data: &[u8]) -> Result<(), Self::Error> { let request: PcduRequest = serde_json::from_slice(data).expect("expected a PCDU request"); self.sim_request_tx - .send(SimRequest::new_with_epoch_time(request)) + .send(SimRequestWithTime::new_with_epoch_time(request)) .expect("failed to send request to simulation"); Ok(()) } @@ -190,7 +190,7 @@ impl SerialInterface for SerialInterfaceDummy { } PcduRequest::RequestSwitchInfo => { let mut reply_deque_mut = self.reply_deque.borrow_mut(); - reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo( + reply_deque_mut.push_back(SimReply::from(PcduReply::SwitchInfo( switch_map_mut.clone(), ))); } @@ -510,7 +510,10 @@ impl PcduHandler { pub fn poll_and_handle_replies(&mut self) { if let Err(e) = self.com_interface.try_recv_replies(|reply| { let sim_reply: SimReply = serde_json::from_slice(reply).expect("invalid reply format"); - let pcdu_reply = PcduReply::from_sim_message(&sim_reply).expect("invalid reply format"); + let SimReply::Pcdu(pcdu_reply) = sim_reply else { + log::warn!("unexpected PCDU SIM reply: {sim_reply:?}"); + return; + }; match pcdu_reply { PcduReply::SwitchInfo(switch_info) => { let switch_map_wrapper = @@ -674,8 +677,10 @@ mod tests { assert_eq!(reply_received_mut.len(), expected_queue_len); let reply_received = reply_received_mut.pop_front().unwrap(); let sim_reply: SimReply = serde_json::from_str(&reply_received).unwrap(); - let pcdu_reply = PcduReply::from_sim_message(&sim_reply).unwrap(); - assert_eq!(pcdu_reply, PcduReply::SwitchInfo(expected_map)); + assert_eq!( + sim_reply, + SimReply::Pcdu(PcduReply::SwitchInfo(expected_map)) + ); } } diff --git a/satrs-example/src/interface/sim_client_udp.rs b/satrs-example/src/interface/sim_client_udp.rs index 87b2d50..5b4ddbb 100644 --- a/satrs-example/src/interface/sim_client_udp.rs +++ b/satrs-example/src/interface/sim_client_udp.rs @@ -6,15 +6,14 @@ use std::{ }; use satrs::HandlingStatus; -use satrs_minisim::{ - SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimReply, SimRequest, - udp::SIM_CTRL_PORT, -}; +use satrs_minisim::{ComponentId, SimReply, SimRequestWithTime, udp::SIM_CTRL_PORT}; use satrs_minisim::{SimCtrlReply, SimCtrlRequest}; -struct SimReplyMap(pub HashMap>); +struct SimReplyMap(pub HashMap>); -pub fn create_sim_client(sim_request_rx: mpsc::Receiver) -> Option { +pub fn create_sim_client( + sim_request_rx: mpsc::Receiver, +) -> Option { match SimClientUdp::new( SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, SIM_CTRL_PORT)), sim_request_rx, @@ -45,7 +44,7 @@ pub enum SimClientCreationError { pub struct SimClientUdp { udp_client: UdpSocket, simulator_addr: SocketAddr, - sim_request_rx: mpsc::Receiver, + sim_request_rx: mpsc::Receiver, reply_map: SimReplyMap, reply_buf: [u8; 4096], } @@ -53,7 +52,7 @@ pub struct SimClientUdp { impl SimClientUdp { pub fn new( simulator_addr: SocketAddr, - sim_request_rx: mpsc::Receiver, + sim_request_rx: mpsc::Receiver, ) -> Result { let mut reply_buf: [u8; 4096] = [0; 4096]; let mut udp_client = UdpSocket::bind("127.0.0.1:0")?; @@ -74,22 +73,15 @@ impl SimClientUdp { simulator_addr: SocketAddr, reply_buf: &mut [u8], ) -> Result<(), SimClientCreationError> { - let sim_req = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping); + let sim_req = SimRequestWithTime::new_with_epoch_time(SimCtrlRequest::Ping); let sim_req_json = serde_json::to_string(&sim_req).expect("failed to serialize SimRequest"); udp_client.send_to(sim_req_json.as_bytes(), simulator_addr)?; match udp_client.recv(reply_buf) { Ok(reply_len) => { let sim_reply: SimReply = serde_json::from_slice(&reply_buf[0..reply_len])?; - if sim_reply.component() != SimComponent::SimCtrl { - return Err(SimClientCreationError::ReplyIsNotPong(sim_reply)); - } - let sim_ctrl_reply = - SimCtrlReply::from_sim_message(&sim_reply).expect("invalid SIM reply"); - match sim_ctrl_reply { - SimCtrlReply::InvalidRequest(_) => { - panic!("received invalid request reply from UDP sim server") - } - SimCtrlReply::Pong => Ok(()), + match sim_reply { + SimReply::SimCtrl(SimCtrlReply::Pong) => Ok(()), + _ => Err(SimClientCreationError::ReplyIsNotPong(sim_reply)), } } Err(e) => { @@ -174,7 +166,7 @@ impl SimClientUdp { pub fn add_reply_recipient( &mut self, - component: SimComponent, + component: ComponentId, reply_sender: mpsc::Sender, ) { self.reply_map.0.insert(component, reply_sender); @@ -195,8 +187,7 @@ pub mod tests { }; use satrs_minisim::{ - SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider, - SimReply, SimRequest, + ComponentId, SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, eps::{PcduReply, PcduRequest}, }; @@ -204,7 +195,7 @@ pub mod tests { struct UdpSimTestServer { udp_server: UdpSocket, - request_tx: mpsc::Sender, + request_tx: mpsc::Sender, reply_rx: mpsc::Receiver, last_sender: Option, stop_signal: Arc, @@ -213,7 +204,7 @@ pub mod tests { impl UdpSimTestServer { pub fn new( - request_tx: mpsc::Sender, + request_tx: mpsc::Sender, reply_rx: mpsc::Receiver, stop_signal: Arc, ) -> Self { @@ -262,28 +253,19 @@ pub mod tests { loop { match self.udp_server.recv_from(&mut self.recv_buf) { Ok((read_bytes, from)) => { - let sim_request: SimRequest = + let sim_request: SimRequestWithTime = serde_json::from_slice(&self.recv_buf[0..read_bytes]) .expect("failed to deserialize SimRequest"); - if sim_request.component() == SimComponent::SimCtrl { - // For a ping, we perform the reply handling here directly - let sim_ctrl_request = - SimCtrlRequest::from_sim_message(&sim_request) - .expect("failed to convert SimRequest to SimCtrlRequest"); - match sim_ctrl_request { - SimCtrlRequest::Ping => { - no_data_received = false; - self.last_sender = Some(from); - let sim_reply = SimReply::new(&SimCtrlReply::Pong); - let sim_reply_json = serde_json::to_string(&sim_reply) - .expect("failed to serialize SimReply"); - self.udp_server - .send_to(sim_reply_json.as_bytes(), from) - .expect( - "failed to send reply to client from UDP server", - ); - } - }; + // For a ping, we perform the reply handling here directly + if sim_request.request == SimRequest::SimCtrl(SimCtrlRequest::Ping) { + no_data_received = false; + self.last_sender = Some(from); + let sim_reply = SimReply::from(SimCtrlReply::Pong); + let sim_reply_json = serde_json::to_string(&sim_reply) + .expect("failed to serialize SimReply"); + self.udp_server + .send_to(sim_reply_json.as_bytes(), from) + .expect("failed to send reply to client from UDP server"); } // Forward each SIM request for testing purposes. self.request_tx @@ -332,9 +314,10 @@ pub mod tests { let sim_request = server_sim_request_rx .recv_timeout(Duration::from_millis(50)) .expect("no SIM request received"); - let ping_request = SimCtrlRequest::from_sim_message(&sim_request) - .expect("failed to create SimCtrlRequest"); - assert_eq!(ping_request, SimCtrlRequest::Ping); + assert_eq!( + sim_request.request, + SimRequest::SimCtrl(SimCtrlRequest::Ping) + ); // Stop the server. stop_signal.store(true, Ordering::Relaxed); jh0.join().unwrap(); @@ -360,18 +343,19 @@ pub mod tests { // Creating the client also performs the connection test. let mut client = SimClientUdp::new(server_addr, client_sim_req_rx).unwrap(); - client.add_reply_recipient(SimComponent::Pcdu, client_pcdu_reply_tx); + client.add_reply_recipient(ComponentId::Pcdu, client_pcdu_reply_tx); let sim_request = server_sim_request_rx .recv_timeout(Duration::from_millis(50)) .expect("no SIM request received"); - let ping_request = SimCtrlRequest::from_sim_message(&sim_request) - .expect("failed to create SimCtrlRequest"); - assert_eq!(ping_request, SimCtrlRequest::Ping); + assert_eq!( + sim_request.request, + SimRequest::SimCtrl(SimCtrlRequest::Ping) + ); let pcdu_req = PcduRequest::RequestSwitchInfo; client_sim_req_tx - .send(SimRequest::new_with_epoch_time(pcdu_req)) + .send(SimRequestWithTime::new_with_epoch_time(pcdu_req)) .expect("send failed"); client.operation(); @@ -379,14 +363,15 @@ pub mod tests { let sim_request = server_sim_request_rx .recv_timeout(Duration::from_millis(50)) .expect("no SIM request received"); - let req_recvd_on_server = - PcduRequest::from_sim_message(&sim_request).expect("failed to create SimCtrlRequest"); - matches!(req_recvd_on_server, PcduRequest::RequestSwitchInfo); + assert_eq!( + sim_request.request, + SimRequest::Pcdu(PcduRequest::RequestSwitchInfo) + ); // We inject the reply ourselves. let pcdu_reply = PcduReply::SwitchInfo(HashMap::new()); server_sim_reply_tx - .send(SimReply::new(&pcdu_reply)) + .send(SimReply::from(pcdu_reply.clone())) .expect("sending PCDU reply failed"); // Now we verify that the reply is sent by the UDP server back to the client, and then @@ -397,10 +382,8 @@ pub mod tests { match client_pcdu_reply_rx.try_recv() { Ok(sim_reply) => { - assert_eq!(sim_reply.component(), SimComponent::Pcdu); - let pcdu_reply_from_client = PcduReply::from_sim_message(&sim_reply) - .expect("failed to create PcduReply"); - assert_eq!(pcdu_reply_from_client, pcdu_reply); + assert_eq!(sim_reply.component(), ComponentId::Pcdu); + assert_eq!(sim_reply, SimReply::Pcdu(pcdu_reply.clone())); pcdu_reply_received = true; break; } diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 8b56378..900498a 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -173,30 +173,29 @@ fn main() { let shared_mgm_0_set = Arc::default(); let shared_mgm_1_set = Arc::default(); - let (mgm_0_spi_interface, mgm_1_spi_interface) = - if let Some(sim_client) = opt_sim_client.as_mut() { - sim_client - .add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx); - sim_client - .add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx); - ( - mgm::SpiCommunication::Sim(mgm::SpiSimInterface { - id: mgm::MgmId::_0, - sim_request_tx: sim_request_tx.clone(), - sim_reply_rx: mgm_0_sim_reply_rx, - }), - mgm::SpiCommunication::Sim(mgm::SpiSimInterface { - id: mgm::MgmId::_1, - sim_request_tx: sim_request_tx.clone(), - sim_reply_rx: mgm_1_sim_reply_rx, - }), - ) - } else { - ( - mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), - mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), - ) - }; + let (mgm_0_spi_interface, mgm_1_spi_interface) = if let Some(sim_client) = + opt_sim_client.as_mut() + { + sim_client.add_reply_recipient(satrs_minisim::ComponentId::Mgm0Lis3Mdl, mgm_0_sim_reply_tx); + sim_client.add_reply_recipient(satrs_minisim::ComponentId::Mgm1Lis3Mdl, mgm_1_sim_reply_tx); + ( + mgm::SpiCommunication::Sim(mgm::SpiSimInterface { + id: mgm::MgmId::_0, + sim_request_tx: sim_request_tx.clone(), + sim_reply_rx: mgm_0_sim_reply_rx, + }), + mgm::SpiCommunication::Sim(mgm::SpiSimInterface { + id: mgm::MgmId::_1, + sim_request_tx: sim_request_tx.clone(), + sim_reply_rx: mgm_1_sim_reply_rx, + }), + ) + } else { + ( + mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), + mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), + ) + }; let mut mgm_0_handler = mgm::MgmHandlerLis3Mdl::new( mgm::MgmId::_0, TmtcQueues { @@ -276,7 +275,7 @@ fn main() { ); let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() { - sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx); + sim_client.add_reply_recipient(satrs_minisim::ComponentId::Pcdu, pcdu_sim_reply_tx); SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new( sim_request_tx.clone(), pcdu_sim_reply_rx,