diff --git a/satrs-example/client/src/main.rs b/satrs-example/client/src/main.rs index 8294107..3dbfe51 100644 --- a/satrs-example/client/src/main.rs +++ b/satrs-example/client/src/main.rs @@ -1,7 +1,7 @@ use anyhow::bail; use arbitrary_int::u11; use clap::Parser as _; -use models::{Apid, MessageType, TcHeader}; +use models::{Apid, MessageType, TcHeader, mgm::request::HkRequest}; use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT}; use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader}; use std::{ @@ -19,6 +19,41 @@ pub struct Cli { ping: bool, #[arg(short, long)] test_event: bool, + + #[command(subcommand)] + commands: Option, +} + +#[derive(clap::Subcommand)] +enum Commands { + Mgm0(MgmArgs), + Mgm1(MgmArgs), +} + +impl Commands { + #[inline] + pub fn target_id(&self) -> models::ComponentId { + match self { + Commands::Mgm0(_mgm_args) => models::ComponentId::AcsMgm0, + Commands::Mgm1(_mgm_args) => models::ComponentId::AcsMgm1, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)] +struct MgmArgs { + #[arg(short, long)] + ping: bool, + #[arg(long)] + request_hk: bool, + #[arg(short, long)] + mode: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)] +pub enum ModeSelect { + Off, + Normal, } fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> { @@ -76,6 +111,66 @@ fn main() -> anyhow::Result<()> { let request_packet = request.to_vec(); client.send_to(&request_packet, addr).unwrap(); } + if let Some(cmd) = cli.commands { + let target_id = cmd.target_id(); + match cmd { + Commands::Mgm0(args) | Commands::Mgm1(args) => { + if args.ping { + let request = models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(cmd.target_id(), models::MessageType::Ping), + models::mgm::request::Request::Ping, + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} ping request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + if args.request_hk { + let request = models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(target_id, models::MessageType::Hk), + models::mgm::request::Request::Hk(HkRequest { + id: models::mgm::request::HkId::Sensor, + req_type: models::HkRequestType::OneShot, + }), + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} HK request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + if let Some(mode) = args.mode { + let dev_mode = match mode { + ModeSelect::Off => models::DeviceMode::Off, + ModeSelect::Normal => models::DeviceMode::Normal, + }; + + let request = models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(target_id, models::MessageType::Mode), + models::mgm::request::Request::Mode(dev_mode), + ); + let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header); + log::info!( + "sending {:?} HK request with TC ID {:#010x}", + target_id, + sent_tc_id.raw() + ); + let request_packet = request.to_vec(); + client.send_to(&request_packet, addr).unwrap(); + } + } + } + } let mut recv_buf: Box<[u8; 2048]> = Box::new([0; 2048]); log::info!("entering listening loop"); @@ -102,7 +197,6 @@ fn main() -> anyhow::Result<()> { 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 { @@ -145,8 +239,16 @@ fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> { } models::ComponentId::AcsSubsystem => todo!(), models::ComponentId::AcsMgmAssembly => todo!(), - models::ComponentId::AcsMgm0 => todo!(), - models::ComponentId::AcsMgm1 => todo!(), + models::ComponentId::AcsMgm0 => { + let response = + postcard::from_bytes::(remainder); + log::info!("Received response from MGM0: {:?}", response.unwrap()); + } + models::ComponentId::AcsMgm1 => { + let response = + postcard::from_bytes::(remainder); + log::info!("Received response from MGM1: {:?}", response.unwrap()); + } models::ComponentId::EpsSubsystem => todo!(), models::ComponentId::UdpServer => todo!(), models::ComponentId::TcpServer => todo!(), diff --git a/satrs-example/models/src/lib.rs b/satrs-example/models/src/lib.rs index 0eb97a4..3206a3a 100644 --- a/satrs-example/models/src/lib.rs +++ b/satrs-example/models/src/lib.rs @@ -152,7 +152,7 @@ pub trait Message { fn message_type(&self) -> MessageType; } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Copy, Clone)] pub enum DeviceMode { Off = 0, On = 1, @@ -163,8 +163,10 @@ pub enum DeviceMode { #[non_exhaustive] pub enum HkRequestType { OneShot, + /// Enable periodic HK generation with a specified frequency. EnablePeriodic(core::time::Duration), DisablePeriodic, + /// Modify periodic HK generation interval. ModifyInterval(core::time::Duration), } diff --git a/satrs-example/models/src/mgm.rs b/satrs-example/models/src/mgm.rs index 5c10859..3bd88fb 100644 --- a/satrs-example/models/src/mgm.rs +++ b/satrs-example/models/src/mgm.rs @@ -1,5 +1,5 @@ pub mod request { - use crate::HkRequestType; + use crate::{HkRequestType, Message}; #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum HkId { @@ -16,6 +16,22 @@ pub mod request { pub enum Request { Ping, Hk(HkRequest), + Mode(crate::DeviceMode), + } + + impl Request { + fn message_type(&self) -> crate::MessageType { + match self { + Request::Ping => crate::MessageType::Verification, + Request::Hk(_hk_request) => crate::MessageType::Hk, + Request::Mode(_mode) => crate::MessageType::Mode, + } + } + } + impl Message for Request { + fn message_type(&self) -> crate::MessageType { + self.message_type() + } } } @@ -41,7 +57,7 @@ pub mod response { Hk(HkResponse), } - impl Message for Response { + impl Response { fn message_type(&self) -> crate::MessageType { match self { Response::Ok => crate::MessageType::Verification, @@ -49,4 +65,10 @@ pub mod response { } } } + + impl Message for Response { + fn message_type(&self) -> crate::MessageType { + self.message_type() + } + } } diff --git a/satrs-example/models/src/pcdu.rs b/satrs-example/models/src/pcdu.rs index 7f8f896..cc1f592 100644 --- a/satrs-example/models/src/pcdu.rs +++ b/satrs-example/models/src/pcdu.rs @@ -93,15 +93,37 @@ impl SwitchRequest { } pub mod request { + use crate::{DeviceMode, Message}; + use super::*; #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] pub enum Request { + Mode(DeviceMode), Ping, GetSwitches, EnableSwitches(SwitchesBitfield), DisableSwitches(SwitchesBitfield), } + + impl Request { + pub fn message_type(&self) -> crate::MessageType { + match self { + Request::Mode(_mode) => crate::MessageType::Mode, + Request::Ping => crate::MessageType::Verification, + Request::GetSwitches => crate::MessageType::Action, + Request::EnableSwitches(_switches) | Request::DisableSwitches(_switches) => { + crate::MessageType::Action + } + } + } + } + + impl Message for Request { + fn message_type(&self) -> crate::MessageType { + self.message_type() + } + } } pub mod response { diff --git a/satrs-example/src/acs/assembly.rs b/satrs-example/src/acs/assembly.rs deleted file mode 100644 index 09e2227..0000000 --- a/satrs-example/src/acs/assembly.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: Write the assembly diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index c15e356..304e640 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -1,10 +1,9 @@ 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 models::{mgm, ComponentId, DeviceMode, HkRequestType}; use satrs::spacepackets::CcsdsPacketIdAndPsc; -use satrs_example::{DeviceMode, TimestampHelper}; +use satrs_example::{HkHelperSingleSet, TimestampHelper}; use satrs_minisim::acs::lis3mdl::{ MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, }; @@ -15,13 +14,9 @@ use std::sync::mpsc::{self}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use satrs::mode::{ - ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, - ModeRequestHandlerMpscBounded, -}; -use satrs::request::{GenericMessage, MessageMetadata}; -use satrs_example::config::components::NO_SENDER; +use satrs::request::MessageMetadata; +use crate::acs::mgm_assembly; use crate::ccsds::pack_ccsds_tm_packet_for_now; use crate::eps::PowerSwitchHelper; use crate::spi::SpiInterface; @@ -116,38 +111,43 @@ pub struct BufWrapper { } pub struct ModeHelpers { - current: ModeAndSubmode, - target: Option, - requestor_info: Option, + current: DeviceMode, + target: Option, + tc_id: Option, transition_state: TransitionState, } impl Default for ModeHelpers { fn default() -> Self { Self { - current: ModeAndSubmode::new(DeviceMode::Off as u32, 0), + current: DeviceMode::Off, target: Default::default(), - requestor_info: Default::default(), + tc_id: Default::default(), transition_state: Default::default(), } } } +/// Helper component for communication with a parent component, which is usually as assembly. +pub struct ModeLeafHelper { + pub request_rx: mpsc::Receiver, + pub report_tx: mpsc::SyncSender, +} + /// Example MGM device handler strongly based on the LIS3MDL MEMS device. -#[allow(clippy::too_many_arguments)] pub struct MgmHandlerLis3Mdl { id: ComponentId, dev_str: &'static str, - mode_node: ModeRequestHandlerMpscBounded, tc_rx: mpsc::Receiver, tm_tx: mpsc::SyncSender, switch_helper: PowerSwitchHelper, pub com_interface: ComInterface, shared_mgm_set: Arc>, - //hk_helper: PusHkHelper, - mode_helpers: ModeHelpers, - bufs: BufWrapper, + buffers: BufWrapper, stamp_helper: TimestampHelper, + hk_helper: HkHelperSingleSet, + mode_helpers: ModeHelpers, + mode_leaf_helper: ModeLeafHelper, } impl MgmHandlerLis3Mdl { @@ -155,39 +155,57 @@ impl MgmHandlerLis3Mdl { 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>, + mode_leaf_helper: ModeLeafHelper, ) -> 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(), + buffers: BufWrapper::default(), stamp_helper: TimestampHelper::default(), + hk_helper: HkHelperSingleSet::new(false, Duration::from_millis(200)), + mode_leaf_helper, } } + + #[inline] + pub fn mode(&self) -> DeviceMode { + self.mode_helpers.current + } + pub fn periodic_operation(&mut self) { + // Update current time. self.stamp_helper.update_from_now(); + // Handle 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); - } - if self.mode() == DeviceMode::Normal as u32 { + + // Handle assembly related messages. + self.handle_mode_leaf_handling(); + + // Handle mode transitions first. + self.handle_mode_transition(); + + // Poll sensor before checking and generating HK. + if self.mode() == DeviceMode::Normal { log::trace!("polling LIS3MDL sensor {}", self.dev_str); self.poll_sensor(); } + + // Finally check whether any HK generation is necessary. + if self.hk_helper.needs_generation() { + self.generate_hk(None); + } } pub fn handle_telecommands(&mut self) { @@ -208,7 +226,11 @@ impl MgmHandlerLis3Mdl { } mgm::request::Request::Hk(hk_request) => { self.handle_hk_request(Some(tc_id), &hk_request) - } //mgm::request::Request::Mo + } + mgm::request::Request::Mode(device_mode) => { + self.mode_helpers.tc_id = Some(tc_id); + self.start_transition(device_mode, false); + } } } Err(e) => { @@ -226,6 +248,25 @@ impl MgmHandlerLis3Mdl { } } + pub fn handle_mode_leaf_handling(&mut self) { + loop { + match self.mode_leaf_helper.request_rx.try_recv() { + Ok(request) => match request { + mgm_assembly::ModeRequest::SetMode(device_mode) => { + self.start_transition(device_mode, false) + } + mgm_assembly::ModeRequest::ReadMode => self.report_mode_to_parent(), + }, + 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, @@ -250,46 +291,28 @@ impl MgmHandlerLis3Mdl { ) { 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)), - ) + self.generate_hk(tc_id); } - HkRequestType::EnablePeriodic(_duration) => todo!(), - HkRequestType::DisablePeriodic => todo!(), - HkRequestType::ModifyInterval(_duration) => todo!(), - _ => todo!(), + HkRequestType::EnablePeriodic(duration) => { + self.hk_helper.enabled = true; + self.hk_helper.frequency = duration; + } + HkRequestType::DisablePeriodic => { + self.hk_helper.enabled = false; + } + HkRequestType::ModifyInterval(duration) => { + self.hk_helper.frequency = duration; + } + _ => log::warn!("unhandled HK request"), } } - pub fn handle_mode_requests(&mut self) { - loop { - // TODO: Only allow one set mode request per cycle? - match self.mode_node.try_recv_mode_request() { - Ok(opt_msg) => { - if let Some(msg) = opt_msg { - let result = self.handle_mode_request(msg); - // TODO: Trigger event? - if result.is_err() { - log::warn!( - "{}: mode request failed with error {:?}", - self.dev_str, - result.err().unwrap() - ); - } - } else { - break; - } - } - Err(e) => match e { - satrs::queue::GenericReceiveError::Empty => break, - satrs::queue::GenericReceiveError::TxDisconnected(e) => { - log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e); - } - }, - } - } + pub fn generate_hk(&self, opt_tc_id: Option) { + let mgm_snapshot = *self.shared_mgm_set.lock().unwrap(); + self.send_telemetry( + opt_tc_id, + mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(mgm_snapshot)), + ) } pub fn poll_sensor(&mut self) { @@ -297,22 +320,22 @@ impl MgmHandlerLis3Mdl { // SPI interface. self.com_interface .transfer( - &self.bufs.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], - &mut self.bufs.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], + &self.buffers.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], + &mut self.buffers.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1], ) .expect("failed to transfer data"); let x_raw = i16::from_le_bytes( - self.bufs.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2] + self.buffers.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2] .try_into() .unwrap(), ); let y_raw = i16::from_le_bytes( - self.bufs.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2] + self.buffers.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2] .try_into() .unwrap(), ); let z_raw = i16::from_le_bytes( - self.bufs.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2] + self.buffers.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2] .try_into() .unwrap(), ); @@ -325,10 +348,21 @@ impl MgmHandlerLis3Mdl { drop(mgm_guard); } - pub fn handle_mode_transition(&mut self, target_mode_submode: ModeAndSubmode) { - if target_mode_submode.mode() == DeviceMode::On as u32 - || target_mode_submode.mode() == DeviceMode::Normal as u32 - { + fn start_transition(&mut self, target_mode: DeviceMode, _forced: bool) { + log::info!("{}: transitioning to mode {:?}", self.dev_str, target_mode); + if target_mode == DeviceMode::Off { + self.shared_mgm_set.lock().unwrap().valid = false; + } + self.mode_helpers.transition_state = TransitionState::Idle; + self.mode_helpers.target = Some(target_mode); + } + + pub fn handle_mode_transition(&mut self) { + if self.mode_helpers.target.is_none() { + return; + } + let target_mode = self.mode_helpers.target.unwrap(); + if target_mode == DeviceMode::On || target_mode == DeviceMode::Normal { if self.mode_helpers.transition_state == TransitionState::Idle { let result = self .switch_helper @@ -346,142 +380,84 @@ impl MgmHandlerLis3Mdl { } if self.mode_helpers.transition_state == TransitionState::Done { self.mode_helpers.current = self.mode_helpers.target.unwrap(); - self.handle_mode_reached(self.mode_helpers.requestor_info) - .expect("failed to handle mode reached"); + self.handle_mode_reached(); self.mode_helpers.transition_state = TransitionState::Idle; } } } -} -impl ModeProvider for MgmHandlerLis3Mdl { - fn mode_and_submode(&self) -> ModeAndSubmode { - self.mode_helpers.current - } -} - -impl ModeRequestHandler for MgmHandlerLis3Mdl { - type Error = ModeError; - - fn start_transition( - &mut self, - requestor: MessageMetadata, - mode_and_submode: ModeAndSubmode, - _forced: bool, - ) -> Result<(), satrs::mode::ModeError> { - log::info!( - "{}: transitioning to mode {:?}", - self.dev_str, - mode_and_submode - ); - self.mode_helpers.current = mode_and_submode; - if mode_and_submode.mode() == DeviceMode::Off as u32 { - self.shared_mgm_set.lock().unwrap().valid = false; - self.handle_mode_reached(Some(requestor))?; - } else if mode_and_submode.mode() == DeviceMode::Normal as u32 - || mode_and_submode.mode() == DeviceMode::On as u32 - { - // TODO: Write helper method for the struct? Might help for other handlers as well.. - self.mode_helpers.transition_state = TransitionState::Idle; - self.mode_helpers.requestor_info = Some(requestor); - self.mode_helpers.target = Some(mode_and_submode); - } - Ok(()) - } - - fn announce_mode(&self, _requestor_info: Option, _recursive: bool) { + fn handle_mode_reached(&mut self) { + self.mode_helpers.target = None; log::info!( "{} announcing mode: {:?}", self.dev_str, - self.mode_and_submode() + self.mode_helpers.current ); - } - - fn handle_mode_reached( - &mut self, - requestor: Option, - ) -> Result<(), Self::Error> { - self.mode_helpers.target = None; - self.announce_mode(requestor, false); - if let Some(requestor) = requestor { - if requestor.sender_id() == NO_SENDER { - return Ok(()); - } - if requestor.sender_id() != ComponentId::Ground as u32 { - log::warn!( - "can not send back mode reply to sender {:x}", - requestor.sender_id() - ); - } else { - self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?; - } + if let Some(requestor) = self.mode_helpers.tc_id { + self.send_mode_tm(requestor); } - Ok(()) + // Inform our parent about mode changes. + self.report_mode_to_parent(); } - fn send_mode_reply( - &self, - requestor: MessageMetadata, - reply: ModeReply, - ) -> Result<(), Self::Error> { - if requestor.sender_id() != ComponentId::Ground as u32 { - log::warn!( - "can not send back mode reply to sender {}", - requestor.sender_id() - ); - } - self.mode_node - .send_mode_reply(requestor, reply) - .map_err(ModeError::Send)?; - Ok(()) + fn report_mode_to_parent(&self) { + self.mode_leaf_helper + .report_tx + .send(mgm_assembly::ModeReport::Mode(self.mode_helpers.current)) + .unwrap(); } - fn handle_mode_info( - &mut self, - _requestor_info: MessageMetadata, - _info: ModeAndSubmode, - ) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl ModeNode for MgmHandlerLis3Mdl { - fn id(&self) -> satrs::ComponentId { - self.id as u32 - } -} - -impl ModeChild for MgmHandlerLis3Mdl { - type Sender = mpsc::SyncSender>; - - fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) { - self.mode_node.add_message_target(id, reply_sender); + fn send_mode_tm(&self, requestor: CcsdsPacketIdAndPsc) { + self.send_telemetry(Some(requestor), mgm::response::Response::Ok); } } #[cfg(test)] mod tests { - use std::{ - collections::HashMap, - sync::{mpsc, Arc}, + use std::sync::{ + mpsc::{self, TryRecvError}, + Arc, }; + use arbitrary_int::u11; use models::{ + mgm::request::HkRequest, pcdu::{SwitchRequest, SwitchState, SwitchStateBinary}, - ComponentId, - }; - use satrs::{ - mode::{ModeReply, ModeRequest}, - mode_tree::ModeParent, - request::GenericMessage, - tmtc::PacketAsVec, + Apid, ComponentId, TcHeader, }; + use satrs::{request::GenericMessage, spacepackets::SpacePacketHeader}; use satrs_minisim::acs::lis3mdl::MgmLis3RawValues; use crate::eps::pcdu::{SharedSwitchSet, SwitchMap, SwitchSet}; use super::*; + #[derive(Debug, Copy, Clone)] + pub enum MgmSelect { + _0, + _1, + } + + impl MgmSelect { + pub fn id(&self) -> ComponentId { + match self { + MgmSelect::_0 => ComponentId::AcsMgm0, + MgmSelect::_1 => ComponentId::AcsMgm1, + } + } + } + + pub fn create_request_tc( + select: MgmSelect, + request: models::mgm::request::Request, + ) -> models::ccsds::CcsdsTcPacketOwned { + models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)), + TcHeader::new(select.id(), models::MessageType::Ping), + request, + ) + } + #[derive(Default)] pub struct TestSpiInterface { pub call_count: u32, @@ -505,89 +481,44 @@ mod tests { #[allow(dead_code)] pub struct MgmTestbench { - pub mode_request_tx: mpsc::SyncSender>, - pub mode_reply_rx_to_ground: mpsc::Receiver>, - pub mode_reply_rx_to_parent: mpsc::Receiver>, + pub assembly_mode_request_tx: mpsc::SyncSender, + pub mode_report_rx: mpsc::Receiver, pub shared_switch_set: SharedSwitchSet, pub tc_tx: mpsc::SyncSender, - pub tm_rx: mpsc::Receiver, + pub tm_rx: mpsc::Receiver, pub switch_rx: mpsc::Receiver>, pub handler: MgmHandlerLis3Mdl, } - #[derive(Default)] - #[allow(dead_code)] - pub struct MgmAssemblyMock( - pub HashMap>>, - ); - - impl ModeNode for MgmAssemblyMock { - fn id(&self) -> satrs::ComponentId { - ComponentId::AcsMgmAssembly as u32 - } - } - - impl ModeParent for MgmAssemblyMock { - type Sender = mpsc::SyncSender>; - - fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) { - self.0.insert(id, request_sender); - } - } - - #[derive(Default)] - #[allow(dead_code)] - pub struct GroundMock { - pub request_sender_map: - HashMap>>, - } - - impl ModeNode for GroundMock { - fn id(&self) -> satrs::ComponentId { - ComponentId::Ground as u32 - } - } - - impl ModeParent for GroundMock { - type Sender = mpsc::SyncSender>; - - fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) { - self.request_sender_map.insert(id, request_sender); - } - } - impl MgmTestbench { pub fn new() -> Self { - let (request_tx, request_rx) = 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 mode_node = - ModeRequestHandlerMpscBounded::new(ComponentId::Ground as u32, request_rx); + let (assembly_mode_request_tx, assembly_mode_request_rx) = mpsc::sync_channel(5); + let (mode_report_tx, mode_report_rx) = mpsc::sync_channel(5); + let mode_leaf_helper = ModeLeafHelper { + request_rx: assembly_mode_request_rx, + report_tx: mode_report_tx, + }; 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 (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( + let handler = MgmHandlerLis3Mdl::new( ComponentId::AcsMgm0, "TEST_MGM", - mode_node, tc_rx, - hk_reply_tx, + tm_tx, PowerSwitchHelper::new(switcher_tx, shared_switch_set.clone()), TestSpiInterface::default(), shared_mgm_set, + mode_leaf_helper, ); - 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_ground: reply_rx_to_ground, - mode_reply_rx_to_parent: reply_rx_to_parent, + assembly_mode_request_tx, + mode_report_rx, shared_switch_set, switch_rx, handler, @@ -601,40 +532,25 @@ mod tests { fn test_basic_handler() { let mut testbench = MgmTestbench::new(); assert_eq!(testbench.handler.com_interface.call_count, 0); - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Off as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16); + assert_eq!(testbench.handler.mode(), DeviceMode::Off); testbench.handler.periodic_operation(); // Handler is OFF, no changes expected. assert_eq!(testbench.handler.com_interface.call_count, 0); - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Off as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16); + assert_eq!(testbench.handler.mode(), DeviceMode::Off); } #[test] fn test_normal_handler() { let mut testbench = MgmTestbench::new(); testbench - .mode_request_tx - .send(GenericMessage::new( - MessageMetadata::new(0, ComponentId::Ground as u32), - ModeRequest::SetMode { - mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), - forced: false, - }, + .tc_tx + .send(create_request_tc( + MgmSelect::_0, + mgm::request::Request::Mode(DeviceMode::Normal), )) - .expect("failed to send mode request"); + .unwrap(); testbench.handler.periodic_operation(); - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Normal as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0); + assert_eq!(testbench.handler.mode(), DeviceMode::Off); // Verify power switch handling. let switch_req = testbench.switch_rx.try_recv().expect("no switch request"); @@ -651,24 +567,24 @@ mod tests { // 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_ground - .try_recv() - .expect("no mode reply generated"); - match mode_reply.message { - ModeReply::ModeReply(mode) => { - assert_eq!(mode.mode(), DeviceMode::Normal as u32); - assert_eq!(mode.submode(), 0); - } - _ => panic!("unexpected mode reply"), - } + assert_eq!(testbench.handler.mode(), DeviceMode::Normal); + + let tm_packet = testbench.tm_rx.try_recv().expect("no mode reply generated"); + + assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0); + + let response = postcard::from_bytes::(&tm_packet.payload) + .expect("failed to deserialize mode reply"); + matches!(response, models::mgm::response::Response::Ok); // The device should have been polled once. - assert_eq!(testbench.handler.com_interface.call_count, 2); + assert_eq!(testbench.handler.com_interface.call_count, 1); let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap(); assert!(mgm_set.x < 0.001); assert!(mgm_set.y < 0.001); assert!(mgm_set.z < 0.001); assert!(mgm_set.valid); + + matches!(testbench.tm_rx.try_recv(), Err(TryRecvError::Empty)); } #[test] @@ -681,16 +597,24 @@ mod tests { }; testbench.handler.com_interface.next_mgm_data = raw_values; testbench - .mode_request_tx - .send(GenericMessage::new( - MessageMetadata::new(0, ComponentId::Ground as u32), - ModeRequest::SetMode { - mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), - forced: false, - }, + .tc_tx + .send(create_request_tc( + MgmSelect::_0, + mgm::request::Request::Mode(DeviceMode::Normal), )) - .expect("failed to send mode request"); + .unwrap(); testbench.handler.periodic_operation(); + + // 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 mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap(); let expected_x = raw_values.x as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS; @@ -706,4 +630,110 @@ mod tests { assert!(z_diff < 0.001, "z diff too large: {}", z_diff); assert!(mgm_set.valid); } + + #[test] + fn test_hk_one_shot_device_off() { + let mut testbench = MgmTestbench::new(); + // Device handler is initially off, first set will be invalid. + testbench + .tc_tx + .send(create_request_tc( + MgmSelect::_0, + mgm::request::Request::Hk(HkRequest { + id: mgm::request::HkId::Sensor, + req_type: HkRequestType::OneShot, + }), + )) + .unwrap(); + testbench.handler.periodic_operation(); + + // 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 tm_packet = testbench.tm_rx.try_recv().expect("no mode reply generated"); + + assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0); + + let response = postcard::from_bytes::(&tm_packet.payload) + .expect("failed to deserialize mode reply"); + if let models::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) = + response + { + assert_eq!(data.valid, false); + assert!(data.x < 0.001); + assert!(data.y < 0.001); + assert!(data.z < 0.001); + } else { + panic!("expected hk response"); + } + + matches!(testbench.tm_rx.try_recv(), Err(TryRecvError::Empty)); + } + + #[test] + fn test_hk_device_normal() { + let mut testbench = MgmTestbench::new(); + testbench + .tc_tx + .send(create_request_tc( + MgmSelect::_0, + mgm::request::Request::Mode(DeviceMode::Normal), + )) + .unwrap(); + // This simulates one cycle for the power switch to update. + testbench + .shared_switch_set + .lock() + .unwrap() + .set_switch_state(SwitchId::Mgm0, SwitchState::On); + testbench.handler.periodic_operation(); + assert_eq!(testbench.handler.mode(), DeviceMode::Normal); + + testbench + .tc_tx + .send(create_request_tc( + MgmSelect::_0, + mgm::request::Request::Hk(HkRequest { + id: mgm::request::HkId::Sensor, + req_type: HkRequestType::OneShot, + }), + )) + .unwrap(); + testbench.handler.periodic_operation(); + + let mode_tm = testbench.tm_rx.try_recv().expect("no mode reply generated"); + + assert_eq!(mode_tm.tm_header.sender_id, ComponentId::AcsMgm0); + + let response = postcard::from_bytes::(&mode_tm.payload) + .expect("failed to deserialize mode reply"); + matches!(response, models::mgm::response::Response::Ok); + + let hk_tm = testbench.tm_rx.try_recv().expect("no hk reply generated"); + + assert_eq!(hk_tm.tm_header.sender_id, ComponentId::AcsMgm0); + + let response = postcard::from_bytes::(&hk_tm.payload) + .expect("failed to deserialize mode reply"); + if let models::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) = + response + { + // Set is now valid. + assert_eq!(data.valid, true); + assert!(data.x < 0.001); + assert!(data.y < 0.001); + assert!(data.z < 0.001); + } else { + panic!("expected hk response"); + } + + matches!(testbench.tm_rx.try_recv(), Err(TryRecvError::Empty)); + } } diff --git a/satrs-example/src/acs/mgm_assembly.rs b/satrs-example/src/acs/mgm_assembly.rs new file mode 100644 index 0000000..9f7298d --- /dev/null +++ b/satrs-example/src/acs/mgm_assembly.rs @@ -0,0 +1,51 @@ +// TODO: Program assembly. +// TODO: Remove dead_code lint as soon as assembly is done. +#![allow(dead_code)] + +use std::sync::mpsc; + +use models::DeviceMode; + +pub enum ModeRequest { + SetMode(DeviceMode), + ReadMode, +} + +pub enum ModeReport { + Mode(DeviceMode), +} + +/// Helper component for communication with a parent component, which is usually as assembly. +pub struct QueueHelper { + pub request_tx: [mpsc::SyncSender; 2], + pub report_rx: [mpsc::Receiver; 2], +} + +pub struct Assembly { + pub(crate) helper: QueueHelper, +} + +impl Assembly { + pub fn periodic_operation(&mut self) { + self.handle_mode_queue(); + } + + pub fn handle_mode_queue(&mut self) { + loop { + for rx in &mut self.helper.report_rx { + match rx.try_recv() { + // TODO: Do something with the report. + Ok(report) => match report { + ModeReport::Mode(_device_mode) => (), + }, + Err(e) => match e { + mpsc::TryRecvError::Empty => break, + mpsc::TryRecvError::Disconnected => { + log::warn!("packet sender disconnected") + } + }, + } + } + } + } +} diff --git a/satrs-example/src/acs/mod.rs b/satrs-example/src/acs/mod.rs index e9223e5..60382dd 100644 --- a/satrs-example/src/acs/mod.rs +++ b/satrs-example/src/acs/mod.rs @@ -1,4 +1,6 @@ -pub mod assembly; pub mod ctrl; + pub mod mgm; +pub mod mgm_assembly; + pub mod subsystem; diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index 99b07ad..f2b8b92 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -11,20 +11,11 @@ use models::{ self, SwitchId, SwitchMapBinary, SwitchMapBinaryWrapper, SwitchRequest, SwitchState, SwitchStateBinary, SwitchesBitfield, }, - ComponentId, + ComponentId, DeviceMode, }; use num_enum::{IntoPrimitive, TryFromPrimitive}; -use satrs::{ - mode::{ - ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, - ModeRequestHandlerMpscBounded, - }, - mode_tree::{ModeChild, ModeNode}, - queue::GenericSendError, - request::{GenericMessage, MessageMetadata}, - spacepackets::CcsdsPacketIdAndPsc, -}; -use satrs_example::{config::components::NO_SENDER, DeviceMode, TimestampHelper}; +use satrs::{request::GenericMessage, spacepackets::CcsdsPacketIdAndPsc}; +use satrs_example::TimestampHelper; use satrs_minisim::{ eps::{PcduReply, PcduRequest}, SerializableSimMsgPayload, SimReply, SimRequest, @@ -273,19 +264,17 @@ pub enum OpCode { #[allow(clippy::too_many_arguments)] pub struct PcduHandler { dev_str: &'static str, - mode_node: ModeRequestHandlerMpscBounded, switch_request_rx: mpsc::Receiver>, tc_rx: std::sync::mpsc::Receiver, tm_tx: mpsc::SyncSender, pub com_interface: ComInterface, shared_switch_map: Arc>, - mode_and_submode: ModeAndSubmode, + mode: DeviceMode, stamp_helper: TimestampHelper, } 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>, @@ -294,27 +283,27 @@ impl PcduHandler { ) -> Self { Self { dev_str: "PCDU", - mode_node, + //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(), + mode: DeviceMode::Off, } } + pub fn periodic_operation(&mut self, op_code: OpCode) { match op_code { OpCode::RegularOp => { self.stamp_helper.update_from_now(); // Handle requests. self.handle_telecommands(); - self.handle_mode_requests(); + //self.handle_mode_requests(); self.handle_switch_requests(); // Poll the switch states and/or telemetry regularly here. - if self.mode() == DeviceMode::Normal as u32 || self.mode() == DeviceMode::On as u32 - { + if self.mode() == DeviceMode::Normal || self.mode() == DeviceMode::On { self.handle_periodic_commands(); } } @@ -324,6 +313,11 @@ impl PcduHandler { } } + #[inline] + pub fn mode(&self) -> DeviceMode { + self.mode + } + pub fn handle_telecommands(&mut self) { loop { match self.tc_rx.try_recv() { @@ -362,6 +356,9 @@ impl PcduHandler { SwitchStateBinary::Off, ); } + pcdu::request::Request::Mode(device_mode) => { + self.switch_mode(tc_id, device_mode) + } } } Err(e) => { @@ -408,6 +405,33 @@ impl PcduHandler { } } + fn switch_mode(&mut self, requestor: CcsdsPacketIdAndPsc, mode: DeviceMode) { + log::info!("{}: transitioning to mode {:?}", self.dev_str, mode); + self.mode = mode; + if self.mode() == DeviceMode::Off { + self.shared_switch_map.lock().unwrap().valid = false; + } + log::info!("{} announcing mode: {:?}", self.dev_str, self.mode); + self.send_telemetry(Some(requestor), pcdu::response::Response::Ok); + } + + pub fn send_telemetry( + &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); + } + } + Err(e) => { + log::warn!("failed to pack TM packet: {}", e); + } + } + } + pub fn handle_periodic_commands(&self) { let pcdu_req = PcduRequest::RequestSwitchInfo; let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap(); @@ -416,6 +440,7 @@ impl PcduHandler { } } + /* pub fn handle_mode_requests(&mut self) { loop { // TODO: Only allow one set mode request per cycle? @@ -446,6 +471,7 @@ impl PcduHandler { } } } + */ pub fn handle_device_switching(&mut self, switch_id: SwitchId, state: SwitchStateBinary) { let pcdu_req = PcduRequest::SwitchDevice { @@ -500,111 +526,33 @@ impl PcduHandler { } } -impl ModeProvider for PcduHandler { - fn mode_and_submode(&self) -> ModeAndSubmode { - self.mode_and_submode - } -} - -impl ModeRequestHandler for PcduHandler { - type Error = ModeError; - fn start_transition( - &mut self, - requestor: MessageMetadata, - mode_and_submode: ModeAndSubmode, - _forced: bool, - ) -> Result<(), satrs::mode::ModeError> { - log::info!( - "{}: transitioning to mode {:?}", - self.dev_str, - mode_and_submode - ); - self.mode_and_submode = mode_and_submode; - if mode_and_submode.mode() == DeviceMode::Off as u32 { - self.shared_switch_map.lock().unwrap().valid = false; - } - self.handle_mode_reached(Some(requestor))?; - Ok(()) - } - - fn announce_mode(&self, _requestor_info: Option, _recursive: bool) { - log::info!( - "{} announcing mode: {:?}", - self.dev_str, - self.mode_and_submode - ); - } - - fn handle_mode_reached( - &mut self, - requestor: Option, - ) -> Result<(), Self::Error> { - self.announce_mode(requestor, false); - if let Some(requestor) = requestor { - if requestor.sender_id() == NO_SENDER { - return Ok(()); - } - if requestor.sender_id() != ComponentId::Ground as u32 { - log::warn!( - "can not send back mode reply to sender {}", - requestor.sender_id() - ); - } else { - self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?; - } - } - Ok(()) - } - - fn send_mode_reply( - &self, - requestor: MessageMetadata, - reply: ModeReply, - ) -> Result<(), Self::Error> { - if requestor.sender_id() != ComponentId::Ground as u32 { - log::warn!( - "can not send back mode reply to sender {}", - requestor.sender_id() - ); - } - self.mode_node - .send_mode_reply(requestor, reply) - .map_err(|_| GenericSendError::RxDisconnected)?; - Ok(()) - } - - fn handle_mode_info( - &mut self, - _requestor_info: MessageMetadata, - _info: ModeAndSubmode, - ) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl ModeNode for PcduHandler { - fn id(&self) -> satrs::ComponentId { - ComponentId::EpsPcdu as u32 - } -} - -impl ModeChild for PcduHandler { - type Sender = mpsc::SyncSender>; - - fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) { - self.mode_node.add_message_target(id, reply_sender); - } -} - #[cfg(test)] mod tests { use std::sync::mpsc; - use models::pcdu::{SwitchMapBinary, SwitchStateBinary}; - use satrs::{mode::ModeRequest, request::GenericMessage}; + use arbitrary_int::u11; + use models::{ + pcdu::{SwitchMapBinary, SwitchStateBinary}, + Apid, TcHeader, + }; + use satrs::{ + mode::{ModeReply, ModeRequest}, + request::{GenericMessage, MessageMetadata}, + spacepackets::SpacePacketHeader, + }; use super::*; + pub fn create_request_tc( + request: models::pcdu::request::Request, + ) -> models::ccsds::CcsdsTcPacketOwned { + models::ccsds::CcsdsTcPacketOwned::new_with_request( + SpacePacketHeader::new_from_apid(u11::new(Apid::Eps as u16)), + TcHeader::new(ComponentId::EpsPcdu, request.message_type()), + request, + ) + } + #[derive(Default)] pub struct SerialInterfaceTest { pub inner: SerialInterfaceDummy, @@ -643,7 +591,6 @@ 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 tc_tx: mpsc::SyncSender, pub tm_rx: mpsc::Receiver, @@ -653,29 +600,23 @@ 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_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5); - let mode_node = - ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, mode_request_rx); + let (mode_request_tx, _mode_request_rx) = mpsc::sync_channel(5); + let (_mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5); 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::new_with_init_switches_unknown())); - let mut handler = PcduHandler::new( - mode_node, + let handler = PcduHandler::new( + //mode_node, tc_rx, tm_tx.clone(), switch_reqest_rx, SerialInterfaceTest::default(), shared_switch_map, ); - 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, tc_tx, tm_rx, @@ -738,11 +679,7 @@ mod tests { testbench.handler.com_interface.reply_queue.borrow().len(), 0 ); - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Off as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16); + assert_eq!(testbench.handler.mode(), DeviceMode::Off); testbench.handler.periodic_operation(OpCode::RegularOp); testbench .handler @@ -753,26 +690,18 @@ mod tests { testbench.handler.com_interface.reply_queue.borrow().len(), 0 ); - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Off as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16); + assert_eq!(testbench.handler.mode(), DeviceMode::Off); } #[test] fn test_normal_mode() { let mut testbench = PcduTestbench::new(); testbench - .mode_request_tx - .send(GenericMessage::new( - MessageMetadata::new(0, ComponentId::Ground as u32), - ModeRequest::SetMode { - mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), - forced: false, - }, - )) - .expect("failed to send mode request"); + .tc_tx + .send(create_request_tc(pcdu::request::Request::Mode( + DeviceMode::Normal, + ))) + .unwrap(); let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap(); assert!(switch_map_shared.valid); drop(switch_map_shared); @@ -781,11 +710,7 @@ mod tests { .handler .periodic_operation(OpCode::PollAndRecvReplies); // Check correctness of mode. - assert_eq!( - testbench.handler.mode_and_submode().mode(), - DeviceMode::Normal as u32 - ); - assert_eq!(testbench.handler.mode_and_submode().submode(), 0); + assert_eq!(testbench.handler.mode(), DeviceMode::Normal); testbench.verify_switch_info_req_was_sent(1); testbench.verify_switch_reply_received(1, SwitchMapBinaryWrapper::default().0); @@ -799,15 +724,11 @@ mod tests { fn test_switch_request_handling() { let mut testbench = PcduTestbench::new(); testbench - .mode_request_tx - .send(GenericMessage::new( - MessageMetadata::new(0, ComponentId::Ground as u32), - ModeRequest::SetMode { - mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), - forced: false, - }, - )) - .expect("failed to send mode request"); + .tc_tx + .send(create_request_tc(pcdu::request::Request::Mode( + DeviceMode::Normal, + ))) + .unwrap(); testbench .switch_request_tx .send(GenericMessage::new( diff --git a/satrs-example/src/lib.rs b/satrs-example/src/lib.rs index 0432644..6967898 100644 --- a/satrs-example/src/lib.rs +++ b/satrs-example/src/lib.rs @@ -1,5 +1,7 @@ extern crate alloc; +use std::time::{Duration, Instant}; + pub use models::ComponentId; use satrs::spacepackets::time::cds::CdsTime; @@ -19,13 +21,6 @@ impl PacketAsVec { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub enum DeviceMode { - Off = 0, - On = 1, - Normal = 2, -} - pub struct TimestampHelper { stamper: CdsTime, time_stamp: [u8; 7], @@ -54,3 +49,44 @@ impl Default for TimestampHelper { } } } + +/// Helper structure for periodic HK generation of a single set. +#[derive(Debug)] +pub struct HkHelperSingleSet { + pub enabled: bool, + pub frequency: Duration, + pub last_generated: Option, +} + +impl HkHelperSingleSet { + #[inline] + pub const fn new(enabled: bool, init_frequency: Duration) -> Self { + Self { + enabled, + frequency: init_frequency, + last_generated: None, + } + } + + #[inline] + pub const fn enabled(&self) -> bool { + self.enabled + } + + /// Check whether a new HK packet needs to be generated. + pub fn needs_generation(&mut self) -> bool { + if !self.enabled { + return false; + } + if self.last_generated.is_none() { + self.last_generated = Some(Instant::now()); + return true; + } + let last_generated = self.last_generated.unwrap(); + if Instant::now() - last_generated >= self.frequency { + self.last_generated = Some(Instant::now()); + return true; + } + false + } +} diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 0a1e157..5af90fb 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -16,27 +16,29 @@ use interface::{ }; use log::info; use logger::setup_logger; -use models::ComponentId; +use models::{ComponentId, DeviceMode}; use satrs::{ hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer}, - mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded}, + mode::{Mode, ModeAndSubmode, ModeRequest}, pus::HandlingStatus, request::{GenericMessage, MessageMetadata}, spacepackets::time::cds::CdsTime, }; -use satrs_example::{ - config::{ - components::NO_SENDER, - tasks::{FREQ_MS_AOCS, FREQ_MS_CONTROLLER, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS}, - OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT, - }, - DeviceMode, +use satrs_example::config::{ + components::NO_SENDER, + tasks::{FREQ_MS_AOCS, FREQ_MS_CONTROLLER, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS}, + OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT, }; use tmtc::sender::TmTcSender; use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink}; use crate::{ - acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper}, + acs::{ + mgm::{ + self, MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper, + }, + mgm_assembly, + }, control::Controller, eps::pcdu::SwitchSet, event_manager::EventManager, @@ -73,9 +75,13 @@ fn main() { 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); + // These message handles need to go into the MGM assembly. + let (mgm_0_mode_request_tx, mgm_0_mode_request_rx) = mpsc::sync_channel(5); + let (mgm_1_mode_request_tx, mgm_1_mode_request_rx) = mpsc::sync_channel(5); + let (mgm_0_mode_report_tx, mgm_0_mode_report_rx) = mpsc::sync_channel(5); + let (mgm_1_mode_report_tx, mgm_1_mode_report_rx) = mpsc::sync_channel(5); + + let (pcdu_handler_mode_tx, _pcdu_handler_mode_rx) = mpsc::sync_channel(5); let (event_ctrl_tx, event_ctrl_rx) = mpsc::sync_channel(10); let mut event_manager = EventManager { @@ -134,10 +140,6 @@ fn main() { let shared_mgm_0_set = Arc::default(); let shared_mgm_1_set = Arc::default(); - 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 @@ -163,23 +165,35 @@ fn main() { let mut mgm_0_handler = MgmHandlerLis3Mdl::new( ComponentId::AcsMgm0, "MGM_0", - mgm_0_mode_node, mgm_0_handler_tc_rx, tm_sink_tx.clone(), switch_helper.clone(), mgm_0_spi_interface, shared_mgm_0_set, + mgm::ModeLeafHelper { + request_rx: mgm_0_mode_request_rx, + report_tx: mgm_0_mode_report_tx, + }, ); let mut mgm_1_handler = MgmHandlerLis3Mdl::new( ComponentId::AcsMgm1, "MGM_1", - mgm_1_mode_node, mgm_1_handler_tc_rx, tm_sink_tx.clone(), switch_helper.clone(), mgm_1_spi_interface, shared_mgm_1_set, + mgm::ModeLeafHelper { + request_rx: mgm_1_mode_request_rx, + report_tx: mgm_1_mode_report_tx, + }, ); + let mut mgm_assembly = mgm_assembly::Assembly { + helper: mgm_assembly::QueueHelper { + request_tx: [mgm_0_mode_request_tx, mgm_1_mode_request_tx], + report_rx: [mgm_0_mode_report_rx, mgm_1_mode_report_rx], + }, + }; // Connect PUS service to device handlers. /* connect_mode_nodes( @@ -205,10 +219,9 @@ fn main() { } else { SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) }; - let pcdu_mode_node = - ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, 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_mode_node, pcdu_handler_tc_rx, tm_sink_tx.clone(), switch_request_rx, @@ -288,6 +301,7 @@ fn main() { .spawn(move || loop { mgm_0_handler.periodic_operation(); mgm_1_handler.periodic_operation(); + mgm_assembly.periodic_operation(); thread::sleep(Duration::from_millis(FREQ_MS_AOCS)); }) .unwrap(); diff --git a/satrs/src/device.rs b/satrs/src/device.rs deleted file mode 100644 index e69de29..0000000 diff --git a/satrs/src/hk.rs b/satrs/src/hk.rs deleted file mode 100644 index 50edfda..0000000 --- a/satrs/src/hk.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::ComponentId; - -pub type CollectionIntervalFactor = u32; -/// Unique Identifier for a certain housekeeping dataset. -pub type UniqueId = u32; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct HkRequest { - pub unique_id: UniqueId, - pub variant: HkRequestVariant, -} - -impl HkRequest { - pub fn new(unique_id: UniqueId, variant: HkRequestVariant) -> Self { - Self { unique_id, variant } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum HkRequestVariant { - OneShot, - EnablePeriodic, - DisablePeriodic, - ModifyCollectionInterval(CollectionIntervalFactor), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct TargetedHkRequest { - pub target_id: ComponentId, - pub hk_request: HkRequestVariant, -} - -impl TargetedHkRequest { - pub fn new(target_id: ComponentId, hk_request: HkRequestVariant) -> Self { - Self { - target_id, - hk_request, - } - } -} diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index 6f9c384..2d39285 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -29,14 +29,12 @@ pub mod encoding; 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; pub mod params; pub mod pool; -pub mod power; pub mod pus; pub mod queue; pub mod request; diff --git a/satrs/src/power.rs b/satrs/src/power.rs deleted file mode 100644 index 3872a9f..0000000 --- a/satrs/src/power.rs +++ /dev/null @@ -1,312 +0,0 @@ -use core::time::Duration; - -use derive_new::new; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -#[allow(unused_imports)] -pub use std_mod::*; - -use crate::request::MessageMetadata; - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum SwitchState { - Off = 0, - On = 1, - Unknown = 2, - Faulty = 3, -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum SwitchStateBinary { - Off = 0, - On = 1, -} - -impl TryFrom for SwitchStateBinary { - type Error = (); - fn try_from(value: SwitchState) -> Result { - match value { - SwitchState::Off => Ok(SwitchStateBinary::Off), - SwitchState::On => Ok(SwitchStateBinary::On), - _ => Err(()), - } - } -} - -impl> From for SwitchStateBinary { - fn from(value: T) -> Self { - if value.into() == 0 { - return SwitchStateBinary::Off; - } - SwitchStateBinary::On - } -} - -impl From for SwitchState { - fn from(value: SwitchStateBinary) -> Self { - match value { - SwitchStateBinary::Off => SwitchState::Off, - SwitchStateBinary::On => SwitchState::On, - } - } -} - -pub type SwitchId = u16; - -/// Generic trait for a device capable of turning on and off switches. -pub trait PowerSwitcherCommandSender> { - type Error: core::fmt::Debug; - - fn send_switch_on_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error>; - fn send_switch_off_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error>; -} - -pub trait PowerSwitchInfo { - type Error: core::fmt::Debug; - - /// Retrieve the switch state - fn switch_state(&self, switch_id: SwitchType) -> Result; - - fn is_switch_on(&self, switch_id: SwitchType) -> Result { - Ok(self.switch_state(switch_id)? == SwitchState::On) - } - - /// The maximum delay it will take to change a switch. - /// - /// This may take into account the time to send a command, wait for it to be executed, and - /// see the switch changed. - fn switch_delay_ms(&self) -> Duration; -} - -#[derive(new)] -pub struct SwitchRequest { - switch_id: SwitchId, - target_state: SwitchStateBinary, -} - -impl SwitchRequest { - pub fn switch_id(&self) -> SwitchId { - self.switch_id - } - - pub fn target_state(&self) -> SwitchStateBinary { - self.target_state - } -} - -#[cfg(feature = "std")] -pub mod std_mod { - use std::sync::mpsc; - - use crate::{ - queue::GenericSendError, - request::{GenericMessage, MessageMetadata}, - }; - - use super::*; - - pub type MpscSwitchCmdSender = mpsc::Sender>; - pub type MpscSwitchCmdSenderBounded = mpsc::SyncSender>; - - impl> PowerSwitcherCommandSender for MpscSwitchCmdSender { - type Error = GenericSendError; - - fn send_switch_on_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error> { - self.send(GenericMessage::new( - requestor_info, - SwitchRequest::new(switch_id.into(), SwitchStateBinary::On), - )) - .map_err(|_| GenericSendError::RxDisconnected) - } - - fn send_switch_off_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error> { - self.send(GenericMessage::new( - requestor_info, - SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off), - )) - .map_err(|_| GenericSendError::RxDisconnected) - } - } - - impl> PowerSwitcherCommandSender for MpscSwitchCmdSenderBounded { - type Error = GenericSendError; - - fn send_switch_on_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error> { - self.try_send(GenericMessage::new( - requestor_info, - SwitchRequest::new(switch_id.into(), SwitchStateBinary::On), - )) - .map_err(|e| match e { - mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None), - mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected, - }) - } - - fn send_switch_off_cmd( - &self, - requestor_info: MessageMetadata, - switch_id: SwitchType, - ) -> Result<(), Self::Error> { - self.try_send(GenericMessage::new( - requestor_info, - SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off), - )) - .map_err(|e| match e { - mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None), - mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected, - }) - } - } -} - -#[cfg(test)] -mod tests { - // TODO: Add unittests for PowerSwitcherCommandSender impls for mpsc. - - use std::sync::mpsc::{self, TryRecvError}; - - use crate::{ComponentId, queue::GenericSendError, request::GenericMessage}; - - use super::*; - - const TEST_REQ_ID: u32 = 2; - const TEST_SENDER_ID: ComponentId = 5; - - const TEST_SWITCH_ID: u16 = 0x1ff; - - fn common_checks(request: &GenericMessage) { - assert_eq!(request.requestor_info.sender_id(), TEST_SENDER_ID); - assert_eq!(request.requestor_info.request_id(), TEST_REQ_ID); - assert_eq!(request.message.switch_id(), TEST_SWITCH_ID); - } - - #[test] - fn test_comand_switch_sending_mpsc_regular_on_cmd() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::channel::>(); - switch_cmd_tx - .send_switch_on_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ) - .expect("sending switch cmd failed"); - let request = switch_cmd_rx - .recv() - .expect("receiving switch request failed"); - common_checks(&request); - assert_eq!(request.message.target_state(), SwitchStateBinary::On); - } - - #[test] - fn test_comand_switch_sending_mpsc_regular_off_cmd() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::channel::>(); - switch_cmd_tx - .send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ) - .expect("sending switch cmd failed"); - let request = switch_cmd_rx - .recv() - .expect("receiving switch request failed"); - common_checks(&request); - assert_eq!(request.message.target_state(), SwitchStateBinary::Off); - } - - #[test] - fn test_comand_switch_sending_mpsc_regular_rx_disconnected() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::channel::>(); - drop(switch_cmd_rx); - let result = switch_cmd_tx.send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ); - assert!(result.is_err()); - matches!(result.unwrap_err(), GenericSendError::RxDisconnected); - } - - #[test] - fn test_comand_switch_sending_mpsc_sync_on_cmd() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::sync_channel::>(3); - switch_cmd_tx - .send_switch_on_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ) - .expect("sending switch cmd failed"); - let request = switch_cmd_rx - .recv() - .expect("receiving switch request failed"); - common_checks(&request); - assert_eq!(request.message.target_state(), SwitchStateBinary::On); - } - - #[test] - fn test_comand_switch_sending_mpsc_sync_off_cmd() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::sync_channel::>(3); - switch_cmd_tx - .send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ) - .expect("sending switch cmd failed"); - let request = switch_cmd_rx - .recv() - .expect("receiving switch request failed"); - common_checks(&request); - assert_eq!(request.message.target_state(), SwitchStateBinary::Off); - } - - #[test] - fn test_comand_switch_sending_mpsc_sync_rx_disconnected() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::sync_channel::>(1); - drop(switch_cmd_rx); - let result = switch_cmd_tx.send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ); - assert!(result.is_err()); - matches!(result.unwrap_err(), GenericSendError::RxDisconnected); - } - - #[test] - fn test_comand_switch_sending_mpsc_sync_queue_full() { - let (switch_cmd_tx, switch_cmd_rx) = mpsc::sync_channel::>(1); - let mut result = switch_cmd_tx.send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ); - assert!(result.is_ok()); - result = switch_cmd_tx.send_switch_off_cmd( - MessageMetadata::new(TEST_REQ_ID, TEST_SENDER_ID), - TEST_SWITCH_ID, - ); - assert!(result.is_err()); - matches!(result.unwrap_err(), GenericSendError::QueueFull(None)); - matches!(switch_cmd_rx.try_recv(), Err(TryRecvError::Empty)); - } -}