continue integrating power subsystem
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2024-05-19 17:33:37 +02:00
parent 27e88ed7f7
commit fe60cb9ccf
5 changed files with 285 additions and 49 deletions

View File

@ -1,5 +1,6 @@
use derive_new::new;
use satrs::hk::{HkRequest, HkRequestVariant};
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs::spacepackets::ecss::hk;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
@ -9,6 +10,7 @@ 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};
@ -127,31 +129,44 @@ pub struct MpscModeLeafInterface {
pub reply_to_parent_tx: mpsc::Sender<GenericMessage<ModeReply>>,
}
#[derive(Default)]
pub struct BufWrapper {
tx_buf: [u8; 32],
rx_buf: [u8; 32],
tm_buf: [u8; 32],
}
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
#[derive(new)]
#[allow(clippy::too_many_arguments)]
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender> {
pub struct MgmHandlerLis3Mdl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> {
id: UniqueApidTargetId,
dev_str: &'static str,
mode_interface: MpscModeLeafInterface,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
switch_helper: SwitchHelper,
tm_sender: TmSender,
pub com_interface: ComInterface,
shared_mgm_set: Arc<Mutex<MgmData>>,
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
mode_and_submode: ModeAndSubmode,
#[new(default)]
tx_buf: [u8; 32],
#[new(default)]
rx_buf: [u8; 32],
#[new(default)]
tm_buf: [u8; 32],
bufs: BufWrapper,
#[new(default)]
stamp_helper: TimestampHelper,
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{
pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now();
// Handle requests.
@ -210,17 +225,17 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
self.stamp_helper.stamp(),
);
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
self.tm_buf[0..4].copy_from_slice(&self.id.unique_id.to_be_bytes());
self.tm_buf[4..8].copy_from_slice(&(SetId::SensorData as u32).to_be_bytes());
self.bufs.tm_buf[0..4].copy_from_slice(&self.id.unique_id.to_be_bytes());
self.bufs.tm_buf[4..8].copy_from_slice(&(SetId::SensorData as u32).to_be_bytes());
// Use binary serialization here. We want the data to be tightly packed.
self.tm_buf[8] = mgm_snapshot.valid as u8;
self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
self.tm_buf[13..17].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
self.tm_buf[17..21].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
self.bufs.tm_buf[8] = mgm_snapshot.valid as u8;
self.bufs.tm_buf[9..13].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
self.bufs.tm_buf[13..17].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
self.bufs.tm_buf[17..21].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
let hk_tm = PusTmCreator::new(
SpHeader::new_from_apid(self.id.apid),
sec_header,
&self.tm_buf[0..21],
&self.bufs.tm_buf[0..21],
true,
);
self.tm_sender
@ -264,22 +279,22 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
// SPI interface.
self.com_interface
.transfer(
&self.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
&mut self.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
&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],
)
.expect("failed to transfer data");
let x_raw = i16::from_le_bytes(
self.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
self.bufs.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
.try_into()
.unwrap(),
);
let y_raw = i16::from_le_bytes(
self.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
self.bufs.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
.try_into()
.unwrap(),
);
let z_raw = i16::from_le_bytes(
self.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
self.bufs.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
.try_into()
.unwrap(),
);
@ -293,16 +308,22 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
}
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeProvider
for MgmHandlerLis3Mdl<ComInterface, TmSender>
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{
fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_and_submode
}
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
for MgmHandlerLis3Mdl<ComInterface, TmSender>
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{
type Error = ModeError;
fn start_transition(
@ -388,17 +409,17 @@ mod tests {
use satrs_example::config::components::Apid;
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
use crate::{pus::hk::HkReply, requests::CompositeRequest};
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
use super::*;
#[derive(Default)]
pub struct TestInterface {
pub struct TestSpiInterface {
pub call_count: u32,
pub next_mgm_data: MgmLis3RawValues,
}
impl SpiInterface for TestInterface {
impl SpiInterface for TestSpiInterface {
type Error = ();
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
@ -420,7 +441,8 @@ mod tests {
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>,
pub handler: MgmHandlerLis3Mdl<TestInterface, mpsc::Sender<PacketAsVec>>,
pub handler:
MgmHandlerLis3Mdl<TestSpiInterface, mpsc::Sender<PacketAsVec>, TestSwitchHelper>,
}
impl MgmTestbench {
@ -450,8 +472,9 @@ mod tests {
mode_interface,
composite_request_rx,
hk_reply_tx,
TestSwitchHelper::default(),
tm_tx,
TestInterface::default(),
TestSpiInterface::default(),
shared_mgm_set,
),
}

View File

@ -1 +1,204 @@
use derive_new::new;
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::VecDeque,
sync::mpsc,
time::{Duration, Instant},
};
use satrs::{
power::{
PowerSwitchInfo, PowerSwitcherCommandSender, SwitchId, SwitchRequest, SwitchState,
SwitchStateBinary,
},
queue::GenericSendError,
request::{GenericMessage, MessageMetadata},
};
use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper};
use thiserror::Error;
use self::pcdu::SharedSwitchSet;
pub mod pcdu;
#[derive(new, Clone)]
pub struct PowerSwitchHelper {
switcher_tx: mpsc::SyncSender<GenericMessage<SwitchRequest>>,
shared_switch_set: SharedSwitchSet,
#[new(default)]
switch_cmd_sent_instant: Option<Instant>,
}
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
pub enum SwitchCommandingError {
#[error("invalid switch id")]
InvalidSwitchId(SwitchId),
#[error("send error: {0}")]
Send(#[from] GenericSendError),
}
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
pub enum SwitchInfoError {
/// This is a configuration error which should not occur.
#[error("switch ID not in map")]
SwitchIdNotInMap(PcduSwitch),
#[error("switch set invalid")]
SwitchSetInvalid,
}
impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
type Error = SwitchInfoError;
fn switch_state(
&self,
switch_id: PcduSwitch,
) -> Result<satrs::power::SwitchState, Self::Error> {
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))
}
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)
}
}
impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
type Error = SwitchCommandingError;
fn send_switch_on_cmd(
&self,
requestor_info: satrs::request::MessageMetadata,
switch_id: PcduSwitch,
) -> Result<(), Self::Error> {
self.switcher_tx
.send_switch_on_cmd(requestor_info, switch_id)?;
Ok(())
}
fn send_switch_off_cmd(
&self,
requestor_info: satrs::request::MessageMetadata,
switch_id: PcduSwitch,
) -> Result<(), Self::Error> {
self.switcher_tx
.send_switch_off_cmd(requestor_info, switch_id)?;
Ok(())
}
}
#[derive(new)]
pub struct SwitchRequestInfo {
pub requestor_info: MessageMetadata,
pub switch_id: PcduSwitch,
pub target_state: satrs::power::SwitchStateBinary,
}
// Test switch helper which can be used for unittests.
pub struct TestSwitchHelper {
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
pub switch_delay_request_count: u32,
pub next_switch_delay: Duration,
pub switch_map: RefCell<SwitchMapWrapper>,
pub switch_map_valid: bool,
}
impl Default for TestSwitchHelper {
fn default() -> Self {
Self {
switch_requests: Default::default(),
switch_info_requests: Default::default(),
switch_delay_request_count: Default::default(),
next_switch_delay: Duration::from_millis(1000),
switch_map: Default::default(),
switch_map_valid: Default::default(),
}
}
}
impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
type Error = SwitchInfoError;
fn switch_state(
&self,
switch_id: PcduSwitch,
) -> Result<satrs::power::SwitchState, Self::Error> {
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 {
return Err(SwitchInfoError::SwitchSetInvalid);
}
let switch_map_mut = self.switch_map.borrow_mut();
if let Some(state) = switch_map_mut.0.get(&switch_id) {
return Ok(*state);
}
Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
}
fn switch_delay_ms(&self) -> Duration {
self.next_switch_delay
}
}
impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
type Error = SwitchCommandingError;
fn send_switch_on_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: PcduSwitch,
) -> Result<(), Self::Error> {
let mut switch_requests_mut = self.switch_requests.borrow_mut();
switch_requests_mut.push_back(SwitchRequestInfo {
requestor_info,
switch_id,
target_state: SwitchStateBinary::On,
});
// By default, the test helper immediately acknowledges the switch request by setting
// the appropriate switch state in the internal switch map.
let mut switch_map_mut = self.switch_map.borrow_mut();
if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
*switch_state = SwitchState::On;
}
Ok(())
}
fn send_switch_off_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: PcduSwitch,
) -> Result<(), Self::Error> {
let mut switch_requests_mut = self.switch_requests.borrow_mut();
switch_requests_mut.push_back(SwitchRequestInfo {
requestor_info,
switch_id,
target_state: SwitchStateBinary::Off,
});
// By default, the test helper immediately acknowledges the switch request by setting
// the appropriate switch state in the internal switch map.
let mut switch_map_mut = self.switch_map.borrow_mut();
if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
*switch_state = SwitchState::Off;
}
Ok(())
}
}
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) {
self.switch_map.get_mut().0.insert(switch, state);
}
}

