added some more tests
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
This commit is contained in:
parent
55df55a39c
commit
2679815c28
@ -4,9 +4,9 @@ use asynchronix::{
|
|||||||
model::{Model, Output},
|
model::{Model, Output},
|
||||||
time::Scheduler,
|
time::Scheduler,
|
||||||
};
|
};
|
||||||
use satrs::power::{SwitchState, SwitchStateBinary};
|
use satrs::power::SwitchStateBinary;
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{MgmSensorValues, MgtDipole, MGT_GEN_MAGNETIC_FIELD},
|
acs::{MgmSensorValues, MgtDipole, MgtReply, MGT_GEN_MAGNETIC_FIELD},
|
||||||
SimDevice, SimReply,
|
SimDevice, SimReply,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ impl MagnetometerModel {
|
|||||||
if let Some(ext_field) = self.external_mag_field {
|
if let Some(ext_field) = self.external_mag_field {
|
||||||
return ext_field;
|
return ext_field;
|
||||||
}
|
}
|
||||||
let base_sin_val = 2.0 * PI as f32 * FREQUENCY_MGM * (time_ms as f32 / 1000.0);
|
let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0);
|
||||||
return MgmSensorValues {
|
return MgmSensorValues {
|
||||||
x: AMPLITUDE_MGM * (base_sin_val + PHASE_X).sin(),
|
x: AMPLITUDE_MGM * (base_sin_val + PHASE_X).sin(),
|
||||||
y: AMPLITUDE_MGM * (base_sin_val + PHASE_Y).sin(),
|
y: AMPLITUDE_MGM * (base_sin_val + PHASE_Y).sin(),
|
||||||
@ -94,23 +94,33 @@ impl MagnetometerModel {
|
|||||||
impl Model for MagnetometerModel {}
|
impl Model for MagnetometerModel {}
|
||||||
|
|
||||||
pub struct MagnetorquerModel {
|
pub struct MagnetorquerModel {
|
||||||
switch_state: SwitchState,
|
switch_state: SwitchStateBinary,
|
||||||
torquing: bool,
|
torquing: bool,
|
||||||
torque_dipole: Option<MgtDipole>,
|
torque_dipole: MgtDipole,
|
||||||
gen_magnetic_field: Output<MgmSensorValues>,
|
pub gen_magnetic_field: Output<MgmSensorValues>,
|
||||||
|
reply_sender: mpsc::Sender<SimReply>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MagnetorquerModel {
|
impl MagnetorquerModel {
|
||||||
|
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||||
|
Self {
|
||||||
|
switch_state: SwitchStateBinary::Off,
|
||||||
|
torquing: false,
|
||||||
|
torque_dipole: MgtDipole::default(),
|
||||||
|
gen_magnetic_field: Output::new(),
|
||||||
|
reply_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn apply_torque(
|
pub async fn apply_torque(
|
||||||
&mut self,
|
&mut self,
|
||||||
dipole: MgtDipole,
|
duration_and_dipole: (Duration, MgtDipole),
|
||||||
torque_duration: Duration,
|
|
||||||
scheduler: &Scheduler<Self>,
|
scheduler: &Scheduler<Self>,
|
||||||
) {
|
) {
|
||||||
self.torque_dipole = Some(dipole);
|
self.torque_dipole = duration_and_dipole.1;
|
||||||
self.torquing = true;
|
self.torquing = true;
|
||||||
if scheduler
|
if scheduler
|
||||||
.schedule_event(torque_duration, Self::clear_torque, ())
|
.schedule_event(duration_and_dipole.0, Self::clear_torque, ())
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
log::warn!("torque clearing can only be set for a future time.");
|
log::warn!("torque clearing can only be set for a future time.");
|
||||||
@ -119,16 +129,32 @@ impl MagnetorquerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_torque(&mut self, _: ()) {
|
pub async fn clear_torque(&mut self, _: ()) {
|
||||||
self.torque_dipole = None;
|
self.torque_dipole = MgtDipole::default();
|
||||||
self.torquing = false;
|
self.torquing = false;
|
||||||
self.generate_magnetic_field(()).await;
|
self.generate_magnetic_field(()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_device(&mut self, switch_state: SwitchState) {
|
pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) {
|
||||||
self.switch_state = switch_state;
|
self.switch_state = switch_state;
|
||||||
self.generate_magnetic_field(()).await;
|
self.generate_magnetic_field(()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn request_housekeeping_data(&mut self, _: (), scheduler: &Scheduler<Self>) {
|
||||||
|
scheduler
|
||||||
|
.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
|
||||||
|
.expect("requesting housekeeping data failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_housekeeping_data(&mut self) {
|
||||||
|
let mgt_reply = MgtReply::Hk(self.torque_dipole);
|
||||||
|
self.reply_sender
|
||||||
|
.send(SimReply {
|
||||||
|
device: SimDevice::Mgt,
|
||||||
|
reply: serde_json::to_string(&mgt_reply).expect("failed to serialize MgtReply"),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValues {
|
fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValues {
|
||||||
// Simplified model: Just returns some fixed magnetic field for now.
|
// Simplified model: Just returns some fixed magnetic field for now.
|
||||||
// Later, we could make this more fancy by incorporating the commanded dipole.
|
// Later, we could make this more fancy by incorporating the commanded dipole.
|
||||||
@ -138,13 +164,101 @@ impl MagnetorquerModel {
|
|||||||
/// A torquing magnetorquer generates a magnetic field. This function can be used to apply
|
/// A torquing magnetorquer generates a magnetic field. This function can be used to apply
|
||||||
/// the magnetic field.
|
/// the magnetic field.
|
||||||
async fn generate_magnetic_field(&mut self, _: ()) {
|
async fn generate_magnetic_field(&mut self, _: ()) {
|
||||||
if self.switch_state != SwitchState::On || !self.torquing {
|
if self.switch_state != SwitchStateBinary::On || !self.torquing {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.gen_magnetic_field
|
self.gen_magnetic_field
|
||||||
.send(self.calc_magnetic_field(self.torque_dipole.expect("expected valid dipole")))
|
.send(self.calc_magnetic_field(self.torque_dipole))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for MagnetorquerModel {}
|
impl Model for MagnetorquerModel {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use satrs::power::SwitchStateBinary;
|
||||||
|
use satrs_minisim::{
|
||||||
|
acs::{MgmRequest, MgmSensorValues},
|
||||||
|
SimDevice, SimRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
eps::{self, PcduRequest},
|
||||||
|
test_helpers::SimTestbench,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_mgm_request() {
|
||||||
|
let mut sim_testbench = SimTestbench::new();
|
||||||
|
let mgm_request = MgmRequest::RequestSensorData;
|
||||||
|
let request = SimRequest::new(SimDevice::Mgm, mgm_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply.is_some());
|
||||||
|
let sim_reply = sim_reply.unwrap();
|
||||||
|
assert_eq!(sim_reply.device, SimDevice::Mgm);
|
||||||
|
let reply: MgmSensorValues = serde_json::from_str(&sim_reply.reply)
|
||||||
|
.expect("failed to deserialize MGM sensor values");
|
||||||
|
assert_eq!(reply.x, 0.0);
|
||||||
|
assert_eq!(reply.y, 0.0);
|
||||||
|
assert_eq!(reply.z, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_mgm_request_switched_on() {
|
||||||
|
let mut sim_testbench = SimTestbench::new();
|
||||||
|
let pcdu_request = PcduRequest::SwitchDevice {
|
||||||
|
switch: eps::PcduSwitch::Mgm,
|
||||||
|
state: SwitchStateBinary::On,
|
||||||
|
};
|
||||||
|
let mut request = SimRequest::new(SimDevice::Pcdu, pcdu_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM switch request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
let mut sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply_res.is_none());
|
||||||
|
|
||||||
|
let mgm_request = MgmRequest::RequestSensorData;
|
||||||
|
request = SimRequest::new(SimDevice::Mgm, mgm_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
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.device, SimDevice::Mgm);
|
||||||
|
let first_reply: MgmSensorValues = serde_json::from_str(&sim_reply.reply)
|
||||||
|
.expect("failed to deserialize MGM sensor values");
|
||||||
|
let mgm_request = MgmRequest::RequestSensorData;
|
||||||
|
sim_testbench.step_by(Duration::from_millis(50));
|
||||||
|
|
||||||
|
request = SimRequest::new(SimDevice::Mgm, mgm_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply_res.is_some());
|
||||||
|
sim_reply = sim_reply_res.unwrap();
|
||||||
|
|
||||||
|
let second_reply: MgmSensorValues = serde_json::from_str(&sim_reply.reply)
|
||||||
|
.expect("failed to deserialize MGM sensor values");
|
||||||
|
// Check that the values are changing.
|
||||||
|
assert!(first_reply != second_reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mgm_request_with_mgt_switched_on() {}
|
||||||
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use std::{
|
use std::{sync::mpsc, time::Duration};
|
||||||
sync::mpsc,
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
use asynchronix::{
|
use asynchronix::{
|
||||||
simulation::{Address, Mailbox, SimInit, Simulation},
|
simulation::{Address, Simulation},
|
||||||
time::{Clock, MonotonicTime, SystemClock},
|
time::{Clock, MonotonicTime, SystemClock},
|
||||||
};
|
};
|
||||||
use satrs_minisim::{acs::MgmRequest, SimRequest};
|
use satrs_minisim::{
|
||||||
|
acs::{MgmRequest, MgtRequest},
|
||||||
|
SimRequest,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
acs::MagnetometerModel,
|
acs::{MagnetometerModel, MagnetorquerModel},
|
||||||
eps::{PcduModel, PcduRequest},
|
eps::{PcduModel, PcduRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ pub struct SimController {
|
|||||||
pub simulation: Simulation,
|
pub simulation: Simulation,
|
||||||
pub mgm_addr: Address<MagnetometerModel>,
|
pub mgm_addr: Address<MagnetometerModel>,
|
||||||
pub pcdu_addr: Address<PcduModel>,
|
pub pcdu_addr: Address<PcduModel>,
|
||||||
|
pub mgt_addr: Address<MagnetorquerModel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimController {
|
impl SimController {
|
||||||
@ -30,6 +31,7 @@ impl SimController {
|
|||||||
simulation: Simulation,
|
simulation: Simulation,
|
||||||
mgm_addr: Address<MagnetometerModel>,
|
mgm_addr: Address<MagnetometerModel>,
|
||||||
pcdu_addr: Address<PcduModel>,
|
pcdu_addr: Address<PcduModel>,
|
||||||
|
mgt_addr: Address<MagnetorquerModel>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sys_clock,
|
sys_clock,
|
||||||
@ -37,18 +39,20 @@ impl SimController {
|
|||||||
simulation,
|
simulation,
|
||||||
mgm_addr,
|
mgm_addr,
|
||||||
pcdu_addr,
|
pcdu_addr,
|
||||||
|
mgt_addr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, start_time: MonotonicTime) {
|
pub fn run(&mut self, start_time: MonotonicTime, udp_polling_interval_ms: u64) {
|
||||||
let mut t = start_time + Duration::from_millis(1);
|
let mut t = start_time + Duration::from_millis(udp_polling_interval_ms);
|
||||||
self.sys_clock.synchronize(t);
|
self.sys_clock.synchronize(t);
|
||||||
loop {
|
loop {
|
||||||
|
// Check for UDP requests every millisecond. Shift the simulator ahead here to prevent
|
||||||
|
// replies lying in the past.
|
||||||
|
t += Duration::from_millis(udp_polling_interval_ms);
|
||||||
self.simulation
|
self.simulation
|
||||||
.step_until(t)
|
.step_until(t)
|
||||||
.expect("simulation step failed");
|
.expect("simulation step failed");
|
||||||
// Check for UDP requests every millisecond.
|
|
||||||
t += Duration::from_millis(1);
|
|
||||||
self.handle_sim_requests();
|
self.handle_sim_requests();
|
||||||
|
|
||||||
self.sys_clock.synchronize(t);
|
self.sys_clock.synchronize(t);
|
||||||
@ -101,10 +105,41 @@ impl SimController {
|
|||||||
}
|
}
|
||||||
let pcdu_request = pcdu_request.unwrap();
|
let pcdu_request = pcdu_request.unwrap();
|
||||||
match pcdu_request {
|
match pcdu_request {
|
||||||
PcduRequest::RequestSwitchInfo => todo!(),
|
PcduRequest::RequestSwitchInfo => {
|
||||||
PcduRequest::SwitchDevice => todo!(),
|
self.simulation
|
||||||
|
.send_event(PcduModel::request_switch_info, (), &self.pcdu_addr);
|
||||||
|
}
|
||||||
|
PcduRequest::SwitchDevice { switch, state } => {
|
||||||
|
self.simulation.send_event(
|
||||||
|
PcduModel::switch_device,
|
||||||
|
(switch, state),
|
||||||
|
&self.pcdu_addr,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mgt_request(&mut self, request: &str) {}
|
fn handle_mgt_request(&mut self, request: &str) {
|
||||||
|
let mgt_request: serde_json::Result<MgtRequest> = serde_json::from_str(request);
|
||||||
|
if mgt_request.is_err() {
|
||||||
|
log::warn!(
|
||||||
|
"received invalid PCDU request: {}",
|
||||||
|
mgt_request.unwrap_err()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mgt_request = mgt_request.unwrap();
|
||||||
|
match mgt_request {
|
||||||
|
MgtRequest::ApplyTorque { duration, dipole } => self.simulation.send_event(
|
||||||
|
MagnetorquerModel::apply_torque,
|
||||||
|
(duration, dipole),
|
||||||
|
&self.mgt_addr,
|
||||||
|
),
|
||||||
|
MgtRequest::RequestHk => self.simulation.send_event(
|
||||||
|
MagnetorquerModel::request_housekeeping_data,
|
||||||
|
(),
|
||||||
|
&self.mgt_addr,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{sync::mpsc, time::Duration};
|
use std::{collections::HashMap, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
use asynchronix::{
|
use asynchronix::{
|
||||||
model::{Model, Output},
|
model::{Model, Output},
|
||||||
@ -10,23 +10,25 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
pub type SwitchMap = HashMap<PcduSwitch, SwitchStateBinary>;
|
||||||
pub struct SwitchInfo(Vec<SwitchStateBinary>);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
pub enum PcduSwitches {
|
pub enum PcduSwitch {
|
||||||
Mgm = 0,
|
Mgm = 0,
|
||||||
Mgt = 1,
|
Mgt = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
pub enum PcduRequest {
|
pub enum PcduRequest {
|
||||||
SwitchDevice,
|
SwitchDevice {
|
||||||
|
switch: PcduSwitch,
|
||||||
|
state: SwitchStateBinary,
|
||||||
|
},
|
||||||
RequestSwitchInfo,
|
RequestSwitchInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PcduModel {
|
pub struct PcduModel {
|
||||||
pub current_switch_info: Vec<SwitchStateBinary>,
|
pub switcher_map: SwitchMap,
|
||||||
pub mgm_switch: Output<SwitchStateBinary>,
|
pub mgm_switch: Output<SwitchStateBinary>,
|
||||||
pub mgt_switch: Output<SwitchStateBinary>,
|
pub mgt_switch: Output<SwitchStateBinary>,
|
||||||
pub reply_sender: mpsc::Sender<SimReply>,
|
pub reply_sender: mpsc::Sender<SimReply>,
|
||||||
@ -34,8 +36,12 @@ pub struct PcduModel {
|
|||||||
|
|
||||||
impl PcduModel {
|
impl PcduModel {
|
||||||
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||||
|
let mut switcher_map = HashMap::new();
|
||||||
|
switcher_map.insert(PcduSwitch::Mgm, SwitchStateBinary::Off);
|
||||||
|
switcher_map.insert(PcduSwitch::Mgt, SwitchStateBinary::Off);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
current_switch_info: vec![SwitchStateBinary::Off; 2],
|
switcher_map,
|
||||||
mgm_switch: Output::new(),
|
mgm_switch: Output::new(),
|
||||||
mgt_switch: Output::new(),
|
mgt_switch: Output::new(),
|
||||||
reply_sender,
|
reply_sender,
|
||||||
@ -53,22 +59,114 @@ impl PcduModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_switch_info(&mut self) {
|
pub fn send_switch_info(&mut self) {
|
||||||
let switch_info = SwitchInfo(self.current_switch_info.clone());
|
let switch_info = self.switcher_map.clone();
|
||||||
let reply = SimReply::new(SimDevice::Pcdu, switch_info);
|
let reply = SimReply::new(SimDevice::Pcdu, switch_info);
|
||||||
self.reply_sender.send(reply).unwrap();
|
self.reply_sender.send(reply).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_device(&mut self, switch: PcduSwitches, switch_state: SwitchStateBinary) {
|
pub async fn switch_device(
|
||||||
self.current_switch_info[switch as usize] = switch_state;
|
&mut self,
|
||||||
match switch {
|
switch_and_target_state: (PcduSwitch, SwitchStateBinary),
|
||||||
PcduSwitches::Mgm => {
|
) {
|
||||||
self.mgm_switch.send(switch_state).await;
|
let val = self
|
||||||
|
.switcher_map
|
||||||
|
.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;
|
||||||
|
match switch_and_target_state.0 {
|
||||||
|
PcduSwitch::Mgm => {
|
||||||
|
self.mgm_switch.send(switch_and_target_state.1).await;
|
||||||
}
|
}
|
||||||
PcduSwitches::Mgt => {
|
PcduSwitch::Mgt => {
|
||||||
self.mgt_switch.send(switch_state).await;
|
self.mgt_switch.send(switch_and_target_state.1).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for PcduModel {}
|
impl Model for PcduModel {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use satrs_minisim::{SimDevice, SimRequest};
|
||||||
|
|
||||||
|
use crate::test_helpers::SimTestbench;
|
||||||
|
|
||||||
|
fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMap) {
|
||||||
|
let pcdu_request = PcduRequest::RequestSwitchInfo;
|
||||||
|
let request = SimRequest::new(SimDevice::Pcdu, pcdu_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply.is_some());
|
||||||
|
let sim_reply = sim_reply.unwrap();
|
||||||
|
assert_eq!(sim_reply.device, SimDevice::Pcdu);
|
||||||
|
let switch_map: super::SwitchMap =
|
||||||
|
serde_json::from_str(&sim_reply.reply).expect("failed to deserialize PCDU switch info");
|
||||||
|
assert_eq!(&switch_map, expected_switch_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_off_switch_map() -> SwitchMap {
|
||||||
|
let mut switcher_map = SwitchMap::new();
|
||||||
|
switcher_map.insert(super::PcduSwitch::Mgm, super::SwitchStateBinary::Off);
|
||||||
|
switcher_map.insert(super::PcduSwitch::Mgt, super::SwitchStateBinary::Off);
|
||||||
|
switcher_map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_pcdu_switching_single_switch(switch: PcduSwitch) {
|
||||||
|
let mut sim_testbench = SimTestbench::new();
|
||||||
|
let pcdu_request = PcduRequest::SwitchDevice {
|
||||||
|
switch,
|
||||||
|
state: SwitchStateBinary::On,
|
||||||
|
};
|
||||||
|
let request = SimRequest::new(SimDevice::Pcdu, pcdu_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step();
|
||||||
|
let mut switcher_map = get_all_off_switch_map();
|
||||||
|
*switcher_map.get_mut(&switch).unwrap() = SwitchStateBinary::On;
|
||||||
|
check_switch_state(&mut sim_testbench, &switcher_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pcdu_switcher_request() {
|
||||||
|
let mut sim_testbench = SimTestbench::new();
|
||||||
|
let pcdu_request = PcduRequest::RequestSwitchInfo;
|
||||||
|
let request = SimRequest::new(SimDevice::Pcdu, pcdu_request);
|
||||||
|
sim_testbench
|
||||||
|
.send_request(request)
|
||||||
|
.expect("sending MGM request failed");
|
||||||
|
sim_testbench.handle_sim_requests();
|
||||||
|
sim_testbench.step_by(Duration::from_millis(1));
|
||||||
|
|
||||||
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply.is_none());
|
||||||
|
// Reply takes 20ms
|
||||||
|
sim_testbench.step_by(Duration::from_millis(25));
|
||||||
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
|
assert!(sim_reply.is_some());
|
||||||
|
let sim_reply = sim_reply.unwrap();
|
||||||
|
assert_eq!(sim_reply.device, SimDevice::Pcdu);
|
||||||
|
let switch_map: super::SwitchMap =
|
||||||
|
serde_json::from_str(&sim_reply.reply).expect("failed to deserialize PCDU switch info");
|
||||||
|
assert_eq!(switch_map, get_all_off_switch_map());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pcdu_switching_mgm() {
|
||||||
|
test_pcdu_switching_single_switch(PcduSwitch::Mgm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pcdu_switching_mgt() {
|
||||||
|
test_pcdu_switching_single_switch(PcduSwitch::Mgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl SimRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct SimReply {
|
pub struct SimReply {
|
||||||
pub device: SimDevice,
|
pub device: SimDevice,
|
||||||
pub reply: String,
|
pub reply: String,
|
||||||
@ -50,6 +50,8 @@ impl SimReply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod acs {
|
pub mod acs {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
@ -74,10 +76,24 @@ pub mod acs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Simple model using i16 values.
|
// Simple model using i16 values.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MgtDipole {
|
pub struct MgtDipole {
|
||||||
pub x: i16,
|
pub x: i16,
|
||||||
pub y: i16,
|
pub y: i16,
|
||||||
pub z: i16,
|
pub z: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum MgtRequest {
|
||||||
|
ApplyTorque {
|
||||||
|
duration: Duration,
|
||||||
|
dipole: MgtDipole,
|
||||||
|
},
|
||||||
|
RequestHk,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum MgtReply {
|
||||||
|
Hk(MgtDipole),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use acs::MagnetometerModel;
|
use acs::{MagnetometerModel, MagnetorquerModel};
|
||||||
use asynchronix::simulation::{Mailbox, SimInit};
|
use asynchronix::simulation::{Mailbox, SimInit};
|
||||||
use asynchronix::time::{MonotonicTime, SystemClock};
|
use asynchronix::time::{MonotonicTime, SystemClock};
|
||||||
use controller::SimController;
|
use controller::SimController;
|
||||||
@ -12,6 +12,8 @@ use udp::{SharedSocketAddr, UdpTcServer, UdpTmClient};
|
|||||||
mod acs;
|
mod acs;
|
||||||
mod controller;
|
mod controller;
|
||||||
mod eps;
|
mod eps;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_helpers;
|
||||||
mod time;
|
mod time;
|
||||||
mod udp;
|
mod udp;
|
||||||
|
|
||||||
@ -34,13 +36,24 @@ fn create_sim_controller(
|
|||||||
let mgm_addr = mgm_mailbox.address();
|
let mgm_addr = mgm_mailbox.address();
|
||||||
let pcdu_mailbox = Mailbox::new();
|
let pcdu_mailbox = Mailbox::new();
|
||||||
let pcdu_addr = pcdu_mailbox.address();
|
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());
|
let mut pcdu_model = PcduModel::new(reply_sender.clone());
|
||||||
|
|
||||||
pcdu_model
|
pcdu_model
|
||||||
.mgm_switch
|
.mgm_switch
|
||||||
.connect(MagnetometerModel::switch_device, &mgm_addr);
|
.connect(MagnetometerModel::switch_device, &mgm_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_addr);
|
||||||
|
|
||||||
// Instantiate the simulator
|
// Instantiate the simulator
|
||||||
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
||||||
let sim_init = if threading_model == ThreadingModel::Single {
|
let sim_init = if threading_model == ThreadingModel::Single {
|
||||||
@ -51,8 +64,16 @@ fn create_sim_controller(
|
|||||||
let simulation = sim_init
|
let simulation = sim_init
|
||||||
.add_model(mgm_model, mgm_mailbox)
|
.add_model(mgm_model, mgm_mailbox)
|
||||||
.add_model(pcdu_model, pcdu_mailbox)
|
.add_model(pcdu_model, pcdu_mailbox)
|
||||||
|
.add_model(mgt_model, mgt_mailbox)
|
||||||
.init(start_time);
|
.init(start_time);
|
||||||
SimController::new(sys_clock, request_receiver, simulation, mgm_addr, pcdu_addr)
|
SimController::new(
|
||||||
|
sys_clock,
|
||||||
|
request_receiver,
|
||||||
|
simulation,
|
||||||
|
mgm_addr,
|
||||||
|
pcdu_addr,
|
||||||
|
mgt_addr,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -65,7 +86,7 @@ fn main() {
|
|||||||
|
|
||||||
// This thread schedules the simulator.
|
// This thread schedules the simulator.
|
||||||
let sim_thread = thread::spawn(move || {
|
let sim_thread = thread::spawn(move || {
|
||||||
sim_ctrl.run(t0);
|
sim_ctrl.run(t0, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut server = UdpTcServer::new(request_sender, shared_socket_addr.clone()).unwrap();
|
let mut server = UdpTcServer::new(request_sender, shared_socket_addr.clone()).unwrap();
|
||||||
@ -84,92 +105,3 @@ fn main() {
|
|||||||
udp_tc_thread.join().expect("joining UDP TC thread failed");
|
udp_tc_thread.join().expect("joining UDP TC thread failed");
|
||||||
udp_tm_thread.join().expect("joining UDP TM thread failed");
|
udp_tm_thread.join().expect("joining UDP TM thread failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use delegate::delegate;
|
|
||||||
use satrs_minisim::{
|
|
||||||
acs::{MgmRequest, MgmSensorValues},
|
|
||||||
SimDevice, SimReply, SimRequest,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::eps::PcduRequest;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
struct SimTestbench {
|
|
||||||
pub sim_controller: SimController,
|
|
||||||
pub reply_receiver: mpsc::Receiver<SimReply>,
|
|
||||||
pub request_sender: mpsc::Sender<SimRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimTestbench {
|
|
||||||
fn new() -> Self {
|
|
||||||
let (request_sender, request_receiver) = mpsc::channel();
|
|
||||||
let (reply_sender, reply_receiver) = mpsc::channel();
|
|
||||||
let t0 = MonotonicTime::EPOCH;
|
|
||||||
let sim_ctrl =
|
|
||||||
create_sim_controller(ThreadingModel::Single, t0, reply_sender, request_receiver);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
sim_controller: sim_ctrl,
|
|
||||||
reply_receiver,
|
|
||||||
request_sender,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate! {
|
|
||||||
to self.sim_controller {
|
|
||||||
pub fn handle_sim_requests(&mut self);
|
|
||||||
}
|
|
||||||
to self.sim_controller.simulation {
|
|
||||||
pub fn step(&mut self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_request(&self, request: SimRequest) -> Result<(), mpsc::SendError<SimRequest>> {
|
|
||||||
self.request_sender.send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_receive_next_reply(&self) -> Option<SimReply> {
|
|
||||||
match self.reply_receiver.try_recv() {
|
|
||||||
Ok(reply) => Some(reply),
|
|
||||||
Err(e) => {
|
|
||||||
if e == mpsc::TryRecvError::Empty {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
panic!("reply_receiver disconnected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_mgm_request() {
|
|
||||||
let mut sim_testbench = SimTestbench::new();
|
|
||||||
let mgm_request = MgmRequest::RequestSensorData;
|
|
||||||
let request = SimRequest::new(SimDevice::Mgm, mgm_request);
|
|
||||||
sim_testbench
|
|
||||||
.send_request(request)
|
|
||||||
.expect("sending MGM request failed");
|
|
||||||
sim_testbench.handle_sim_requests();
|
|
||||||
sim_testbench.step();
|
|
||||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
|
||||||
assert!(sim_reply.is_some());
|
|
||||||
let sim_reply = sim_reply.unwrap();
|
|
||||||
assert_eq!(sim_reply.device, SimDevice::Mgm);
|
|
||||||
let reply: MgmSensorValues = serde_json::from_str(&sim_reply.reply)
|
|
||||||
.expect("failed to deserialize MGM sensor values");
|
|
||||||
assert_eq!(reply.x, 0.0);
|
|
||||||
assert_eq!(reply.y, 0.0);
|
|
||||||
assert_eq!(reply.z, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_mgm_request_switched_on() {
|
|
||||||
let mut sim_testbench = SimTestbench::new();
|
|
||||||
let pcdu_request = PcduRequest::RequestSwitchInfo;
|
|
||||||
let request = SimRequest::new(SimDevice::Pcdu, pcdu_request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
56
satrs-minisim/src/test_helpers.rs
Normal file
56
satrs-minisim/src/test_helpers.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use delegate::delegate;
|
||||||
|
use std::{sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
use asynchronix::time::MonotonicTime;
|
||||||
|
use satrs_minisim::{SimReply, SimRequest};
|
||||||
|
|
||||||
|
use crate::{controller::SimController, create_sim_controller, ThreadingModel};
|
||||||
|
|
||||||
|
pub struct SimTestbench {
|
||||||
|
pub sim_controller: SimController,
|
||||||
|
pub reply_receiver: mpsc::Receiver<SimReply>,
|
||||||
|
pub request_sender: mpsc::Sender<SimRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimTestbench {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (request_sender, request_receiver) = mpsc::channel();
|
||||||
|
let (reply_sender, reply_receiver) = mpsc::channel();
|
||||||
|
let t0 = MonotonicTime::EPOCH;
|
||||||
|
let sim_ctrl =
|
||||||
|
create_sim_controller(ThreadingModel::Single, t0, reply_sender, request_receiver);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sim_controller: sim_ctrl,
|
||||||
|
reply_receiver,
|
||||||
|
request_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate! {
|
||||||
|
to self.sim_controller {
|
||||||
|
pub fn handle_sim_requests(&mut self);
|
||||||
|
}
|
||||||
|
to self.sim_controller.simulation {
|
||||||
|
pub fn step(&mut self);
|
||||||
|
pub fn step_by(&mut self, duration: Duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request(&self, request: SimRequest) -> Result<(), mpsc::SendError<SimRequest>> {
|
||||||
|
self.request_sender.send(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_receive_next_reply(&self) -> Option<SimReply> {
|
||||||
|
match self.reply_receiver.try_recv() {
|
||||||
|
Ok(reply) => Some(reply),
|
||||||
|
Err(e) => {
|
||||||
|
if e == mpsc::TryRecvError::Empty {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
panic!("reply_receiver disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -150,3 +150,9 @@ impl UdpTmClient {
|
|||||||
sent_replies
|
sent_replies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn test_basic_udp_tc_reception() {}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user