mode request/responses to models

This commit is contained in:
Robin Mueller
2026-03-17 15:06:21 +01:00
parent b10e1b7806
commit 42a0ced3de
9 changed files with 247 additions and 563 deletions
+3 -1
View File
@@ -157,7 +157,9 @@ fn main() -> anyhow::Result<()> {
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),
models::mgm::request::Request::Mode(
models::mgm::request::ModeRequest::SetMode(dev_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
+17 -8
View File
@@ -1,5 +1,11 @@
pub mod request {
use crate::{HkRequestType, Message};
use crate::{DeviceMode, HkRequestType, Message};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(DeviceMode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum HkId {
@@ -16,7 +22,7 @@ pub mod request {
pub enum Request {
Ping,
Hk(HkRequest),
Mode(crate::DeviceMode),
Mode(ModeRequest),
}
impl Request {
@@ -44,23 +50,26 @@ pub struct MgmData {
}
pub mod response {
use crate::{Message, mgm::MgmData};
use crate::{DeviceMode, Message, mgm::MgmData};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum HkResponse {
MgmData(MgmData),
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum ModeFailure {
Timeout,
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
pub enum ModeResponse {
/// New mode has been set.
Mode(DeviceMode),
/// Setting a mode timed out.
SetModeTimeout,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Hk(HkResponse),
ModeFailure(ModeFailure),
Mode(ModeResponse),
}
impl Response {
@@ -68,7 +77,7 @@ pub mod response {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Hk(_hk_response) => crate::MessageType::Hk,
Response::ModeFailure(_mode_failure) => crate::MessageType::Mode,
Response::Mode(_mode_failure) => crate::MessageType::Mode,
}
}
}
+22 -4
View File
@@ -33,6 +33,12 @@ pub mod request {
Sensor,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeRequest {
SetMode(AssemblyMode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct HkRequest {
pub id: HkId,
@@ -42,7 +48,7 @@ pub mod request {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Mode(AssemblyMode),
Mode(ModeRequest),
}
impl Request {
@@ -61,24 +67,36 @@ pub mod request {
}
pub mod response {
use crate::Message;
use crate::{DeviceMode, Message};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeCommandFailure {
Timeout,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeReport {
/// Mode of the assembly.
Mode(super::AssemblyMode),
/// Timeout failure setting the children modes.
SetModeTimeout([Option<DeviceMode>; 2]),
/// Children are in wrong mode after commanding.
WrongMode([Option<DeviceMode>; 2]),
/// An assembly tried modekeeping but can not keep its mode.
CanNotKeepMode([Option<DeviceMode>; 2]),
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
ModeFailure(ModeCommandFailure),
Mode(ModeReport),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::ModeFailure(_mode_failure) => crate::MessageType::Mode,
Response::Mode(_mode_report) => crate::MessageType::Mode,
}
}
}
+22 -27
View File
@@ -1,5 +1,6 @@
use models::mgm::MgmData;
use models::mgm::response::ModeFailure;
use models::mgm::request::ModeRequest;
use models::mgm::response::ModeResponse;
use models::pcdu::SwitchId;
use models::{ComponentId, DeviceMode, HkRequestType, mgm};
use satrs::spacepackets::CcsdsPacketIdAndPsc;
@@ -56,20 +57,6 @@ pub enum TransitionState {
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(DeviceMode),
ReadMode,
}
#[derive(Debug, Clone, Copy)]
pub enum ModeReport {
/// New mode has been set.
Mode(DeviceMode),
/// Setting a mode timed out.
SetModeTimeout,
}
#[derive(Default)]
pub struct SpiDummyInterface {
pub dummy_values: MgmLis3RawValues,
@@ -157,7 +144,7 @@ pub struct BufWrapper {
/// Helper component for communication with a parent component, which is usually as assembly.
pub struct ModeLeafHelper {
pub request_rx: mpsc::Receiver<ModeRequest>,
pub report_tx: mpsc::SyncSender<ModeReport>,
pub report_tx: mpsc::SyncSender<ModeResponse>,
}
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
@@ -254,10 +241,18 @@ impl MgmHandlerLis3Mdl {
mgm::request::Request::Hk(hk_request) => {
self.handle_hk_request(Some(tc_id), &hk_request)
}
mgm::request::Request::Mode(device_mode) => {
self.mode_helpers.tc_commander = Some(tc_id);
self.start_transition(device_mode, false);
}
mgm::request::Request::Mode(device_mode) => match device_mode {
ModeRequest::SetMode(device_mode) => {
self.mode_helpers.tc_commander = Some(tc_id);
self.start_transition(device_mode, false);
}
ModeRequest::ReadMode => self.send_telemetry(
Some(tc_id),
mgm::response::Response::Mode(ModeResponse::Mode(
self.mode(),
)),
),
},
}
}
Err(e) => {
@@ -414,12 +409,12 @@ impl MgmHandlerLis3Mdl {
if tc_commander.is_some() {
self.send_telemetry(
tc_commander,
mgm::response::Response::ModeFailure(ModeFailure::Timeout),
mgm::response::Response::Mode(ModeResponse::SetModeTimeout),
);
}
self.mode_leaf_helper
.report_tx
.send(ModeReport::SetModeTimeout)
.send(ModeResponse::SetModeTimeout)
.unwrap();
}
@@ -446,7 +441,7 @@ impl MgmHandlerLis3Mdl {
fn report_mode_to_parent(&self) {
self.mode_leaf_helper
.report_tx
.send(ModeReport::Mode(self.mode_helpers.current))
.send(ModeResponse::Mode(self.mode_helpers.current))
.unwrap();
}
@@ -505,7 +500,7 @@ mod tests {
#[allow(dead_code)]
pub struct MgmTestbench {
pub assembly_mode_request_tx: mpsc::SyncSender<ModeRequest>,
pub mode_report_rx: mpsc::Receiver<ModeReport>,
pub mode_report_rx: mpsc::Receiver<ModeResponse>,
pub shared_switch_set: SharedSwitchSet,
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
@@ -576,7 +571,7 @@ mod tests {
.tc_tx
.send(create_request_tc(
MgmSelect::_0,
mgm::request::Request::Mode(DeviceMode::Normal),
mgm::request::Request::Mode(ModeRequest::SetMode(DeviceMode::Normal)),
))
.unwrap();
testbench.handler.periodic_operation();
@@ -630,7 +625,7 @@ mod tests {
.tc_tx
.send(create_request_tc(
MgmSelect::_0,
mgm::request::Request::Mode(DeviceMode::Normal),
mgm::request::Request::Mode(ModeRequest::SetMode(DeviceMode::Normal)),
))
.unwrap();
testbench.handler.periodic_operation();
@@ -714,7 +709,7 @@ mod tests {
.tc_tx
.send(create_request_tc(
MgmSelect::_0,
mgm::request::Request::Mode(DeviceMode::Normal),
mgm::request::Request::Mode(ModeRequest::SetMode(DeviceMode::Normal)),
))
.unwrap();
// This simulates one cycle for the power switch to update.
+180 -70
View File
@@ -1,43 +1,23 @@
#![allow(dead_code)]
use std::{sync::mpsc, time::Duration};
use models::{
ComponentId, DeviceMode,
mgm_assembly::{
AssemblyMode,
response::{self, ModeCommandFailure},
},
mgm_assembly::{AssemblyMode, request, response},
};
use satrs::spacepackets::CcsdsPacketIdAndPsc;
use satrs_example::{ModeHelper, TmtcQueues};
use crate::ccsds::pack_ccsds_tm_packet_for_now;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(AssemblyMode),
ReadMode,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ModeReport {
/// Mode of the assembly.
Mode(AssemblyMode),
/// Failure setting the children mode.
SetModeTimeout([Option<DeviceMode>; 2]),
/// An assembly tried modekeeping but can not keep its mode.
CanNotKeepMode([Option<DeviceMode>; 2]),
}
pub struct ParentQueueHelper {
pub request_rx: mpsc::Receiver<ModeRequest>,
pub report_tx: mpsc::SyncSender<ModeReport>,
pub request_rx: mpsc::Receiver<request::ModeRequest>,
pub report_tx: mpsc::SyncSender<response::ModeReport>,
}
/// Helper component for communication with a parent component, which is usually as assembly.
pub struct ChildrenQueueHelper {
pub request_tx_queues: [mpsc::SyncSender<super::mgm::ModeRequest>; 2],
pub report_rx_queues: [mpsc::Receiver<super::mgm::ModeReport>; 2],
pub request_tx_queues: [mpsc::SyncSender<models::mgm::request::ModeRequest>; 2],
pub report_rx_queues: [mpsc::Receiver<models::mgm::response::ModeResponse>; 2],
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -107,8 +87,18 @@ impl Assembly {
models::mgm_assembly::request::Request::Ping => {
self.send_telemetry(Some(tc_id), response::Response::Ok)
}
models::mgm_assembly::request::Request::Mode(assembly_mode) => {
self.start_transition(false, assembly_mode, Some(tc_id))
models::mgm_assembly::request::Request::Mode(request) => {
match request {
request::ModeRequest::SetMode(assembly_mode) => {
self.start_transition(false, assembly_mode, Some(tc_id))
}
request::ModeRequest::ReadMode => self.send_telemetry(
Some(tc_id),
response::Response::Mode(response::ModeReport::Mode(
self.mode(),
)),
),
}
}
},
Err(e) => {
@@ -145,18 +135,18 @@ impl Assembly {
loop {
match self.parent_queues.request_rx.try_recv() {
Ok(request) => match request {
ModeRequest::SetMode(assembly_mode) => match assembly_mode {
request::ModeRequest::SetMode(assembly_mode) => match assembly_mode {
AssemblyMode::Device(_device_mode) => {
self.start_transition(false, assembly_mode, None);
}
AssemblyMode::NoModeKeeping => {
self.mode_helper.current = AssemblyMode::NoModeKeeping
self.mode_helper.current = AssemblyMode::NoModeKeeping;
}
},
ModeRequest::ReadMode => self
request::ModeRequest::ReadMode => self
.parent_queues
.report_tx
.send(ModeReport::Mode(self.mode_helper.current))
.send(response::ModeReport::Mode(self.mode_helper.current))
.unwrap(),
},
Err(e) => match e {
@@ -175,12 +165,12 @@ impl Assembly {
loop {
match rx.try_recv() {
Ok(report) => match report {
super::mgm::ModeReport::Mode(device_mode) => {
models::mgm::response::ModeResponse::Mode(device_mode) => {
self.mgm_modes[idx].mode = Some(device_mode);
self.mgm_modes[idx].reply_received = true;
mode_report_received = true;
}
super::mgm::ModeReport::SetModeTimeout => {
models::mgm::response::ModeResponse::SetModeTimeout => {
// Ignore, handle this with our own timeout.
log::warn!("MGM {} mode timeout", idx);
}
@@ -199,13 +189,21 @@ impl Assembly {
}
// Transition is active, check for completion.
// If at least one child reached the correct mode, we are done.
if self.mode_helper.transition_active()
&& let AssemblyMode::Device(device_mode) = self.mode_helper.target.unwrap()
&& self.mgm_modes.iter().all(|i| i.reply_received)
&& self.mgm_modes.iter().any(|i| i.mode == Some(device_mode))
&& let AssemblyMode::Device(device_mode) = self.mode_helper.target.unwrap()
{
self.handle_mode_reached();
// If at least one child reached the correct mode, we are done.
if self.mgm_modes.iter().any(|i| i.mode == Some(device_mode)) {
self.handle_mode_reached(true);
} else {
let report = if self.mode_keeping_transition {
response::ModeReport::CanNotKeepMode(self.mgm_modes.map(|info| info.mode))
} else {
response::ModeReport::WrongMode(self.mgm_modes.map(|info| info.mode))
};
self.handle_mode_transition_failure(report);
}
}
// Mode keeping active: Check children modes.
@@ -223,14 +221,14 @@ impl Assembly {
pub fn handle_mode_transition(&mut self) {
if self.mode_helper.target.is_none() {
self.handle_mode_reached();
self.handle_mode_reached(true);
return;
}
let target = self.mode_helper.target.unwrap();
let device_mode = match target {
AssemblyMode::Device(device_mode) => device_mode,
AssemblyMode::NoModeKeeping => {
self.handle_mode_reached();
self.handle_mode_reached(true);
return;
}
};
@@ -241,32 +239,34 @@ impl Assembly {
if self.mode_helper.transition_state == TransitionState::AwaitingReplies
&& self.mode_helper.timed_out()
{
self.handle_mode_transition_failure();
let report = if self.mode_keeping_transition {
response::ModeReport::CanNotKeepMode(self.mgm_modes.map(|info| info.mode))
} else {
response::ModeReport::SetModeTimeout(self.mgm_modes.map(|info| info.mode))
};
self.handle_mode_transition_failure(report);
}
}
pub fn handle_mode_reached(&mut self) {
let tc_commander = self.mode_helper.finish(true);
pub fn handle_mode_reached(&mut self, success: bool) {
let tc_commander = self.mode_helper.finish(success);
self.announce_mode();
if tc_commander.is_some() {
self.send_telemetry(tc_commander, response::Response::Ok);
}
self.parent_queues
.report_tx
.send(ModeReport::Mode(self.mode_helper.current))
.send(response::ModeReport::Mode(self.mode_helper.current))
.unwrap();
}
pub fn handle_mode_transition_failure(&mut self) {
let report = if self.mode_keeping_transition {
ModeReport::CanNotKeepMode(self.mgm_modes.map(|info| info.mode))
} else {
ModeReport::SetModeTimeout(self.mgm_modes.map(|info| info.mode))
};
pub fn handle_mode_transition_failure(&mut self, report: response::ModeReport) {
if self.mode_helper.tc_commander.is_some() {
self.send_telemetry(
self.mode_helper.tc_commander,
response::Response::ModeFailure(ModeCommandFailure::Timeout),
response::Response::Mode(response::ModeReport::SetModeTimeout(
self.mgm_modes.map(|info| info.mode),
)),
);
}
self.parent_queues.report_tx.send(report).unwrap();
@@ -275,7 +275,8 @@ impl Assembly {
pub fn command_children(&self, mode: DeviceMode) {
for tx in &self.children_queues.request_tx_queues {
tx.send(super::mgm::ModeRequest::SetMode(mode)).unwrap();
tx.send(models::mgm::request::ModeRequest::SetMode(mode))
.unwrap();
}
}
@@ -308,7 +309,8 @@ impl Assembly {
}
#[inline]
pub fn mode_transition_active(&self) -> bool {
#[cfg(test)]
fn mode_transition_active(&self) -> bool {
self.mode_helper.transition_active()
}
}
@@ -328,10 +330,10 @@ mod tests {
use super::*;
pub struct Testbench {
subsystem_req_tx: mpsc::SyncSender<ModeRequest>,
subsystem_report_rx: mpsc::Receiver<ModeReport>,
mgm_request_rx: [mpsc::Receiver<crate::mgm::ModeRequest>; 2],
mgm_report_tx: [mpsc::SyncSender<crate::mgm::ModeReport>; 2],
subsystem_req_tx: mpsc::SyncSender<request::ModeRequest>,
subsystem_report_rx: mpsc::Receiver<response::ModeReport>,
mgm_request_rx: [mpsc::Receiver<models::mgm::request::ModeRequest>; 2],
mgm_report_tx: [mpsc::SyncSender<models::mgm::response::ModeResponse>; 2],
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
assembly: Assembly,
@@ -417,7 +419,7 @@ mod tests {
let mut tb = Testbench::new();
tb.tc_tx
.send(create_request_tc(mgm_assembly::request::Request::Mode(
AssemblyMode::Device(DeviceMode::Normal),
request::ModeRequest::SetMode(AssemblyMode::Device(DeviceMode::Normal)),
)))
.unwrap();
tb.assembly.periodic_operation();
@@ -427,14 +429,16 @@ mod tests {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
crate::mgm::ModeRequest::SetMode(DeviceMode::Normal)
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(crate::mgm::ModeReport::Mode(DeviceMode::Normal))
.unwrap();
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
@@ -452,7 +456,7 @@ mod tests {
fn test_parent_commanded_transition() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(ModeRequest::SetMode(AssemblyMode::Device(
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
@@ -463,14 +467,16 @@ mod tests {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
crate::mgm::ModeRequest::SetMode(DeviceMode::Normal)
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(crate::mgm::ModeReport::Mode(DeviceMode::Normal))
.unwrap();
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
@@ -480,7 +486,7 @@ mod tests {
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
}
@@ -488,7 +494,7 @@ mod tests {
fn test_one_mgm_is_sufficient() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(ModeRequest::SetMode(AssemblyMode::Device(
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
@@ -499,16 +505,18 @@ mod tests {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
crate::mgm::ModeRequest::SetMode(DeviceMode::Normal)
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// One device is sufficient.
tb.mgm_report_tx[0]
.send(crate::mgm::ModeReport::Mode(DeviceMode::Normal))
.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
tb.mgm_report_tx[1]
.send(crate::mgm::ModeReport::Mode(DeviceMode::Off))
.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
tb.assembly.periodic_operation();
@@ -518,7 +526,109 @@ mod tests {
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
}
#[test]
fn test_mode_commanding_fails() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::NoModeKeeping);
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::WrongMode([Some(DeviceMode::Off), Some(DeviceMode::Off)])
);
}
#[test]
fn test_mode_keeping_fails() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
// This should start mode keeping.
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Let the mode keeping fail.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
tb.assembly.periodic_operation();
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::CanNotKeepMode([Some(DeviceMode::Off), Some(DeviceMode::Off)])
);
}
}
+1 -3
View File
@@ -144,9 +144,7 @@ impl<Mode: Copy + Clone, TransitionState: Default> ModeHelper<Mode, TransitionSt
}
pub fn finish(&mut self, success: bool) -> Option<CcsdsPacketIdAndPsc> {
if self.target.is_none() {
return None;
}
self.target?;
if success {
self.current = self.target.take().unwrap();
} else {
-448
View File
@@ -1,448 +0,0 @@
use crate::{
ComponentId,
mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender},
mode_tree::{ModeStoreProvider, ModeStoreVec},
queue::{GenericSendError, GenericTargetedMessagingError},
request::{GenericMessage, RequestId},
};
use core::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ActiveModeCommandContext {
pub target_mode: ModeAndSubmode,
pub active_request_id: RequestId,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub enum DevManagerHelperResult {
#[default]
Idle,
Busy,
ModeCommandingDone(ActiveModeCommandContext),
}
#[derive(Debug)]
pub enum DevManagerHelperError {
ChildNotInStore,
}
pub trait DevManagerUserHook: Debug {
fn send_mode_cmd_to_child(
&self,
request_id: RequestId,
target_id: ComponentId,
mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError>;
fn send_mode_cmds_to_children(
&self,
request_id: RequestId,
commanded_parent_mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError>;
}
#[derive(Debug, Default)]
pub struct TransparentDevManagerHook {}
impl DevManagerUserHook for TransparentDevManagerHook {
fn send_mode_cmds_to_children(
&self,
request_id: RequestId,
commanded_parent_mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
for child in children_mode_store {
mode_req_sender.send_mode_request(
request_id,
child.id(),
ModeRequest::SetMode {
mode_and_submode: commanded_parent_mode,
forced,
},
)?;
child.awaiting_reply = true;
}
Ok(())
}
fn send_mode_cmd_to_child(
&self,
request_id: RequestId,
target_id: ComponentId,
mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
let mut_val = children_mode_store
.get_mut(target_id)
.ok_or(GenericSendError::TargetDoesNotExist(target_id))?;
mut_val.awaiting_reply = true;
mode_req_sender.send_mode_request(
request_id,
target_id,
ModeRequest::SetMode {
mode_and_submode: mode,
forced,
},
)?;
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DevManagerCommandingState {
#[default]
Idle,
AwaitingReplies(ActiveModeCommandContext),
}
impl DevManagerCommandingState {
fn new_active_cmd(mode_and_submode: ModeAndSubmode, active_request_id: RequestId) -> Self {
DevManagerCommandingState::AwaitingReplies(ActiveModeCommandContext {
target_mode: mode_and_submode,
active_request_id,
})
}
}
/// A generic helper for manager components which manage child components in a mode tree.
///
/// Mode commands are usually forwarded to all children components transparently.
/// For example, this could be used in an Assembly component which manages multiple redundant
/// child components. It can also be used inside a manager component which only manages one device.
#[derive(Debug, Default)]
pub struct DevManagerCommandingHelper<UserHook: DevManagerUserHook> {
/// The IDs, modes and reply awaition status of all children are tracked in this data
/// structure.
pub children_mode_store: ModeStoreVec,
pub user_hook: UserHook,
pub state: DevManagerCommandingState,
}
impl<UserHook: DevManagerUserHook> DevManagerCommandingHelper<UserHook> {
pub fn new(user_hook: UserHook) -> Self {
Self {
children_mode_store: Default::default(),
user_hook,
state: Default::default(),
}
}
pub fn send_mode_cmd_to_one_child(
&mut self,
request_id: RequestId,
target_id: ComponentId,
mode_and_submode: ModeAndSubmode,
forced: bool,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
self.user_hook.send_mode_cmd_to_child(
request_id,
target_id,
mode_and_submode,
forced,
&mut self.children_mode_store,
mode_req_sender,
)?;
Ok(())
}
pub fn send_mode_cmd_to_all_children(
&mut self,
request_id: RequestId,
mode_and_submode: ModeAndSubmode,
forced: bool,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
self.user_hook.send_mode_cmds_to_children(
request_id,
mode_and_submode,
forced,
&mut self.children_mode_store,
mode_req_sender,
)?;
Ok(())
}
pub fn target_mode(&self) -> Option<ModeAndSubmode> {
match self.state {
DevManagerCommandingState::Idle => None,
DevManagerCommandingState::AwaitingReplies(context) => Some(context.target_mode),
}
}
pub fn state(&self) -> DevManagerCommandingState {
self.state
}
pub fn send_announce_mode_cmd_to_children(
&self,
request_id: RequestId,
mode_req_sender: &impl ModeRequestSender,
recursive: bool,
) -> Result<(), GenericTargetedMessagingError> {
let mut request = ModeRequest::AnnounceMode;
if recursive {
request = ModeRequest::AnnounceModeRecursive;
}
for child in self.children_mode_store.0.iter() {
mode_req_sender.send_mode_request(request_id, child.id(), request)?;
}
Ok(())
}
pub fn add_mode_child(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
self.children_mode_store.add_component(target_id, mode);
}
/// Helper method which counts the number of children which have a certain mode.
pub fn count_number_of_children_with_mode(&self, mode_and_submode: ModeAndSubmode) -> usize {
let mut children_in_target_mode = 0;
for child in &self.children_mode_store {
if child.mode_and_submode() == mode_and_submode {
children_in_target_mode += 1;
}
}
children_in_target_mode
}
pub fn handle_mode_reply(
&mut self,
mode_reply: &GenericMessage<ModeReply>,
) -> Result<DevManagerHelperResult, DevManagerHelperError> {
let context = match self.state {
DevManagerCommandingState::Idle => return Ok(DevManagerHelperResult::Idle),
DevManagerCommandingState::AwaitingReplies(active_mode_command_context) => {
Some(active_mode_command_context)
}
};
if !self
.children_mode_store
.has_component(mode_reply.sender_id())
{
return Err(DevManagerHelperError::ChildNotInStore);
}
let mut generic_mode_reply_handler = |mode_and_submode: Option<ModeAndSubmode>| {
// Tying the reply awaition to the request ID ensures that something like replies
// belonging to older requests do not interfere with the completion handling of
// the mode commanding. This is important for forced mode commands.
let mut handle_awaition = false;
if let DevManagerCommandingState::AwaitingReplies { .. } = self.state {
handle_awaition = true;
}
let still_awating_replies = self.children_mode_store.mode_reply_handler(
mode_reply.sender_id(),
mode_and_submode,
handle_awaition,
);
// It is okay to unwrap: If awaition should be handled, the returned value should
// always be some valid value.
if handle_awaition && !still_awating_replies.unwrap() {
self.state = DevManagerCommandingState::Idle;
return Ok(DevManagerHelperResult::ModeCommandingDone(context.unwrap()));
}
Ok(DevManagerHelperResult::Busy)
};
match mode_reply.message {
ModeReply::ModeInfo(mode_and_submode) | ModeReply::ModeReply(mode_and_submode) => {
generic_mode_reply_handler(Some(mode_and_submode))
}
ModeReply::CantReachMode(_result_u16) => generic_mode_reply_handler(None),
ModeReply::WrongMode {
expected: _,
reached,
} => generic_mode_reply_handler(Some(reached)),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
mode::{UNKNOWN_MODE, tests::ModeReqSenderMock},
request::MessageMetadata,
};
use super::*;
pub enum ExampleId {
Id1 = 1,
Id2 = 2,
}
pub enum ExampleMode {
Mode1 = 1,
Mode2 = 2,
}
#[test]
fn test_basic() {
let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
assert_eq!(assy_helper.state(), DevManagerCommandingState::Idle);
}
#[test]
fn test_mode_announce() {
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
assy_helper.add_mode_child(ExampleId::Id1 as ComponentId, UNKNOWN_MODE);
assy_helper.add_mode_child(ExampleId::Id2 as ComponentId, UNKNOWN_MODE);
assy_helper
.send_announce_mode_cmd_to_children(1, &mode_req_sender, false)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceMode);
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceMode);
}
#[test]
fn test_mode_announce_recursive() {
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
assy_helper.add_mode_child(ExampleId::Id1 as ComponentId, UNKNOWN_MODE);
assy_helper.add_mode_child(ExampleId::Id2 as ComponentId, UNKNOWN_MODE);
assy_helper
.send_announce_mode_cmd_to_children(1, &mode_req_sender, true)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
}
#[test]
fn test_mode_commanding_one_child() {
let mut dev_mgmt_helper =
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as ComponentId, UNKNOWN_MODE);
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0);
dev_mgmt_helper
.send_mode_cmd_to_one_child(
1,
ExampleId::Id1 as ComponentId,
expected_mode,
false,
&mode_req_sender,
)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 1);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
matches!(
dev_mgmt_helper.state(),
DevManagerCommandingState::AwaitingReplies { .. }
);
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
assert_eq!(ctx.target_mode, expected_mode);
assert_eq!(ctx.active_request_id, 1);
}
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id1 as ComponentId),
ModeReply::ModeReply(expected_mode),
);
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
target_mode,
active_request_id,
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
{
assert_eq!(target_mode, expected_mode);
assert_eq!(active_request_id, 1);
}
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
}
#[test]
fn test_mode_commanding_multi_child() {
let mut dev_mgmt_helper =
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as ComponentId, UNKNOWN_MODE);
dev_mgmt_helper.add_mode_child(ExampleId::Id2 as ComponentId, UNKNOWN_MODE);
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0);
dev_mgmt_helper
.send_mode_cmd_to_all_children(1, expected_mode, false, &mode_req_sender)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as ComponentId);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
matches!(
dev_mgmt_helper.state(),
DevManagerCommandingState::AwaitingReplies { .. }
);
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
assert_eq!(ctx.target_mode, expected_mode);
assert_eq!(ctx.active_request_id, 1);
}
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id1 as ComponentId),
ModeReply::ModeReply(expected_mode),
);
assert_eq!(
dev_mgmt_helper.handle_mode_reply(&reply).unwrap(),
DevManagerHelperResult::Busy
);
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id2 as ComponentId),
ModeReply::ModeReply(expected_mode),
);
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
target_mode,
active_request_id,
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
{
assert_eq!(target_mode, expected_mode);
assert_eq!(active_request_id, 1);
}
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
}
}
-2
View File
@@ -22,8 +22,6 @@ extern crate std;
pub mod action;
pub mod ccsds;
#[cfg(feature = "alloc")]
pub mod dev_mgmt;
pub mod encoding;
#[cfg(feature = "std")]
pub mod executable;
+2
View File
@@ -1,3 +1,4 @@
/*
use core::cell::Cell;
use num_enum::TryFromPrimitive;
use satrs::dev_mgmt::{
@@ -1608,3 +1609,4 @@ fn command_safe_mode() {
expected_req_id_not_ctrl,
);
}
*/