diff --git a/satrs-example/minisim/Cargo.toml b/satrs-example/minisim/Cargo.toml index ab835ab..c654647 100644 --- a/satrs-example/minisim/Cargo.toml +++ b/satrs-example/minisim/Cargo.toml @@ -15,7 +15,7 @@ strum = { version = "0.28", features = ["derive"] } num_enum = "0.7" humantime = "2" tai-time = { version = "0.3", features = ["serde"] } -nexosim = { version = "0.3.1" } +nexosim = "1" satrs = { path = "../../satrs" } types = { path = "../types" } diff --git a/satrs-example/minisim/src/acs/mgm.rs b/satrs-example/minisim/src/acs/mgm.rs index 4684f66..e0573b1 100644 --- a/satrs-example/minisim/src/acs/mgm.rs +++ b/satrs-example/minisim/src/acs/mgm.rs @@ -1,7 +1,11 @@ -use std::{f32::consts::PI, sync::mpsc}; +use std::f32::consts::PI; -use nexosim::model::{Context, Model}; +use nexosim::{ + model::{Context, Model}, + ports::Output, +}; use satrs_minisim::{acs::mgm, SimReply}; +use serde::{Deserialize, Serialize}; use types::pcdu::SwitchStateBinary; use crate::time::current_millis; @@ -19,22 +23,24 @@ 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. +#[derive(Serialize, Deserialize)] pub struct MgmModel { - pub id: mgm::Id, - pub switch_state: SwitchStateBinary, - pub external_mag_field: Option, - pub spi_fault: mgm::SpiFault, - pub reply_sender: mpsc::Sender, + id: mgm::Id, + switch_state: SwitchStateBinary, + external_mag_field: Option, + spi_fault: mgm::SpiFault, + pub reply: Output, } +#[Model] impl MgmModel { - pub fn new(mgm_id: mgm::Id, reply_sender: mpsc::Sender) -> Self { + pub fn new(mgm_id: mgm::Id) -> Self { Self { id: mgm_id, switch_state: SwitchStateBinary::Off, external_mag_field: None, spi_fault: mgm::SpiFault::default(), - reply_sender, + reply: Output::new(), } } @@ -50,18 +56,16 @@ impl MgmModel { self.spi_fault = fault; } - pub async fn send_sensor_values(&mut self, _: (), scheduler: &mut Context) { + pub async fn send_sensor_values(&mut self, _: (), cx: &Context) { let reply = SimReply::Mgm { id: self.id, reply: mgm::Reply::new( self.switch_state, - self.calculate_current_mgm_tuple(current_millis(scheduler.time())), + self.calculate_current_mgm_tuple(current_millis(cx.time())), self.spi_fault.mode, ), }; - self.reply_sender - .send(reply) - .expect("sending MGM sensor values failed"); + self.reply.send(reply).await; } // Devices like magnetorquers generate a strong magnetic field which overrides the default @@ -94,8 +98,6 @@ impl MgmModel { } } -impl Model for MgmModel {} - #[cfg(test)] mod tests { use std::time::Duration; diff --git a/satrs-example/minisim/src/acs/mgt.rs b/satrs-example/minisim/src/acs/mgt.rs index dbd9b96..d9e98e7 100644 --- a/satrs-example/minisim/src/acs/mgt.rs +++ b/satrs-example/minisim/src/acs/mgt.rs @@ -1,45 +1,48 @@ use nexosim::{ - model::{Context, Model}, + model::{schedulable, Context, Model}, ports::Output, }; use satrs_minisim::{ acs::{mgm, mgt}, SimReply, }; -use std::{sync::mpsc, time::Duration}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; use types::pcdu::SwitchStateBinary; /// Simple magnetorquer simulation model. +#[derive(Serialize, Deserialize)] pub struct MgtModel { switch_state: SwitchStateBinary, torquing: bool, torque_dipole: mgt::Dipole, pub gen_magnetic_field: Output, pub clear_magnetic_field: Output<()>, - reply_sender: mpsc::Sender, + pub reply: Output, } +#[Model] impl MgtModel { - pub fn new(reply_sender: mpsc::Sender) -> Self { + pub fn new() -> Self { Self { switch_state: SwitchStateBinary::Off, torquing: false, torque_dipole: mgt::Dipole::default(), gen_magnetic_field: Output::new(), clear_magnetic_field: Output::new(), - reply_sender, + reply: Output::new(), } } pub async fn apply_torque( &mut self, duration_and_dipole: (Duration, mgt::Dipole), - cx: &mut Context, + cx: &Context, ) { self.torque_dipole = duration_and_dipole.1; self.torquing = true; if cx - .schedule_event(duration_and_dipole.0, Self::clear_torque, ()) + .schedule_event(duration_and_dipole.0, schedulable!(Self::clear_torque), ()) .is_err() { log::warn!("torque clearing can only be set for a future time."); @@ -47,7 +50,8 @@ impl MgtModel { self.generate_magnetic_field(()).await; } - pub async fn clear_torque(&mut self, _: ()) { + #[nexosim(schedulable)] + async fn clear_torque(&mut self) { self.torque_dipole = mgt::Dipole::default(); self.torquing = false; self.clear_magnetic_field.send(()).await; @@ -57,25 +61,30 @@ impl MgtModel { self.switch_state = switch_state; match switch_state { SwitchStateBinary::On => self.generate_magnetic_field(()).await, - SwitchStateBinary::Off => self.clear_torque(()).await, + SwitchStateBinary::Off => self.clear_torque().await, } } - pub async fn request_housekeeping_data(&mut self, _: (), cx: &mut Context) { + pub async fn request_housekeeping_data(&mut self, _: (), cx: &Context) { if self.switch_state != SwitchStateBinary::On { return; } - cx.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ()) - .expect("requesting housekeeping data failed") + cx.schedule_event( + Duration::from_millis(15), + schedulable!(Self::send_housekeeping_data), + (), + ) + .expect("requesting housekeeping data failed") } - pub fn send_housekeeping_data(&mut self) { - self.reply_sender + #[nexosim(schedulable)] + async fn send_housekeeping_data(&mut self) { + self.reply .send(SimReply::from(mgt::Reply::Hk(mgt::HkSet { dipole: self.torque_dipole, torquing: self.torquing, }))) - .unwrap(); + .await; } fn calc_magnetic_field(&self, _: mgt::Dipole) -> mgm::SensorValuesMicroTesla { @@ -96,8 +105,6 @@ impl MgtModel { } } -impl Model for MgtModel {} - #[cfg(test)] mod tests { use std::time::Duration; diff --git a/satrs-example/minisim/src/controller.rs b/satrs-example/minisim/src/controller.rs index 80cdd66..8346680 100644 --- a/satrs-example/minisim/src/controller.rs +++ b/satrs-example/minisim/src/controller.rs @@ -4,14 +4,16 @@ use std::{ }; use nexosim::{ - simulation::{Address, Mailbox, SimInit, Simulation}, - time::{Clock, MonotonicTime, SystemClock}, + ports::{event_queue, EventQueueReader, EventSinkReader, EventSource, SinkState}, + simulation::{EventId, ExecutionError, Mailbox, SimInit, Simulation}, + time::{Clock, Deadline, MonotonicTime, SystemClock}, }; use satrs_minisim::{ acs::{mgm, mgt}, eps::PcduRequest, SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, }; +use types::pcdu::{SwitchId, SwitchStateBinary}; use crate::{ acs::{mgm::MgmModel, mgt::MgtModel}, @@ -31,11 +33,32 @@ pub enum ThreadingModel { Single = 1, } -struct ModelAddresses { - mgm_0: Address, - mgm_1: Address, - pcdu: Address, - mgt: Address, +struct MgmInputs { + send_sensor_values: EventId<()>, + set_spi_fault: EventId, +} + +impl MgmInputs { + fn register(sim_init: &mut SimInit, mailbox: &Mailbox) -> Self { + Self { + send_sensor_values: EventSource::new() + .connect(MgmModel::send_sensor_values, mailbox) + .register(sim_init), + set_spi_fault: EventSource::new() + .connect(MgmModel::set_spi_fault, mailbox) + .register(sim_init), + } + } +} + +/// Model inputs which are driven by simulation requests. +struct ModelInputs { + mgm_0: MgmInputs, + mgm_1: MgmInputs, + pcdu_request_switch_info: EventId<()>, + pcdu_switch_device: EventId<(SwitchId, SwitchStateBinary)>, + mgt_apply_torque: EventId<(Duration, mgt::Dipole)>, + mgt_request_hk: EventId<()>, } // The simulation controller processes requests and drives the simulation. @@ -43,8 +66,9 @@ pub struct SimController { sys_clock: SystemClock, request_receiver: mpsc::Receiver, reply_sender: mpsc::Sender, - pub simulation: Simulation, - addrs: ModelAddresses, + simulation: Simulation, + inputs: ModelInputs, + model_replies: EventQueueReader, } impl SimController { @@ -54,50 +78,66 @@ impl SimController { reply_sender: mpsc::Sender, 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 mut mgm_0_model = MgmModel::new(mgm::Id::Mgm0); + let mut mgm_1_model = MgmModel::new(mgm::Id::Mgm1); + let mut pcdu_model = PcduModel::new(); + let mut mgt_model = MgtModel::new(); 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); + .connect(MgmModel::switch_device, &mgm_0_mailbox); pcdu_model .mgm_1_switch - .connect(MgmModel::switch_device, &addrs.mgm_1); + .connect(MgmModel::switch_device, &mgm_1_mailbox); pcdu_model .mgt_switch - .connect(MgtModel::switch_device, &addrs.mgt); + .connect(MgtModel::switch_device, &mgt_mailbox); mgt_model .gen_magnetic_field - .connect(MgmModel::apply_external_magnetic_field, &addrs.mgm_0); + .connect(MgmModel::apply_external_magnetic_field, &mgm_0_mailbox); mgt_model .gen_magnetic_field - .connect(MgmModel::apply_external_magnetic_field, &addrs.mgm_1); + .connect(MgmModel::apply_external_magnetic_field, &mgm_1_mailbox); mgt_model .clear_magnetic_field - .connect(MgmModel::clear_external_magnetic_field, &addrs.mgm_0); + .connect(MgmModel::clear_external_magnetic_field, &mgm_0_mailbox); mgt_model .clear_magnetic_field - .connect(MgmModel::clear_external_magnetic_field, &addrs.mgm_1); + .connect(MgmModel::clear_external_magnetic_field, &mgm_1_mailbox); - let sim_init = if threading_model == ThreadingModel::Single { + let (reply_sink, model_replies) = event_queue(SinkState::Enabled); + mgm_0_model.reply.connect_sink(reply_sink.clone()); + mgm_1_model.reply.connect_sink(reply_sink.clone()); + pcdu_model.reply.connect_sink(reply_sink.clone()); + mgt_model.reply.connect_sink(reply_sink); + + let mut sim_init = if threading_model == ThreadingModel::Single { SimInit::with_num_threads(1) } else { SimInit::new() }; - let (simulation, _scheduler) = sim_init + let inputs = ModelInputs { + mgm_0: MgmInputs::register(&mut sim_init, &mgm_0_mailbox), + mgm_1: MgmInputs::register(&mut sim_init, &mgm_1_mailbox), + pcdu_request_switch_info: EventSource::new() + .connect(PcduModel::request_switch_info, &pcdu_mailbox) + .register(&mut sim_init), + pcdu_switch_device: EventSource::new() + .connect(PcduModel::switch_device, &pcdu_mailbox) + .register(&mut sim_init), + mgt_apply_torque: EventSource::new() + .connect(MgtModel::apply_torque, &mgt_mailbox) + .register(&mut sim_init), + mgt_request_hk: EventSource::new() + .connect(MgtModel::request_housekeeping_data, &mgt_mailbox) + .register(&mut sim_init), + }; + let simulation = 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") @@ -109,7 +149,29 @@ impl SimController { request_receiver, reply_sender, simulation, - addrs, + inputs, + model_replies, + } + } + + #[cfg(test)] + pub fn step(&mut self) -> Result<(), ExecutionError> { + self.simulation.step()?; + self.forward_model_replies(); + Ok(()) + } + + pub fn step_until(&mut self, deadline: impl Deadline) -> Result<(), ExecutionError> { + self.simulation.step_until(deadline)?; + self.forward_model_replies(); + Ok(()) + } + + fn forward_model_replies(&mut self) { + while let Some(reply) = self.model_replies.try_read() { + self.reply_sender + .send(reply) + .expect("sending model reply failed"); } } @@ -122,9 +184,7 @@ impl SimController { t += Duration::from_millis(udp_polling_interval_ms); let _synch_status = self.sys_clock.synchronize(t); self.handle_sim_requests(t_old); - self.simulation - .step_until(t) - .expect("simulation step failed"); + self.step_until(t).expect("simulation step failed"); } } @@ -150,6 +210,7 @@ impl SimController { }, } } + self.forward_model_replies(); } fn handle_ctrl_request(&mut self, sim_ctrl_request: SimCtrlRequest) { @@ -167,9 +228,9 @@ impl SimController { } 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, + let inputs = match mgm_id { + mgm::Id::Mgm0 => &self.inputs.mgm_0, + mgm::Id::Mgm1 => &self.inputs.mgm_1, }; if MGM_REQ_WIRETAPPING { log::info!("received {mgm_id:?} request: {mgm_request:?}"); @@ -177,13 +238,13 @@ impl SimController { match mgm_request { mgm::Request::RequestSensorData => { self.simulation - .process_event(MgmModel::send_sensor_values, (), addr) + .process_event(&inputs.send_sensor_values, ()) .expect("event execution error for mgm"); } mgm::Request::SetSpiFault(fault_mode) => { log::info!("{mgm_id:?}: setting SPI fault mode to {fault_mode:?}"); self.simulation - .process_event(MgmModel::set_spi_fault, fault_mode, addr) + .process_event(&inputs.set_spi_fault, fault_mode) .expect("event execution error for mgm"); } } @@ -196,12 +257,12 @@ impl SimController { match pcdu_request { PcduRequest::RequestSwitchInfo => { self.simulation - .process_event(PcduModel::request_switch_info, (), &self.addrs.pcdu) + .process_event(&self.inputs.pcdu_request_switch_info, ()) .unwrap(); } PcduRequest::SwitchDevice { switch, state } => { self.simulation - .process_event(PcduModel::switch_device, (switch, state), &self.addrs.pcdu) + .process_event(&self.inputs.pcdu_switch_device, (switch, state)) .unwrap(); } } @@ -214,11 +275,11 @@ impl SimController { match mgt_request { mgt::Request::ApplyTorque { duration, dipole } => self .simulation - .process_event(MgtModel::apply_torque, (duration, dipole), &self.addrs.mgt) + .process_event(&self.inputs.mgt_apply_torque, (duration, dipole)) .unwrap(), mgt::Request::RequestHk => self .simulation - .process_event(MgtModel::request_housekeeping_data, (), &self.addrs.mgt) + .process_event(&self.inputs.mgt_request_hk, ()) .unwrap(), }; } diff --git a/satrs-example/minisim/src/eps.rs b/satrs-example/minisim/src/eps.rs index 97d2149..e07b7da 100644 --- a/satrs-example/minisim/src/eps.rs +++ b/satrs-example/minisim/src/eps.rs @@ -1,45 +1,49 @@ -use std::{sync::mpsc, time::Duration}; +use std::time::Duration; use nexosim::{ - model::{Context, Model}, + model::{schedulable, Context, Model}, ports::Output, }; use satrs_minisim::{eps::PcduReply, SimReply}; -use types::pcdu::{SwitchId, SwitchMapBinaryWrapper, SwitchStateBinary}; +use serde::{Deserialize, Serialize}; +use types::pcdu::{SwitchId, SwitchMapBinary, SwitchMapBinaryWrapper, SwitchStateBinary}; pub const SWITCH_INFO_DELAY_MS: u64 = 10; +#[derive(Serialize, Deserialize)] pub struct PcduModel { - pub switcher_map: SwitchMapBinaryWrapper, + switcher_map: SwitchMapBinary, pub mgm_0_switch: Output, pub mgm_1_switch: Output, pub mgt_switch: Output, - pub reply_sender: mpsc::Sender, + pub reply: Output, } +#[Model] impl PcduModel { - pub fn new(reply_sender: mpsc::Sender) -> Self { + pub fn new() -> Self { Self { - switcher_map: Default::default(), + switcher_map: SwitchMapBinaryWrapper::default().0, mgm_0_switch: Output::new(), mgm_1_switch: Output::new(), mgt_switch: Output::new(), - reply_sender, + reply: Output::new(), } } - pub async fn request_switch_info(&mut self, _: (), cx: &mut Context) { + pub async fn request_switch_info(&mut self, _: (), cx: &Context) { cx.schedule_event( Duration::from_millis(SWITCH_INFO_DELAY_MS), - Self::send_switch_info, + schedulable!(Self::send_switch_info), (), ) .expect("requesting switch info failed"); } - pub fn send_switch_info(&mut self) { - let reply = SimReply::from(PcduReply::SwitchInfo(self.switcher_map.0.clone())); - self.reply_sender.send(reply).unwrap(); + #[nexosim(schedulable)] + async fn send_switch_info(&mut self) { + let reply = SimReply::from(PcduReply::SwitchInfo(self.switcher_map.clone())); + self.reply.send(reply).await; } pub async fn switch_device(&mut self, switch_and_target_state: (SwitchId, SwitchStateBinary)) { @@ -50,7 +54,6 @@ impl PcduModel { ); let val = self .switcher_map - .0 .get_mut(&switch_and_target_state.0) .unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0)); *val = switch_and_target_state.1; @@ -68,8 +71,6 @@ impl PcduModel { } } -impl Model for PcduModel {} - #[cfg(test)] pub(crate) mod tests { use super::*; diff --git a/satrs-example/minisim/src/test_helpers.rs b/satrs-example/minisim/src/test_helpers.rs index 47a06bc..ca579df 100644 --- a/satrs-example/minisim/src/test_helpers.rs +++ b/satrs-example/minisim/src/test_helpers.rs @@ -36,8 +36,6 @@ impl SimTestbench { delegate! { to self.sim_controller { pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime); - } - to self.sim_controller.simulation { pub fn step(&mut self) -> Result<(), ExecutionError>; pub fn step_until(&mut self, duration: impl Deadline) -> Result<(), ExecutionError>; }