View File

@ -8,14 +8,14 @@ use derive_new::new;
use satrs::{
hk::{HkRequest, HkRequestVariant},
mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler},
power::{SwitchRequest, SwitchStateBinary},
power::SwitchRequest,
pus::EcssTmSender,
queue::{GenericSendError, GenericTargetedMessagingError},
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
};
use satrs_example::{config::components::PUS_MODE_SERVICE, DeviceMode, TimestampHelper};
use satrs_minisim::{
eps::{PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinary, SwitchMapBinaryWrapper},
eps::{PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper},
SerializableSimMsgPayload, SimReply, SimRequest,
};

View File

@ -11,6 +11,7 @@ mod tmtc;
use crate::eps::pcdu::{
PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper,
};
use crate::eps::PowerSwitchHelper;
use crate::events::EventHandler;
use crate::interface::udp::DynamicUdpTmHandler;
use crate::pus::stack::PusStack;
@ -50,7 +51,7 @@ use satrs::pus::event_man::EventRequestWithToken;
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
use satrs_example::config::components::{MGM_HANDLER_0, PCDU_HANDLER, TCP_SERVER, UDP_SERVER};
use std::net::{IpAddr, SocketAddr};
use std::sync::mpsc;
use std::sync::{mpsc, Mutex};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
@ -222,8 +223,9 @@ fn static_tmtc_pool_main() {
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel();
let shared_switch_set = Arc::default();
let shared_switch_set = Arc::new(Mutex::default());
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_set = Arc::default();
let mgm_mode_leaf_interface = MpscModeLeafInterface {
@ -247,6 +249,7 @@ fn static_tmtc_pool_main() {
mgm_mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sink_tx.clone(),
mgm_spi_interface,
shared_mgm_set,
@ -507,6 +510,10 @@ fn dyn_tmtc_pool_main() {
let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
let shared_switch_set = Arc::new(Mutex::default());
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel();
let shared_mgm_set = Arc::default();
@ -531,6 +538,7 @@ fn dyn_tmtc_pool_main() {
mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx,
switch_helper.clone(),
tm_funnel_tx,
mgm_spi_interface,
shared_mgm_set,

View File

@ -1,3 +1,5 @@
use core::time::Duration;
use derive_new::new;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -57,28 +59,28 @@ impl From<SwitchStateBinary> for SwitchState {
pub type SwitchId = u16;
/// Generic trait for a device capable of turning on and off switches.
pub trait PowerSwitcherCommandSender {
pub trait PowerSwitcherCommandSender<SwitchType: Into<u16>> {
type Error;
fn send_switch_on_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error>;
fn send_switch_off_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error>;
}
pub trait PowerSwitchInfo {
pub trait PowerSwitchInfo<SwitchType> {
type Error;
/// Retrieve the switch state
fn switch_state(&self, switch_id: SwitchId) -> Result<SwitchState, Self::Error>;
fn switch_state(&self, switch_id: SwitchType) -> Result<SwitchState, Self::Error>;
fn is_switch_on(&self, switch_id: SwitchId) -> Result<bool, Self::Error> {
fn is_switch_on(&self, switch_id: SwitchType) -> Result<bool, Self::Error> {
Ok(self.switch_state(switch_id)? == SwitchState::On)
}
@ -86,7 +88,7 @@ pub trait PowerSwitchInfo {
///
/// 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) -> u32;
fn switch_delay_ms(&self) -> Duration;
}
#[derive(new)]
@ -119,17 +121,17 @@ pub mod std_mod {
pub type MpscSwitchCmdSender = mpsc::Sender<GenericMessage<SwitchRequest>>;
pub type MpscSwitchCmdSenderBounded = mpsc::SyncSender<GenericMessage<SwitchRequest>>;
impl PowerSwitcherCommandSender for MpscSwitchCmdSender {
impl<SwitchType: Into<u16>> PowerSwitcherCommandSender<SwitchType> for MpscSwitchCmdSender {
type Error = GenericSendError;
fn send_switch_on_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error> {
self.send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::On),
SwitchRequest::new(switch_id.into(), SwitchStateBinary::On),
))
.map_err(|_| GenericSendError::RxDisconnected)
}
@ -137,27 +139,27 @@ pub mod std_mod {
fn send_switch_off_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error> {
self.send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off),
))
.map_err(|_| GenericSendError::RxDisconnected)
}
}
impl PowerSwitcherCommandSender for MpscSwitchCmdSenderBounded {
impl<SwitchType: Into<u16>> PowerSwitcherCommandSender<SwitchType> for MpscSwitchCmdSenderBounded {
type Error = GenericSendError;
fn send_switch_on_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error> {
self.try_send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::On),
SwitchRequest::new(switch_id.into(), SwitchStateBinary::On),
))
.map_err(|e| match e {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),
@ -168,11 +170,11 @@ pub mod std_mod {
fn send_switch_off_cmd(
&self,
requestor_info: MessageMetadata,
switch_id: SwitchId,
switch_id: SwitchType,
) -> Result<(), Self::Error> {
self.try_send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off),
))
.map_err(|e| match e {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),