FDIR extensions and improvements for MGM device handler

This commit is contained in:
Robin Mueller
2026-09-23 17:26:28 +02:00
parent 1c6e777d24
commit ea31e89433
9 changed files with 1116 additions and 131 deletions
+22 -6
View File
@@ -4,7 +4,8 @@ use clap::Parser as _;
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use satrs_minisim::{
SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider,
SimReply, SimRequest, acs::MgmRequestLis3Mdl, acs::SpiFaultMode, udp::SIM_CTRL_PORT,
SimReply, SimRequest, acs::MgmRequestLis3Mdl, acs::SpiFault, acs::SpiFaultMode,
udp::SIM_CTRL_PORT,
};
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
use std::{
@@ -105,6 +106,15 @@ impl From<SpiFaultModeSelect> for SpiFaultMode {
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum SpiFaultKindSelect {
/// Cleared when the device is switched off, so a power cycle recovers from it.
Transient,
/// Survives power cycles.
#[default]
Permanent,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum HealthStateSelect {
Healthy,
@@ -140,6 +150,9 @@ struct MgmArgs {
/// regardless of which MGM the request names (a pre-existing minisim limitation).
#[arg(long, value_enum)]
spi_fault: Option<SpiFaultModeSelect>,
/// Whether a power cycle clears the injected SPI fault.
#[arg(long, value_enum, default_value_t)]
spi_fault_kind: SpiFaultKindSelect,
/// Override the device's FDIR health state, for example to clear a `Faulty` state set by
/// the handler after the underlying issue has been fixed or worked around.
#[arg(long, value_enum)]
@@ -191,7 +204,10 @@ fn handle_mgm_command(
if target_id != types::ComponentId::AcsMgm0 {
bail!("SPI fault injection is only supported for MGM0 right now (minisim limitation)");
}
inject_mgm_failure(mode.into())?;
inject_mgm_failure(SpiFault {
mode: mode.into(),
cleared_by_power_cycle: args.spi_fault_kind == SpiFaultKindSelect::Transient,
})?;
}
if args.ping {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
@@ -477,12 +493,12 @@ fn main() -> anyhow::Result<()> {
Ok(())
}
/// Injects the given SPI fault mode directly into minisim's MGM0 model, bypassing the OBSW.
/// Injects the given SPI fault directly into minisim's MGM0 model, bypassing the OBSW.
///
/// 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(mode: SpiFaultMode) -> anyhow::Result<()> {
fn inject_mgm_failure(fault: 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)))?;
@@ -514,9 +530,9 @@ fn inject_mgm_failure(mode: SpiFaultMode) -> anyhow::Result<()> {
Err(e) => return Err(e.into()),
}
let request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::SetSpiFault(mode));
let request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::SetSpiFault(fault));
sim_socket.send_to(&serde_json::to_vec(&request)?, sim_addr)?;
log::info!("injected SPI fault mode {mode:?} into minisim MGM0");
log::info!("injected SPI fault {fault:?} into minisim MGM0");
Ok(())
}