probably need to re-work the mode model.. #261
@@ -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<Commands>,
|
||||
}
|
||||
|
||||
#[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<ModeSelect>,
|
||||
}
|
||||
|
||||
#[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::<models::TmHeader>(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::<models::mgm::response::Response>(remainder);
|
||||
log::info!("Received response from MGM0: {:?}", response.unwrap());
|
||||
}
|
||||
models::ComponentId::AcsMgm1 => {
|
||||
let response =
|
||||
postcard::from_bytes::<models::mgm::response::Response>(remainder);
|
||||
log::info!("Received response from MGM1: {:?}", response.unwrap());
|
||||
}
|
||||
models::ComponentId::EpsSubsystem => todo!(),
|
||||
models::ComponentId::UdpServer => todo!(),
|
||||
models::ComponentId::TcpServer => todo!(),
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// TODO: Write the assembly
|
||||
+312
-282
@@ -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<ModeAndSubmode>,
|
||||
requestor_info: Option<MessageMetadata>,
|
||||
current: DeviceMode,
|
||||
target: Option<DeviceMode>,
|
||||
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||
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<super::mgm_assembly::ModeRequest>,
|
||||
pub report_tx: mpsc::SyncSender<super::mgm_assembly::ModeReport>,
|
||||
}
|
||||
|
||||
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface> {
|
||||
id: ComponentId,
|
||||
dev_str: &'static str,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
tc_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
switch_helper: PowerSwitchHelper,
|
||||
pub com_interface: ComInterface,
|
||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||
//hk_helper: PusHkHelper,
|
||||
mode_helpers: ModeHelpers,
|
||||
bufs: BufWrapper,
|
||||
buffers: BufWrapper,
|
||||
stamp_helper: TimestampHelper,
|
||||
hk_helper: HkHelperSingleSet,
|
||||
mode_helpers: ModeHelpers,
|
||||
mode_leaf_helper: ModeLeafHelper,
|
||||
}
|
||||
|
||||
impl<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
@@ -155,39 +155,57 @@ impl<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
pub fn new(
|
||||
id: ComponentId,
|
||||
dev_str: &'static str,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
tc_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
switch_helper: PowerSwitchHelper,
|
||||
com_interface: ComInterface,
|
||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||
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<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
}
|
||||
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<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
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<CcsdsPacketIdAndPsc>,
|
||||
@@ -250,46 +291,28 @@ impl<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
) {
|
||||
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<CcsdsPacketIdAndPsc>) {
|
||||
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<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
// 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<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
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<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||
}
|
||||
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<ComInterface: SpiInterface> ModeProvider for MgmHandlerLis3Mdl<ComInterface> {
|
||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||
self.mode_helpers.current
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SpiInterface> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface> {
|
||||
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<MessageMetadata>, _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<MessageMetadata>,
|
||||
) -> 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<ComInterface: SpiInterface> ModeNode for MgmHandlerLis3Mdl<ComInterface> {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
self.id as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SpiInterface> ModeChild for MgmHandlerLis3Mdl<ComInterface> {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
||||
|
||||
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<GenericMessage<ModeRequest>>,
|
||||
pub mode_reply_rx_to_ground: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub assembly_mode_request_tx: mpsc::SyncSender<mgm_assembly::ModeRequest>,
|
||||
pub mode_report_rx: mpsc::Receiver<mgm_assembly::ModeReport>,
|
||||
pub shared_switch_set: SharedSwitchSet,
|
||||
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
pub switch_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||
pub handler: MgmHandlerLis3Mdl<TestSpiInterface>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MgmAssemblyMock(
|
||||
pub HashMap<satrs::ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
);
|
||||
|
||||
impl ModeNode for MgmAssemblyMock {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
ComponentId::AcsMgmAssembly as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeParent for MgmAssemblyMock {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||
|
||||
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<satrs::ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
}
|
||||
|
||||
impl ModeNode for GroundMock {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
ComponentId::Ground as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeParent for GroundMock {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||
|
||||
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::<models::mgm::response::Response>(&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::<models::mgm::response::Response>(&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::<models::mgm::response::Response>(&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::<models::mgm::response::Response>(&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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<super::mgm_assembly::ModeRequest>; 2],
|
||||
pub report_rx: [mpsc::Receiver<super::mgm_assembly::ModeReport>; 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")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod assembly;
|
||||
pub mod ctrl;
|
||||
|
||||
pub mod mgm;
|
||||
pub mod mgm_assembly;
|
||||
|
||||
pub mod subsystem;
|
||||
|
||||
+83
-162
@@ -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<ComInterface: SerialInterface> {
|
||||
dev_str: &'static str,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
pub com_interface: ComInterface,
|
||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
mode: DeviceMode,
|
||||
stamp_helper: TimestampHelper,
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
pub fn new(
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||
@@ -294,27 +283,27 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
) -> 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<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
#[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<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
SwitchStateBinary::Off,
|
||||
);
|
||||
}
|
||||
pcdu::request::Request::Mode(device_mode) => {
|
||||
self.switch_mode(tc_id, device_mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -408,6 +405,33 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
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<CcsdsPacketIdAndPsc>,
|
||||
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<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn handle_mode_requests(&mut self) {
|
||||
loop {
|
||||
// TODO: Only allow one set mode request per cycle?
|
||||
@@ -446,6 +471,7 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn handle_device_switching(&mut self, switch_id: SwitchId, state: SwitchStateBinary) {
|
||||
let pcdu_req = PcduRequest::SwitchDevice {
|
||||
@@ -500,111 +526,33 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> {
|
||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||
self.mode_and_submode
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterface> {
|
||||
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<MessageMetadata>, _recursive: bool) {
|
||||
log::info!(
|
||||
"{} announcing mode: {:?}",
|
||||
self.dev_str,
|
||||
self.mode_and_submode
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_mode_reached(
|
||||
&mut self,
|
||||
requestor: Option<MessageMetadata>,
|
||||
) -> 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<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
ComponentId::EpsPcdu as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
||||
|
||||
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<GenericMessage<ModeRequest>>,
|
||||
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Instant>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+36
-22
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<SwitchState> for SwitchStateBinary {
|
||||
type Error = ();
|
||||
fn try_from(value: SwitchState) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
SwitchState::Off => Ok(SwitchStateBinary::Off),
|
||||
SwitchState::On => Ok(SwitchStateBinary::On),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<u64>> From<T> for SwitchStateBinary {
|
||||
fn from(value: T) -> Self {
|
||||
if value.into() == 0 {
|
||||
return SwitchStateBinary::Off;
|
||||
}
|
||||
SwitchStateBinary::On
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SwitchStateBinary> 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<SwitchType: Into<u16>> {
|
||||
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<SwitchType> {
|
||||
type Error: core::fmt::Debug;
|
||||
|
||||
/// Retrieve the switch state
|
||||
fn switch_state(&self, switch_id: SwitchType) -> Result<SwitchState, Self::Error>;
|
||||
|
||||
fn is_switch_on(&self, switch_id: SwitchType) -> Result<bool, Self::Error> {
|
||||
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<GenericMessage<SwitchRequest>>;
|
||||
pub type MpscSwitchCmdSenderBounded = mpsc::SyncSender<GenericMessage<SwitchRequest>>;
|
||||
|
||||
impl<SwitchType: Into<u16>> PowerSwitcherCommandSender<SwitchType> 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<SwitchType: Into<u16>> PowerSwitcherCommandSender<SwitchType> 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<SwitchRequest>) {
|
||||
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::<GenericMessage<SwitchRequest>>();
|
||||
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::<GenericMessage<SwitchRequest>>();
|
||||
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::<GenericMessage<SwitchRequest>>();
|
||||
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::<GenericMessage<SwitchRequest>>(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::<GenericMessage<SwitchRequest>>(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::<GenericMessage<SwitchRequest>>(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::<GenericMessage<SwitchRequest>>(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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user