diff --git a/satrs-book/src/images/.gitignore b/satrs-book/src/images/.gitignore new file mode 100644 index 0000000..8d71bf9 --- /dev/null +++ b/satrs-book/src/images/.gitignore @@ -0,0 +1 @@ +*.bkp diff --git a/satrs-example/client/src/main.rs b/satrs-example/client/src/main.rs index 985f880..26b7e45 100644 --- a/satrs-example/client/src/main.rs +++ b/satrs-example/client/src/main.rs @@ -30,31 +30,29 @@ pub struct Cli { #[derive(clap::Subcommand)] enum Commands { + /// Commands addressed to a component in the OBSW. + #[command(subcommand)] + Obsw(ObswCommand), + /// Commands that talk straight to minisim's own control port, bypassing the OBSW entirely. + /// + /// This is the same control port the OBSW's internal sim client uses. Kept separate from + /// `Obsw`, since these commands have no OBSW component to address and only make sense in a + /// test/simulation environment, never something the real flight software could ask for. + #[command(subcommand)] + Sim(SimCommand), +} + +#[derive(clap::Subcommand)] +enum ObswCommand { Mgm0(MgmArgs), Mgm1(MgmArgs), MgmAssy(MgmAssemblyArgs), AcsSubsystem(SubsystemArgs), - /// Inject a fault directly into the simulator, bypassing the OBSW entirely. - /// - /// This talks straight to minisim's own control port (the same one the OBSW's internal sim - /// client uses), since fault injection is a test/simulation-environment capability, not - /// something the real flight software could ever ask for. - SimFault(SimFaultArgs), } -impl Commands { - /// The OBSW component this command is addressed to, if any. `SimFault` has none, since it - /// never goes through the OBSW. - #[inline] - pub fn target_id(&self) -> Option { - match self { - Commands::Mgm0(_mgm_args) => Some(types::ComponentId::AcsMgm0), - Commands::Mgm1(_mgm_args) => Some(types::ComponentId::AcsMgm1), - Commands::MgmAssy(_mgm_assembly_args) => Some(types::ComponentId::AcsMgmAssembly), - Commands::AcsSubsystem(_subsystem_args) => Some(types::ComponentId::AcsSubsystem), - Commands::SimFault(_) => None, - } - } +#[derive(clap::Subcommand)] +enum SimCommand { + Fault(SimFaultArgs), } #[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)] @@ -129,6 +127,69 @@ pub enum SubsystemModeSelect { Safe, } +fn handle_mgm_command( + client: &UdpSocket, + addr: SocketAddr, + target_id: types::ComponentId, + args: MgmArgs, +) { + if args.ping { + let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(target_id, types::MessageType::Ping), + types::acs::mgm::request::Request::Ping, + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} ping request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + if args.request_hk { + let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(target_id, types::MessageType::Hk), + types::acs::mgm::request::Request::Hk(HkRequest { + id: types::acs::mgm::request::HkId::Sensor, + req_type: types::HkRequestType::OneShot, + }), + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} HK request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + if let Some(mode) = args.mode { + let dev_mode = match mode { + DeviceModeSelect::Off => types::DeviceMode::Off, + DeviceModeSelect::Normal => types::DeviceMode::Normal, + }; + + let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(target_id, types::MessageType::Mode), + types::acs::mgm::request::Request::Mode( + types::acs::mgm::request::ModeRequest::SetMode(dev_mode), + ), + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} HK request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } +} + fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> { fern::Dispatch::new() .format(|out, message, record| { @@ -185,68 +246,18 @@ fn main() -> anyhow::Result<()> { client.send_to(&request_packet, addr).unwrap(); } if let Some(cmd) = cli.commands { - let target_id = cmd.target_id(); match cmd { - Commands::Mgm0(args) | Commands::Mgm1(args) => { - let target_id = target_id.expect("mgm commands always have a target id"); - if args.ping { - let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( - SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), - TcHeader::new(target_id, types::MessageType::Ping), - types::acs::mgm::request::Request::Ping, - ); - let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); - log::info!( - "sending {:?} ping request with TC ID {:#010x}", - target_id, - sent_tc_id.raw() - ); - let request_packet = request.to_vec(); - client.send_to(&request_packet, addr).unwrap(); - } - if args.request_hk { - let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( - SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), - TcHeader::new(target_id, types::MessageType::Hk), - types::acs::mgm::request::Request::Hk(HkRequest { - id: types::acs::mgm::request::HkId::Sensor, - req_type: types::HkRequestType::OneShot, - }), - ); - let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); - log::info!( - "sending {:?} HK request with TC ID {:#010x}", - target_id, - sent_tc_id.raw() - ); - let request_packet = request.to_vec(); - client.send_to(&request_packet, addr).unwrap(); - } - if let Some(mode) = args.mode { - let dev_mode = match mode { - DeviceModeSelect::Off => types::DeviceMode::Off, - DeviceModeSelect::Normal => types::DeviceMode::Normal, - }; - - let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( - SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), - TcHeader::new(target_id, types::MessageType::Mode), - types::acs::mgm::request::Request::Mode( - types::acs::mgm::request::ModeRequest::SetMode(dev_mode), - ), - ); - let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); - log::info!( - "sending {:?} HK request with TC ID {:#010x}", - target_id, - sent_tc_id.raw() - ); - let request_packet = request.to_vec(); - client.send_to(&request_packet, addr).unwrap(); - } + Commands::Sim(SimCommand::Fault(sim_fault_args)) => { + inject_sim_fault(sim_fault_args.mode.into())?; } - Commands::MgmAssy(mgm_assembly_args) => { - let target_id = target_id.expect("mgm assembly command always has a target id"); + Commands::Obsw(ObswCommand::Mgm0(args)) => { + handle_mgm_command(&client, addr, types::ComponentId::AcsMgm0, args) + } + Commands::Obsw(ObswCommand::Mgm1(args)) => { + handle_mgm_command(&client, addr, types::ComponentId::AcsMgm1, args) + } + Commands::Obsw(ObswCommand::MgmAssy(mgm_assembly_args)) => { + let target_id = types::ComponentId::AcsMgmAssembly; if mgm_assembly_args.ping { let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), @@ -292,8 +303,8 @@ fn main() -> anyhow::Result<()> { client.send_to(&request_packet, addr).unwrap(); } } - Commands::AcsSubsystem(subsystem_args) => { - let target_id = target_id.expect("acs subsystem command always has a target id"); + Commands::Obsw(ObswCommand::AcsSubsystem(subsystem_args)) => { + let target_id = types::ComponentId::AcsSubsystem; if subsystem_args.ping { let request = types::ccsds::CcsdsTcPacketOwned::new_with_request( SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), @@ -332,9 +343,6 @@ fn main() -> anyhow::Result<()> { client.send_to(&request_packet, addr).unwrap(); } } - Commands::SimFault(sim_fault_args) => { - inject_sim_fault(sim_fault_args.mode.into())?; - } } } diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index f70f84d..0eae501 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -74,11 +74,10 @@ pub struct SpiDummyInterface { } impl SpiDummyInterface { - fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> bool { + fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) { rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.x.to_le_bytes()); rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.y.to_be_bytes()); rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.z.to_be_bytes()); - true } } @@ -89,12 +88,11 @@ pub struct TestSpiInterface { } impl TestSpiInterface { - fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> bool { + fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) { rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2].copy_from_slice(&self.next_mgm_data.x.to_le_bytes()); rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2].copy_from_slice(&self.next_mgm_data.y.to_le_bytes()); rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2].copy_from_slice(&self.next_mgm_data.z.to_le_bytes()); self.call_count += 1; - true } } @@ -105,14 +103,7 @@ pub struct SpiSimInterface { impl SpiSimInterface { // Right now, we only support requesting sensor data and not configuration of the sensor. - // - // Returns whether a reply arrived at all. Real SPI cannot time out, since the master clocks - // bytes in lockstep with the transfer, so this can only be `false` here because the sim - // itself failed to answer the request in time over its mpsc channel. That is a - // simulator/testbed liveness issue, not a device fault a real SPI bus could ever produce, so - // the caller must not treat it as an FDIR-relevant SPI fault. It still means `rx` was not - // written, so the caller has to discard the (stale) buffer contents either way. - fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> bool { + fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) { let mgm_sensor_request = MgmRequestLis3Mdl::RequestSensorData; if let Err(e) = self .sim_request_tx @@ -130,11 +121,9 @@ impl SpiSimInterface { .copy_from_slice(&sim_reply_lis3.raw.y.to_le_bytes()); rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2] .copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes()); - true } Err(e) => { - log::warn!("MGM LIS3 SIM reply did not arrive in time: {e}"); - false + log::warn!("MGM LIS3 SIM reply timeout: {e}"); } } } @@ -148,11 +137,7 @@ pub enum SpiCommunication { } impl SpiCommunication { - /// Performs the transfer, returning whether `rx` was actually written. - /// - /// This can only be `false` for the simulated backend (see [SpiSimInterface::transfer]); a - /// real SPI transfer always completes. - fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> bool { + fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) { match self { SpiCommunication::Dummy(dummy) => dummy.transfer(tx, rx), SpiCommunication::Sim(sim_if) => sim_if.transfer(tx, rx), @@ -371,16 +356,10 @@ impl MgmHandlerLis3Mdl { pub fn poll_sensor(&mut self) { // Communicate with the device. This is actually how to read the data from the LIS3 device // SPI interface. - let transfer_ok = self.spi_com.transfer( + self.spi_com.transfer( &self.buffers.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], &mut self.buffers.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], ); - if !transfer_ok { - // Not a real SPI fault (see SpiSimInterface::transfer), just a simulator hiccup: - // discard the stale buffer contents without touching the FDIR fault counter. - self.shared_mgm_set.lock().unwrap().valid = false; - return; - } let x_raw = i16::from_le_bytes( self.buffers.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2] .try_into()