more simplifications

This commit is contained in:
Robin Mueller
2026-09-24 12:04:58 +02:00
parent 8525b62e34
commit 0a2039a4c0
7 changed files with 220 additions and 235 deletions
+11 -11
View File
@@ -3,8 +3,8 @@ use arbitrary_int::u11;
use clap::Parser as _;
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use satrs_minisim::{
SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, acs,
acs::MgmRequestLis3Mdl, acs::SpiFault, acs::mgm::MgmId, udp::SIM_CTRL_PORT,
SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, acs::mgm,
udp::SIM_CTRL_PORT,
};
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
use std::{
@@ -97,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,
}
}
}
@@ -204,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,
},
@@ -499,7 +499,7 @@ 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)))?;
@@ -526,13 +526,13 @@ fn inject_mgm_failure(target_id: types::ComponentId, fault: SpiFault) -> anyhow:
}
let id = match target_id {
types::ComponentId::AcsMgm0 => MgmId::Mgm0,
types::ComponentId::AcsMgm1 => MgmId::Mgm1,
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: MgmRequestLis3Mdl::SetSpiFault(fault),
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:?}");
+37 -45
View File
@@ -1,13 +1,7 @@
use std::{f32::consts::PI, sync::mpsc, time::Duration};
use nexosim::model::{Context, Model};
use satrs_minisim::{
acs::{
mgm::{MgmId, MgmReply},
MgmSensorValuesMicroTesla, SpiFault,
},
SimReply,
};
use satrs_minisim::{acs::mgm, SimReply};
use types::pcdu::SwitchStateBinary;
use crate::time::current_millis;
@@ -25,24 +19,28 @@ 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,
periodicity: Duration,
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,19 +48,19 @@ 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 = SimReply::Mgm {
id: self.id,
reply: MgmReply::new(
reply: mgm::Reply::new(
self.switch_state,
self.calculate_current_mgm_tuple(current_millis(scheduler.time())),
self.spi_fault.mode,
@@ -75,23 +73,23 @@ impl MagnetometerModel {
// 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,19 +97,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},
MgmRequestLis3Mdl, SpiFault, SpiFaultMode,
},
SimComponent, SimReply, SimRequest, SimRequestWithTime,
};
use satrs_minisim::{acs::mgm, SimComponent, SimReply, SimRequest, SimRequestWithTime};
use types::pcdu::{SwitchId, SwitchStateBinary};
use crate::{
@@ -119,11 +111,11 @@ mod tests {
test_helpers::SimTestbench,
};
fn mgm_request(id: MgmId, request: MgmRequestLis3Mdl) -> SimRequestWithTime {
fn mgm_request(id: mgm::Id, request: mgm::Request) -> SimRequestWithTime {
SimRequestWithTime::new_with_epoch_time(SimRequest::Mgm { id, request })
}
fn unwrap_mgm_reply(sim_reply: SimReply) -> (MgmId, MgmReply) {
fn unwrap_mgm_reply(sim_reply: SimReply) -> (mgm::Id, mgm::Reply) {
match sim_reply {
SimReply::Mgm { id, reply } => (id, reply),
_ => panic!("unexpected reply {sim_reply:?}"),
@@ -133,7 +125,7 @@ mod tests {
#[test]
fn test_basic_mgm_request() {
let mut sim_testbench = SimTestbench::new();
let request = mgm_request(MgmId::Mgm0, MgmRequestLis3Mdl::RequestSensorData);
let request = mgm_request(mgm::Id::Mgm0, mgm::Request::RequestSensorData);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
@@ -144,7 +136,7 @@ mod tests {
let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
let (id, reply) = unwrap_mgm_reply(sim_reply);
assert_eq!(id, MgmId::Mgm0);
assert_eq!(id, 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);
@@ -153,9 +145,9 @@ mod tests {
fn inject_spi_fault(sim_testbench: &mut SimTestbench, cleared_by_power_cycle: bool) {
let fault_request = mgm_request(
MgmId::Mgm0,
MgmRequestLis3Mdl::SetSpiFault(SpiFault {
mode: SpiFaultMode::AllOnes,
mgm::Id::Mgm0,
mgm::Request::SetSpiFault(mgm::SpiFault {
mode: mgm::SpiFaultMode::AllOnes,
cleared_by_power_cycle,
}),
);
@@ -166,8 +158,8 @@ mod tests {
sim_testbench.step().unwrap();
}
fn request_mgm_reply(sim_testbench: &mut SimTestbench) -> MgmReply {
let data_request = mgm_request(MgmId::Mgm0, MgmRequestLis3Mdl::RequestSensorData);
fn request_mgm_reply(sim_testbench: &mut SimTestbench) -> mgm::Reply {
let data_request = mgm_request(mgm::Id::Mgm0, mgm::Request::RequestSensorData);
sim_testbench
.send_request(data_request)
.expect("sending MGM request failed");
@@ -179,7 +171,7 @@ mod tests {
unwrap_mgm_reply(sim_reply).1
}
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
}
@@ -226,7 +218,7 @@ mod tests {
let mut sim_testbench = SimTestbench::new();
switch_device_on(&mut sim_testbench, SwitchId::Mgm0);
let mut request = mgm_request(MgmId::Mgm0, MgmRequestLis3Mdl::RequestSensorData);
let mut request = mgm_request(mgm::Id::Mgm0, mgm::Request::RequestSensorData);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
@@ -239,7 +231,7 @@ mod tests {
let first_reply = unwrap_mgm_reply(sim_reply).1;
sim_testbench.step_until(Duration::from_millis(50)).unwrap();
request = mgm_request(MgmId::Mgm0, MgmRequestLis3Mdl::RequestSensorData);
request = mgm_request(mgm::Id::Mgm0, mgm::Request::RequestSensorData);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
@@ -276,8 +268,8 @@ mod tests {
switch_device_on(&mut sim_testbench, SwitchId::Mgm1);
for request in [
mgm_request(MgmId::Mgm0, MgmRequestLis3Mdl::RequestSensorData),
mgm_request(MgmId::Mgm1, MgmRequestLis3Mdl::RequestSensorData),
mgm_request(mgm::Id::Mgm0, mgm::Request::RequestSensorData),
mgm_request(mgm::Id::Mgm1, mgm::Request::RequestSensorData),
] {
sim_testbench
.send_request(request)
@@ -291,7 +283,7 @@ mod tests {
.expect("no MGM0 reply received");
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
let (id, reply) = unwrap_mgm_reply(sim_reply);
assert_eq!(id, MgmId::Mgm0);
assert_eq!(id, mgm::Id::Mgm0);
assert_eq!(reply.switch_state, SwitchStateBinary::Off);
let sim_reply = sim_testbench
@@ -299,7 +291,7 @@ mod tests {
.expect("no MGM1 reply received");
assert_eq!(sim_reply.component(), SimComponent::Mgm1Lis3Mdl);
let (id, reply) = unwrap_mgm_reply(sim_reply);
assert_eq!(id, MgmId::Mgm1);
assert_eq!(id, mgm::Id::Mgm1);
assert_eq!(reply.switch_state, SwitchStateBinary::On);
}
}
+22 -25
View File
@@ -3,7 +3,7 @@ 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};
@@ -12,8 +12,8 @@ use types::pcdu::SwitchStateBinary;
pub struct MagnetorquerModel {
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>,
}
@@ -22,7 +22,7 @@ impl MagnetorquerModel {
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 +30,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 +45,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 +65,17 @@ impl MagnetorquerModel {
pub fn send_housekeeping_data(&mut self) {
self.reply_sender
.send(SimReply::from(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
@@ -96,10 +96,7 @@ impl Model for MagnetorquerModel {}
mod tests {
use std::time::Duration;
use satrs_minisim::{
acs::{MgtDipole, MgtHkSet, MgtReply, MgtRequest},
SimReply, SimRequestWithTime,
};
use satrs_minisim::{acs::mgt, SimReply, SimRequestWithTime};
use types::pcdu::SwitchId;
use crate::{eps::tests::switch_device_on, test_helpers::SimTestbench};
@@ -107,7 +104,7 @@ mod tests {
#[test]
fn test_basic_mgt_request_is_off() {
let mut sim_testbench = SimTestbench::new();
let request = SimRequestWithTime::new_with_epoch_time(MgtRequest::RequestHk);
let request = SimRequestWithTime::new_with_epoch_time(mgt::Request::RequestHk);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
@@ -121,7 +118,7 @@ mod tests {
fn test_basic_mgt_request_is_on() {
let mut sim_testbench = SimTestbench::new();
switch_device_on(&mut sim_testbench, SwitchId::Mgt);
let request = SimRequestWithTime::new_with_epoch_time(MgtRequest::RequestHk);
let request = SimRequestWithTime::new_with_epoch_time(mgt::Request::RequestHk);
sim_testbench
.send_request(request)
@@ -135,16 +132,16 @@ mod tests {
panic!("unexpected reply");
};
match mgt_reply {
MgtReply::Hk(hk) => {
assert_eq!(hk.dipole, MgtDipole::default());
mgt::Reply::Hk(hk) => {
assert_eq!(hk.dipole, mgt::Dipole::default());
assert!(!hk.torquing);
}
_ => panic!("unexpected reply"),
}
}
fn check_mgt_hk(sim_testbench: &mut SimTestbench, expected_hk_set: MgtHkSet) {
let request = SimRequestWithTime::new_with_epoch_time(MgtRequest::RequestHk);
fn check_mgt_hk(sim_testbench: &mut SimTestbench, expected_hk_set: mgt::HkSet) {
let request = SimRequestWithTime::new_with_epoch_time(mgt::Request::RequestHk);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
@@ -157,7 +154,7 @@ mod tests {
panic!("unexpected reply");
};
match mgt_reply {
MgtReply::Hk(hk) => {
mgt::Reply::Hk(hk) => {
assert_eq!(hk, expected_hk_set);
}
_ => panic!("unexpected reply"),
@@ -168,12 +165,12 @@ mod tests {
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 = SimRequestWithTime::new_with_epoch_time(MgtRequest::ApplyTorque {
let request = SimRequestWithTime::new_with_epoch_time(mgt::Request::ApplyTorque {
duration: Duration::from_millis(100),
dipole: commanded_dipole,
});
@@ -185,7 +182,7 @@ mod tests {
check_mgt_hk(
&mut sim_testbench,
MgtHkSet {
mgt::HkSet {
dipole: commanded_dipole,
torquing: true,
},
@@ -195,8 +192,8 @@ mod tests {
.unwrap();
check_mgt_hk(
&mut sim_testbench,
MgtHkSet {
dipole: MgtDipole::default(),
mgt::HkSet {
dipole: mgt::Dipole::default(),
torquing: false,
},
);
+16 -16
View File
@@ -5,13 +5,13 @@ use nexosim::{
time::{Clock, MonotonicTime, SystemClock},
};
use satrs_minisim::{
acs::{mgm::MgmId, MgmRequestLis3Mdl, MgtRequest},
acs::{mgm, mgt},
eps::PcduRequest,
SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime,
};
use crate::{
acs::{mgm::MagnetometerModel, mgt::MagnetorquerModel},
acs::{mgm::MgmModel, mgt::MagnetorquerModel},
eps::PcduModel,
};
@@ -23,8 +23,8 @@ 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>,
}
@@ -42,8 +42,8 @@ pub struct SimController {
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>,
) -> Self {
@@ -127,24 +127,24 @@ impl SimController {
}
}
fn handle_mgm_request(&mut self, mgm_id: MgmId, mgm_request: MgmRequestLis3Mdl) {
fn handle_mgm_request(&mut self, mgm_id: mgm::Id, mgm_request: mgm::Request) {
let addr = match mgm_id {
MgmId::Mgm0 => &self.addr_wrapper.mgm_0_addr,
MgmId::Mgm1 => &self.addr_wrapper.mgm_1_addr,
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_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) => {
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");
}
}
@@ -176,12 +176,12 @@ impl SimController {
}
}
fn handle_mgt_request(&mut self, mgt_request: MgtRequest) {
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,
@@ -189,7 +189,7 @@ impl SimController {
&self.addr_wrapper.mgt_addr,
)
.unwrap(),
MgtRequest::RequestHk => self
mgt::Request::RequestHk => self
.simulation
.process_event(
MagnetorquerModel::request_housekeeping_data,
+97 -105
View File
@@ -2,10 +2,7 @@ use nexosim::time::MonotonicTime;
use serde::{Deserialize, Serialize};
use crate::{
acs::{
mgm::{MgmId, MgmReply},
MgmRequestLis3Mdl, MgtReply, MgtRequest,
},
acs::{mgm, mgt},
eps::{PcduReply, PcduRequest},
};
@@ -22,11 +19,8 @@ pub enum SimComponent {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SimRequest {
SimCtrl(SimCtrlRequest),
Mgm {
id: MgmId,
request: MgmRequestLis3Mdl,
},
Mgt(MgtRequest),
Mgm { id: mgm::Id, request: mgm::Request },
Mgt(mgt::Request),
Pcdu(PcduRequest),
}
@@ -36,8 +30,8 @@ impl From<SimCtrlRequest> for SimRequest {
}
}
impl From<MgtRequest> for SimRequest {
fn from(request: MgtRequest) -> Self {
impl From<mgt::Request> for SimRequest {
fn from(request: mgt::Request) -> Self {
Self::Mgt(request)
}
}
@@ -70,8 +64,8 @@ impl SimRequestWithTime {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SimReply {
SimCtrl(SimCtrlReply),
Mgm { id: MgmId, reply: MgmReply },
Mgt(MgtReply),
Mgm { id: mgm::Id, reply: mgm::Reply },
Mgt(mgt::Reply),
Pcdu(PcduReply),
}
@@ -92,8 +86,8 @@ impl From<SimCtrlReply> for SimReply {
}
}
impl From<MgtReply> for SimReply {
fn from(reply: MgtReply) -> Self {
impl From<mgt::Reply> for SimReply {
fn from(reply: mgt::Reply) -> Self {
Self::Mgt(reply)
}
}
@@ -142,62 +136,50 @@ pub mod eps {
}
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, PartialEq, Eq, 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),
}
// 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,
}
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::SimComponent;
/// 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;
@@ -212,35 +194,42 @@ 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 {
impl Id {
pub const fn sim_component(&self) -> SimComponent {
match self {
MgmId::Mgm0 => SimComponent::Mgm0Lis3Mdl,
MgmId::Mgm1 => SimComponent::Mgm1Lis3Mdl,
Id::Mgm0 => SimComponent::Mgm0Lis3Mdl,
Id::Mgm1 => SimComponent::Mgm1Lis3Mdl,
}
}
}
impl MgmReply {
impl Reply {
pub fn new(
switch_state: SwitchStateBinary,
sensor_values: MgmSensorValuesMicroTesla,
sensor_values: SensorValuesMicroTesla,
fault_mode: SpiFaultMode,
) -> Self {
match fault_mode {
@@ -308,39 +297,42 @@ pub mod acs {
}
}
// 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, Eq, Serialize, Deserialize)]
pub enum MgtRequestType {
ApplyTorque,
}
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Eq, 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,
}
#[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 enum RequestType {
ApplyTorque,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MgtReply {
Ack(MgtRequestType),
Nak(MgtRequestType),
Hk(MgtHkSet),
#[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 HkSet {
pub dipole: Dipole,
pub torquing: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Reply {
Ack(RequestType),
Nak(RequestType),
Hk(HkSet),
}
}
}
+20 -16
View File
@@ -1,9 +1,9 @@
use acs::{mgm::MagnetometerModel, mgt::MagnetorquerModel};
use acs::{mgm::MgmModel, mgt::MagnetorquerModel};
use controller::{ModelAddrWrapper, SimController};
use eps::PcduModel;
use nexosim::simulation::{Mailbox, SimInit};
use nexosim::time::{MonotonicTime, SystemClock};
use satrs_minisim::acs::mgm::MgmId;
use satrs_minisim::acs::mgm;
use satrs_minisim::udp::SIM_CTRL_PORT;
use satrs_minisim::{SimReply, SimRequestWithTime};
use std::sync::mpsc;
@@ -32,10 +32,16 @@ fn create_sim_controller(
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,
Duration::from_millis(50),
reply_sender.clone(),
);
let mgm_1_model = MgmModel::new(
mgm::Id::Mgm1,
Duration::from_millis(50),
reply_sender.clone(),
);
let mgm_0_mailbox = Mailbox::new();
let mgm_0_addr = mgm_0_mailbox.address();
@@ -49,10 +55,10 @@ 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());
// Input connections.
@@ -60,14 +66,12 @@ fn create_sim_controller(
.mgt_switch
.connect(MagnetorquerModel::switch_device, &mgt_addr);
// Output connections.
mgt_model.gen_magnetic_field.connect(
MagnetometerModel::apply_external_magnetic_field,
&mgm_0_addr,
);
mgt_model.gen_magnetic_field.connect(
MagnetometerModel::apply_external_magnetic_field,
&mgm_1_addr,
);
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());
+17 -17
View File
@@ -2,8 +2,8 @@ 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::MgmRequestLis3Mdl;
use satrs_minisim::acs::mgm::{FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, RawValues};
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};
@@ -74,7 +74,7 @@ impl MgmId {
#[derive(Default)]
pub struct SpiDummyInterface {
pub dummy_values: RawValues,
pub dummy_values: sim_mgm::RawValues,
}
impl SpiDummyInterface {
@@ -88,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 {
@@ -110,12 +110,12 @@ 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 sim_id = match self.id {
MgmId::_0 => satrs_minisim::acs::mgm::MgmId::Mgm0,
MgmId::_1 => satrs_minisim::acs::mgm::MgmId::Mgm1,
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: MgmRequestLis3Mdl::RequestSensorData,
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}");
@@ -652,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,
@@ -771,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,
@@ -912,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,
@@ -1065,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,
@@ -1106,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();
@@ -1279,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 {
@@ -1317,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(
@@ -1382,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,
@@ -1401,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,
@@ -1410,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()),