simplify minisim
This commit is contained in:
@@ -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<FaultMode> for acs::SpiFaultMode {
|
||||
impl From<FaultMode> 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(())
|
||||
|
||||
@@ -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<MgmSensorValuesMicroTesla>,
|
||||
pub spi_fault: SpiFault,
|
||||
pub external_mag_field: Option<mgm::SensorValuesMicroTesla>,
|
||||
pub spi_fault: mgm::SpiFault,
|
||||
pub reply_sender: mpsc::Sender<SimReply>,
|
||||
}
|
||||
|
||||
impl MagnetometerModel {
|
||||
pub fn new(mgm_id: MgmId, periodicity: Duration, reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||
impl MgmModel {
|
||||
pub fn new(mgm_id: mgm::Id, reply_sender: mpsc::Sender<SimReply>) -> 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<Self>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MgmSensorValuesMicroTesla>,
|
||||
torque_dipole: mgt::Dipole,
|
||||
pub gen_magnetic_field: Output<mgm::SensorValuesMicroTesla>,
|
||||
reply_sender: mpsc::Sender<SimReply>,
|
||||
}
|
||||
|
||||
impl MagnetorquerModel {
|
||||
impl MgtModel {
|
||||
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> 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>,
|
||||
) {
|
||||
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<mgt::HkSet> {
|
||||
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,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
|
||||
use nexosim::{
|
||||
simulation::{Address, Scheduler, Simulation},
|
||||
simulation::{Address, 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,
|
||||
};
|
||||
|
||||
@@ -24,29 +23,27 @@ const PCDU_REQ_WIRETAPPING: bool = false;
|
||||
const MGT_REQ_WIRETAPPING: bool = false;
|
||||
|
||||
pub struct ModelAddrWrapper {
|
||||
mgm_0_addr: Address<MagnetometerModel>,
|
||||
mgm_1_addr: Address<MagnetometerModel>,
|
||||
mgm_0_addr: Address<MgmModel>,
|
||||
mgm_1_addr: Address<MgmModel>,
|
||||
pcdu_addr: Address<PcduModel>,
|
||||
mgt_addr: Address<MagnetorquerModel>,
|
||||
mgt_addr: Address<MgtModel>,
|
||||
}
|
||||
|
||||
// The simulation controller processes requests and drives the simulation.
|
||||
#[allow(dead_code)]
|
||||
pub struct SimController {
|
||||
pub sys_clock: SystemClock,
|
||||
pub request_receiver: mpsc::Receiver<SimRequest>,
|
||||
pub request_receiver: mpsc::Receiver<SimRequestWithTime>,
|
||||
pub reply_sender: mpsc::Sender<SimReply>,
|
||||
pub simulation: Simulation,
|
||||
pub scheduler: Scheduler,
|
||||
pub addr_wrapper: ModelAddrWrapper,
|
||||
}
|
||||
|
||||
impl ModelAddrWrapper {
|
||||
pub fn new(
|
||||
mgm_0_addr: Address<MagnetometerModel>,
|
||||
mgm_1_addr: Address<MagnetometerModel>,
|
||||
mgm_0_addr: Address<MgmModel>,
|
||||
mgm_1_addr: Address<MgmModel>,
|
||||
pcdu_addr: Address<PcduModel>,
|
||||
mgt_addr: Address<MagnetorquerModel>,
|
||||
mgt_addr: Address<MgtModel>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mgm_0_addr,
|
||||
@@ -59,10 +56,9 @@ impl ModelAddrWrapper {
|
||||
impl SimController {
|
||||
pub fn new(
|
||||
sys_clock: SystemClock,
|
||||
request_receiver: mpsc::Receiver<SimRequest>,
|
||||
request_receiver: mpsc::Receiver<SimRequestWithTime>,
|
||||
reply_sender: mpsc::Sender<SimReply>,
|
||||
simulation: Simulation,
|
||||
scheduler: Scheduler,
|
||||
addr_wrapper: ModelAddrWrapper,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -70,7 +66,6 @@ impl SimController {
|
||||
request_receiver,
|
||||
reply_sender,
|
||||
simulation,
|
||||
scheduler,
|
||||
addr_wrapper,
|
||||
}
|
||||
}
|
||||
@@ -97,14 +92,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 +109,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,50 +117,36 @@ 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.addr_wrapper.mgm_0_addr,
|
||||
mgm::Id::Mgm1 => &self.addr_wrapper.mgm_1_addr,
|
||||
};
|
||||
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:?}");
|
||||
}
|
||||
@@ -193,48 +170,30 @@ impl SimController {
|
||||
.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,
|
||||
MgtModel::apply_torque,
|
||||
(duration, dipole),
|
||||
&self.addr_wrapper.mgt_addr,
|
||||
)
|
||||
.unwrap(),
|
||||
MgtRequest::RequestHk => self
|
||||
mgt::Request::RequestHk => self
|
||||
.simulation
|
||||
.process_event(
|
||||
MagnetorquerModel::request_housekeeping_data,
|
||||
MgtModel::request_housekeeping_data,
|
||||
(),
|
||||
&self.addr_wrapper.mgt_addr,
|
||||
)
|
||||
.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 +206,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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
+189
-369
@@ -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<SimCtrlRequest> for SimRequest {
|
||||
fn from(request: SimCtrlRequest) -> Self {
|
||||
Self::SimCtrl(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mgt::Request> for SimRequest {
|
||||
fn from(request: mgt::Request) -> Self {
|
||||
Self::Mgt(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PcduRequest> 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<P: SimMessageProvider>:
|
||||
Serialize + DeserializeOwned + Sized
|
||||
{
|
||||
const TARGET: SimComponent;
|
||||
|
||||
fn from_sim_message(sim_message: &P) -> Result<Self, SimMessageError<P>> {
|
||||
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<Self> {
|
||||
serde_json::from_slice(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimRequest {
|
||||
pub fn new_with_epoch_time<T: SerializableSimMsgPayload<SimRequest>>(
|
||||
serializable_request: T,
|
||||
) -> Self {
|
||||
Self::new(serializable_request, MonotonicTime::EPOCH)
|
||||
}
|
||||
|
||||
pub fn new<T: SerializableSimMsgPayload<SimRequest>>(
|
||||
serializable_request: T,
|
||||
timestamp: MonotonicTime,
|
||||
) -> Self {
|
||||
impl SimRequestWithTime {
|
||||
pub fn new(request: impl Into<SimRequest>, 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<SimRequest>) -> 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<T: SerializableSimMsgPayload<SimReply>>(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<T: Serialize>(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<SimCtrlReply> for SimReply {
|
||||
fn from(reply: SimCtrlReply) -> Self {
|
||||
Self::SimCtrl(reply)
|
||||
}
|
||||
fn payload(&self) -> &String {
|
||||
&self.inner.payload
|
||||
}
|
||||
|
||||
impl From<mgt::Reply> for SimReply {
|
||||
fn from(reply: mgt::Reply) -> Self {
|
||||
Self::Mgt(reply)
|
||||
}
|
||||
fn msg_type(&self) -> SimMessageType {
|
||||
SimMessageType::Reply
|
||||
}
|
||||
|
||||
impl From<PcduReply> for SimReply {
|
||||
fn from(reply: PcduReply) -> Self {
|
||||
Self::Pcdu(reply)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,52 +103,15 @@ pub enum SimCtrlRequest {
|
||||
Ping,
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimRequest> for SimCtrlRequest {
|
||||
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||
}
|
||||
|
||||
pub type SimReplyError = SimMessageError<SimReply>;
|
||||
pub type SimRequestError = SimMessageError<SimRequest>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SimMessageError<P> {
|
||||
SerdeJson(String),
|
||||
TargetRequestMissmatch(P),
|
||||
}
|
||||
|
||||
impl<P> From<serde_json::Error> for SimMessageError<P> {
|
||||
fn from(error: serde_json::Error) -> SimMessageError<P> {
|
||||
SimMessageError::SerdeJson(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SimCtrlReply {
|
||||
Pong,
|
||||
InvalidRequest(SimRequestError),
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimReply> for SimCtrlReply {
|
||||
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||
}
|
||||
|
||||
impl From<SimRequestError> 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<SimRequest> for PcduRequest {
|
||||
const TARGET: SimComponent = SimComponent::Pcdu;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PcduReply {
|
||||
// Ack,
|
||||
SwitchInfo(SwitchMapBinary),
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimReply> 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<SimRequest> for MgmRequestLis3MdlMgm0 {
|
||||
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct MgmRequestLis3MdlMgm1(pub MgmRequestLis3Mdl);
|
||||
|
||||
impl SerializableSimMsgPayload<SimRequest> 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<Self, SimReplyError> {
|
||||
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<SimRequest> 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<SimReply> 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<SimRequest> for DummyRequest {
|
||||
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DummyReply {
|
||||
Pong,
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimReply> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use acs::{mgm::MagnetometerModel, mgt::MagnetorquerModel};
|
||||
use acs::{mgm::MgmModel, mgt::MgtModel};
|
||||
use controller::{ModelAddrWrapper, SimController};
|
||||
use eps::PcduModel;
|
||||
use nexosim::simulation::{Mailbox, SimInit};
|
||||
use nexosim::time::{MonotonicTime, SystemClock};
|
||||
use satrs_minisim::acs::mgm::MgmId;
|
||||
use satrs_minisim::acs::mgm;
|
||||
use satrs_minisim::udp::SIM_CTRL_PORT;
|
||||
use satrs_minisim::{SimReply, SimRequest};
|
||||
use satrs_minisim::{SimReply, SimRequestWithTime};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::SystemTime;
|
||||
use udp::SimUdpServer;
|
||||
|
||||
mod acs;
|
||||
@@ -29,13 +29,11 @@ fn create_sim_controller(
|
||||
threading_model: ThreadingModel,
|
||||
start_time: MonotonicTime,
|
||||
reply_sender: mpsc::Sender<SimReply>,
|
||||
request_receiver: mpsc::Receiver<SimRequest>,
|
||||
request_receiver: mpsc::Receiver<SimRequestWithTime>,
|
||||
) -> 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_model = MgmModel::new(mgm::Id::Mgm0, reply_sender.clone());
|
||||
let mgm_1_model = MgmModel::new(mgm::Id::Mgm1, reply_sender.clone());
|
||||
|
||||
let mgm_0_mailbox = Mailbox::new();
|
||||
let mgm_0_addr = mgm_0_mailbox.address();
|
||||
@@ -49,25 +47,23 @@ fn create_sim_controller(
|
||||
let mut pcdu_model = PcduModel::new(reply_sender.clone());
|
||||
pcdu_model
|
||||
.mgm_0_switch
|
||||
.connect(MagnetometerModel::switch_device, &mgm_0_addr);
|
||||
.connect(MgmModel::switch_device, &mgm_0_addr);
|
||||
pcdu_model
|
||||
.mgm_1_switch
|
||||
.connect(MagnetometerModel::switch_device, &mgm_1_addr);
|
||||
.connect(MgmModel::switch_device, &mgm_1_addr);
|
||||
|
||||
let mut mgt_model = MagnetorquerModel::new(reply_sender.clone());
|
||||
let mut mgt_model = MgtModel::new(reply_sender.clone());
|
||||
// Input connections.
|
||||
pcdu_model
|
||||
.mgt_switch
|
||||
.connect(MagnetorquerModel::switch_device, &mgt_addr);
|
||||
.connect(MgtModel::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,
|
||||
);
|
||||
mgt_model
|
||||
.gen_magnetic_field
|
||||
.connect(MgmModel::apply_external_magnetic_field, &mgm_0_addr);
|
||||
mgt_model
|
||||
.gen_magnetic_field
|
||||
.connect(MgmModel::apply_external_magnetic_field, &mgm_1_addr);
|
||||
|
||||
// Instantiate the simulator
|
||||
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
||||
@@ -77,21 +73,14 @@ fn create_sim_controller(
|
||||
SimInit::new()
|
||||
};
|
||||
let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr);
|
||||
let (simulation, scheduler) = sim_init
|
||||
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,
|
||||
)
|
||||
SimController::new(sys_clock, request_receiver, reply_sender, simulation, addrs)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -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};
|
||||
|
||||
pub struct SimTestbench {
|
||||
pub sim_controller: SimController,
|
||||
pub reply_receiver: mpsc::Receiver<SimReply>,
|
||||
pub request_sender: mpsc::Sender<SimRequest>,
|
||||
pub request_sender: mpsc::Sender<SimRequestWithTime>,
|
||||
}
|
||||
|
||||
impl SimTestbench {
|
||||
@@ -43,10 +43,26 @@ impl SimTestbench {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_request(&self, request: SimRequest) -> Result<(), mpsc::SendError<SimRequest>> {
|
||||
pub fn send_request(
|
||||
&self,
|
||||
request: SimRequestWithTime,
|
||||
) -> Result<(), mpsc::SendError<SimRequestWithTime>> {
|
||||
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<SimRequest>) {
|
||||
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<SimRequest>) -> Option<SimReply> {
|
||||
self.send_and_step(request);
|
||||
self.try_receive_next_reply()
|
||||
}
|
||||
|
||||
pub fn try_receive_next_reply(&self) -> Option<SimReply> {
|
||||
match self.reply_receiver.try_recv() {
|
||||
Ok(reply) => Some(reply),
|
||||
|
||||
@@ -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<SimRequest>,
|
||||
// shared_last_sender: SharedSocketAddr,
|
||||
request_sender: mpsc::Sender<SimRequestWithTime>,
|
||||
reply_receiver: mpsc::Receiver<SimReply>,
|
||||
reply_queue: VecDeque<SimReply>,
|
||||
max_num_replies: usize,
|
||||
@@ -27,7 +26,7 @@ pub struct SimUdpServer {
|
||||
impl SimUdpServer {
|
||||
pub fn new(
|
||||
local_port: u16,
|
||||
request_sender: mpsc::Sender<SimRequest>,
|
||||
request_sender: mpsc::Sender<SimRequestWithTime>,
|
||||
reply_receiver: mpsc::Receiver<SimReply>,
|
||||
max_num_replies: usize,
|
||||
stop_signal: Option<Arc<AtomicBool>>,
|
||||
@@ -47,7 +46,7 @@ impl SimUdpServer {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
pub fn server_addr(&self) -> std::io::Result<SocketAddr> {
|
||||
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::<SimRequestWithTime>(&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<usize> {
|
||||
pub fn send_request(&self, sim_request: &SimRequestWithTime) -> std::io::Result<usize> {
|
||||
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<AtomicBool>,
|
||||
request_receiver: mpsc::Receiver<SimRequest>,
|
||||
request_receiver: mpsc::Receiver<SimRequestWithTime>,
|
||||
reply_sender: mpsc::Sender<SimReply>,
|
||||
}
|
||||
|
||||
@@ -256,7 +255,7 @@ mod tests {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn try_recv_request(&self) -> Result<SimRequest, mpsc::TryRecvError> {
|
||||
pub fn try_recv_request(&self) -> Result<SimRequestWithTime, mpsc::TryRecvError> {
|
||||
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<usize>;
|
||||
pub fn send_request(&self, sim_request: &SimRequestWithTime) -> std::io::Result<usize>;
|
||||
pub fn recv_sim_reply(&mut self) -> Result<SimReply, ReceptionError>;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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<SimRequest>,
|
||||
pub sim_request_tx: mpsc::Sender<SimRequestWithTime>,
|
||||
pub sim_reply_rx: mpsc::Receiver<SimReply>,
|
||||
}
|
||||
|
||||
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()),
|
||||
|
||||
@@ -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<SimRequest>,
|
||||
pub sim_request_tx: mpsc::Sender<SimRequestWithTime>,
|
||||
pub sim_reply_rx: mpsc::Receiver<SimReply>,
|
||||
}
|
||||
|
||||
@@ -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<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SimComponent, mpsc::Sender<SimReply>>);
|
||||
struct SimReplyMap(pub HashMap<ComponentId, mpsc::Sender<SimReply>>);
|
||||
|
||||
pub fn create_sim_client(sim_request_rx: mpsc::Receiver<SimRequest>) -> Option<SimClientUdp> {
|
||||
pub fn create_sim_client(
|
||||
sim_request_rx: mpsc::Receiver<SimRequestWithTime>,
|
||||
) -> Option<SimClientUdp> {
|
||||
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<SimRequest>,
|
||||
sim_request_rx: mpsc::Receiver<SimRequestWithTime>,
|
||||
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<SimRequest>,
|
||||
sim_request_rx: mpsc::Receiver<SimRequestWithTime>,
|
||||
) -> Result<Self, SimClientCreationError> {
|
||||
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<SimReply>,
|
||||
) {
|
||||
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<SimRequest>,
|
||||
request_tx: mpsc::Sender<SimRequestWithTime>,
|
||||
reply_rx: mpsc::Receiver<SimReply>,
|
||||
last_sender: Option<SocketAddr>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
@@ -213,7 +204,7 @@ pub mod tests {
|
||||
|
||||
impl UdpSimTestServer {
|
||||
pub fn new(
|
||||
request_tx: mpsc::Sender<SimRequest>,
|
||||
request_tx: mpsc::Sender<SimRequestWithTime>,
|
||||
reply_rx: mpsc::Receiver<SimReply>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
) -> 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;
|
||||
}
|
||||
|
||||
+24
-25
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user