diff --git a/Cargo.toml b/Cargo.toml index 4b29c5f..bda3c87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,9 @@ members = [ "satrs", "satrs-mib", "satrs-example", - "satrs-minisim", + "satrs-example/models", + "satrs-example/client", + "satrs-example/minisim", "satrs-shared", "embedded-examples/embedded-client", ] diff --git a/satrs-example/Cargo.toml b/satrs-example/Cargo.toml index bbdaa89..684d432 100644 --- a/satrs-example/Cargo.toml +++ b/satrs-example/Cargo.toml @@ -23,23 +23,18 @@ derive-new = "0.7" cfg-if = "1" arbitrary-int = "2" bitbybit = "1.4" +postcard = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" -[dependencies.satrs] -path = "../satrs" -features = ["test_util"] - -[dependencies.satrs-minisim] -path = "../satrs-minisim" - -[dependencies.satrs-mib] -version = "0.1.1" -path = "../satrs-mib" +satrs = { path = "../satrs", features = ["test_util"] } +models = { path = "./models" } +satrs-minisim = { path = "./minisim" } +satrs-mib = { path = "../satrs-mib" } [features] -default = ["heap_tmtc"] -heap_tmtc = [] +# default = ["heap_tmtc"] +# heap_tmtc = [] [dev-dependencies] env_logger = "0.11" diff --git a/satrs-example/client/Cargo.toml b/satrs-example/client/Cargo.toml new file mode 100644 index 0000000..56a97e7 --- /dev/null +++ b/satrs-example/client/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4", features = ["derive"] } +log = "0.4" +fern = "0.7" +humantime = "2" +serde = { version = "1" } +satrs-example = { path = ".." } +models = { path = "../models" } +spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false } +bitbybit = "1.4" +arbitrary-int = "2" +ctrlc = { version = "3.5" } +postcard = { version = "1" } +anyhow = "1" diff --git a/satrs-example/client/src/main.rs b/satrs-example/client/src/main.rs new file mode 100644 index 0000000..8294107 --- /dev/null +++ b/satrs-example/client/src/main.rs @@ -0,0 +1,160 @@ +use anyhow::bail; +use arbitrary_int::u11; +use clap::Parser as _; +use models::{Apid, MessageType, TcHeader}; +use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT}; +use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader}; +use std::{ + net::{IpAddr, SocketAddr, UdpSocket}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::{Duration, SystemTime}, +}; + +#[derive(clap::Parser)] +pub struct Cli { + #[arg(short, long)] + ping: bool, + #[arg(short, long)] + test_event: bool, +} + +fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> { + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}] {}", + humantime::format_rfc3339_seconds(SystemTime::now()), + record.level(), + record.target(), + message + )) + }) + .level(level) + .chain(std::io::stdout()) + .chain(fern::log_file("output.log")?) + .apply()?; + Ok(()) +} + +fn main() -> anyhow::Result<()> { + setup_logger(log::LevelFilter::Debug).unwrap(); + let kill_signal = Arc::new(AtomicBool::new(false)); + let ctrl_kill_signal = kill_signal.clone(); + ctrlc::set_handler(move || ctrl_kill_signal.store(true, Ordering::Relaxed)).unwrap(); + let cli = Cli::parse(); + + let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); + let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed"); + client.set_nonblocking(true)?; + client.set_read_timeout(Some(Duration::from_millis(200)))?; + + if cli.ping { + let request = models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)), + TcHeader::new(models::ComponentId::Controller, models::MessageType::Ping), + models::control::request::Request::Ping, + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!("sending ping request with TC ID {:#010x}", sent_tc_id.raw()); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + if cli.test_event { + let request = models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)), + TcHeader::new(models::ComponentId::Controller, models::MessageType::Event), + models::control::request::Request::TestEvent, + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending event request with TC ID {:#010x}", + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + + let mut recv_buf: Box<[u8; 2048]> = Box::new([0; 2048]); + log::info!("entering listening loop"); + loop { + if kill_signal.load(std::sync::atomic::Ordering::Relaxed) { + log::info!("received kill signal, exiting"); + break; + } + match client.recv(recv_buf.as_mut_slice()) { + Ok(received_bytes) => handle_raw_tm_packet(&recv_buf.as_slice()[0..received_bytes])?, + Err(e) => { + if e.kind() == std::io::ErrorKind::WouldBlock + || e.kind() == std::io::ErrorKind::TimedOut + { + continue; + } + log::warn!("UDP reception error: {}", e) + } + } + } + Ok(()) +} + +fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> { + match spacepackets::CcsdsPacketReader::new_with_checksum(data) { + Ok(packet) => { + //let (tm_header, response, remainder) = unpack_tm_header_and_response(&packet)?; + let tm_header_result = + postcard::take_from_bytes::(packet.user_data()); + if let Err(e) = tm_header_result { + bail!("Failed to deserialize TM header: {}", e); + } + let (tm_header, remainder) = tm_header_result.unwrap(); + if let Some(tc_id) = tm_header.tc_id { + log::info!( + "Received TM with APID {} and from sender {:?} for TC ID {:#010x}", + packet.apid(), + tm_header.sender_id, + tc_id.raw() + ); + } else { + log::info!( + "Received unsolicited TM with APID {} and from sender {:?}", + packet.apid(), + tm_header.sender_id, + ); + } + if tm_header.message_type == MessageType::Event { + let response = postcard::from_bytes::(remainder); + log::info!( + "Received event from {:?}: {:?}", + tm_header.sender_id, + response.unwrap() + ); + return Ok(()); + } + match tm_header.sender_id { + models::ComponentId::EpsPcdu => { + let response = + postcard::from_bytes::(remainder); + log::info!("Received response from PCDU: {:?}", response.unwrap()); + } + models::ComponentId::Controller => { + let response = + postcard::from_bytes::(remainder); + log::info!("Received response from controller: {:?}", response.unwrap()); + } + models::ComponentId::AcsSubsystem => todo!(), + models::ComponentId::AcsMgmAssembly => todo!(), + models::ComponentId::AcsMgm0 => todo!(), + models::ComponentId::AcsMgm1 => todo!(), + models::ComponentId::EpsSubsystem => todo!(), + models::ComponentId::UdpServer => todo!(), + models::ComponentId::TcpServer => todo!(), + models::ComponentId::Ground => todo!(), + models::ComponentId::EventManager => {} + } + } + Err(_) => todo!(), + } + Ok(()) +} diff --git a/satrs-minisim/Cargo.toml b/satrs-example/minisim/Cargo.toml similarity index 82% rename from satrs-minisim/Cargo.toml rename to satrs-example/minisim/Cargo.toml index d941ff2..c54ef13 100644 --- a/satrs-minisim/Cargo.toml +++ b/satrs-example/minisim/Cargo.toml @@ -15,12 +15,10 @@ strum = { version = "0.27", features = ["derive"] } num_enum = "0.7" humantime = "2" tai-time = { version = "0.3", features = ["serde"] } +nexosim = { version = "0.3.1" } -[dependencies.nexosim] -version = "0.3.1" - -[dependencies.satrs] -path = "../satrs" +satrs = { path = "../../satrs" } +models = { path = "../models" } [dev-dependencies] delegate = "0.13" diff --git a/satrs-minisim/README.md b/satrs-example/minisim/README.md similarity index 100% rename from satrs-minisim/README.md rename to satrs-example/minisim/README.md diff --git a/satrs-minisim/src/acs.rs b/satrs-example/minisim/src/acs.rs similarity index 97% rename from satrs-minisim/src/acs.rs rename to satrs-example/minisim/src/acs.rs index c4c5692..fe5546c 100644 --- a/satrs-minisim/src/acs.rs +++ b/satrs-example/minisim/src/acs.rs @@ -1,10 +1,10 @@ use std::{f32::consts::PI, sync::mpsc, time::Duration}; +use models::pcdu::SwitchStateBinary; use nexosim::{ model::{Context, Model}, ports::Output, }; -use satrs::power::SwitchStateBinary; use satrs_minisim::{ acs::{ lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla, @@ -179,13 +179,12 @@ impl Model for MagnetorquerModel {} pub mod tests { use std::time::Duration; - use satrs::power::SwitchStateBinary; + use models::pcdu::{SwitchId, SwitchStateBinary}; use satrs_minisim::{ acs::{ lis3mdl::{self, MgmLis3MdlReply}, MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest, }, - eps::PcduSwitch, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, }; @@ -215,7 +214,7 @@ pub mod tests { #[test] fn test_basic_mgm_request_switched_on() { let mut sim_testbench = SimTestbench::new(); - switch_device_on(&mut sim_testbench, PcduSwitch::Mgm); + switch_device_on(&mut sim_testbench, SwitchId::Mgm0); let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData); sim_testbench @@ -279,7 +278,7 @@ pub mod tests { #[test] fn test_basic_mgt_request_is_on() { let mut sim_testbench = SimTestbench::new(); - switch_device_on(&mut sim_testbench, PcduSwitch::Mgt); + switch_device_on(&mut sim_testbench, SwitchId::Mgt); let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); sim_testbench @@ -324,7 +323,7 @@ pub mod tests { #[test] fn test_basic_mgt_request_is_on_and_torquing() { let mut sim_testbench = SimTestbench::new(); - switch_device_on(&mut sim_testbench, PcduSwitch::Mgt); + switch_device_on(&mut sim_testbench, SwitchId::Mgt); let commanded_dipole = MgtDipole { x: -200, y: 200, diff --git a/satrs-minisim/src/controller.rs b/satrs-example/minisim/src/controller.rs similarity index 100% rename from satrs-minisim/src/controller.rs rename to satrs-example/minisim/src/controller.rs diff --git a/satrs-minisim/src/eps.rs b/satrs-example/minisim/src/eps.rs similarity index 84% rename from satrs-minisim/src/eps.rs rename to satrs-example/minisim/src/eps.rs index 466a1cd..60eadc8 100644 --- a/satrs-minisim/src/eps.rs +++ b/satrs-example/minisim/src/eps.rs @@ -1,14 +1,11 @@ use std::{sync::mpsc, time::Duration}; +use models::pcdu::{SwitchId, SwitchMapBinaryWrapper, SwitchStateBinary}; use nexosim::{ model::{Context, Model}, ports::Output, }; -use satrs::power::SwitchStateBinary; -use satrs_minisim::{ - eps::{PcduReply, PcduSwitch, SwitchMapBinaryWrapper}, - SimReply, -}; +use satrs_minisim::{eps::PcduReply, SimReply}; pub const SWITCH_INFO_DELAY_MS: u64 = 10; @@ -45,10 +42,7 @@ impl PcduModel { self.reply_sender.send(reply).unwrap(); } - pub async fn switch_device( - &mut self, - switch_and_target_state: (PcduSwitch, SwitchStateBinary), - ) { + pub async fn switch_device(&mut self, switch_and_target_state: (SwitchId, SwitchStateBinary)) { let val = self .switcher_map .0 @@ -56,12 +50,13 @@ impl PcduModel { .unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0)); *val = switch_and_target_state.1; match switch_and_target_state.0 { - PcduSwitch::Mgm => { + SwitchId::Mgm0 => { self.mgm_0_switch.send(switch_and_target_state.1).await; } - PcduSwitch::Mgt => { + SwitchId::Mgt => { self.mgt_switch.send(switch_and_target_state.1).await; } + SwitchId::Mgm1 => todo!(), } } } @@ -73,16 +68,16 @@ pub(crate) mod tests { use super::*; use std::time::Duration; + use models::pcdu::SwitchMapBinary; use satrs_minisim::{ - eps::{PcduRequest, SwitchMapBinary}, - SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, + eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, }; use crate::test_helpers::SimTestbench; fn switch_device( sim_testbench: &mut SimTestbench, - switch: PcduSwitch, + switch: SwitchId, target: SwitchStateBinary, ) { let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice { @@ -97,10 +92,10 @@ pub(crate) mod tests { } #[allow(dead_code)] - pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: PcduSwitch) { + pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: SwitchId) { switch_device(sim_testbench, switch, SwitchStateBinary::Off); } - pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: PcduSwitch) { + pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: SwitchId) { switch_device(sim_testbench, switch, SwitchStateBinary::On); } @@ -128,7 +123,7 @@ pub(crate) mod tests { } } - fn test_pcdu_switching_single_switch(switch: PcduSwitch, target: SwitchStateBinary) { + fn test_pcdu_switching_single_switch(switch: SwitchId, target: SwitchStateBinary) { let mut sim_testbench = SimTestbench::new(); switch_device(&mut sim_testbench, switch, target); let mut switcher_map = get_all_off_switch_map(); @@ -165,17 +160,17 @@ pub(crate) mod tests { #[test] fn test_pcdu_switching_mgm_on() { - test_pcdu_switching_single_switch(PcduSwitch::Mgm, SwitchStateBinary::On); + test_pcdu_switching_single_switch(SwitchId::Mgm0, SwitchStateBinary::On); } #[test] fn test_pcdu_switching_mgt_on() { - test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On); + test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On); } #[test] fn test_pcdu_switching_mgt_off() { - test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On); - test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::Off); + test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On); + test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::Off); } } diff --git a/satrs-minisim/src/lib.rs b/satrs-example/minisim/src/lib.rs similarity index 86% rename from satrs-minisim/src/lib.rs rename to satrs-example/minisim/src/lib.rs index 508f358..92c82bd 100644 --- a/satrs-minisim/src/lib.rs +++ b/satrs-example/minisim/src/lib.rs @@ -1,5 +1,4 @@ use nexosim::time::MonotonicTime; -use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] @@ -162,73 +161,7 @@ impl From for SimCtrlReply { pub mod eps { use super::*; - use satrs::power::{SwitchState, SwitchStateBinary}; - use std::collections::HashMap; - use strum::{EnumIter, IntoEnumIterator}; - - pub type SwitchMap = HashMap; - pub type SwitchMapBinary = HashMap; - - pub struct SwitchMapWrapper(pub SwitchMap); - pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary); - - #[derive( - Debug, - Copy, - Clone, - PartialEq, - Eq, - Serialize, - Deserialize, - Hash, - EnumIter, - IntoPrimitive, - TryFromPrimitive, - )] - #[repr(u16)] - pub enum PcduSwitch { - Mgm = 0, - Mgt = 1, - } - - impl Default for SwitchMapBinaryWrapper { - fn default() -> Self { - let mut switch_map = SwitchMapBinary::default(); - for entry in PcduSwitch::iter() { - switch_map.insert(entry, SwitchStateBinary::Off); - } - Self(switch_map) - } - } - - impl Default for SwitchMapWrapper { - fn default() -> Self { - let mut switch_map = SwitchMap::default(); - for entry in PcduSwitch::iter() { - switch_map.insert(entry, SwitchState::Unknown); - } - Self(switch_map) - } - } - - impl SwitchMapWrapper { - pub fn new_with_init_switches_off() -> Self { - let mut switch_map = SwitchMap::default(); - for entry in PcduSwitch::iter() { - switch_map.insert(entry, SwitchState::Off); - } - Self(switch_map) - } - - pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self { - Self( - switch_map - .iter() - .map(|(key, value)| (*key, SwitchState::from(*value))) - .collect(), - ) - } - } + use models::pcdu::{SwitchId, SwitchMapBinary, SwitchStateBinary}; #[derive(Debug, Copy, Clone)] #[repr(u8)] @@ -240,7 +173,7 @@ pub mod eps { #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PcduRequest { SwitchDevice { - switch: PcduSwitch, + switch: SwitchId, state: SwitchStateBinary, }, RequestSwitchInfo, @@ -264,7 +197,7 @@ pub mod eps { pub mod acs { use std::time::Duration; - use satrs::power::SwitchStateBinary; + use models::pcdu::SwitchStateBinary; use super::*; diff --git a/satrs-minisim/src/main.rs b/satrs-example/minisim/src/main.rs similarity index 100% rename from satrs-minisim/src/main.rs rename to satrs-example/minisim/src/main.rs diff --git a/satrs-minisim/src/test_helpers.rs b/satrs-example/minisim/src/test_helpers.rs similarity index 100% rename from satrs-minisim/src/test_helpers.rs rename to satrs-example/minisim/src/test_helpers.rs diff --git a/satrs-minisim/src/time.rs b/satrs-example/minisim/src/time.rs similarity index 100% rename from satrs-minisim/src/time.rs rename to satrs-example/minisim/src/time.rs diff --git a/satrs-minisim/src/udp.rs b/satrs-example/minisim/src/udp.rs similarity index 98% rename from satrs-minisim/src/udp.rs rename to satrs-example/minisim/src/udp.rs index e177547..6093a74 100644 --- a/satrs-minisim/src/udp.rs +++ b/satrs-example/minisim/src/udp.rs @@ -91,11 +91,8 @@ impl SimUdpServer { self.sender_addr = Some(src); let sim_req = SimRequest::from_raw_data(&self.req_buf[..bytes_read]); - if sim_req.is_err() { - log::warn!( - "received UDP request with invalid format: {}", - sim_req.unwrap_err() - ); + if let Err(e) = sim_req { + log::warn!("received UDP request with invalid format: {}", e); return processed_requests; } self.request_sender.send(sim_req.unwrap()).unwrap(); diff --git a/satrs-example/models/Cargo.toml b/satrs-example/models/Cargo.toml new file mode 100644 index 0000000..aacf377 --- /dev/null +++ b/satrs-example/models/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "models" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1", features = ["derive"] } +spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false } +satrs = { path = "../../satrs" } +num_enum = { version = "0.7" } +strum = { version = "0.27", features = ["derive"] } +postcard = { version = "1" } +thiserror = { version = "2" } +bitbybit = "1.4" +arbitrary-int = "2" diff --git a/satrs-example/models/src/ccsds.rs b/satrs-example/models/src/ccsds.rs new file mode 100644 index 0000000..fe1256a --- /dev/null +++ b/satrs-example/models/src/ccsds.rs @@ -0,0 +1,130 @@ +use crate::TmHeader; +use serde::Serialize; +use spacepackets::{ + CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpHeader, SpacePacketHeader, + ccsds_packet_len_for_user_data_len_with_checksum, +}; + +use crate::TcHeader; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CcsdsTcPacketOwned { + pub sp_header: SpacePacketHeader, + pub tc_header: TcHeader, + pub payload: alloc::vec::Vec, +} + +impl CcsdsTcPacketOwned { + pub fn new_with_request( + sp_header: SpacePacketHeader, + tc_header: TcHeader, + request: R, + ) -> Self { + let request_serialized = postcard::to_allocvec(&request).unwrap(); + Self::new(sp_header, tc_header, request_serialized) + } + + pub fn new( + sp_header: SpacePacketHeader, + tc_header: TcHeader, + payload: alloc::vec::Vec, + ) -> Self { + Self { + sp_header, + tc_header, + payload, + } + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let response_len = + postcard::experimental::serialized_size(&self.tc_header)? + self.payload.len(); + let mut ccsds_tc = CcsdsPacketCreatorWithReservedData::new_tc_with_checksum( + self.sp_header, + response_len, + buf, + )?; + let user_data = ccsds_tc.packet_data_mut(); + let ser_len = postcard::to_slice(&self.tc_header, user_data)?.len(); + user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload); + let ccsds_packet_len = ccsds_tc.finish(); + Ok(ccsds_packet_len) + } + + pub fn len_written(&self) -> usize { + ccsds_packet_len_for_user_data_len_with_checksum( + postcard::experimental::serialized_size(&self.tc_header).unwrap() as usize + + postcard::experimental::serialized_size(&self.payload).unwrap() as usize, + ) + .unwrap() + } + + pub fn to_vec(&self) -> alloc::vec::Vec { + let mut buf = alloc::vec![0u8; self.len_written()]; + let len = self.write_to_bytes(&mut buf).unwrap(); + buf.truncate(len); + buf + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CcsdsCreationError { + #[error("CCSDS packet creation error: {0}")] + CcsdsPacketCreation(#[from] CcsdsPacketCreationError), + #[error("postcard error: {0}")] + Postcard(#[from] postcard::Error), + #[error("timestamp generation error")] + Time, +} + +/// Unserialized owned TM packet which can be cloned and sent around. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CcsdsTmPacketOwned { + pub sp_header: SpacePacketHeader, + pub tm_header: TmHeader, + pub payload: alloc::vec::Vec, +} + +impl CcsdsTmPacketOwned { + pub fn new_with_serde_payload( + sp_header: SpHeader, + tm_header: &TmHeader, + payload: &impl Serialize, + ) -> Result { + Ok(CcsdsTmPacketOwned { + sp_header, + tm_header: *tm_header, + payload: postcard::to_allocvec(&payload)?, + }) + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let response_len = + postcard::experimental::serialized_size(&self.tm_header)? + self.payload.len(); + let mut ccsds_tm = CcsdsPacketCreatorWithReservedData::new_tm_with_checksum( + self.sp_header, + response_len, + buf, + )?; + let user_data = ccsds_tm.packet_data_mut(); + let ser_len = postcard::to_slice(&self.tm_header, user_data)?.len(); + user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload); + let ccsds_packet_len = ccsds_tm.finish(); + Ok(ccsds_packet_len) + } + + pub fn len_written(&self) -> usize { + ccsds_packet_len_for_user_data_len_with_checksum( + postcard::experimental::serialized_size(&self.tm_header).unwrap() as usize + + postcard::experimental::serialized_size(&self.payload).unwrap() as usize, + ) + .unwrap() + } + + pub fn to_vec(&self) -> alloc::vec::Vec { + let mut buf = alloc::vec![0u8; self.len_written()]; + let len = self.write_to_bytes(&mut buf).unwrap(); + buf.truncate(len); + buf + } +} diff --git a/satrs-example/models/src/control.rs b/satrs-example/models/src/control.rs new file mode 100644 index 0000000..a56743f --- /dev/null +++ b/satrs-example/models/src/control.rs @@ -0,0 +1,39 @@ +use crate::Message; + +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] +pub enum Event { + TestEvent, +} + +impl Message for Event { + fn message_type(&self) -> crate::MessageType { + crate::MessageType::Event + } +} + +pub mod request { + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Request { + Ping, + TestEvent, + } +} + +pub mod response { + use crate::Message; + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Response { + Ok, + Event(super::Event), + } + + impl Message for Response { + fn message_type(&self) -> crate::MessageType { + match self { + Response::Ok => crate::MessageType::Verification, + Response::Event(_event) => crate::MessageType::Event, + } + } + } +} diff --git a/satrs-example/models/src/lib.rs b/satrs-example/models/src/lib.rs new file mode 100644 index 0000000..0eb97a4 --- /dev/null +++ b/satrs-example/models/src/lib.rs @@ -0,0 +1,172 @@ +extern crate alloc; +use spacepackets::{ + CcsdsPacketIdAndPsc, + time::cds::{CdsTime, MIN_CDS_FIELD_LEN}, +}; + +pub mod ccsds; +pub mod control; +pub mod mgm; +pub mod pcdu; + +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(u64)] +pub enum ComponentId { + Controller, + + AcsSubsystem, + AcsMgmAssembly, + AcsMgm0, + AcsMgm1, + + EpsSubsystem, + EpsPcdu, + + UdpServer, + TcpServer, + EventManager, + + Ground, +} + +#[derive(Debug, PartialEq, Eq, strum::EnumIter)] +#[bitbybit::bitenum(u11)] +pub enum Apid { + Tmtc = 1, + Cfdp = 2, + + Acs = 3, + Eps = 6, +} + +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum Event { + ControllerEvent(control::Event), +} + +impl Message for Event { + fn message_type(&self) -> MessageType { + MessageType::Event + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct TmHeader { + pub sender_id: ComponentId, + pub target_id: ComponentId, + pub message_type: MessageType, + /// Telemetry can either be sent unsolicited, or as a response to telecommands. + pub tc_id: Option, + /// Raw CDS short timestamp. + pub timestamp: Option<[u8; 7]>, +} + +impl TmHeader { + pub fn new( + sender_id: ComponentId, + target_id: ComponentId, + message_type: MessageType, + tc_id: Option, + cds_timestamp: &CdsTime, + ) -> Self { + // Can not fail, CDS short always requires 7 bytes. + let mut stamp_buf: [u8; MIN_CDS_FIELD_LEN] = [0; MIN_CDS_FIELD_LEN]; + cds_timestamp.write_to_bytes(&mut stamp_buf).unwrap(); + Self { + sender_id, + target_id, + tc_id, + message_type, + timestamp: Some(stamp_buf), + } + } + pub fn new_for_unsolicited_tm( + sender_id: ComponentId, + target_id: ComponentId, + message_type: MessageType, + cds_timestamp: &CdsTime, + ) -> Self { + Self::new(sender_id, target_id, message_type, None, cds_timestamp) + } + + pub fn new_for_tc_response( + sender_id: ComponentId, + target_id: ComponentId, + message_type: MessageType, + tc_id: CcsdsPacketIdAndPsc, + cds_timestamp: &CdsTime, + ) -> Self { + Self::new( + sender_id, + target_id, + message_type, + Some(tc_id), + cds_timestamp, + ) + } + + pub fn from_bytes_postcard(data: &[u8]) -> Result<(Self, &[u8]), postcard::Error> { + postcard::take_from_bytes::(data) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct TcHeader { + pub target_id: ComponentId, + pub request_type: MessageType, +} + +impl TcHeader { + pub fn new(target_id: ComponentId, request_type: MessageType) -> Self { + Self { + target_id, + request_type, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum MessageType { + Ping, + Mode, + Hk, + Action, + Event, + Verification, +} + +pub trait Message { + fn message_type(&self) -> MessageType; +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum DeviceMode { + Off = 0, + On = 1, + Normal = 2, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub enum HkRequestType { + OneShot, + EnablePeriodic(core::time::Duration), + DisablePeriodic, + ModifyInterval(core::time::Duration), +} + +#[cfg(test)] +mod tests {} diff --git a/satrs-example/models/src/mgm.rs b/satrs-example/models/src/mgm.rs new file mode 100644 index 0000000..5c10859 --- /dev/null +++ b/satrs-example/models/src/mgm.rs @@ -0,0 +1,52 @@ +pub mod request { + use crate::HkRequestType; + + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)] + pub enum HkId { + Sensor, + } + + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)] + pub struct HkRequest { + pub id: HkId, + pub req_type: HkRequestType, + } + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Request { + Ping, + Hk(HkRequest), + } +} + +#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub struct MgmData { + pub valid: bool, + pub x: f32, + pub y: f32, + pub z: f32, +} + +pub mod response { + use crate::{Message, mgm::MgmData}; + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum HkResponse { + MgmData(MgmData), + } + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Response { + Ok, + Hk(HkResponse), + } + + impl Message for Response { + fn message_type(&self) -> crate::MessageType { + match self { + Response::Ok => crate::MessageType::Verification, + Response::Hk(_hk_response) => crate::MessageType::Hk, + } + } + } +} diff --git a/satrs-example/models/src/pcdu.rs b/satrs-example/models/src/pcdu.rs new file mode 100644 index 0000000..7f8f896 --- /dev/null +++ b/satrs-example/models/src/pcdu.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; + +use strum::IntoEnumIterator as _; + +#[bitbybit::bitfield(u16, debug, default = 0x0)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct SwitchesBitfield { + #[bit(2, rw)] + magnetorquer: bool, + #[bit(1, rw)] + mgm1: bool, + #[bit(0, rw)] + mgm0: bool, +} + +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + Hash, + strum::EnumIter, + num_enum::IntoPrimitive, + num_enum::TryFromPrimitive, +)] +#[repr(u16)] +pub enum SwitchId { + Mgm0 = 0, + Mgm1 = 1, + Mgt = 2, +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum SwitchState { + Off = 0, + On = 1, + Unknown = 2, + Faulty = 3, +} + +impl From for SwitchState { + fn from(value: SwitchStateBinary) -> Self { + match value { + SwitchStateBinary::Off => SwitchState::Off, + SwitchStateBinary::On => SwitchState::On, + } + } +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum SwitchStateBinary { + Off = 0, + On = 1, +} + +pub type SwitchMapBinary = HashMap; + +pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary); + +impl Default for SwitchMapBinaryWrapper { + fn default() -> Self { + let mut switch_map = SwitchMapBinary::default(); + for entry in SwitchId::iter() { + switch_map.insert(entry, SwitchStateBinary::Off); + } + Self(switch_map) + } +} + +pub struct SwitchRequest { + pub switch_id: SwitchId, + pub target_state: SwitchStateBinary, +} + +impl SwitchRequest { + pub fn new(switch_id: SwitchId, target_state: SwitchStateBinary) -> Self { + Self { + switch_id, + target_state, + } + } + + pub fn switch_id(&self) -> SwitchId { + self.switch_id + } + + pub fn target_state(&self) -> SwitchStateBinary { + self.target_state + } +} + +pub mod request { + use super::*; + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Request { + Ping, + GetSwitches, + EnableSwitches(SwitchesBitfield), + DisableSwitches(SwitchesBitfield), + } +} + +pub mod response { + use super::*; + use crate::Message; + + #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] + pub enum Response { + Ok, + Switches(SwitchesBitfield), + } + + impl Message for Response { + fn message_type(&self) -> crate::MessageType { + match self { + Response::Ok => crate::MessageType::Verification, + Response::Switches(_switches) => crate::MessageType::Action, + } + } + } +} diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index 5bb18e8..c15e356 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -1,14 +1,14 @@ -use derive_new::new; -use satrs::hk::{HkRequest, HkRequestVariant}; +use models::ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned}; +use models::mgm::MgmData; +use models::pcdu::SwitchId; +use models::{mgm, ComponentId, HkRequestType}; use satrs::mode_tree::{ModeChild, ModeNode}; -use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender}; -use satrs_example::ids::generic_pus::PUS_MODE; +use satrs::spacepackets::CcsdsPacketIdAndPsc; use satrs_example::{DeviceMode, TimestampHelper}; use satrs_minisim::acs::lis3mdl::{ MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, }; use satrs_minisim::acs::MgmRequestLis3Mdl; -use satrs_minisim::eps::PcduSwitch; use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest}; use std::fmt::Debug; use std::sync::mpsc::{self}; @@ -19,17 +19,12 @@ use satrs::mode::{ ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, ModeRequestHandlerMpscBounded, }; -use satrs::pus::{EcssTmSender, PusTmVariant}; -use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId}; +use satrs::request::{GenericMessage, MessageMetadata}; use satrs_example::config::components::NO_SENDER; -use crate::hk::PusHkHelper; -use crate::pus::hk::{HkReply, HkReplyVariant}; -use crate::requests::CompositeRequest; +use crate::ccsds::pack_ccsds_tm_packet_for_now; +use crate::eps::PowerSwitchHelper; use crate::spi::SpiInterface; -use crate::tmtc::sender::TmTcSender; - -use serde::{Deserialize, Serialize}; pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14; @@ -38,12 +33,6 @@ pub const X_LOWBYTE_IDX: usize = 9; pub const Y_LOWBYTE_IDX: usize = 11; pub const Z_LOWBYTE_IDX: usize = 13; -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[repr(u32)] -pub enum SetId { - SensorData = 0, -} - #[derive(Default, Debug, PartialEq, Eq)] pub enum TransitionState { #[default] @@ -120,19 +109,10 @@ impl SpiInterface for SpiSimInterfaceWrapper { } } -#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] -pub struct MgmData { - pub valid: bool, - pub x: f32, - pub y: f32, - pub z: f32, -} - #[derive(Default)] pub struct BufWrapper { tx_buf: [u8; 32], rx_buf: [u8; 32], - tm_buf: [u8; 32], } pub struct ModeHelpers { @@ -154,40 +134,52 @@ impl Default for ModeHelpers { } /// Example MGM device handler strongly based on the LIS3MDL MEMS device. -#[derive(new)] #[allow(clippy::too_many_arguments)] -pub struct MgmHandlerLis3Mdl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, -> { - id: UniqueApidTargetId, +pub struct MgmHandlerLis3Mdl { + id: ComponentId, dev_str: &'static str, mode_node: ModeRequestHandlerMpscBounded, - composite_request_rx: mpsc::Receiver>, - hk_reply_tx: mpsc::SyncSender>, - switch_helper: SwitchHelper, - tm_sender: TmTcSender, + tc_rx: mpsc::Receiver, + tm_tx: mpsc::SyncSender, + switch_helper: PowerSwitchHelper, pub com_interface: ComInterface, shared_mgm_set: Arc>, - #[new(value = "PusHkHelper::new(id)")] - hk_helper: PusHkHelper, - #[new(default)] + //hk_helper: PusHkHelper, mode_helpers: ModeHelpers, - #[new(default)] bufs: BufWrapper, - #[new(default)] stamp_helper: TimestampHelper, } -impl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > MgmHandlerLis3Mdl -{ +impl MgmHandlerLis3Mdl { + #[allow(clippy::too_many_arguments)] + pub fn new( + id: ComponentId, + dev_str: &'static str, + mode_node: ModeRequestHandlerMpscBounded, + tc_rx: mpsc::Receiver, + tm_tx: mpsc::SyncSender, + switch_helper: PowerSwitchHelper, + com_interface: ComInterface, + shared_mgm_set: Arc>, + ) -> Self { + Self { + id, + dev_str, + mode_node, + tc_rx, + tm_tx, + switch_helper, + com_interface, + shared_mgm_set, + mode_helpers: ModeHelpers::default(), + bufs: BufWrapper::default(), + stamp_helper: TimestampHelper::default(), + } + } pub fn periodic_operation(&mut self) { self.stamp_helper.update_from_now(); // Handle requests. - self.handle_composite_requests(); + self.handle_telecommands(); self.handle_mode_requests(); if let Some(target_mode_submode) = self.mode_helpers.target { self.handle_mode_transition(target_mode_submode); @@ -198,67 +190,76 @@ impl< } } - pub fn handle_composite_requests(&mut self) { + pub fn handle_telecommands(&mut self) { loop { - match self.composite_request_rx.try_recv() { - Ok(ref msg) => match &msg.message { - CompositeRequest::Hk(hk_request) => { - self.handle_hk_request(&msg.requestor_info, hk_request) - } - // TODO: This object does not have actions (yet).. Still send back completion failure - // reply. - CompositeRequest::Action(_action_req) => {} - }, - - Err(e) => { - if e != mpsc::TryRecvError::Empty { - log::warn!( - "{}: failed to receive composite request: {:?}", - self.dev_str, - e - ); - } else { - break; + match self.tc_rx.try_recv() { + Ok(packet) => { + let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header); + match postcard::from_bytes::(&packet.payload) { + Ok(request) => { + log::info!( + "received request {:?} with TC ID {:#010x}", + request, + tc_id.raw() + ); + match request { + mgm::request::Request::Ping => { + self.send_telemetry(Some(tc_id), mgm::response::Response::Ok) + } + mgm::request::Request::Hk(hk_request) => { + self.handle_hk_request(Some(tc_id), &hk_request) + } //mgm::request::Request::Mo + } + } + Err(e) => { + log::warn!("failed to deserialize request: {}", e); + } } } + Err(e) => match e { + std::sync::mpsc::TryRecvError::Empty => break, + std::sync::mpsc::TryRecvError::Disconnected => { + log::warn!("packet sender disconnected") + } + }, } } } - pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) { - match hk_request.variant { - HkRequestVariant::OneShot => { - let mgm_snapshot = *self.shared_mgm_set.lock().unwrap(); - if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet( - self.stamp_helper.stamp(), - SetId::SensorData as u32, - &mut |hk_buf| { - hk_buf[0] = mgm_snapshot.valid as u8; - hk_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes()); - hk_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes()); - hk_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes()); - Ok(13) - }, - &mut self.bufs.tm_buf, - ) { - // TODO: If sending the TM fails, we should also send a failure reply. - self.tm_sender - .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm)) - .expect("failed to send HK TM"); - self.hk_reply_tx - .send(GenericMessage::new( - *requestor_info, - HkReply::new(hk_request.unique_id, HkReplyVariant::Ack), - )) - .expect("failed to send HK reply"); - } else { - // TODO: Send back failure reply. Need result code for this. - log::error!("TM buffer too small to generate HK data"); + pub fn send_telemetry( + &self, + tc_id: Option, + response: mgm::response::Response, + ) { + match pack_ccsds_tm_packet_for_now(self.id, tc_id, &response) { + Ok(packet) => { + if let Err(e) = self.tm_tx.send(packet) { + log::warn!("failed to send TM packet: {}", e); } } - HkRequestVariant::EnablePeriodic => todo!(), - HkRequestVariant::DisablePeriodic => todo!(), - HkRequestVariant::ModifyCollectionInterval(_) => todo!(), + Err(e) => { + log::warn!("failed to pack TM packet: {}", e); + } + } + } + + pub fn handle_hk_request( + &mut self, + tc_id: Option, + hk_request: &models::mgm::request::HkRequest, + ) { + match hk_request.req_type { + HkRequestType::OneShot => { + let mgm_snapshot = *self.shared_mgm_set.lock().unwrap(); + self.send_telemetry( + tc_id, + mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(mgm_snapshot)), + ) + } + HkRequestType::EnablePeriodic(_duration) => todo!(), + HkRequestType::DisablePeriodic => todo!(), + HkRequestType::ModifyInterval(_duration) => todo!(), + _ => todo!(), } } @@ -331,7 +332,7 @@ impl< if self.mode_helpers.transition_state == TransitionState::Idle { let result = self .switch_helper - .send_switch_on_cmd(MessageMetadata::new(0, self.id.id()), PcduSwitch::Mgm); + .send_switch_on_cmd(MessageMetadata::new(0, self.id as u32), SwitchId::Mgm0); if result.is_err() { // Could not send switch command.. still continue with transition. log::error!("failed to send switch on command"); @@ -339,10 +340,7 @@ impl< self.mode_helpers.transition_state = TransitionState::PowerSwitching; } if self.mode_helpers.transition_state == TransitionState::PowerSwitching - && self - .switch_helper - .is_switch_on(PcduSwitch::Mgm) - .expect("switch info error") + && self.switch_helper.is_switch_on(SwitchId::Mgm0) { self.mode_helpers.transition_state = TransitionState::Done; } @@ -356,21 +354,13 @@ impl< } } -impl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeProvider for MgmHandlerLis3Mdl -{ +impl ModeProvider for MgmHandlerLis3Mdl { fn mode_and_submode(&self) -> ModeAndSubmode { self.mode_helpers.current } } -impl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeRequestHandler for MgmHandlerLis3Mdl -{ +impl ModeRequestHandler for MgmHandlerLis3Mdl { type Error = ModeError; fn start_transition( @@ -417,7 +407,7 @@ impl< if requestor.sender_id() == NO_SENDER { return Ok(()); } - if requestor.sender_id() != PUS_MODE.id() { + if requestor.sender_id() != ComponentId::Ground as u32 { log::warn!( "can not send back mode reply to sender {:x}", requestor.sender_id() @@ -434,7 +424,7 @@ impl< requestor: MessageMetadata, reply: ModeReply, ) -> Result<(), Self::Error> { - if requestor.sender_id() != PUS_MODE.id() { + if requestor.sender_id() != ComponentId::Ground as u32 { log::warn!( "can not send back mode reply to sender {}", requestor.sender_id() @@ -455,21 +445,13 @@ impl< } } -impl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeNode for MgmHandlerLis3Mdl -{ +impl ModeNode for MgmHandlerLis3Mdl { fn id(&self) -> satrs::ComponentId { - self.id.into() + self.id as u32 } } -impl< - ComInterface: SpiInterface, - SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeChild for MgmHandlerLis3Mdl -{ +impl ModeChild for MgmHandlerLis3Mdl { type Sender = mpsc::SyncSender>; fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) { @@ -484,19 +466,19 @@ mod tests { sync::{mpsc, Arc}, }; - use arbitrary_int::u21; + use models::{ + pcdu::{SwitchRequest, SwitchState, SwitchStateBinary}, + ComponentId, + }; use satrs::{ mode::{ModeReply, ModeRequest}, mode_tree::ModeParent, - power::SwitchStateBinary, - request::{GenericMessage, UniqueApidTargetId}, + request::GenericMessage, tmtc::PacketAsVec, - ComponentId, }; - use satrs_example::ids::{acs::ASSEMBLY, Apid}; use satrs_minisim::acs::lis3mdl::MgmLis3RawValues; - use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest}; + use crate::eps::pcdu::{SharedSwitchSet, SwitchMap, SwitchSet}; use super::*; @@ -524,22 +506,24 @@ mod tests { #[allow(dead_code)] pub struct MgmTestbench { pub mode_request_tx: mpsc::SyncSender>, - pub mode_reply_rx_to_pus: mpsc::Receiver>, + pub mode_reply_rx_to_ground: mpsc::Receiver>, pub mode_reply_rx_to_parent: mpsc::Receiver>, - pub composite_request_tx: mpsc::Sender>, - pub hk_reply_rx: mpsc::Receiver>, + pub shared_switch_set: SharedSwitchSet, + pub tc_tx: mpsc::SyncSender, pub tm_rx: mpsc::Receiver, - pub handler: MgmHandlerLis3Mdl, + pub switch_rx: mpsc::Receiver>, + pub handler: MgmHandlerLis3Mdl, } #[derive(Default)] + #[allow(dead_code)] pub struct MgmAssemblyMock( - pub HashMap>>, + pub HashMap>>, ); impl ModeNode for MgmAssemblyMock { fn id(&self) -> satrs::ComponentId { - PUS_MODE.into() + ComponentId::AcsMgmAssembly as u32 } } @@ -552,17 +536,19 @@ mod tests { } #[derive(Default)] - pub struct PusMock { - pub request_sender_map: HashMap>>, + #[allow(dead_code)] + pub struct GroundMock { + pub request_sender_map: + HashMap>>, } - impl ModeNode for PusMock { + impl ModeNode for GroundMock { fn id(&self) -> satrs::ComponentId { - PUS_MODE.into() + ComponentId::Ground as u32 } } - impl ModeParent for PusMock { + impl ModeParent for GroundMock { type Sender = mpsc::SyncSender>; fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) { @@ -573,36 +559,40 @@ mod tests { impl MgmTestbench { pub fn new() -> Self { let (request_tx, request_rx) = mpsc::sync_channel(5); - let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5); + let (reply_tx_to_ground, reply_rx_to_ground) = mpsc::sync_channel(5); let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5); - let id = UniqueApidTargetId::new(Apid::Acs.raw_value(), u21::new(1)); - let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx); - let (composite_request_tx, composite_request_rx) = mpsc::channel(); - let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10); - let (tm_tx, tm_rx) = mpsc::sync_channel(10); - let tm_sender = TmTcSender::Heap(tm_tx); + let mode_node = + ModeRequestHandlerMpscBounded::new(ComponentId::Ground as u32, request_rx); + let (tc_tx, tc_rx) = mpsc::sync_channel(10); + let (hk_reply_tx, _hk_reply_rx) = mpsc::sync_channel(10); + let (_tm_tx, tm_rx) = mpsc::sync_channel(10); + let (switcher_tx, switch_rx) = mpsc::sync_channel(10); let shared_mgm_set = Arc::default(); + let mut switch_map = SwitchMap::new(); + switch_map.insert(SwitchId::Mgm0, SwitchState::Off); + let switch_map = SwitchSet::new(switch_map); + let shared_switch_set = SharedSwitchSet::new(Mutex::new(switch_map)); let mut handler = MgmHandlerLis3Mdl::new( - id, + ComponentId::AcsMgm0, "TEST_MGM", mode_node, - composite_request_rx, + tc_rx, hk_reply_tx, - TestSwitchHelper::default(), - tm_sender, + PowerSwitchHelper::new(switcher_tx, shared_switch_set.clone()), TestSpiInterface::default(), shared_mgm_set, ); - handler.add_mode_parent(PUS_MODE.into(), reply_tx_to_pus); - handler.add_mode_parent(ASSEMBLY.into(), reply_tx_to_parent); + handler.add_mode_parent(ComponentId::Ground as u32, reply_tx_to_ground); + handler.add_mode_parent(ComponentId::AcsMgmAssembly as u32, reply_tx_to_parent); Self { mode_request_tx: request_tx, - mode_reply_rx_to_pus: reply_rx_to_pus, + mode_reply_rx_to_ground: reply_rx_to_ground, mode_reply_rx_to_parent: reply_rx_to_parent, - composite_request_tx, + shared_switch_set, + switch_rx, handler, tm_rx, - hk_reply_rx, + tc_tx, } } } @@ -632,7 +622,7 @@ mod tests { testbench .mode_request_tx .send(GenericMessage::new( - MessageMetadata::new(0, PUS_MODE.id()), + MessageMetadata::new(0, ComponentId::Ground as u32), ModeRequest::SetMode { mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), forced: false, @@ -647,22 +637,22 @@ mod tests { assert_eq!(testbench.handler.mode_and_submode().submode(), 0); // Verify power switch handling. - let mut switch_requests = testbench.handler.switch_helper.switch_requests.borrow_mut(); - assert_eq!(switch_requests.len(), 1); - let switch_req = switch_requests.pop_front().expect("no switch request"); - assert_eq!(switch_req.target_state, SwitchStateBinary::On); - assert_eq!(switch_req.switch_id, PcduSwitch::Mgm); - let mut switch_info_requests = testbench - .handler - .switch_helper - .switch_info_requests - .borrow_mut(); - assert_eq!(switch_info_requests.len(), 1); - let switch_info_req = switch_info_requests.pop_front().expect("no switch request"); - assert_eq!(switch_info_req, PcduSwitch::Mgm); + let switch_req = testbench.switch_rx.try_recv().expect("no switch request"); + assert_eq!(switch_req.message.switch_id, SwitchId::Mgm0); + assert_eq!(switch_req.message.target_state, SwitchStateBinary::On); + + // This simulates one cycle for the power switch to update. + testbench + .shared_switch_set + .lock() + .unwrap() + .set_switch_state(SwitchId::Mgm0, SwitchState::On); + + // Now the power switch is updated and the mode request should be completed. + testbench.handler.periodic_operation(); let mode_reply = testbench - .mode_reply_rx_to_pus + .mode_reply_rx_to_ground .try_recv() .expect("no mode reply generated"); match mode_reply.message { @@ -673,7 +663,7 @@ mod tests { _ => panic!("unexpected mode reply"), } // The device should have been polled once. - assert_eq!(testbench.handler.com_interface.call_count, 1); + assert_eq!(testbench.handler.com_interface.call_count, 2); let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap(); assert!(mgm_set.x < 0.001); assert!(mgm_set.y < 0.001); @@ -693,7 +683,7 @@ mod tests { testbench .mode_request_tx .send(GenericMessage::new( - MessageMetadata::new(0, PUS_MODE.id()), + MessageMetadata::new(0, ComponentId::Ground as u32), ModeRequest::SetMode { mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), forced: false, diff --git a/satrs-example/src/bin/simpleclient.rs b/satrs-example/src/bin/simpleclient.rs deleted file mode 100644 index 60a31a7..0000000 --- a/satrs-example/src/bin/simpleclient.rs +++ /dev/null @@ -1,87 +0,0 @@ -use arbitrary_int::u11; -use satrs::pus::verification::RequestId; -use satrs::spacepackets::ecss::tc::PusTcCreator; -use satrs::spacepackets::ecss::tm::PusTmReader; -use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId}; -use satrs::spacepackets::SpHeader; -use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT}; -use std::net::{IpAddr, SocketAddr, UdpSocket}; -use std::time::Duration; - -fn main() { - let mut buf = [0; 32]; - let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); - let pus_tc = PusTcCreator::new_simple( - SpHeader::new_from_apid(u11::new(0x02)), - MessageTypeId::new(17, 1), - &[], - CreatorConfig::default(), - ); - let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed"); - let tc_req_id = RequestId::new(&pus_tc); - println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}"); - let size = pus_tc - .write_to_bytes(&mut buf) - .expect("Creating PUS TC failed"); - client - .send_to(&buf[0..size], addr) - .unwrap_or_else(|_| panic!("Sending to {addr:?} failed")); - client - .set_read_timeout(Some(Duration::from_secs(2))) - .expect("Setting read timeout failed"); - loop { - let res = client.recv(&mut buf); - match res { - Ok(_len) => { - let pus_tm = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed"); - if pus_tm.service_type_id() == 17 && pus_tm.message_subtype_id() == 2 { - println!("Received PUS Ping Reply TM[17,2]") - } else if pus_tm.service_type_id() == 1 { - if pus_tm.source_data().is_empty() { - println!("Invalid verification TM, no source data"); - } - let src_data = pus_tm.source_data(); - if src_data.len() < 4 { - println!("Invalid verification TM source data, less than 4 bytes") - } - let req_id = RequestId::from_bytes(src_data).unwrap(); - let subtype_id = pus_tm.message_subtype_id(); - if subtype_id == 1 { - println!("Received TM[1,1] acceptance success for request ID {req_id}") - } else if subtype_id == 2 { - println!("Received TM[1,2] acceptance failure for request ID {req_id}") - } else if subtype_id == 3 { - println!("Received TM[1,3] start success for request ID {req_id}") - } else if subtype_id == 4 { - println!("Received TM[1,2] start failure for request ID {req_id}") - } else if subtype_id == 5 { - println!("Received TM[1,5] step success for request ID {req_id}") - } else if subtype_id == 6 { - println!("Received TM[1,6] step failure for request ID {req_id}") - } else if subtype_id == 7 { - println!("Received TM[1,7] completion success for request ID {req_id}") - } else if subtype_id == 8 { - println!("Received TM[1,8] completion failure for request ID {req_id}"); - } - } else { - println!( - "Received TM[{}, {}] with {} bytes", - pus_tm.service_type_id(), - pus_tm.message_subtype_id(), - size - ); - } - } - Err(ref e) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::TimedOut => - { - println!("No reply received for 2 seconds"); - break; - } - _ => { - println!("UDP receive error {:?}", res.unwrap_err()); - } - } - } -} diff --git a/satrs-example/src/bin/test.rs b/satrs-example/src/bin/test.rs deleted file mode 100644 index c40a04d..0000000 --- a/satrs-example/src/bin/test.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(dead_code)] - -use crossbeam_channel::{bounded, Receiver, Sender}; -use std::sync::atomic::{AtomicU16, Ordering}; -use std::thread; -use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16}; - -trait FieldDataProvider: Send { - fn get_data(&self) -> &[u8]; -} - -struct FixedFieldDataWrapper { - data: [u8; 8], -} - -impl FixedFieldDataWrapper { - pub fn from_two_u32(p0: u32, p1: u32) -> Self { - let mut data = [0; 8]; - data[0..4].copy_from_slice(p0.to_be_bytes().as_slice()); - data[4..8].copy_from_slice(p1.to_be_bytes().as_slice()); - Self { data } - } -} - -impl FieldDataProvider for FixedFieldDataWrapper { - fn get_data(&self) -> &[u8] { - self.data.as_slice() - } -} - -type FieldDataTraitObj = Box; - -struct ExampleMgmSet { - mgm_vec: [f32; 3], - temperature: u16, -} - -#[derive(FromBytes, IntoBytes, Immutable, Unaligned)] -#[repr(C)] -struct ExampleMgmSetZc { - mgm_vec: [u8; 12], - temperatur: U16, -} - -fn main() { - let (s0, r0): (Sender, Receiver) = bounded(5); - let data_wrapper = FixedFieldDataWrapper::from_two_u32(2, 3); - s0.send(Box::new(data_wrapper)).unwrap(); - let jh0 = thread::spawn(move || { - let data = r0.recv().unwrap(); - let raw = data.get_data(); - println!("Received data {raw:?}"); - }); - let jh1 = thread::spawn(|| {}); - jh0.join().unwrap(); - jh1.join().unwrap(); - //let mut max_val: u16 = u16::MAX; - //max_val += 1; - //println!("Max val: {}", max_val); - let atomic_u16: AtomicU16 = AtomicU16::new(u16::MAX); - atomic_u16.fetch_add(1, Ordering::SeqCst); - println!( - "atomic after overflow: {}", - atomic_u16.load(Ordering::SeqCst) - ); -} diff --git a/satrs-example/src/ccsds.rs b/satrs-example/src/ccsds.rs new file mode 100644 index 0000000..39b6b14 --- /dev/null +++ b/satrs-example/src/ccsds.rs @@ -0,0 +1,34 @@ +use arbitrary_int::u11; +use models::{ccsds::CcsdsTmPacketOwned, Apid, ComponentId, Message, TmHeader}; +use satrs::spacepackets::{ + time::{cds::CdsTime, StdTimestampError}, + CcsdsPacketIdAndPsc, SpHeader, +}; +use serde::Serialize; + +#[derive(Debug, thiserror::Error)] +pub enum CcsdsTmCreationError { + #[error("postcard error: {0}")] + Postcard(#[from] postcard::Error), + #[error("timestamp error: {0}")] + Time(#[from] StdTimestampError), +} + +pub fn pack_ccsds_tm_packet_for_now( + sender_id: ComponentId, + tc_id: Option, + payload: &(impl Serialize + Message), +) -> Result { + let now = CdsTime::now_with_u16_days()?; + let sp_header = SpHeader::new_from_apid(u11::new(Apid::Tmtc as u16)); + let tm_header = TmHeader::new( + sender_id, + ComponentId::Ground, + payload.message_type(), + tc_id, + &now, + ); + Ok(CcsdsTmPacketOwned::new_with_serde_payload( + sp_header, &tm_header, payload, + )?) +} diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs index 908bce1..fb7f75b 100644 --- a/satrs-example/src/config.rs +++ b/satrs-example/src/config.rs @@ -7,13 +7,10 @@ use satrs::{ use satrs_mib::res_code::ResultU16Info; use satrs_mib::resultcode; use std::{collections::HashSet, net::Ipv4Addr}; -use strum::IntoEnumIterator; +use strum::IntoEnumIterator as _; use num_enum::{IntoPrimitive, TryFromPrimitive}; -use satrs::{ - events_legacy::{EventU32TypedSev, SeverityInfo}, - pool::{StaticMemoryPool, StaticPoolConfig}, -}; +use satrs::pool::{StaticMemoryPool, StaticPoolConfig}; #[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] @@ -39,19 +36,17 @@ pub enum GroupId { pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED; pub const SERVER_PORT: u16 = 7301; -pub const TEST_EVENT: EventU32TypedSev = EventU32TypedSev::::new(0, 0); - lazy_static! { pub static ref PACKET_ID_VALIDATOR: HashSet = { let mut set = HashSet::new(); - for id in crate::ids::Apid::iter() { + for id in models::Apid::iter() { set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16))); } set }; pub static ref APID_VALIDATOR: HashSet = { let mut set = HashSet::new(); - for id in crate::ids::Apid::iter() { + for id in models::Apid::iter() { set.insert(id as u16); } set @@ -175,6 +170,6 @@ pub mod pool { pub mod tasks { pub const FREQ_MS_UDP_TMTC: u64 = 200; pub const FREQ_MS_AOCS: u64 = 500; - pub const FREQ_MS_PUS_STACK: u64 = 200; + pub const FREQ_MS_CONTROLLER: u64 = 200; pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5; } diff --git a/satrs-example/src/control.rs b/satrs-example/src/control.rs new file mode 100644 index 0000000..f27835b --- /dev/null +++ b/satrs-example/src/control.rs @@ -0,0 +1,83 @@ +use models::{ + ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned}, + control, ComponentId, +}; +use satrs::spacepackets::CcsdsPacketIdAndPsc; + +use crate::ccsds::pack_ccsds_tm_packet_for_now; + +pub struct Controller { + pub tc_rx: std::sync::mpsc::Receiver, + pub tm_tx: std::sync::mpsc::SyncSender, + pub event_ctrl_tx: std::sync::mpsc::SyncSender, +} + +impl Controller { + pub fn new( + tc_rx: std::sync::mpsc::Receiver, + tm_tx: std::sync::mpsc::SyncSender, + event_ctrl_tx: std::sync::mpsc::SyncSender, + ) -> Self { + Self { + tc_rx, + tm_tx, + event_ctrl_tx, + } + } + + pub fn periodic_operation(&mut self) { + self.handle_telecommands(); + } + + pub fn handle_telecommands(&mut self) { + loop { + match self.tc_rx.try_recv() { + Ok(packet) => { + let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header); + match postcard::from_bytes::(&packet.payload) { + Ok(request) => { + log::info!( + "received request {:?} with TC ID {:#010x}", + request, + tc_id.raw() + ); + match request { + control::request::Request::Ping => self + .send_telemetry(Some(tc_id), control::response::Response::Ok), + control::request::Request::TestEvent => { + self.event_ctrl_tx.send(control::Event::TestEvent).unwrap() + } + } + } + Err(e) => { + log::warn!("failed to deserialize request: {}", e); + } + } + } + Err(e) => match e { + std::sync::mpsc::TryRecvError::Empty => break, + std::sync::mpsc::TryRecvError::Disconnected => { + log::warn!("packet sender disconnected") + } + }, + } + } + } + + pub fn send_telemetry( + &self, + tc_id: Option, + response: control::response::Response, + ) { + match pack_ccsds_tm_packet_for_now(ComponentId::Controller, tc_id, &response) { + Ok(packet) => { + if let Err(e) = self.tm_tx.send(packet) { + log::warn!("failed to send TM packet: {}", e); + } + } + Err(e) => { + log::warn!("failed to pack TM packet: {}", e); + } + } + } +} diff --git a/satrs-example/src/eps/mod.rs b/satrs-example/src/eps/mod.rs index 333edb0..b6c06b4 100644 --- a/satrs-example/src/eps/mod.rs +++ b/satrs-example/src/eps/mod.rs @@ -1,16 +1,15 @@ use derive_new::new; +use models::pcdu::{SwitchId, SwitchRequest, SwitchState, SwitchStateBinary}; use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration}; use satrs::{ - power::{ - PowerSwitchInfo, PowerSwitcherCommandSender, SwitchRequest, SwitchState, SwitchStateBinary, - }, queue::GenericSendError, request::{GenericMessage, MessageMetadata}, }; -use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper}; use thiserror::Error; +use crate::eps::pcdu::SwitchMapWrapper; + use self::pcdu::SharedSwitchSet; pub mod pcdu; @@ -22,6 +21,7 @@ pub struct PowerSwitchHelper { } #[derive(Debug, Error, Copy, Clone, PartialEq, Eq)] +#[allow(dead_code)] pub enum SwitchCommandingError { #[error("send error: {0}")] Send(#[from] GenericSendError), @@ -31,18 +31,72 @@ pub enum SwitchCommandingError { pub enum SwitchInfoError { /// This is a configuration error which should not occur. #[error("switch ID not in map")] - SwitchIdNotInMap(PcduSwitch), + SwitchIdNotInMap(SwitchId), #[error("switch set invalid")] SwitchSetInvalid, } -impl PowerSwitchInfo for PowerSwitchHelper { +impl PowerSwitchHelper { + pub fn send_switch_on_cmd( + &self, + requestor_info: satrs::request::MessageMetadata, + switch_id: SwitchId, + ) -> Result<(), GenericSendError> { + self.switcher_tx.send(GenericMessage::new( + requestor_info, + SwitchRequest::new(switch_id, SwitchStateBinary::On), + ))?; + Ok(()) + } + + #[allow(dead_code)] + pub fn send_switch_off_cmd( + &self, + requestor_info: satrs::request::MessageMetadata, + switch_id: SwitchId, + ) -> Result<(), GenericSendError> { + self.switcher_tx.send(GenericMessage::new( + requestor_info, + SwitchRequest::new(switch_id, SwitchStateBinary::Off), + ))?; + Ok(()) + } + + pub fn switch_state(&self, switch_id: SwitchId) -> Result { + let switch_set = self + .shared_switch_set + .lock() + .expect("failed to lock switch set"); + if !switch_set.valid { + return Err(SwitchInfoError::SwitchSetInvalid); + } + + if let Some(state) = switch_set.switch_map.get(&switch_id) { + return Ok(*state); + } + Err(SwitchInfoError::SwitchIdNotInMap(switch_id)) + } + + #[allow(dead_code)] + fn switch_delay_ms(&self) -> Duration { + // Here, we could set device specific switch delays theoretically. Set it to this value + // for now. + Duration::from_millis(1000) + } + + pub fn is_switch_on(&self, switch_id: SwitchId) -> bool { + if let Ok(state) = self.switch_state(switch_id) { + state == SwitchState::On + } else { + false + } + } +} +/* +impl PowerSwitchInfo for PowerSwitchHelper { type Error = SwitchInfoError; - fn switch_state( - &self, - switch_id: PcduSwitch, - ) -> Result { + fn switch_state(&self, switch_id: SwitchId) -> Result { let switch_set = self .shared_switch_set .lock() @@ -63,43 +117,51 @@ impl PowerSwitchInfo for PowerSwitchHelper { Duration::from_millis(1000) } } +*/ -impl PowerSwitcherCommandSender for PowerSwitchHelper { +/* +impl PowerSwitcherCommandSender for PowerSwitchHelper { type Error = SwitchCommandingError; fn send_switch_on_cmd( &self, requestor_info: satrs::request::MessageMetadata, - switch_id: PcduSwitch, + switch_id: SwitchId, ) -> Result<(), Self::Error> { - self.switcher_tx - .send_switch_on_cmd(requestor_info, switch_id)?; + self.switcher_tx.send(GenericMessage::new( + requestor_info, + SwitchRequest::new(switch_id, SwitchStateBinary::On), + )); Ok(()) } fn send_switch_off_cmd( &self, requestor_info: satrs::request::MessageMetadata, - switch_id: PcduSwitch, + switch_id: SwitchId, ) -> Result<(), Self::Error> { - self.switcher_tx - .send_switch_off_cmd(requestor_info, switch_id)?; + self.switcher_tx.send(GenericMessage::new( + requestor_info, + SwitchRequest::new(switch_id, SwitchStateBinary::Off), + )); Ok(()) } } +*/ #[allow(dead_code)] #[derive(new)] pub struct SwitchRequestInfo { pub requestor_info: MessageMetadata, - pub switch_id: PcduSwitch, - pub target_state: satrs::power::SwitchStateBinary, + pub switch_id: SwitchId, + pub target_state: SwitchStateBinary, } // Test switch helper which can be used for unittests. +#[allow(dead_code)] pub struct TestSwitchHelper { pub switch_requests: RefCell>, - pub switch_info_requests: RefCell>, + pub switch_info_requests: RefCell>, #[allow(dead_code)] pub switch_delay_request_count: u32, pub next_switch_delay: Duration, @@ -120,13 +182,11 @@ impl Default for TestSwitchHelper { } } -impl PowerSwitchInfo for TestSwitchHelper { +/* +impl PowerSwitchInfo for TestSwitchHelper { type Error = SwitchInfoError; - fn switch_state( - &self, - switch_id: PcduSwitch, - ) -> Result { + fn switch_state(&self, switch_id: SwitchId) -> Result { let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut(); switch_info_requests_mut.push_back(switch_id); if !self.switch_map_valid { @@ -144,13 +204,13 @@ impl PowerSwitchInfo for TestSwitchHelper { } } -impl PowerSwitcherCommandSender for TestSwitchHelper { +impl PowerSwitcherCommandSender for TestSwitchHelper { type Error = SwitchCommandingError; fn send_switch_on_cmd( &self, requestor_info: MessageMetadata, - switch_id: PcduSwitch, + switch_id: SwitchId, ) -> Result<(), Self::Error> { let mut switch_requests_mut = self.switch_requests.borrow_mut(); switch_requests_mut.push_back(SwitchRequestInfo { @@ -170,7 +230,7 @@ impl PowerSwitcherCommandSender for TestSwitchHelper { fn send_switch_off_cmd( &self, requestor_info: MessageMetadata, - switch_id: PcduSwitch, + switch_id: SwitchId, ) -> Result<(), Self::Error> { let mut switch_requests_mut = self.switch_requests.borrow_mut(); switch_requests_mut.push_back(SwitchRequestInfo { @@ -187,11 +247,12 @@ impl PowerSwitcherCommandSender for TestSwitchHelper { Ok(()) } } +*/ #[allow(dead_code)] impl TestSwitchHelper { // Helper function which can be used to force a switch to another state for test purposes. - pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) { + pub fn set_switch_state(&mut self, switch: SwitchId, state: SwitchState) { self.switch_map.get_mut().0.insert(switch, state); } } diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index d595d78..99b07ad 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -1,43 +1,118 @@ use std::{ cell::RefCell, - collections::VecDeque, + collections::{HashMap, VecDeque}, sync::{mpsc, Arc, Mutex}, }; use derive_new::new; +use models::{ + ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned}, + pcdu::{ + self, SwitchId, SwitchMapBinary, SwitchMapBinaryWrapper, SwitchRequest, SwitchState, + SwitchStateBinary, SwitchesBitfield, + }, + ComponentId, +}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use satrs::{ - hk::{HkRequest, HkRequestVariant}, mode::{ ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, ModeRequestHandlerMpscBounded, }, mode_tree::{ModeChild, ModeNode}, - power::SwitchRequest, - pus::{EcssTmSender, PusTmVariant}, queue::GenericSendError, - request::{GenericMessage, MessageMetadata, UniqueApidTargetId}, - spacepackets::ByteConversionError, -}; -use satrs_example::{ - config::components::NO_SENDER, - ids::{eps::PCDU, generic_pus::PUS_MODE}, - DeviceMode, TimestampHelper, + request::{GenericMessage, MessageMetadata}, + spacepackets::CcsdsPacketIdAndPsc, }; +use satrs_example::{config::components::NO_SENDER, DeviceMode, TimestampHelper}; use satrs_minisim::{ - eps::{ - PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper, - }, + eps::{PcduReply, PcduRequest}, SerializableSimMsgPayload, SimReply, SimRequest, }; use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator as _; -use crate::{ - hk::PusHkHelper, - pus::hk::{HkReply, HkReplyVariant}, - requests::CompositeRequest, - tmtc::sender::TmTcSender, -}; +use crate::ccsds::pack_ccsds_tm_packet_for_now; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SwitchSet { + pub valid: bool, + pub switch_map: SwitchMap, +} + +impl SwitchSet { + pub fn new(switch_map: SwitchMap) -> Self { + Self { + valid: true, + switch_map, + } + } + + pub fn new_with_init_switches_unknown() -> Self { + let wrapper = SwitchMapWrapper::default(); + Self::new(wrapper.0) + } + + pub fn as_bitfield(&self) -> Option { + for entry in SwitchId::iter() { + if !self.switch_map.contains_key(&entry) { + return None; + } + } + Some( + SwitchesBitfield::builder() + .with_magnetorquer(*self.switch_map.get(&SwitchId::Mgt).unwrap() == SwitchState::On) + .with_mgm1(*self.switch_map.get(&SwitchId::Mgm1).unwrap() == SwitchState::On) + .with_mgm0(*self.switch_map.get(&SwitchId::Mgm0).unwrap() == SwitchState::On) + .build(), + ) + } + + #[allow(dead_code)] + pub fn set_switch_state(&mut self, switch_id: SwitchId, state: SwitchState) -> bool { + if !self.switch_map.contains_key(&switch_id) { + return false; + } + *self.switch_map.get_mut(&switch_id).unwrap() = state; + true + } +} + +pub type SwitchMap = HashMap; + +pub struct SwitchMapWrapper(pub SwitchMap); + +impl Default for SwitchMapWrapper { + fn default() -> Self { + let mut switch_map = SwitchMap::default(); + for entry in SwitchId::iter() { + switch_map.insert(entry, SwitchState::Unknown); + } + Self(switch_map) + } +} + +impl SwitchMapWrapper { + #[allow(dead_code)] + pub fn new_with_init_switches_off() -> Self { + let mut switch_map = SwitchMap::default(); + for entry in SwitchId::iter() { + switch_map.insert(entry, SwitchState::Off); + } + Self(switch_map) + } + + pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self { + Self( + switch_map + .iter() + .map(|(key, value)| (*key, SwitchState::from(*value))) + .collect(), + ) + } +} + +pub type SharedSwitchSet = Arc>; pub trait SerialInterface { type Error: core::fmt::Debug; @@ -194,44 +269,47 @@ pub enum OpCode { PollAndRecvReplies = 1, } -#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SwitchSet { - pub valid: bool, - pub switch_map: SwitchMap, -} - -pub type SharedSwitchSet = Arc>; - /// Example PCDU device handler. -#[derive(new)] #[allow(clippy::too_many_arguments)] pub struct PcduHandler { - id: UniqueApidTargetId, dev_str: &'static str, mode_node: ModeRequestHandlerMpscBounded, - composite_request_rx: mpsc::Receiver>, - hk_reply_tx: mpsc::SyncSender>, switch_request_rx: mpsc::Receiver>, - tm_sender: TmTcSender, + tc_rx: std::sync::mpsc::Receiver, + tm_tx: mpsc::SyncSender, pub com_interface: ComInterface, shared_switch_map: Arc>, - #[new(value = "PusHkHelper::new(id)")] - hk_helper: PusHkHelper, - #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")] mode_and_submode: ModeAndSubmode, - #[new(default)] stamp_helper: TimestampHelper, - #[new(value = "[0; 256]")] - tm_buf: [u8; 256], } impl PcduHandler { + pub fn new( + mode_node: ModeRequestHandlerMpscBounded, + tc_rx: std::sync::mpsc::Receiver, + tm_tx: std::sync::mpsc::SyncSender, + switch_request_rx: mpsc::Receiver>, + com_interface: ComInterface, + shared_switch_map: Arc>, + ) -> Self { + Self { + dev_str: "PCDU", + mode_node, + tc_rx, + switch_request_rx, + tm_tx, + com_interface, + shared_switch_map, + mode_and_submode: ModeAndSubmode::new(0, 0), + stamp_helper: TimestampHelper::default(), + } + } pub fn periodic_operation(&mut self, op_code: OpCode) { match op_code { OpCode::RegularOp => { self.stamp_helper.update_from_now(); // Handle requests. - self.handle_composite_requests(); + self.handle_telecommands(); self.handle_mode_requests(); self.handle_switch_requests(); // Poll the switch states and/or telemetry regularly here. @@ -246,75 +324,87 @@ impl PcduHandler { } } - pub fn handle_composite_requests(&mut self) { + pub fn handle_telecommands(&mut self) { loop { - match self.composite_request_rx.try_recv() { - Ok(ref msg) => match &msg.message { - CompositeRequest::Hk(hk_request) => { - self.handle_hk_request(&msg.requestor_info, hk_request) - } - // TODO: This object does not have actions (yet).. Still send back completion failure - // reply. - CompositeRequest::Action(_action_req) => {} - }, - - Err(e) => { - if e != mpsc::TryRecvError::Empty { - log::warn!( - "{}: failed to receive composite request: {:?}", - self.dev_str, - e - ); - } else { - break; + match self.tc_rx.try_recv() { + Ok(packet) => { + let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header); + match postcard::from_bytes::(&packet.payload) { + Ok(request) => { + log::info!( + "received request {:?} with TC ID {:#010x}", + request, + tc_id.raw() + ); + match request { + pcdu::request::Request::Ping => { + self.send_tm(Some(tc_id), pcdu::response::Response::Ok) + } + pcdu::request::Request::GetSwitches => self.send_tm( + Some(tc_id), + pcdu::response::Response::Switches( + self.shared_switch_map + .lock() + .unwrap() + .as_bitfield() + .expect("could not build switches response"), + ), + ), + pcdu::request::Request::EnableSwitches(switches) => { + self.handle_switches_bitfield_request( + switches, + SwitchStateBinary::On, + ); + } + pcdu::request::Request::DisableSwitches(switches) => { + self.handle_switches_bitfield_request( + switches, + SwitchStateBinary::Off, + ); + } + } + } + Err(e) => { + log::warn!("failed to deserialize request: {}", e); + } } } + Err(e) => match e { + std::sync::mpsc::TryRecvError::Empty => break, + std::sync::mpsc::TryRecvError::Disconnected => { + log::warn!("packet sender disconnected") + } + }, } } } - pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) { - match hk_request.variant { - HkRequestVariant::OneShot => { - if hk_request.unique_id == SetId::SwitcherSet as u32 { - if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet( - self.stamp_helper.stamp(), - SetId::SwitcherSet as u32, - &mut |hk_buf| { - // Send TM down as JSON. - let switch_map_snapshot = self - .shared_switch_map - .lock() - .expect("failed to lock switch map") - .clone(); - let switch_map_json = serde_json::to_string(&switch_map_snapshot) - .expect("failed to serialize switch map"); - if switch_map_json.len() > hk_buf.len() { - log::error!("switch map JSON too large for HK buffer"); - return Err(ByteConversionError::ToSliceTooSmall { - found: hk_buf.len(), - expected: switch_map_json.len(), - }); - } - Ok(switch_map_json.len()) - }, - &mut self.tm_buf, - ) { - self.tm_sender - .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm)) - .expect("failed to send HK TM"); - self.hk_reply_tx - .send(GenericMessage::new( - *requestor_info, - HkReply::new(hk_request.unique_id, HkReplyVariant::Ack), - )) - .expect("failed to send HK reply"); - } + pub fn handle_switches_bitfield_request( + &mut self, + switches: SwitchesBitfield, + state: SwitchStateBinary, + ) { + if switches.mgm0() { + self.handle_device_switching(SwitchId::Mgm0, state); + } + if switches.mgm1() { + self.handle_device_switching(SwitchId::Mgm1, state); + } + if switches.magnetorquer() { + self.handle_device_switching(SwitchId::Mgt, state); + } + } + + pub fn send_tm(&self, tc_id: Option, response: pcdu::response::Response) { + match pack_ccsds_tm_packet_for_now(ComponentId::EpsPcdu, tc_id, &response) { + Ok(packet) => { + if let Err(e) = self.tm_tx.send(packet) { + log::warn!("failed to send TM packet: {}", e); } } - HkRequestVariant::EnablePeriodic => todo!(), - HkRequestVariant::DisablePeriodic => todo!(), - HkRequestVariant::ModifyCollectionInterval(_) => todo!(), + Err(e) => { + log::warn!("failed to pack TM packet: {}", e); + } } } @@ -357,22 +447,26 @@ impl PcduHandler { } } + pub fn handle_device_switching(&mut self, switch_id: SwitchId, state: SwitchStateBinary) { + let pcdu_req = PcduRequest::SwitchDevice { + switch: switch_id, + state, + }; + let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap(); + self.com_interface + .send(pcdu_req_ser.as_bytes()) + .expect("failed to send switch request to PCDU"); + } + pub fn handle_switch_requests(&mut self) { loop { match self.switch_request_rx.try_recv() { - Ok(switch_req) => match PcduSwitch::try_from(switch_req.message.switch_id()) { - Ok(pcdu_switch) => { - let pcdu_req = PcduRequest::SwitchDevice { - switch: pcdu_switch, - state: switch_req.message.target_state(), - }; - let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap(); - self.com_interface - .send(pcdu_req_ser.as_bytes()) - .expect("failed to send switch request to PCDU"); - } - Err(e) => todo!("failed to convert switch ID {:?} to typed PCDU switch", e), - }, + Ok(switch_req) => { + self.handle_device_switching( + switch_req.message.switch_id(), + switch_req.message.target_state(), + ); + } Err(e) => match e { mpsc::TryRecvError::Empty => break, mpsc::TryRecvError::Disconnected => { @@ -450,7 +544,7 @@ impl ModeRequestHandler for PcduHandler ModeRequestHandler for PcduHandler Result<(), Self::Error> { - if requestor.sender_id() != PUS_MODE.id() { + if requestor.sender_id() != ComponentId::Ground as u32 { log::warn!( "can not send back mode reply to sender {}", requestor.sender_id() @@ -490,7 +584,7 @@ impl ModeRequestHandler for PcduHandler ModeNode for PcduHandler { fn id(&self) -> satrs::ComponentId { - PCDU.into() + ComponentId::EpsPcdu as u32 } } @@ -506,12 +600,8 @@ impl ModeChild for PcduHandler { mod tests { use std::sync::mpsc; - use arbitrary_int::u21; - use satrs::{ - mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec, - }; - use satrs_example::ids::{self, Apid}; - use satrs_minisim::eps::SwitchMapBinary; + use models::pcdu::{SwitchMapBinary, SwitchStateBinary}; + use satrs::{mode::ModeRequest, request::GenericMessage}; use super::*; @@ -550,13 +640,13 @@ mod tests { } } + #[allow(dead_code)] pub struct PcduTestbench { pub mode_request_tx: mpsc::SyncSender>, pub mode_reply_rx_to_pus: mpsc::Receiver>, pub mode_reply_rx_to_parent: mpsc::Receiver>, - pub composite_request_tx: mpsc::Sender>, - pub hk_reply_rx: mpsc::Receiver>, - pub tm_rx: mpsc::Receiver, + pub tc_tx: mpsc::SyncSender, + pub tm_rx: mpsc::Receiver, pub switch_request_tx: mpsc::Sender>, pub handler: PcduHandler, } @@ -564,33 +654,30 @@ mod tests { impl PcduTestbench { pub fn new() -> Self { let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5); - let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5); + let (_mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5); let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5); - let mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx); - let (composite_request_tx, composite_request_rx) = mpsc::channel(); - let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10); - let (tm_tx, tm_rx) = mpsc::sync_channel::(5); + let mode_node = + ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, mode_request_rx); + let (tc_tx, tc_rx) = mpsc::sync_channel(5); + let (tm_tx, tm_rx) = mpsc::sync_channel(5); let (switch_request_tx, switch_reqest_rx) = mpsc::channel(); - let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default())); + let shared_switch_map = + Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown())); let mut handler = PcduHandler::new( - UniqueApidTargetId::new(Apid::Eps.raw_value(), u21::new(0)), - "TEST_PCDU", mode_node, - composite_request_rx, - hk_reply_tx, + tc_rx, + tm_tx.clone(), switch_reqest_rx, - TmTcSender::Heap(tm_tx.clone()), SerialInterfaceTest::default(), shared_switch_map, ); - handler.add_mode_parent(ids::eps::SUBSYSTEM.into(), mode_reply_tx_to_parent); - handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus); + handler.add_mode_parent(ComponentId::EpsSubsystem as u32, mode_reply_tx_to_parent); + //handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus); Self { mode_request_tx, mode_reply_rx_to_pus, mode_reply_rx_to_parent, - composite_request_tx, - hk_reply_rx, + tc_tx, tm_rx, switch_request_tx, handler, @@ -610,7 +697,7 @@ mod tests { pub fn verify_switch_req_was_sent( &self, expected_queue_len: usize, - switch_id: PcduSwitch, + switch_id: SwitchId, target_state: SwitchStateBinary, ) { // Check that there is now communication happening. @@ -679,7 +766,7 @@ mod tests { testbench .mode_request_tx .send(GenericMessage::new( - MessageMetadata::new(0, PUS_MODE.id()), + MessageMetadata::new(0, ComponentId::Ground as u32), ModeRequest::SetMode { mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), forced: false, @@ -687,7 +774,7 @@ mod tests { )) .expect("failed to send mode request"); let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap(); - assert!(!switch_map_shared.valid); + assert!(switch_map_shared.valid); drop(switch_map_shared); testbench.handler.periodic_operation(OpCode::RegularOp); testbench @@ -714,7 +801,7 @@ mod tests { testbench .mode_request_tx .send(GenericMessage::new( - MessageMetadata::new(0, PUS_MODE.id()), + MessageMetadata::new(0, ComponentId::Ground as u32), ModeRequest::SetMode { mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), forced: false, @@ -724,8 +811,8 @@ mod tests { testbench .switch_request_tx .send(GenericMessage::new( - MessageMetadata::new(0, ids::acs::MGM0.id()), - SwitchRequest::new(0, SwitchStateBinary::On), + MessageMetadata::new(0, ComponentId::AcsMgm0 as u32), + SwitchRequest::new(SwitchId::Mgm0, SwitchStateBinary::On), )) .expect("failed to send switch request"); testbench.handler.periodic_operation(OpCode::RegularOp); @@ -733,11 +820,11 @@ mod tests { .handler .periodic_operation(OpCode::PollAndRecvReplies); - testbench.verify_switch_req_was_sent(2, PcduSwitch::Mgm, SwitchStateBinary::On); + testbench.verify_switch_req_was_sent(2, SwitchId::Mgm0, SwitchStateBinary::On); testbench.verify_switch_info_req_was_sent(1); let mut switch_map = SwitchMapBinaryWrapper::default().0; *switch_map - .get_mut(&PcduSwitch::Mgm) + .get_mut(&SwitchId::Mgm0) .expect("switch state setting failed") = SwitchStateBinary::On; testbench.verify_switch_reply_received(1, switch_map); diff --git a/satrs-example/src/event_manager.rs b/satrs-example/src/event_manager.rs new file mode 100644 index 0000000..2c7574b --- /dev/null +++ b/satrs-example/src/event_manager.rs @@ -0,0 +1,33 @@ +use models::{ccsds::CcsdsTmPacketOwned, control, ComponentId, Event, Message}; + +use crate::ccsds::pack_ccsds_tm_packet_for_now; + +pub struct EventManager { + pub ctrl_rx: std::sync::mpsc::Receiver, + pub tm_tx: std::sync::mpsc::SyncSender, +} + +impl EventManager { + pub fn periodic_operation(&mut self) { + if let Ok(event) = self.ctrl_rx.try_recv() { + self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event)); + } + } + + pub fn event_to_tm( + &mut self, + sender_id: ComponentId, + event: &(impl serde::Serialize + Message), + ) { + match pack_ccsds_tm_packet_for_now(sender_id, None, event) { + Ok(packet) => { + if let Err(e) = self.tm_tx.send(packet) { + log::warn!("error sending event TM packet: {:?}", e); + } + } + Err(e) => { + log::warn!("error packing event TM packet: {:?}", e); + } + } + } +} diff --git a/satrs-example/src/ids.rs b/satrs-example/src/ids.rs deleted file mode 100644 index 11ae2f7..0000000 --- a/satrs-example/src/ids.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! This is an auto-generated configuration module. -use satrs::request::UniqueApidTargetId; - -#[derive(Debug, PartialEq, Eq, strum::EnumIter)] -#[bitbybit::bitenum(u11)] -pub enum Apid { - Sched = 1, - GenericPus = 2, - Acs = 3, - Cfdp = 4, - Tmtc = 5, - Eps = 6, -} - -pub mod acs { - - #[derive(Debug, PartialEq, Eq)] - #[bitbybit::bitenum(u21, exhaustive = false)] - pub enum Id { - Subsystem = 1, - Assembly = 2, - Mgm0 = 3, - Mgm1 = 4, - } - - pub const SUBSYSTEM: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Subsystem.raw_value()); - pub const ASSEMBLY: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Assembly.raw_value()); - pub const MGM0: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm0.raw_value()); - pub const MGM1: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm1.raw_value()); -} - -pub mod eps { - #[derive(Debug, PartialEq, Eq)] - #[bitbybit::bitenum(u21, exhaustive = false)] - pub enum Id { - Pcdu = 0, - Subsystem = 1, - } - - pub const PCDU: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Pcdu.raw_value()); - pub const SUBSYSTEM: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Subsystem.raw_value()); -} - -pub mod generic_pus { - #[derive(Debug, PartialEq, Eq)] - #[bitbybit::bitenum(u21, exhaustive = false)] - pub enum Id { - PusEventManagement = 0, - PusRouting = 1, - PusTest = 2, - PusAction = 3, - PusMode = 4, - PusHk = 5, - } - - pub const PUS_EVENT_MANAGEMENT: super::UniqueApidTargetId = super::UniqueApidTargetId::new( - super::Apid::GenericPus.raw_value(), - Id::PusEventManagement.raw_value(), - ); - pub const PUS_ROUTING: super::UniqueApidTargetId = super::UniqueApidTargetId::new( - super::Apid::GenericPus.raw_value(), - Id::PusRouting.raw_value(), - ); - pub const PUS_TEST: super::UniqueApidTargetId = super::UniqueApidTargetId::new( - super::Apid::GenericPus.raw_value(), - Id::PusTest.raw_value(), - ); - pub const PUS_ACTION: super::UniqueApidTargetId = super::UniqueApidTargetId::new( - super::Apid::GenericPus.raw_value(), - Id::PusAction.raw_value(), - ); - pub const PUS_MODE: super::UniqueApidTargetId = super::UniqueApidTargetId::new( - super::Apid::GenericPus.raw_value(), - Id::PusMode.raw_value(), - ); - pub const PUS_HK: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::GenericPus.raw_value(), Id::PusHk.raw_value()); -} - -pub mod sched { - #[derive(Debug, PartialEq, Eq)] - #[bitbybit::bitenum(u21, exhaustive = false)] - pub enum Id { - PusSched = 0, - } - - pub const PUS_SCHED: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Sched.raw_value(), Id::PusSched.raw_value()); -} - -pub mod tmtc { - #[derive(Debug, PartialEq, Eq)] - #[bitbybit::bitenum(u21, exhaustive = false)] - pub enum Id { - UdpServer = 0, - TcpServer = 1, - } - - pub const UDP_SERVER: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::UdpServer.raw_value()); - pub const TCP_SERVER: super::UniqueApidTargetId = - super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::TcpServer.raw_value()); -} diff --git a/satrs-example/src/interface/udp.rs b/satrs-example/src/interface/udp.rs index 70efe78..6a0395d 100644 --- a/satrs-example/src/interface/udp.rs +++ b/satrs-example/src/interface/udp.rs @@ -1,66 +1,29 @@ #![allow(dead_code)] +use std::collections::VecDeque; use std::net::{SocketAddr, UdpSocket}; -use std::sync::mpsc; +use std::sync::{mpsc, Arc, Mutex}; -use log::{info, warn}; +use log::warn; +use models::ccsds::CcsdsTmPacketOwned; use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer}; use satrs::pus::HandlingStatus; use satrs::queue::GenericSendError; -use satrs::tmtc::PacketAsVec; - -use satrs::pool::{PoolProviderWithGuards, SharedStaticMemoryPool}; -use satrs::tmtc::PacketInPool; use crate::tmtc::sender::TmTcSender; -pub trait UdpTmHandler { +pub trait UdpTmHandlerProvider { fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr); } -pub struct StaticUdpTmHandler { - pub tm_rx: mpsc::Receiver, - pub tm_store: SharedStaticMemoryPool, +pub struct UdpTmHandlerWithChannel { + pub tm_rx: mpsc::Receiver, } -impl UdpTmHandler for StaticUdpTmHandler { - fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, &recv_addr: &SocketAddr) { - while let Ok(pus_tm_in_pool) = self.tm_rx.try_recv() { - let store_lock = self.tm_store.write(); - if store_lock.is_err() { - warn!("Locking TM store failed"); - continue; - } - let mut store_lock = store_lock.unwrap(); - let pg = store_lock.read_with_guard(pus_tm_in_pool.store_addr); - let read_res = pg.read_as_vec(); - if read_res.is_err() { - warn!("Error reading TM pool data"); - continue; - } - let buf = read_res.unwrap(); - let result = socket.send_to(&buf, recv_addr); - if let Err(e) = result { - warn!("Sending TM with UDP socket failed: {e}") - } - } - } -} - -pub struct DynamicUdpTmHandler { - pub tm_rx: mpsc::Receiver, -} - -impl UdpTmHandler for DynamicUdpTmHandler { +impl UdpTmHandlerProvider for UdpTmHandlerWithChannel { fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) { while let Ok(tm) = self.tm_rx.try_recv() { - if tm.packet.len() > 9 { - let service = tm.packet[7]; - let subservice = tm.packet[8]; - info!("Sending PUS TM[{service},{subservice}]") - } else { - info!("Sending PUS TM"); - } - let result = socket.send_to(&tm.packet, recv_addr); + log::debug!("sending TM from sender {:?}", tm.tm_header.sender_id); + let result = socket.send_to(&tm.to_vec(), recv_addr); if let Err(e) = result { warn!("Sending TM with UDP socket failed: {e}") } @@ -68,12 +31,49 @@ impl UdpTmHandler for DynamicUdpTmHandler { } } -pub struct UdpTmtcServer { - pub udp_tc_server: UdpTcServer, - pub tm_handler: TmHandler, +#[derive(Default, Debug, Clone)] +pub struct TestTmHandler { + addrs_to_send_to: Arc>>, } -impl UdpTmtcServer { +impl UdpTmHandlerProvider for TestTmHandler { + fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) { + self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr); + } +} + +pub enum UdpTmHandler { + Normal(UdpTmHandlerWithChannel), + Test(TestTmHandler), +} + +impl From for UdpTmHandler { + fn from(handler: UdpTmHandlerWithChannel) -> Self { + UdpTmHandler::Normal(handler) + } +} + +impl From for UdpTmHandler { + fn from(handler: TestTmHandler) -> Self { + UdpTmHandler::Test(handler) + } +} + +impl UdpTmHandlerProvider for UdpTmHandler { + fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) { + match self { + UdpTmHandler::Normal(handler) => handler.send_tm_to_udp_client(socket, recv_addr), + UdpTmHandler::Test(handler) => handler.send_tm_to_udp_client(socket, recv_addr), + } + } +} + +pub struct UdpTmtcServer { + pub udp_tc_server: UdpTcServer, + pub tm_handler: UdpTmHandler, +} + +impl UdpTmtcServer { pub fn periodic_operation(&mut self) { loop { if self.poll_tc_server() == HandlingStatus::Empty { @@ -107,15 +107,12 @@ impl UdpTmtcServer { #[cfg(test)] mod tests { + use std::net::IpAddr; use std::net::Ipv4Addr; - use std::{ - collections::VecDeque, - net::IpAddr, - sync::{Arc, Mutex}, - }; use arbitrary_int::traits::Integer as _; use arbitrary_int::u14; + use models::Apid; use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId}; use satrs::{ spacepackets::{ @@ -125,25 +122,13 @@ mod tests { ComponentId, }; use satrs_example::config::OBSW_SERVER_ADDR; - use satrs_example::ids; - use crate::tmtc::sender::{MockSender, TmTcSender}; + use crate::tmtc::sender::MockSender; use super::*; const UDP_SERVER_ID: ComponentId = 0x05; - #[derive(Default, Debug, Clone)] - pub struct TestTmHandler { - addrs_to_send_to: Arc>>, - } - - impl UdpTmHandler for TestTmHandler { - fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) { - self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr); - } - } - #[test] fn test_basic() { let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0); @@ -154,7 +139,7 @@ mod tests { let tm_handler_calls = tm_handler.addrs_to_send_to.clone(); let mut udp_dyn_server = UdpTmtcServer { udp_tc_server, - tm_handler, + tm_handler: tm_handler.into(), }; udp_dyn_server.periodic_operation(); let queue = udp_dyn_server @@ -179,9 +164,9 @@ mod tests { let tm_handler_calls = tm_handler.addrs_to_send_to.clone(); let mut udp_dyn_server = UdpTmtcServer { udp_tc_server, - tm_handler, + tm_handler: tm_handler.into(), }; - let sph = SpHeader::new_for_unseg_tc(ids::Apid::GenericPus.raw_value(), u14::ZERO, 0); + let sph = SpHeader::new_for_unseg_tc(Apid::Tmtc.raw_value(), u14::ZERO, 0); let ping_tc = PusTcCreator::new_simple( sph, MessageTypeId::new(17, 1), diff --git a/satrs-example/src/events.rs b/satrs-example/src/legacy/events.rs similarity index 99% rename from satrs-example/src/events.rs rename to satrs-example/src/legacy/events.rs index bc91add..1f3b161 100644 --- a/satrs-example/src/events.rs +++ b/satrs-example/src/legacy/events.rs @@ -1,6 +1,3 @@ -use std::sync::mpsc::{self}; - -use crate::pus::create_verification_reporter; use arbitrary_int::traits::Integer as _; use arbitrary_int::u11; use satrs::event_man_legacy::{EventMessageU32, EventRoutingError}; @@ -34,6 +31,7 @@ impl EventTmHook for EventApidSetter { } } +/* /// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event /// packets. It also handles the verification completion of PUS event service requests. pub struct PusEventHandler { @@ -292,3 +290,4 @@ mod tests { // TODO: Add test. } } +*/ diff --git a/satrs-example/src/hk.rs b/satrs-example/src/legacy/hk.rs similarity index 100% rename from satrs-example/src/hk.rs rename to satrs-example/src/legacy/hk.rs diff --git a/satrs-example/src/pus/action.rs b/satrs-example/src/legacy/pus/action.rs similarity index 100% rename from satrs-example/src/pus/action.rs rename to satrs-example/src/legacy/pus/action.rs diff --git a/satrs-example/src/pus/event.rs b/satrs-example/src/legacy/pus/event.rs similarity index 100% rename from satrs-example/src/pus/event.rs rename to satrs-example/src/legacy/pus/event.rs diff --git a/satrs-example/src/pus/hk.rs b/satrs-example/src/legacy/pus/hk.rs similarity index 100% rename from satrs-example/src/pus/hk.rs rename to satrs-example/src/legacy/pus/hk.rs diff --git a/satrs-example/src/pus/mod.rs b/satrs-example/src/legacy/pus/mod.rs similarity index 99% rename from satrs-example/src/pus/mod.rs rename to satrs-example/src/legacy/pus/mod.rs index 5bcdb24..61d0a0a 100644 --- a/satrs-example/src/pus/mod.rs +++ b/satrs-example/src/legacy/pus/mod.rs @@ -39,6 +39,7 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi VerificationReporter::new(owner_id, &verif_cfg) } +/* /// Simple router structure which forwards PUS telecommands to dedicated handlers. pub struct PusTcMpscRouter { pub test_tc_sender: mpsc::SyncSender, @@ -187,6 +188,7 @@ impl PusTcDistributor { Ok(HandlingStatus::HandledOne) } } +*/ pub trait TargetedPusService { const SERVICE_ID: u8; diff --git a/satrs-example/src/pus/mode.rs b/satrs-example/src/legacy/pus/mode.rs similarity index 100% rename from satrs-example/src/pus/mode.rs rename to satrs-example/src/legacy/pus/mode.rs diff --git a/satrs-example/src/pus/scheduler.rs b/satrs-example/src/legacy/pus/scheduler.rs similarity index 100% rename from satrs-example/src/pus/scheduler.rs rename to satrs-example/src/legacy/pus/scheduler.rs diff --git a/satrs-example/src/pus/stack.rs b/satrs-example/src/legacy/pus/stack.rs similarity index 100% rename from satrs-example/src/pus/stack.rs rename to satrs-example/src/legacy/pus/stack.rs diff --git a/satrs-example/src/pus/test.rs b/satrs-example/src/legacy/pus/test.rs similarity index 100% rename from satrs-example/src/pus/test.rs rename to satrs-example/src/legacy/pus/test.rs diff --git a/satrs-example/src/requests.rs b/satrs-example/src/legacy/requests.rs similarity index 99% rename from satrs-example/src/requests.rs rename to satrs-example/src/legacy/requests.rs index c6d80aa..c798f5f 100644 --- a/satrs-example/src/requests.rs +++ b/satrs-example/src/legacy/requests.rs @@ -15,8 +15,8 @@ use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::PusPacket; use satrs::ComponentId; use satrs_example::config::tmtc_err; -use satrs_example::ids; +/* #[derive(Clone, Debug)] #[non_exhaustive] pub enum CompositeRequest { @@ -153,3 +153,4 @@ impl PusRequestRouter for GenericRequestRouter { Err(GenericRoutingError::UnknownTargetId(target_id)) } } +*/ diff --git a/satrs-example/src/lib.rs b/satrs-example/src/lib.rs index ff51a19..0432644 100644 --- a/satrs-example/src/lib.rs +++ b/satrs-example/src/lib.rs @@ -1,7 +1,23 @@ +extern crate alloc; + +pub use models::ComponentId; use satrs::spacepackets::time::cds::CdsTime; pub mod config; -pub mod ids; + +/// Simple type modelling packet stored in the heap. This structure is intended to +/// be used when sending a packet via a message queue, so it also contains the sender ID. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PacketAsVec { + pub sender_id: ComponentId, + pub packet: Vec, +} + +impl PacketAsVec { + pub fn new(sender_id: ComponentId, packet: Vec) -> Self { + Self { sender_id, packet } + } +} #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum DeviceMode { diff --git a/satrs-example/src/logger.rs b/satrs-example/src/logger.rs index 5e7163c..3f9638b 100644 --- a/satrs-example/src/logger.rs +++ b/satrs-example/src/logger.rs @@ -9,7 +9,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> { message )) }) - .level(log::LevelFilter::Debug) + .level(log::LevelFilter::Info) .chain(std::io::stdout()) .chain(fern::log_file("output.log")?) .apply()?; diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 4e7577e..0a1e157 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -5,12 +5,10 @@ use std::{ time::Duration, }; -use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper}; use eps::{ pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper}, PowerSwitchHelper, }; -use events::EventHandler; use interface::{ sim_client_udp::create_sim_client, tcp::{SyncTcpTmSource, TcpTask}, @@ -18,256 +16,102 @@ use interface::{ }; use log::info; use logger::setup_logger; -use pus::{ - action::create_action_service, - event::create_event_service, - hk::create_hk_service, - mode::create_mode_service, - scheduler::{create_scheduler_service, TcReleaser}, - stack::PusStack, - test::create_test_service, - PusTcDistributor, PusTcMpscRouter, -}; -use requests::GenericRequestRouter; +use models::ComponentId; use satrs::{ hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer}, mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded}, - mode_tree::connect_mode_nodes, - pus::{event_man::EventRequestWithToken, EcssTcCacher, HandlingStatus}, + pus::HandlingStatus, request::{GenericMessage, MessageMetadata}, spacepackets::time::cds::CdsTime, }; use satrs_example::{ config::{ components::NO_SENDER, - pool::create_sched_tc_pool, - tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS}, + tasks::{FREQ_MS_AOCS, FREQ_MS_CONTROLLER, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS}, OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT, }, - ids::{ - acs::*, - eps::*, - tmtc::{TCP_SERVER, UDP_SERVER}, - }, DeviceMode, }; use tmtc::sender::TmTcSender; use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink}; -cfg_if::cfg_if! { - if #[cfg(feature = "heap_tmtc")] { - use interface::udp::DynamicUdpTmHandler; - use satrs::pus::EcssTcVecCacher; - use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic}; - } else { - use std::sync::RwLock; - use interface::udp::StaticUdpTmHandler; - use satrs::pus::EcssTcInSharedPoolCacher; - use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; - use satrs_example::config::pool::create_static_pools; - use tmtc::{ - tc_source::TcSourceTaskStatic, - tm_sink::TmSinkStatic, - }; - } -} +use crate::{ + acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper}, + control::Controller, + eps::pcdu::SwitchSet, + event_manager::EventManager, + interface::udp::UdpTmHandlerWithChannel, + tmtc::tc_source::CcsdsDistributor, +}; mod acs; +mod ccsds; +mod control; mod eps; -mod events; -mod hk; +mod event_manager; mod interface; mod logger; -mod pus; -mod requests; mod spi; mod tmtc; fn main() { setup_logger().expect("setting up logging with fern failed"); - println!("Running OBSW example"); - - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let (tm_pool, tc_pool) = create_static_pools(); - let shared_tm_pool = Arc::new(RwLock::new(tm_pool)); - let shared_tc_pool = Arc::new(RwLock::new(tc_pool)); - let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); - let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool); - } - } + println!("Runng OBSW example"); let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50); let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50); let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50); - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let tm_sender = TmTcSender::Static( - PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone()) - ); - } else if #[cfg(feature = "heap_tmtc")] { - let tm_sender = TmTcSender::Heap(tm_sink_tx.clone()); - } - } - let (sim_request_tx, sim_request_rx) = mpsc::channel(); let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel(); let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel(); let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel(); let mut opt_sim_client = create_sim_client(sim_request_rx); - let (mgm_0_handler_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10); - let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10); - let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30); - let (mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5); - let (mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5); + let (mgm_0_handler_tc_tx, mgm_0_handler_tc_rx) = mpsc::sync_channel(10); + let (mgm_1_handler_tc_tx, mgm_1_handler_tc_rx) = mpsc::sync_channel(10); + let (pcdu_handler_tc_tx, pcdu_handler_tc_rx) = mpsc::sync_channel(30); + let (controller_tc_tx, controller_tc_rx) = mpsc::sync_channel(10); + + let (_mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5); + let (_mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5); let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5); - // Some request are targetable. This map is used to retrieve sender handles based on a target ID. - let mut request_map = GenericRequestRouter::default(); - request_map - .composite_router_map - .insert(MGM0.id(), mgm_0_handler_composite_tx); - request_map - .composite_router_map - .insert(MGM1.id(), mgm_1_handler_composite_tx); - request_map - .composite_router_map - .insert(PCDU.id(), pcdu_handler_composite_tx); - - // This helper structure is used by all telecommand providers which need to send telecommands - // to the TC source. - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let tc_sender_with_shared_pool = - PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone()); - let tc_in_mem_converter = - EcssTcCacher::Static(EcssTcInSharedPoolCacher::new(shared_tc_pool, 4096)); - } else if #[cfg(feature = "heap_tmtc")] { - let tc_in_mem_converter = EcssTcCacher::Heap(EcssTcVecCacher::default()); - } - } - - // Create event handling components - // These sender handles are used to send event requests, for example to enable or disable - // certain events. - let (event_tx, event_rx) = mpsc::sync_channel(100); - let (event_request_tx, event_request_rx) = mpsc::channel::(); - - // The event task is the core handler to perform the event routing and TM handling as specified - // in the sat-rs documentation. - let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx); - - let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20); - let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10); - let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50); - let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50); - let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50); - let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50); - - let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel(); - let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50); - let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30); - - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let tc_releaser = TcReleaser::Static(tc_sender_with_shared_pool.clone()); - } else if #[cfg(feature = "heap_tmtc")] { - let tc_releaser = TcReleaser::Heap(tc_source_tx.clone()); - } - } - let pus_router = PusTcMpscRouter { - test_tc_sender: pus_test_tx, - event_tc_sender: pus_event_tx, - sched_tc_sender: pus_sched_tx, - hk_tc_sender: pus_hk_tx, - action_tc_sender: pus_action_tx, - mode_tc_sender: pus_mode_tx, + let (event_ctrl_tx, event_ctrl_rx) = mpsc::sync_channel(10); + let mut event_manager = EventManager { + ctrl_rx: event_ctrl_rx, + tm_tx: tm_sink_tx.clone(), }; - let pus_test_service = create_test_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - event_tx.clone(), - pus_test_rx, - ); - let pus_scheduler_service = create_scheduler_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - tc_releaser, - pus_sched_rx, - create_sched_tc_pool(), - ); - let pus_event_service = create_event_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - pus_event_rx, - event_request_tx, - ); - let pus_action_service = create_action_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - pus_action_rx, - request_map.clone(), - pus_action_reply_rx, - ); - let pus_hk_service = create_hk_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - pus_hk_rx, - request_map.clone(), - pus_hk_reply_rx, - ); - let pus_mode_service = create_mode_service( - tm_sender.clone(), - tc_in_mem_converter.clone(), - pus_mode_rx, - request_map, - pus_mode_reply_rx, - ); - let mut pus_stack = PusStack::new( - pus_test_service, - pus_hk_service, - pus_event_service, - pus_action_service, - pus_scheduler_service, - pus_mode_service, - ); - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new( - shared_tc_pool_wrapper.clone(), - tc_source_rx, - PusTcDistributor::new(tm_sender.clone(), pus_router), - )); - let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool); - let udp_tm_handler = StaticUdpTmHandler { - tm_rx: tm_server_rx, - tm_store: shared_tm_pool.clone(), - }; - } else if #[cfg(feature = "heap_tmtc")] { - let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new( - tc_source_rx, - PusTcDistributor::new(tm_sender.clone(), pus_router), - )); - let tc_sender = TmTcSender::Heap(tc_source_tx.clone()); - let udp_tm_handler = DynamicUdpTmHandler { - tm_rx: tm_server_rx, - }; - } - } + let mut controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx); + + let ccsds_distributor = CcsdsDistributor::default(); + let mut tc_source = TcSourceTask::new(tc_source_rx, ccsds_distributor); + tc_source.add_target(ComponentId::EpsPcdu, pcdu_handler_tc_tx); + tc_source.add_target(ComponentId::Controller, controller_tc_tx); + tc_source.add_target(ComponentId::AcsMgm0, mgm_0_handler_tc_tx); + tc_source.add_target(ComponentId::AcsMgm1, mgm_1_handler_tc_tx); + + let tc_sender = TmTcSender::Normal(tc_source_tx.clone()); + let udp_tm_handler = UdpTmHandlerWithChannel { + tm_rx: tm_server_rx, + }; let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); - let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone()) - .expect("creating UDP TMTC server failed"); + let udp_tc_server = UdpTcServer::new( + ComponentId::UdpServer as u32, + sock_addr, + 2048, + tc_sender.clone(), + ) + .expect("creating UDP TMTC server failed"); let mut udp_tmtc_server = UdpTmtcServer { udp_tc_server, - tm_handler: udp_tm_handler, + tm_handler: udp_tm_handler.into(), }; let tcp_server_cfg = ServerConfig::new( - TCP_SERVER.id(), + ComponentId::TcpServer as u32, sock_addr, Duration::from_millis(400), 4096, @@ -282,31 +126,18 @@ fn main() { ) .expect("tcp server creation failed"); - cfg_if::cfg_if! { - if #[cfg(not(feature = "heap_tmtc"))] { - let mut tm_sink = TmSink::Static(TmSinkStatic::new( - shared_tm_pool_wrapper, - sync_tm_tcp_source, - tm_sink_rx, - tm_server_tx, - )); - } else if #[cfg(feature = "heap_tmtc")] { - let mut tm_sink = TmSink::Heap(TmSinkDynamic::new( - sync_tm_tcp_source, - tm_sink_rx, - tm_server_tx, - )); - } - } + let mut tm_sink = TmSink::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx); - let shared_switch_set = Arc::new(Mutex::default()); + let shared_switch_set = Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown())); let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20); let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone()); let shared_mgm_0_set = Arc::default(); let shared_mgm_1_set = Arc::default(); - let mgm_0_mode_node = ModeRequestHandlerMpscBounded::new(MGM0.into(), mgm_0_handler_mode_rx); - let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), mgm_1_handler_mode_rx); + let mgm_0_mode_node = + ModeRequestHandlerMpscBounded::new(ComponentId::AcsMgm0 as u32, mgm_0_handler_mode_rx); + let mgm_1_mode_node = + ModeRequestHandlerMpscBounded::new(ComponentId::AcsMgm1 as u32, mgm_1_handler_mode_rx); let (mgm_0_spi_interface, mgm_1_spi_interface) = if let Some(sim_client) = opt_sim_client.as_mut() { sim_client @@ -330,28 +161,27 @@ fn main() { ) }; let mut mgm_0_handler = MgmHandlerLis3Mdl::new( - MGM0, + ComponentId::AcsMgm0, "MGM_0", mgm_0_mode_node, - mgm_0_handler_composite_rx, - pus_hk_reply_tx.clone(), + mgm_0_handler_tc_rx, + tm_sink_tx.clone(), switch_helper.clone(), - tm_sender.clone(), mgm_0_spi_interface, shared_mgm_0_set, ); let mut mgm_1_handler = MgmHandlerLis3Mdl::new( - MGM1, + ComponentId::AcsMgm1, "MGM_1", mgm_1_mode_node, - mgm_1_handler_composite_rx, - pus_hk_reply_tx.clone(), + mgm_1_handler_tc_rx, + tm_sink_tx.clone(), switch_helper.clone(), - tm_sender.clone(), mgm_1_spi_interface, shared_mgm_1_set, ); // Connect PUS service to device handlers. + /* connect_mode_nodes( &mut pus_stack.mode_srv, mgm_0_handler_mode_tx, @@ -364,6 +194,7 @@ fn main() { &mut mgm_1_handler, pus_mode_reply_tx.clone(), ); + */ 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); @@ -374,24 +205,24 @@ fn main() { } else { SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) }; - let pcdu_mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), pcdu_handler_mode_rx); + let pcdu_mode_node = + ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, pcdu_handler_mode_rx); let mut pcdu_handler = PcduHandler::new( - PCDU, - "PCDU", pcdu_mode_node, - pcdu_handler_composite_rx, - pus_hk_reply_tx, + pcdu_handler_tc_rx, + tm_sink_tx.clone(), switch_request_rx, - tm_sender.clone(), pcdu_serial_interface, shared_switch_set, ); + /* connect_mode_nodes( &mut pus_stack.mode_srv, pcdu_handler_mode_tx.clone(), &mut pcdu_handler, pus_mode_reply_tx, ); + */ // The PCDU is a critical component which should be in normal mode immediately. pcdu_handler_mode_tx @@ -406,12 +237,12 @@ fn main() { info!("Starting TMTC and UDP task"); let jh_udp_tmtc = thread::Builder::new() - .name("SATRS tmtc-udp".to_string()) + .name("TMTC & UDP".to_string()) .spawn(move || { info!("Running UDP server on port {SERVER_PORT}"); loop { udp_tmtc_server.periodic_operation(); - tmtc_task.periodic_operation(); + tc_source.periodic_operation(); thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC)); } }) @@ -419,7 +250,7 @@ fn main() { info!("Starting TCP task"); let jh_tcp = thread::Builder::new() - .name("sat-rs tcp".to_string()) + .name("TCP".to_string()) .spawn(move || { info!("Running TCP server on port {SERVER_PORT}"); loop { @@ -430,7 +261,7 @@ fn main() { info!("Starting TM funnel task"); let jh_tm_funnel = thread::Builder::new() - .name("tm sink".to_string()) + .name("TM SINK".to_string()) .spawn(move || loop { tm_sink.operation(); }) @@ -441,7 +272,7 @@ fn main() { info!("Starting UDP sim client task"); opt_jh_sim_client = Some( thread::Builder::new() - .name("sat-rs sim adapter".to_string()) + .name("SIM ADAPTER".to_string()) .spawn(move || loop { if sim_client.operation() == HandlingStatus::Empty { std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS)); @@ -453,7 +284,7 @@ fn main() { info!("Starting AOCS thread"); let jh_aocs = thread::Builder::new() - .name("sat-rs aocs".to_string()) + .name("AOCS".to_string()) .spawn(move || loop { mgm_0_handler.periodic_operation(); mgm_1_handler.periodic_operation(); @@ -463,12 +294,13 @@ fn main() { info!("Starting EPS thread"); let jh_eps = thread::Builder::new() - .name("sat-rs eps".to_string()) + .name("EPS".to_string()) .spawn(move || loop { // TODO: We should introduce something like a fixed timeslot helper to allow a more // declarative API. It would also be very useful for the AOCS task. // // TODO: The fixed timeslot handler exists.. use it. + // TODO: Why not just use sync code in the PCDU handler, and fully delay there? pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp); thread::sleep(Duration::from_millis(50)); pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies); @@ -478,13 +310,13 @@ fn main() { }) .unwrap(); - info!("Starting PUS handler thread"); - let jh_pus_handler = thread::Builder::new() - .name("sat-rs pus".to_string()) + info!("Starting controller thread"); + let jh_controller_thread = thread::Builder::new() + .name("CTRL".to_string()) .spawn(move || loop { - event_handler.periodic_operation(); - pus_stack.periodic_operation(); - thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK)); + controller.periodic_operation(); + event_manager.periodic_operation(); + thread::sleep(Duration::from_millis(FREQ_MS_CONTROLLER)); }) .unwrap(); @@ -504,7 +336,7 @@ fn main() { } jh_aocs.join().expect("Joining AOCS thread failed"); jh_eps.join().expect("Joining EPS thread failed"); - jh_pus_handler + jh_controller_thread .join() .expect("Joining PUS handler thread failed"); } diff --git a/satrs-example/src/tmtc/sender.rs b/satrs-example/src/tmtc/sender.rs index 4d243b7..71fd17b 100644 --- a/satrs-example/src/tmtc/sender.rs +++ b/satrs-example/src/tmtc/sender.rs @@ -1,10 +1,8 @@ use std::{cell::RefCell, collections::VecDeque, sync::mpsc}; use satrs::{ - pus::EcssTmSender, queue::GenericSendError, - spacepackets::ecss::WritablePusPacket, - tmtc::{PacketAsVec, PacketHandler, PacketSenderWithSharedPool}, + tmtc::{PacketAsVec, PacketHandler}, ComponentId, }; @@ -14,8 +12,7 @@ pub struct MockSender(pub RefCell>); #[allow(dead_code)] #[derive(Debug, Clone)] pub enum TmTcSender { - Static(PacketSenderWithSharedPool), - Heap(mpsc::SyncSender), + Normal(mpsc::SyncSender), Mock(MockSender), } @@ -29,37 +26,13 @@ impl TmTcSender { } } -impl EcssTmSender for TmTcSender { - fn send_tm( - &self, - sender_id: satrs::ComponentId, - tm: satrs::pus::PusTmVariant, - ) -> Result<(), satrs::pus::EcssTmtcError> { - match self { - TmTcSender::Static(sync_sender) => sync_sender.send_tm(sender_id, tm), - TmTcSender::Heap(sync_sender) => match tm { - satrs::pus::PusTmVariant::InStore(_) => panic!("can not send TM in store"), - satrs::pus::PusTmVariant::Direct(pus_tm_creator) => sync_sender - .send(PacketAsVec::new(sender_id, pus_tm_creator.to_vec()?)) - .map_err(|_| GenericSendError::RxDisconnected.into()), - }, - TmTcSender::Mock(_) => Ok(()), - } - } -} - impl PacketHandler for TmTcSender { type Error = GenericSendError; fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> { match self { - TmTcSender::Static(packet_sender_with_shared_pool) => { - if let Err(e) = packet_sender_with_shared_pool.handle_packet(sender_id, packet) { - log::error!("Error sending packet via Static TM/TC sender: {:?}", e); - } - } - TmTcSender::Heap(sync_sender) => { - if let Err(e) = sync_sender.handle_packet(sender_id, packet) { + TmTcSender::Normal(sync_sender) => { + if let Err(e) = sync_sender.send(PacketAsVec::new(sender_id, packet.to_vec())) { log::error!("Error sending packet via Heap TM/TC sender: {:?}", e); } } diff --git a/satrs-example/src/tmtc/tc_source.rs b/satrs-example/src/tmtc/tc_source.rs index 00b6d87..6a26a40 100644 --- a/satrs-example/src/tmtc/tc_source.rs +++ b/satrs-example/src/tmtc/tc_source.rs @@ -1,121 +1,97 @@ +use models::{ccsds::CcsdsTcPacketOwned, ComponentId, TcHeader}; use satrs::{ - pool::PoolProvider, pus::HandlingStatus, - tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}, + spacepackets::{CcsdsPacketReader, ChecksumType}, + tmtc::PacketAsVec, +}; +use std::{ + collections::HashMap, + sync::mpsc::{self, TryRecvError}, }; -use std::sync::mpsc::{self, TryRecvError}; -use crate::pus::PusTcDistributor; - -// TC source components where static pools are the backing memory of the received telecommands. -pub struct TcSourceTaskStatic { - shared_tc_pool: SharedPacketPool, - tc_receiver: mpsc::Receiver, - /// We allocate this buffer from the heap to avoid a clippy warning on large enum variant - /// differences. - tc_buf: Box<[u8; 4096]>, - pus_distributor: PusTcDistributor, -} - -#[allow(dead_code)] -impl TcSourceTaskStatic { - pub fn new( - shared_tc_pool: SharedPacketPool, - tc_receiver: mpsc::Receiver, - pus_receiver: PusTcDistributor, - ) -> Self { - Self { - shared_tc_pool, - tc_receiver, - tc_buf: Box::new([0; 4096]), - pus_distributor: pus_receiver, - } - } - - pub fn periodic_operation(&mut self) { - self.poll_tc(); - } - - pub fn poll_tc(&mut self) -> HandlingStatus { - // Right now, we only expect ECSS PUS packets. - // If packets like CFDP are expected, we might have to check the APID first. - match self.tc_receiver.try_recv() { - Ok(packet_in_pool) => { - let pool = self - .shared_tc_pool - .0 - .read() - .expect("locking tc pool failed"); - pool.read(&packet_in_pool.store_addr, self.tc_buf.as_mut_slice()) - .expect("reading pool failed"); - drop(pool); - self.pus_distributor - .handle_tc_packet_in_store(packet_in_pool, self.tc_buf.as_slice()) - .ok(); - HandlingStatus::HandledOne - } - Err(e) => match e { - TryRecvError::Empty => HandlingStatus::Empty, - TryRecvError::Disconnected => { - log::warn!("tmtc thread: sender disconnected"); - HandlingStatus::Empty - } - }, - } - } -} +pub type CcsdsDistributor = HashMap>; // TC source components where the heap is the backing memory of the received telecommands. -pub struct TcSourceTaskDynamic { +pub struct TcSourceTask { pub tc_receiver: mpsc::Receiver, - pus_distributor: PusTcDistributor, -} - -#[allow(dead_code)] -impl TcSourceTaskDynamic { - pub fn new(tc_receiver: mpsc::Receiver, pus_receiver: PusTcDistributor) -> Self { - Self { - tc_receiver, - pus_distributor: pus_receiver, - } - } - - pub fn periodic_operation(&mut self) { - self.poll_tc(); - } - - pub fn poll_tc(&mut self) -> HandlingStatus { - // Right now, we only expect ECSS PUS packets. - // If packets like CFDP are expected, we might have to check the APID first. - match self.tc_receiver.try_recv() { - Ok(packet_as_vec) => { - self.pus_distributor - .handle_tc_packet_vec(packet_as_vec) - .ok(); - HandlingStatus::HandledOne - } - Err(e) => match e { - TryRecvError::Empty => HandlingStatus::Empty, - TryRecvError::Disconnected => { - log::warn!("tmtc thread: sender disconnected"); - HandlingStatus::Empty - } - }, - } - } -} - -#[allow(dead_code)] -pub enum TcSourceTask { - Static(TcSourceTaskStatic), - Heap(TcSourceTaskDynamic), + ccsds_distributor: CcsdsDistributor, } impl TcSourceTask { + pub fn new( + tc_receiver: mpsc::Receiver, + ccsds_distributor: CcsdsDistributor, + ) -> Self { + Self { + tc_receiver, + ccsds_distributor, + } + } + + pub fn add_target( + &mut self, + target_id: ComponentId, + sender: mpsc::SyncSender, + ) { + self.ccsds_distributor.insert(target_id, sender); + } + pub fn periodic_operation(&mut self) { - match self { - TcSourceTask::Static(task) => task.periodic_operation(), - TcSourceTask::Heap(task) => task.periodic_operation(), + loop { + if self.poll_tc() == HandlingStatus::Empty { + break; + } + } + } + + pub fn poll_tc(&mut self) -> HandlingStatus { + match self.tc_receiver.try_recv() { + Ok(packet) => { + log::debug!("received raw packet: {:?}", packet); + let ccsds_tc_reader_result = + CcsdsPacketReader::new(&packet.packet, Some(ChecksumType::WithCrc16)); + if ccsds_tc_reader_result.is_err() { + log::warn!( + "received invalid CCSDS TC packet: {:?}", + ccsds_tc_reader_result.err() + ); + // TODO: Send a dedicated TM packet. + return HandlingStatus::HandledOne; + } + let ccsds_tc_reader = ccsds_tc_reader_result.unwrap(); + let tc_header_result = + postcard::take_from_bytes::(ccsds_tc_reader.user_data()); + if tc_header_result.is_err() { + log::warn!( + "received CCSDS TC packet with invalid TC header: {:?}", + tc_header_result.err() + ); + // TODO: Send a dedicated TM packet. + return HandlingStatus::HandledOne; + } + let (tc_header, payload) = tc_header_result.unwrap(); + if let Some(sender) = self.ccsds_distributor.get(&tc_header.target_id) { + log::debug!("sending TC packet to target ID: {:?}", tc_header.target_id); + sender + .send(CcsdsTcPacketOwned { + sp_header: *ccsds_tc_reader.sp_header(), + tc_header, + payload: payload.to_vec(), + }) + .ok(); + } else { + log::warn!("no TC handler for target ID {:?}", tc_header.target_id); + // TODO: Send a dedicated TM packet. + } + HandlingStatus::HandledOne + } + Err(e) => match e { + TryRecvError::Empty => HandlingStatus::Empty, + TryRecvError::Disconnected => { + log::warn!("tmtc thread: sender disconnected"); + HandlingStatus::Empty + } + }, } } } diff --git a/satrs-example/src/tmtc/tm_sink.rs b/satrs-example/src/tmtc/tm_sink.rs index 78d22d8..f5092de 100644 --- a/satrs-example/src/tmtc/tm_sink.rs +++ b/satrs-example/src/tmtc/tm_sink.rs @@ -4,18 +4,8 @@ use std::{ }; use arbitrary_int::{u11, u14}; -use log::info; -use satrs::{ - pool::PoolProvider, - spacepackets::{ - ecss::{tm::PusTmZeroCopyWriter, PusPacket}, - seq_count::SequenceCounter, - seq_count::SequenceCounterCcsdsSimple, - time::cds::MIN_CDS_FIELD_LEN, - CcsdsPacket, - }, - tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}, -}; +use models::ccsds::CcsdsTmPacketOwned; +use satrs::spacepackets::seq_count::{SequenceCounter, SequenceCounterCcsdsSimple}; use crate::interface::tcp::SyncTcpTmSource; @@ -34,118 +24,22 @@ impl CcsdsSeqCounterMap { } } -pub struct TmFunnelCommon { +pub struct TmSink { seq_counter_map: CcsdsSeqCounterMap, - msg_counter_map: HashMap, sync_tm_tcp_source: SyncTcpTmSource, + tm_funnel_rx: mpsc::Receiver, + tm_server_tx: mpsc::SyncSender, } -impl TmFunnelCommon { - pub fn new(sync_tm_tcp_source: SyncTcpTmSource) -> Self { +impl TmSink { + pub fn new( + sync_tm_tcp_source: SyncTcpTmSource, + tm_funnel_rx: mpsc::Receiver, + tm_server_tx: mpsc::SyncSender, + ) -> Self { Self { seq_counter_map: Default::default(), - msg_counter_map: Default::default(), sync_tm_tcp_source, - } - } - - // Applies common packet processing operations for PUS TM packets. This includes setting - // a sequence counter - fn apply_packet_processing(&mut self, mut zero_copy_writer: PusTmZeroCopyWriter) { - // zero_copy_writer.set_apid(PUS_APID); - zero_copy_writer.set_seq_count( - self.seq_counter_map - .get_and_increment(zero_copy_writer.apid()), - ); - let entry = self - .msg_counter_map - .entry(zero_copy_writer.service_type_id()) - .or_insert(0); - zero_copy_writer.set_msg_count(*entry); - if *entry == u16::MAX { - *entry = 0; - } else { - *entry += 1; - } - - Self::packet_printout(&zero_copy_writer); - // This operation has to come last! - zero_copy_writer.finish(); - } - - fn packet_printout(tm: &PusTmZeroCopyWriter) { - info!( - "Sending PUS TM[{},{}] with APID {}", - tm.service_type_id(), - tm.message_subtype_id(), - tm.apid() - ); - } -} - -pub struct TmSinkStatic { - common: TmFunnelCommon, - shared_tm_store: SharedPacketPool, - tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::SyncSender, -} - -#[allow(dead_code)] -impl TmSinkStatic { - pub fn new( - shared_tm_store: SharedPacketPool, - sync_tm_tcp_source: SyncTcpTmSource, - tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::SyncSender, - ) -> Self { - Self { - common: TmFunnelCommon::new(sync_tm_tcp_source), - shared_tm_store, - tm_funnel_rx, - tm_server_tx, - } - } - - pub fn operation(&mut self) { - if let Ok(pus_tm_in_pool) = self.tm_funnel_rx.recv() { - // Read the TM, set sequence counter and message counter, and finally update - // the CRC. - let shared_pool = self.shared_tm_store.0.clone(); - let mut pool_guard = shared_pool.write().expect("Locking TM pool failed"); - let mut tm_copy = Vec::new(); - pool_guard - .modify(&pus_tm_in_pool.store_addr, |buf| { - let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN, true) - .expect("Creating TM zero copy writer failed"); - self.common.apply_packet_processing(zero_copy_writer); - tm_copy = buf.to_vec() - }) - .expect("Reading TM from pool failed"); - self.tm_server_tx - .send(pus_tm_in_pool) - .expect("Sending TM to server failed"); - // We could also do this step in the update closure, but I'd rather avoid this, could - // lead to nested locking. - self.common.sync_tm_tcp_source.add_tm(&tm_copy); - } - } -} - -pub struct TmSinkDynamic { - common: TmFunnelCommon, - tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::SyncSender, -} - -#[allow(dead_code)] -impl TmSinkDynamic { - pub fn new( - sync_tm_tcp_source: SyncTcpTmSource, - tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::SyncSender, - ) -> Self { - Self { - common: TmFunnelCommon::new(sync_tm_tcp_source), tm_funnel_rx, tm_server_tx, } @@ -153,31 +47,12 @@ impl TmSinkDynamic { pub fn operation(&mut self) { if let Ok(mut tm) = self.tm_funnel_rx.recv() { - // Read the TM, set sequence counter and message counter, and finally update - // the CRC. - let zero_copy_writer = - PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN, true) - .expect("Creating TM zero copy writer failed"); - self.common.apply_packet_processing(zero_copy_writer); - self.common.sync_tm_tcp_source.add_tm(&tm.packet); + tm.sp_header + .set_seq_count(self.seq_counter_map.get_and_increment(tm.sp_header.apid())); + self.sync_tm_tcp_source.add_tm(&tm.to_vec()); self.tm_server_tx .send(tm) .expect("Sending TM to server failed"); } } } - -#[allow(dead_code)] -pub enum TmSink { - Static(TmSinkStatic), - Heap(TmSinkDynamic), -} - -impl TmSink { - pub fn operation(&mut self) { - match self { - TmSink::Static(static_sink) => static_sink.operation(), - TmSink::Heap(dynamic_sink) => dynamic_sink.operation(), - } - } -} diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index aa35b0d..df9bcda 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -78,8 +78,3 @@ test_util = [] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] - -[[test]] -name = "event_test" -path = "tests/pus_events.rs" -required-features = ["test_util"] diff --git a/satrs/src/event_man.rs b/satrs/src/legacy/event_man.rs similarity index 99% rename from satrs/src/event_man.rs rename to satrs/src/legacy/event_man.rs index 90de176..c774d4f 100644 --- a/satrs/src/event_man.rs +++ b/satrs/src/legacy/event_man.rs @@ -50,7 +50,7 @@ //! show how the event management modules can be integrated into a more complex software. use crate::{ ComponentId, - events::{Event, EventId, GroupId}, + legacy::events::{Event, EventId, GroupId}, queue::GenericSendError, }; use core::marker::PhantomData; @@ -96,6 +96,7 @@ impl EventMessage { pub trait EventSender { type Error; + /// Destination component ID. fn target_id(&self) -> ComponentId; fn send(&self, message: EventMessage) -> Result<(), Self::Error>; @@ -317,7 +318,7 @@ pub mod alloc_mod { use alloc::vec::Vec; use hashbrown::HashMap; - use crate::events::EventErasedAlloc; + use crate::legacy::events::EventErasedAlloc; use super::*; @@ -460,7 +461,7 @@ pub mod alloc_mod { #[cfg(feature = "std")] pub mod std_mod { use crate::{ - events::{EventErasedAlloc, EventErasedHeapless}, + legacy::events::{EventErasedAlloc, EventErasedHeapless}, queue::GenericReceiveError, }; @@ -577,7 +578,7 @@ mod tests { use arbitrary_int::u14; use super::*; - use crate::events::{EventErasedAlloc, Severity}; + use crate::legacy::events::{EventErasedAlloc, Severity}; use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; use std::sync::mpsc; diff --git a/satrs/src/event_man_legacy.rs b/satrs/src/legacy/event_man_legacy.rs similarity index 99% rename from satrs/src/event_man_legacy.rs rename to satrs/src/legacy/event_man_legacy.rs index 79c6c80..9f567a9 100644 --- a/satrs/src/event_man_legacy.rs +++ b/satrs/src/legacy/event_man_legacy.rs @@ -50,7 +50,7 @@ //! The [PUS event](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/pus/event.rs) //! module and the generic [events module](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/events.rs) //! show how the event management modules can be integrated into a more complex software. -use crate::events_legacy::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw}; +use crate::legacy::events_legacy::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw}; use crate::params::Params; use crate::queue::GenericSendError; use core::fmt::Debug; @@ -588,8 +588,8 @@ pub mod std_mod { #[cfg(test)] mod tests { use super::*; - use crate::event_man_legacy::EventManager; - use crate::events_legacy::{EventU32, GenericEvent, Severity}; + use crate::legacy::event_man_legacy::EventManager; + use crate::legacy::events_legacy::{EventU32, GenericEvent, Severity}; use crate::params::{ParamsHeapless, ParamsRaw}; use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; use std::format; diff --git a/satrs/src/events.rs b/satrs/src/legacy/events.rs similarity index 100% rename from satrs/src/events.rs rename to satrs/src/legacy/events.rs diff --git a/satrs/src/events_legacy.rs b/satrs/src/legacy/events_legacy.rs similarity index 100% rename from satrs/src/events_legacy.rs rename to satrs/src/legacy/events_legacy.rs diff --git a/satrs/src/legacy/mod.rs b/satrs/src/legacy/mod.rs new file mode 100644 index 0000000..9ffb557 --- /dev/null +++ b/satrs/src/legacy/mod.rs @@ -0,0 +1,4 @@ +pub mod event_man; +pub mod events; + +pub mod pus; diff --git a/satrs/src/pus/event.rs b/satrs/src/legacy/pus/event.rs similarity index 55% rename from satrs/src/pus/event.rs rename to satrs/src/legacy/pus/event.rs index 767430b..57a3bc1 100644 --- a/satrs/src/pus/event.rs +++ b/satrs/src/legacy/pus/event.rs @@ -267,217 +267,4 @@ mod alloc_mod { } #[cfg(test)] -mod tests { - use super::*; - use crate::ComponentId; - use crate::events_legacy::{EventU32, Severity}; - use crate::pus::test_util::TEST_COMPONENT_ID_0; - use crate::pus::tests::CommonTmInfo; - use crate::pus::{ChannelWithId, EcssTmSender, EcssTmtcError, PusTmVariant}; - use spacepackets::ByteConversionError; - use spacepackets::ecss::PusError; - use std::cell::RefCell; - use std::collections::VecDeque; - use std::vec::Vec; - - const EXAMPLE_APID: u11 = u11::new(0xee); - const EXAMPLE_GROUP_ID: u16 = 2; - const EXAMPLE_EVENT_ID_0: u16 = 1; - #[allow(dead_code)] - const EXAMPLE_EVENT_ID_1: u16 = 2; - - #[derive(Debug, Eq, PartialEq, Clone)] - struct TmInfo { - pub sender_id: ComponentId, - pub common: CommonTmInfo, - pub event: EventU32, - pub aux_data: Vec, - } - - #[derive(Default, Clone)] - struct TestSender { - pub service_queue: RefCell>, - } - - impl ChannelWithId for TestSender { - fn id(&self) -> ComponentId { - 0 - } - } - - impl EcssTmSender for TestSender { - fn send_tm(&self, sender_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(_) => { - panic!("TestSender: unexpected call with address"); - } - PusTmVariant::Direct(tm) => { - assert!(!tm.source_data().is_empty()); - let src_data = tm.source_data(); - assert!(src_data.len() >= 4); - let event = - EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap())); - let mut aux_data = Vec::new(); - if src_data.len() > 4 { - aux_data.extend_from_slice(&src_data[4..]); - } - self.service_queue.borrow_mut().push_back(TmInfo { - sender_id, - common: CommonTmInfo::new_from_tm(&tm), - event, - aux_data, - }); - Ok(()) - } - } - } - } - - fn severity_to_subservice(severity: Severity) -> MessageSubtypeId { - match severity { - Severity::Info => MessageSubtypeId::TmInfoReport, - Severity::Low => MessageSubtypeId::TmLowSeverityReport, - Severity::Medium => MessageSubtypeId::TmMediumSeverityReport, - Severity::High => MessageSubtypeId::TmHighSeverityReport, - } - } - - fn report_basic_event( - reporter: &mut EventReporter, - sender: &mut TestSender, - time_stamp: &[u8], - event: EventU32, - severity: Severity, - aux_data: Option<&[u8]>, - ) { - match severity { - Severity::Info => { - reporter - .event_info(sender, time_stamp, event, aux_data) - .expect("Error reporting info event"); - } - Severity::Low => { - reporter - .event_low_severity(sender, time_stamp, event, aux_data) - .expect("Error reporting low event"); - } - Severity::Medium => { - reporter - .event_medium_severity(sender, time_stamp, event, aux_data) - .expect("Error reporting medium event"); - } - Severity::High => { - reporter - .event_high_severity(sender, time_stamp, event, aux_data) - .expect("Error reporting high event"); - } - } - } - - fn basic_event_test( - max_event_aux_data_buf: usize, - severity: Severity, - error_data: Option<&[u8]>, - ) { - let mut sender = TestSender::default(); - let mut reporter = EventReporter::new( - TEST_COMPONENT_ID_0.id(), - EXAMPLE_APID, - 0, - max_event_aux_data_buf, - ); - let time_stamp_empty: [u8; 7] = [0; 7]; - let mut error_copy = Vec::new(); - if let Some(err_data) = error_data { - error_copy.extend_from_slice(err_data); - } - let event = EventU32::new_checked(severity, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0) - .expect("Error creating example event"); - report_basic_event( - &mut reporter, - &mut sender, - &time_stamp_empty, - event, - severity, - error_data, - ); - let mut service_queue = sender.service_queue.borrow_mut(); - assert_eq!(service_queue.len(), 1); - let tm_info = service_queue.pop_front().unwrap(); - assert_eq!( - tm_info.common.subservice, - severity_to_subservice(severity) as u8 - ); - assert_eq!(tm_info.common.dest_id, 0); - assert_eq!(tm_info.common.timestamp, time_stamp_empty); - assert_eq!(tm_info.common.msg_counter, 0); - assert_eq!(tm_info.common.apid, EXAMPLE_APID); - assert_eq!(tm_info.event, event); - assert_eq!(tm_info.sender_id, TEST_COMPONENT_ID_0.id()); - assert_eq!(tm_info.aux_data, error_copy); - } - - #[test] - fn basic_info_event_generation() { - basic_event_test(4, Severity::Info, None); - } - - #[test] - fn basic_low_severity_event() { - basic_event_test(4, Severity::Low, None); - } - - #[test] - fn basic_medium_severity_event() { - basic_event_test(4, Severity::Medium, None); - } - - #[test] - fn basic_high_severity_event() { - basic_event_test(4, Severity::High, None); - } - - #[test] - fn event_with_info_string() { - let info_string = "Test Information"; - basic_event_test(32, Severity::Info, Some(info_string.as_bytes())); - } - - #[test] - fn low_severity_with_raw_err_data() { - let raw_err_param: i32 = -1; - let raw_err = raw_err_param.to_be_bytes(); - basic_event_test(8, Severity::Low, Some(&raw_err)) - } - - fn check_buf_too_small( - reporter: &mut EventReporter, - sender: &mut TestSender, - expected_found_len: usize, - ) { - let time_stamp_empty: [u8; 7] = [0; 7]; - let event = EventU32::new_checked(Severity::Info, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0) - .expect("Error creating example event"); - let err = reporter.event_info(sender, &time_stamp_empty, event, None); - assert!(err.is_err()); - let err = err.unwrap_err(); - if let EcssTmtcError::Pus(PusError::ByteConversion( - ByteConversionError::ToSliceTooSmall { found, expected }, - )) = err - { - assert_eq!(expected, 4); - assert_eq!(found, expected_found_len); - } else { - panic!("Unexpected error {:?}", err); - } - } - - #[test] - fn insufficient_buffer() { - let mut sender = TestSender::default(); - for i in 0..3 { - let mut reporter = EventReporter::new(0, EXAMPLE_APID, 0, i); - check_buf_too_small(&mut reporter, &mut sender, i); - } - } -} +mod tests {} diff --git a/satrs/src/legacy/pus/mod.rs b/satrs/src/legacy/pus/mod.rs new file mode 100644 index 0000000..53f1126 --- /dev/null +++ b/satrs/src/legacy/pus/mod.rs @@ -0,0 +1 @@ +pub mod event; diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index 5878bd6..6f9c384 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -9,8 +9,6 @@ //! //! The core modules of this crate include //! -//! - The [event manager][event_man] module which provides a publish and -//! and subscribe to route events. //! - The [pus] module which provides special support for projects using //! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). #![no_std] @@ -27,15 +25,12 @@ pub mod ccsds; #[cfg(feature = "alloc")] pub mod dev_mgmt; pub mod encoding; -pub mod event_man; -pub mod event_man_legacy; -pub mod events; -pub mod events_legacy; #[cfg(feature = "std")] pub mod executable; pub mod hal; pub mod health; pub mod hk; +pub mod legacy; pub mod mode; #[cfg(feature = "std")] pub mod mode_tree; diff --git a/satrs/src/pus/event_man.rs b/satrs/src/pus/event_man.rs deleted file mode 100644 index 689f16c..0000000 --- a/satrs/src/pus/event_man.rs +++ /dev/null @@ -1,464 +0,0 @@ -use crate::events_legacy::{EventU32, GenericEvent, Severity}; -#[cfg(feature = "alloc")] -use crate::events_legacy::{EventU32TypedSev, HasSeverity}; -#[cfg(feature = "alloc")] -use core::hash::Hash; -#[cfg(feature = "alloc")] -use hashbrown::HashSet; - -#[cfg(feature = "alloc")] -use crate::pus::EcssTmSender; -use crate::pus::EcssTmtcError; -#[cfg(feature = "alloc")] -pub use crate::pus::event::EventReporter; -use crate::pus::verification::TcStateToken; -#[cfg(feature = "alloc")] -pub use alloc_mod::*; -#[cfg(feature = "heapless")] -pub use heapless_mod::*; - -/// This trait allows the PUS event manager implementation to stay generic over various types -/// of backend containers. -/// -/// These backend containers keep track on whether a particular event is enabled or disabled for -/// reporting and also expose a simple API to enable or disable the event reporting. -/// -/// For example, a straight forward implementation for host systems could use a -/// [hash set](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html) -/// structure to track disabled events. A more primitive and embedded friendly -/// solution could track this information in a static or pre-allocated list which contains -/// the disabled events. -pub trait PusEventReportingMapProvider { - type Error; - - fn event_enabled(&self, event: &Event) -> bool; - fn enable_event_reporting(&mut self, event: &Event) -> Result; - fn disable_event_reporting(&mut self, event: &Event) -> Result; -} - -#[cfg(feature = "heapless")] -pub mod heapless_mod { - use super::*; - use crate::events::LargestEventRaw; - use core::marker::PhantomData; - - // TODO: After a new version of heapless is released which uses hash32 version 0.3, try using - // regular Event type again. - #[derive(Default)] - pub struct HeaplessPusMgmtBackendProvider { - disabled: heapless::index_set::FnvIndexSet, - phantom: PhantomData, - } - - impl PusEventReportingMapProvider - for HeaplessPusMgmtBackendProvider - { - type Error = (); - - fn event_enabled(&self, event: &Provider) -> bool { - self.disabled.contains(&event.raw_as_largest_type()) - } - - fn enable_event_reporting(&mut self, event: &Provider) -> Result { - self.disabled - .insert(event.raw_as_largest_type()) - .map_err(|_| ()) - } - - fn disable_event_reporting(&mut self, event: &Provider) -> Result { - Ok(self.disabled.remove(&event.raw_as_largest_type())) - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum EventRequest { - Enable(Event), - Disable(Event), -} - -#[derive(Debug)] -pub struct EventRequestWithToken { - pub request: EventRequest, - pub token: TcStateToken, -} - -#[derive(Debug)] -pub enum EventManError { - EcssTmtcError(EcssTmtcError), - SeverityMissmatch(Severity, Severity), -} - -impl From for EventManError { - fn from(v: EcssTmtcError) -> Self { - Self::EcssTmtcError(v) - } -} - -#[cfg(feature = "alloc")] -pub mod alloc_mod { - use core::marker::PhantomData; - - use crate::{ - events_legacy::EventU16, - params::{Params, WritableToBeBytes}, - pus::event::{DummyEventHook, EventTmHook}, - }; - - use super::*; - - /// Default backend provider which uses a hash set as the event reporting status container - /// like mentioned in the example of the [PusEventReportingMapProvider] documentation. - /// - /// This provider is a good option for host systems or larger embedded systems where - /// the expected occasional memory allocation performed by the [HashSet] is not an issue. - pub struct DefaultPusEventReportingMap { - disabled: HashSet, - } - - impl Default for DefaultPusEventReportingMap { - fn default() -> Self { - Self { - disabled: HashSet::default(), - } - } - } - - impl - PusEventReportingMapProvider for DefaultPusEventReportingMap - { - type Error = (); - - fn event_enabled(&self, event: &Event) -> bool { - !self.disabled.contains(event) - } - - fn enable_event_reporting(&mut self, event: &Event) -> Result { - Ok(self.disabled.remove(event)) - } - - fn disable_event_reporting(&mut self, event: &Event) -> Result { - Ok(self.disabled.insert(*event)) - } - } - - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub struct EventGenerationResult { - pub event_was_enabled: bool, - pub params_were_propagated: bool, - } - - pub struct PusEventTmCreatorWithMap< - ReportingMap: PusEventReportingMapProvider, - Event: GenericEvent, - EventTmHookInstance: EventTmHook = DummyEventHook, - > { - pub reporter: EventReporter, - reporting_map: ReportingMap, - phantom: PhantomData, - } - - impl< - ReportingMap: PusEventReportingMapProvider, - Event: GenericEvent, - EventTmHookInstance: EventTmHook, - > PusEventTmCreatorWithMap - { - pub fn new(reporter: EventReporter, backend: ReportingMap) -> Self { - Self { - reporter, - reporting_map: backend, - phantom: PhantomData, - } - } - - pub fn enable_tm_for_event(&mut self, event: &Event) -> Result { - self.reporting_map.enable_event_reporting(event) - } - - pub fn disable_tm_for_event(&mut self, event: &Event) -> Result { - self.reporting_map.disable_event_reporting(event) - } - - pub fn generate_pus_event_tm_generic( - &self, - sender: &(impl EcssTmSender + ?Sized), - time_stamp: &[u8], - event: Event, - params: Option<&[u8]>, - ) -> Result { - if !self.reporting_map.event_enabled(&event) { - return Ok(false); - } - match event.severity() { - Severity::Info => self - .reporter - .event_info(sender, time_stamp, event, params) - .map(|_| true) - .map_err(|e| e.into()), - Severity::Low => self - .reporter - .event_low_severity(sender, time_stamp, event, params) - .map(|_| true) - .map_err(|e| e.into()), - Severity::Medium => self - .reporter - .event_medium_severity(sender, time_stamp, event, params) - .map(|_| true) - .map_err(|e| e.into()), - Severity::High => self - .reporter - .event_high_severity(sender, time_stamp, event, params) - .map(|_| true) - .map_err(|e| e.into()), - } - } - - pub fn generate_pus_event_tm_generic_with_generic_params( - &self, - sender: &(impl EcssTmSender + ?Sized), - time_stamp: &[u8], - event: Event, - small_data_buf: &mut [u8], - params: Option<&Params>, - ) -> Result { - let mut result = EventGenerationResult { - event_was_enabled: false, - params_were_propagated: true, - }; - if params.is_none() { - result.event_was_enabled = - self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?; - return Ok(result); - } - let params = params.unwrap(); - result.event_was_enabled = match params { - Params::Heapless(heapless_param) => { - heapless_param - .write_to_be_bytes(&mut small_data_buf[..heapless_param.written_len()]) - .map_err(EcssTmtcError::ByteConversion)?; - self.generate_pus_event_tm_generic( - sender, - time_stamp, - event, - Some(small_data_buf), - )? - } - Params::Vec(vec) => { - self.generate_pus_event_tm_generic(sender, time_stamp, event, Some(vec))? - } - Params::String(string) => self.generate_pus_event_tm_generic( - sender, - time_stamp, - event, - Some(string.as_bytes()), - )?, - _ => { - result.params_were_propagated = false; - self.generate_pus_event_tm_generic(sender, time_stamp, event, None)? - } - }; - Ok(result) - } - } - - impl - PusEventTmCreatorWithMap, Event, EventTmHookInstance> - { - pub fn new_with_default_backend(reporter: EventReporter) -> Self { - Self { - reporter, - reporting_map: DefaultPusEventReportingMap::default(), - phantom: PhantomData, - } - } - } - - impl> - PusEventTmCreatorWithMap - { - pub fn enable_tm_for_event_with_sev( - &mut self, - event: &EventU32TypedSev, - ) -> Result { - self.reporting_map.enable_event_reporting(event.as_ref()) - } - - pub fn disable_tm_for_event_with_sev( - &mut self, - event: &EventU32TypedSev, - ) -> Result { - self.reporting_map.disable_event_reporting(event.as_ref()) - } - - pub fn generate_pus_event_tm( - &self, - sender: &(impl EcssTmSender + ?Sized), - time_stamp: &[u8], - event: EventU32TypedSev, - aux_data: Option<&[u8]>, - ) -> Result { - self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data) - } - } - - pub type DefaultPusEventU16TmCreator = - PusEventTmCreatorWithMap, EventU16, EventTmHook>; - pub type DefaultPusEventU32TmCreator = - PusEventTmCreatorWithMap, EventU32, EventTmHook>; -} -#[cfg(test)] -mod tests { - use alloc::string::{String, ToString}; - use alloc::vec; - use arbitrary_int::{u11, u21}; - use spacepackets::ecss::PusPacket; - use spacepackets::ecss::event::MessageSubtypeId; - use spacepackets::ecss::tm::PusTmReader; - - use super::*; - use crate::request::UniqueApidTargetId; - use crate::{events_legacy::SeverityInfo, tmtc::PacketAsVec}; - use std::sync::mpsc::{self, TryRecvError}; - - const INFO_EVENT: EventU32TypedSev = EventU32TypedSev::::new(1, 0); - const LOW_SEV_EVENT: EventU32 = EventU32::new(Severity::Low, 1, 5); - const EMPTY_STAMP: [u8; 7] = [0; 7]; - const TEST_APID: u11 = u11::new(0x02); - const TEST_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, u21::new(0x05)); - - fn create_basic_man_1() -> DefaultPusEventU32TmCreator { - let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128); - PusEventTmCreatorWithMap::new_with_default_backend(reporter) - } - fn create_basic_man_2() -> DefaultPusEventU32TmCreator { - let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128); - let backend = DefaultPusEventReportingMap::default(); - PusEventTmCreatorWithMap::new(reporter, backend) - } - - #[test] - fn test_basic() { - let event_man = create_basic_man_1(); - let (event_tx, event_rx) = mpsc::channel::(); - let event_sent = event_man - .generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None) - .expect("Sending info event failed"); - - assert!(event_sent); - // Will not check packet here, correctness of packet was tested somewhere else - event_rx.try_recv().expect("Receiving event TM failed"); - } - - #[test] - fn test_disable_event() { - let mut event_man = create_basic_man_2(); - let (event_tx, event_rx) = mpsc::channel::(); - // let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx); - let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT); - assert!(res.is_ok()); - assert!(res.unwrap()); - let mut event_sent = event_man - .generate_pus_event_tm_generic(&event_tx, &EMPTY_STAMP, LOW_SEV_EVENT, None) - .expect("Sending low severity event failed"); - assert!(!event_sent); - let res = event_rx.try_recv(); - assert!(res.is_err()); - assert!(matches!(res.unwrap_err(), TryRecvError::Empty)); - // Check that only the low severity event was disabled - event_sent = event_man - .generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None) - .expect("Sending info event failed"); - assert!(event_sent); - event_rx.try_recv().expect("No info event received"); - } - - #[test] - fn test_reenable_event() { - let mut event_man = create_basic_man_1(); - let (event_tx, event_rx) = mpsc::channel::(); - let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT); - assert!(res.is_ok()); - assert!(res.unwrap()); - res = event_man.enable_tm_for_event_with_sev(&INFO_EVENT); - assert!(res.is_ok()); - assert!(res.unwrap()); - let event_sent = event_man - .generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None) - .expect("Sending info event failed"); - assert!(event_sent); - event_rx.try_recv().expect("No info event received"); - } - - #[test] - fn test_event_with_generic_string_param() { - let event_man = create_basic_man_1(); - let mut small_data_buf = [0; 128]; - let param_data = "hello world"; - let (event_tx, event_rx) = mpsc::channel::(); - let res = event_man.generate_pus_event_tm_generic_with_generic_params( - &event_tx, - &EMPTY_STAMP, - INFO_EVENT.into(), - &mut small_data_buf, - Some(¶m_data.to_string().into()), - ); - assert!(res.is_ok()); - let res = res.unwrap(); - assert!(res.event_was_enabled); - assert!(res.params_were_propagated); - let event_tm = event_rx.try_recv().expect("no event received"); - let tm = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed"); - assert_eq!(tm.service_type_id(), 5); - assert_eq!( - tm.message_subtype_id(), - MessageSubtypeId::TmInfoReport as u8 - ); - assert_eq!(tm.user_data().len(), 4 + param_data.len()); - let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap()); - assert_eq!(u32_event, INFO_EVENT.raw()); - let string_data = String::from_utf8_lossy(&tm.user_data()[4..]); - assert_eq!(string_data, param_data); - } - - #[test] - fn test_event_with_generic_vec_param() { - let event_man = create_basic_man_1(); - let mut small_data_buf = [0; 128]; - let param_data = vec![1, 2, 3, 4]; - let (event_tx, event_rx) = mpsc::channel::(); - let res = event_man.generate_pus_event_tm_generic_with_generic_params( - &event_tx, - &EMPTY_STAMP, - INFO_EVENT.into(), - &mut small_data_buf, - Some(¶m_data.clone().into()), - ); - assert!(res.is_ok()); - let res = res.unwrap(); - assert!(res.event_was_enabled); - assert!(res.params_were_propagated); - let event_tm = event_rx.try_recv().expect("no event received"); - let tm = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed"); - assert_eq!(tm.service_type_id(), 5); - assert_eq!( - tm.message_subtype_id(), - MessageSubtypeId::TmInfoReport as u8 - ); - assert_eq!(tm.user_data().len(), 4 + param_data.len()); - let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap()); - assert_eq!(u32_event, INFO_EVENT.raw()); - let vec_data = tm.user_data()[4..].to_vec(); - assert_eq!(vec_data, param_data); - } - - #[test] - fn test_event_with_generic_store_param_not_propagated() { - // TODO: Test this. - } - - #[test] - fn test_event_with_generic_heapless_param() { - // TODO: Test this. - } -} diff --git a/satrs/src/pus/event_srv.rs b/satrs/src/pus/event_srv.rs deleted file mode 100644 index bd41e88..0000000 --- a/satrs/src/pus/event_srv.rs +++ /dev/null @@ -1,355 +0,0 @@ -use crate::events_legacy::EventU32; -use crate::pus::event_man::{EventRequest, EventRequestWithToken}; -use crate::pus::verification::TcStateToken; -use crate::pus::{DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError}; -use crate::queue::GenericSendError; -use spacepackets::ecss::PusPacket; -use spacepackets::ecss::event::MessageSubtypeId; -use std::sync::mpsc::Sender; - -use super::verification::VerificationReportingProvider; -use super::{ - CacheAndReadRawEcssTc, EcssTcReceiver, EcssTmSender, GenericConversionError, - GenericRoutingError, HandlingStatus, PusServiceHelper, -}; - -pub struct PusEventServiceHandler< - TcReceiver: EcssTcReceiver, - TmSender: EcssTmSender, - TcInMemConverter: CacheAndReadRawEcssTc, - VerificationReporter: VerificationReportingProvider, -> { - pub service_helper: - PusServiceHelper, - event_request_tx: Sender, -} - -impl< - TcReceiver: EcssTcReceiver, - TmSender: EcssTmSender, - TcInMemConverter: CacheAndReadRawEcssTc, - VerificationReporter: VerificationReportingProvider, -> PusEventServiceHandler -{ - pub fn new( - service_helper: PusServiceHelper< - TcReceiver, - TmSender, - TcInMemConverter, - VerificationReporter, - >, - event_request_tx: Sender, - ) -> Self { - Self { - service_helper, - event_request_tx, - } - } - - pub fn poll_and_handle_next_tc( - &mut self, - mut error_callback: ErrorCb, - time_stamp: &[u8], - ) -> Result { - let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?; - if possible_packet.is_none() { - return Ok(HandlingStatus::Empty.into()); - } - let ecss_tc_and_token = possible_packet.unwrap(); - self.service_helper - .tc_in_mem_converter_mut() - .cache(&ecss_tc_and_token.tc_in_memory)?; - let tc = self.service_helper.tc_in_mem_converter().convert()?; - let subservice = tc.message_subtype_id(); - let srv = MessageSubtypeId::try_from(subservice); - if srv.is_err() { - return Ok(DirectPusPacketHandlerResult::CustomSubservice( - tc.message_subtype_id(), - ecss_tc_and_token.token, - )); - } - let mut handle_enable_disable_request = - |enable: bool| -> Result { - if tc.user_data().len() < 4 { - return Err(GenericConversionError::NotEnoughAppData { - expected: 4, - found: tc.user_data().len(), - } - .into()); - } - let user_data = tc.user_data(); - let event_u32 = - EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap())); - let mut token: TcStateToken = ecss_tc_and_token.token.into(); - match self.service_helper.common.verif_reporter.start_success( - &self.service_helper.common.tm_sender, - ecss_tc_and_token.token, - time_stamp, - ) { - Ok(start_token) => { - token = start_token.into(); - } - Err(e) => { - error_callback(&PartialPusHandlingError::Verification(e)); - } - } - - let event_req_with_token = if enable { - EventRequestWithToken { - request: EventRequest::Enable(event_u32), - token, - } - } else { - EventRequestWithToken { - request: EventRequest::Disable(event_u32), - token, - } - }; - self.event_request_tx - .send(event_req_with_token) - .map_err(|_| { - PusPacketHandlingError::RequestRouting(GenericRoutingError::Send( - GenericSendError::RxDisconnected, - )) - })?; - Ok(HandlingStatus::HandledOne.into()) - }; - - match srv.unwrap() { - MessageSubtypeId::TmInfoReport - | MessageSubtypeId::TmLowSeverityReport - | MessageSubtypeId::TmMediumSeverityReport - | MessageSubtypeId::TmHighSeverityReport => { - return Err(PusPacketHandlingError::RequestConversion( - GenericConversionError::WrongService(tc.message_subtype_id()), - )); - } - MessageSubtypeId::TcEnableEventGeneration => { - handle_enable_disable_request(true)?; - } - MessageSubtypeId::TcDisableEventGeneration => { - handle_enable_disable_request(false)?; - } - MessageSubtypeId::TcReportDisabledList | MessageSubtypeId::TmDisabledEventsReport => { - return Ok(DirectPusPacketHandlerResult::SubserviceNotImplemented( - subservice, - ecss_tc_and_token.token, - )); - } - } - - Ok(HandlingStatus::HandledOne.into()) - } -} - -#[cfg(test)] -mod tests { - use arbitrary_int::traits::Integer as _; - use arbitrary_int::u14; - use delegate::delegate; - use spacepackets::ecss::event::MessageSubtypeId; - use spacepackets::ecss::{CreatorConfig, MessageTypeId}; - use spacepackets::time::{TimeWriter, cds}; - use spacepackets::util::UnsignedEnum; - use spacepackets::{ - SpHeader, - ecss::{ - tc::{PusTcCreator, PusTcSecondaryHeader}, - tm::PusTmReader, - }, - }; - use std::sync::mpsc::{self, Sender}; - - use crate::pus::event_man::EventRequest; - use crate::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID}; - use crate::pus::verification::{ - RequestId, VerificationReporter, VerificationReportingProvider, - }; - use crate::pus::{GenericConversionError, HandlingStatus, MpscTcReceiver}; - use crate::tmtc::PacketSenderWithSharedPool; - use crate::{ - events_legacy::EventU32, - pus::{ - DirectPusPacketHandlerResult, EcssTcInSharedPoolCacher, PusPacketHandlingError, - event_man::EventRequestWithToken, - tests::PusServiceHandlerWithSharedStoreCommon, - verification::{TcStateAccepted, VerificationToken}, - }, - }; - - use super::PusEventServiceHandler; - - const TEST_EVENT_0: EventU32 = EventU32::new(crate::events_legacy::Severity::Info, 5, 25); - - struct Pus5HandlerWithStoreTester { - common: PusServiceHandlerWithSharedStoreCommon, - handler: PusEventServiceHandler< - MpscTcReceiver, - PacketSenderWithSharedPool, - EcssTcInSharedPoolCacher, - VerificationReporter, - >, - } - - impl Pus5HandlerWithStoreTester { - pub fn new(event_request_tx: Sender) -> Self { - let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(0); - Self { - common, - handler: PusEventServiceHandler::new(srv_handler, event_request_tx), - } - } - } - - impl PusTestHarness for Pus5HandlerWithStoreTester { - fn start_verification(&mut self, tc: &PusTcCreator) -> VerificationToken { - let init_token = self - .handler - .service_helper - .verif_reporter_mut() - .start_verification(tc); - self.handler - .service_helper - .verif_reporter() - .acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7]) - .expect("acceptance success failure") - } - - fn send_tc(&self, token: &VerificationToken, tc: &PusTcCreator) { - self.common - .send_tc(self.handler.service_helper.id(), token, tc); - } - - delegate! { - to self.common { - fn read_next_tm(&mut self) -> PusTmReader<'_>; - fn check_no_tm_available(&self) -> bool; - fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId); - } - - } - } - - impl SimplePusPacketHandler for Pus5HandlerWithStoreTester { - fn handle_one_tc( - &mut self, - ) -> Result { - let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap(); - self.handler.poll_and_handle_next_tc(|_| {}, &time_stamp) - } - } - - fn event_test( - test_harness: &mut (impl PusTestHarness + SimplePusPacketHandler), - subservice: MessageSubtypeId, - expected_event_req: EventRequest, - event_req_receiver: mpsc::Receiver, - ) { - let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0); - let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(5, subservice as u8)); - let mut app_data = [0; 4]; - TEST_EVENT_0 - .write_to_be_bytes(&mut app_data) - .expect("writing test event failed"); - let ping_tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default()); - let token = test_harness.start_verification(&ping_tc); - test_harness.send_tc(&token, &ping_tc); - let request_id = token.request_id(); - test_harness.handle_one_tc().unwrap(); - test_harness.check_next_verification_tm(1, request_id); - test_harness.check_next_verification_tm(3, request_id); - // Completion TM is not generated for us. - assert!(test_harness.check_no_tm_available()); - let event_request = event_req_receiver - .try_recv() - .expect("no event request received"); - assert_eq!(expected_event_req, event_request.request); - } - - #[test] - fn test_enabling_event_reporting() { - let (event_request_tx, event_request_rx) = mpsc::channel(); - let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx); - event_test( - &mut test_harness, - MessageSubtypeId::TcEnableEventGeneration, - EventRequest::Enable(TEST_EVENT_0), - event_request_rx, - ); - } - - #[test] - fn test_disabling_event_reporting() { - let (event_request_tx, event_request_rx) = mpsc::channel(); - let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx); - event_test( - &mut test_harness, - MessageSubtypeId::TcDisableEventGeneration, - EventRequest::Disable(TEST_EVENT_0), - event_request_rx, - ); - } - - #[test] - fn test_empty_tc_queue() { - let (event_request_tx, _) = mpsc::channel(); - let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx); - let result = test_harness.handle_one_tc(); - assert!(result.is_ok()); - let result = result.unwrap(); - assert!( - matches!( - result, - DirectPusPacketHandlerResult::Handled(HandlingStatus::Empty) - ), - "unexpected result type {result:?}" - ) - } - - #[test] - fn test_sending_custom_subservice() { - let (event_request_tx, _) = mpsc::channel(); - let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx); - let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0); - let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(5, 200)); - let ping_tc = - PusTcCreator::new_no_app_data(sp_header, sec_header, CreatorConfig::default()); - let token = test_harness.start_verification(&ping_tc); - test_harness.send_tc(&token, &ping_tc); - let result = test_harness.handle_one_tc(); - assert!(result.is_ok()); - let result = result.unwrap(); - if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result { - assert_eq!(subservice, 200); - } else { - panic!("unexpected result type {result:?}") - } - } - - #[test] - fn test_sending_invalid_app_data() { - let (event_request_tx, _) = mpsc::channel(); - let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx); - let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0); - let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new( - 5, - MessageSubtypeId::TcEnableEventGeneration as u8, - )); - let ping_tc = - PusTcCreator::new(sp_header, sec_header, &[0, 1, 2], CreatorConfig::default()); - let token = test_harness.start_verification(&ping_tc); - test_harness.send_tc(&token, &ping_tc); - let result = test_harness.handle_one_tc(); - assert!(result.is_err()); - let result = result.unwrap_err(); - if let PusPacketHandlingError::RequestConversion( - GenericConversionError::NotEnoughAppData { expected, found }, - ) = result - { - assert_eq!(expected, 4); - assert_eq!(found, 3); - } else { - panic!("unexpected result type {result:?}") - } - } -} diff --git a/satrs/src/pus/mod.rs b/satrs/src/pus/mod.rs index e6a8b27..b927934 100644 --- a/satrs/src/pus/mod.rs +++ b/satrs/src/pus/mod.rs @@ -25,10 +25,6 @@ use spacepackets::ecss::tm::PusTmCreator; use spacepackets::{ByteConversionError, SpHeader}; pub mod action; -pub mod event; -pub mod event_man; -#[cfg(feature = "std")] -pub mod event_srv; pub mod mode; pub mod scheduler; #[cfg(feature = "std")] diff --git a/satrs/src/pus/scheduler_srv.rs b/satrs/src/pus/scheduler_srv.rs index 3a091d0..629f72e 100644 --- a/satrs/src/pus/scheduler_srv.rs +++ b/satrs/src/pus/scheduler_srv.rs @@ -15,7 +15,7 @@ use std::sync::mpsc; /// This is a helper class for [std] environments to handle generic PUS 11 (scheduling service) /// packets. This handler is able to handle the most important PUS requests for a scheduling -/// service which provides the [PusSchedulerProvider]. +/// service which provides the [PusScheduler]. /// /// Please note that this class does not do the regular periodic handling like releasing any /// telecommands inside the scheduler. The user can retrieve the wrapped scheduler via the diff --git a/satrs/tests/pus_events.rs b/satrs/tests/pus_events.rs deleted file mode 100644 index 12e94d1..0000000 --- a/satrs/tests/pus_events.rs +++ /dev/null @@ -1,164 +0,0 @@ -use arbitrary_int::{u11, u21}; -use satrs::event_man_legacy::{ - EventManagerWithMpsc, EventMessage, EventMessageU32, EventRoutingError, EventSendProvider, - EventU32SenderMpsc, -}; -use satrs::events_legacy::{EventU32, EventU32TypedSev, Severity, SeverityInfo}; -use satrs::params::U32Pair; -use satrs::params::{Params, ParamsHeapless, WritableToBeBytes}; -use satrs::pus::event_man::{DefaultPusEventReportingMap, EventReporter, PusEventTmCreatorWithMap}; -use satrs::request::UniqueApidTargetId; -use satrs::tmtc::PacketAsVec; -use spacepackets::ecss::PusError; -use spacepackets::ecss::tm::PusTmReader; -use std::sync::mpsc::{self, SendError, TryRecvError}; -use std::thread; - -const INFO_EVENT: EventU32TypedSev = EventU32TypedSev::::new(1, 0); -const LOW_SEV_EVENT: EventU32 = EventU32::new(Severity::Low, 1, 5); -const EMPTY_STAMP: [u8; 7] = [0; 7]; -const TEST_APID: u11 = u11::new(0x02); -const TEST_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, u21::new(0x05)); - -#[derive(Debug, Clone)] -pub enum CustomTmSenderError { - SendError(SendError>), - PusError(PusError), -} - -#[test] -fn test_threaded_usage() { - let (event_tx, event_rx) = mpsc::sync_channel(100); - let mut event_man = EventManagerWithMpsc::new(event_rx); - - let (pus_event_man_tx, pus_event_man_rx) = mpsc::channel(); - let pus_event_man_send_provider = EventU32SenderMpsc::new(1, pus_event_man_tx); - event_man.subscribe_all(pus_event_man_send_provider.target_id()); - event_man.add_sender(pus_event_man_send_provider); - let (event_packet_tx, event_packet_rx) = mpsc::channel::(); - let reporter = EventReporter::new(TEST_ID.raw(), u11::new(0x02), 0, 128); - let pus_event_man = - PusEventTmCreatorWithMap::new(reporter, DefaultPusEventReportingMap::default()); - let error_handler = |event_msg: &EventMessageU32, error: EventRoutingError| { - panic!("received routing error for event {event_msg:?}: {error:?}"); - }; - // PUS + Generic event manager thread - let jh0 = thread::spawn(move || { - let mut event_cnt = 0; - let mut params_array: [u8; 128] = [0; 128]; - loop { - event_man.try_event_handling(error_handler); - match pus_event_man_rx.try_recv() { - Ok(event_msg) => { - let gen_event = |aux_data| { - pus_event_man.generate_pus_event_tm_generic( - &event_packet_tx, - &EMPTY_STAMP, - event_msg.event(), - aux_data, - ) - }; - let res = if let Some(aux_data) = event_msg.params() { - match aux_data { - Params::Heapless(heapless) => match heapless { - ParamsHeapless::Raw(raw) => { - raw.write_to_be_bytes(&mut params_array) - .expect("Writing raw parameter failed"); - gen_event(Some(¶ms_array[0..raw.written_len()])) - } - ParamsHeapless::EcssEnum(e) => { - e.write_to_be_bytes(&mut params_array) - .expect("Writing ECSS enum failed"); - gen_event(Some(¶ms_array[0..e.written_len()])) - } - }, - Params::Vec(vec) => gen_event(Some(vec.as_slice())), - Params::String(str) => gen_event(Some(str.as_bytes())), - Params::Store(_) => gen_event(None), - _ => panic!("unsupported parameter type"), - } - } else { - gen_event(None) - }; - event_cnt += 1; - assert!(res.is_ok()); - assert!(res.unwrap()); - if event_cnt == 2 { - break; - } - } - Err(e) => { - if let TryRecvError::Disconnected = e { - panic!("Event receiver disconnected!") - } - } - } - } - }); - - // Event sender and TM checker thread - let jh1 = thread::spawn(move || { - event_tx - .send(EventMessage::new(TEST_ID.id(), INFO_EVENT.into())) - .expect("Sending info event failed"); - loop { - match event_packet_rx.try_recv() { - // Event TM received successfully - Ok(event_tm) => { - let tm = PusTmReader::new(event_tm.packet.as_slice(), 7) - .expect("Deserializing TM failed"); - assert_eq!(tm.service_type_id(), 5); - assert_eq!(tm.message_subtype_id(), 1); - let src_data = tm.source_data(); - assert!(!src_data.is_empty()); - assert_eq!(src_data.len(), 4); - let event = - EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap())); - assert_eq!(event, INFO_EVENT); - break; - } - Err(e) => { - if let TryRecvError::Disconnected = e { - panic!("Event sender disconnected!") - } - } - } - } - event_tx - .send(EventMessage::new_with_params( - TEST_ID.id(), - LOW_SEV_EVENT, - &Params::Heapless((2_u32, 3_u32).into()), - )) - .expect("Sending low severity event failed"); - loop { - match event_packet_rx.try_recv() { - // Event TM received successfully - Ok(event_tm) => { - let tm = PusTmReader::new(event_tm.packet.as_slice(), 7) - .expect("Deserializing TM failed"); - assert_eq!(tm.service_type_id(), 5); - assert_eq!(tm.message_subtype_id(), 2); - let src_data = tm.source_data(); - assert!(!src_data.is_empty()); - assert_eq!(src_data.len(), 12); - let event = - EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap())); - assert_eq!(event, LOW_SEV_EVENT); - let u32_pair: U32Pair = - src_data[4..].try_into().expect("Creating U32Pair failed"); - assert_eq!(u32_pair.0, 2); - assert_eq!(u32_pair.1, 3); - break; - } - Err(e) => { - if let TryRecvError::Disconnected = e { - panic!("Event sender disconnected!") - } - } - } - } - }); - jh0.join().expect("Joining manager thread failed"); - jh1.join().expect("Joining creator thread failed"); -}