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
+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 {