add granular event management control

This commit is contained in:
Robin Mueller
2026-09-23 15:31:35 +02:00
parent 883998e31d
commit 919f1aa65d
11 changed files with 500 additions and 26 deletions
+5 -1
View File
@@ -46,7 +46,11 @@ impl Controller {
control::request::Request::Ping => self
.send_telemetry(Some(tc_id), control::response::Response::Ok),
control::request::Request::TestEvent => {
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap()
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap();
self.send_telemetry(
Some(tc_id),
control::response::Response::Ok,
);
}
}
}
+282 -9
View File
@@ -1,15 +1,19 @@
use std::collections::HashSet;
use satrs::spacepackets::CcsdsPacketIdAndPsc;
use types::{
ComponentId, Event, Message,
ComponentId, Event, EventId, Message,
acs::{mgm, mgm_assembly},
ccsds::CcsdsTmPacketOwned,
control, pcdu, tmtc,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
control,
event_manager::{request::Request, response::Response},
pcdu, tmtc,
};
use crate::ccsds::pack_ccsds_tm_packet_for_now;
// TODO: We should add the capability to enable/disable the TM generation of individual events and
// event groups as well.
pub struct EventManager {
pub tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
pub ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
/// Shared by all MGM instances, which is why the sender ID is part of the message.
pub mgm_rx: std::sync::mpsc::Receiver<(ComponentId, mgm::Event)>,
@@ -18,10 +22,62 @@ pub struct EventManager {
/// Shared by all TC sources, which is why the sender ID is part of the message.
pub tc_source_rx: std::sync::mpsc::Receiver<(ComponentId, tmtc::Event)>,
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
/// Senders with all their event TM generation disabled.
disabled_components: HashSet<ComponentId>,
/// Individual (sender, event ID) pairs with their TM generation disabled.
disabled_events: HashSet<(ComponentId, u16)>,
}
impl EventManager {
pub fn new(
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
mgm_rx: std::sync::mpsc::Receiver<(ComponentId, mgm::Event)>,
mgm_assembly_rx: std::sync::mpsc::Receiver<mgm_assembly::Event>,
pcdu_rx: std::sync::mpsc::Receiver<pcdu::Event>,
tc_source_rx: std::sync::mpsc::Receiver<(ComponentId, tmtc::Event)>,
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
) -> Self {
Self {
tc_rx,
ctrl_rx,
mgm_rx,
mgm_assembly_rx,
pcdu_rx,
tc_source_rx,
tm_tx,
disabled_components: HashSet::new(),
disabled_events: HashSet::new(),
}
}
/// Silences all event TM generation for `sender_id`, regardless of the specific event.
fn disable_component(&mut self, sender_id: ComponentId) {
self.disabled_components.insert(sender_id);
}
fn enable_component(&mut self, sender_id: ComponentId) {
self.disabled_components.remove(&sender_id);
}
/// Silences TM generation for one specific event of `sender_id`, leaving its other events
/// unaffected.
fn disable_event(&mut self, sender_id: ComponentId, event_id: u16) {
self.disabled_events.insert((sender_id, event_id));
}
fn enable_event(&mut self, sender_id: ComponentId, event_id: u16) {
self.disabled_events.remove(&(sender_id, event_id));
}
fn tm_generation_enabled(&self, sender_id: ComponentId, event_id: u16) -> bool {
!self.disabled_components.contains(&sender_id)
&& !self.disabled_events.contains(&(sender_id, event_id))
}
pub fn periodic_operation(&mut self) {
// Telecommands first, so filter changes already apply to the events of this cycle.
self.handle_telecommands();
while let Ok(event) = self.ctrl_rx.try_recv() {
self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event));
}
@@ -39,20 +95,237 @@ impl EventManager {
}
}
fn handle_telecommands(&mut self) {
while let Ok(packet) = self.tc_rx.try_recv() {
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
let request = match postcard::from_bytes::<Request>(&packet.payload) {
Ok(request) => request,
Err(e) => {
log::warn!("failed to deserialize event manager request: {}", e);
continue;
}
};
log::info!(
"received request {:?} with TC ID {:#010x}",
request,
tc_id.raw()
);
match request {
Request::EnableComponent(sender_id) => self.enable_component(sender_id),
Request::DisableComponent(sender_id) => self.disable_component(sender_id),
Request::EnableEvent {
sender_id,
event_id,
} => self.enable_event(sender_id, event_id),
Request::DisableEvent {
sender_id,
event_id,
} => self.disable_event(sender_id, event_id),
}
self.send_tm(ComponentId::EventManager, Some(tc_id), &Response::Ok);
}
}
pub fn event_to_tm(
&mut self,
sender_id: ComponentId,
event: &(impl serde::Serialize + Message),
event: &(impl serde::Serialize + Message + EventId + core::fmt::Debug),
) {
match pack_ccsds_tm_packet_for_now(sender_id, None, event) {
let tm_enabled = self.tm_generation_enabled(sender_id, event.event_id());
log::info!(
"event {:?} (ID {}) from {:?}{}",
event,
event.event_id(),
sender_id,
if tm_enabled { "" } else { ", TM disabled" }
);
if !tm_enabled {
return;
}
self.send_tm(sender_id, None, event);
}
fn send_tm(
&self,
sender_id: ComponentId,
tc_id: Option<CcsdsPacketIdAndPsc>,
payload: &(impl serde::Serialize + Message),
) {
match pack_ccsds_tm_packet_for_now(sender_id, tc_id, payload) {
Ok(packet) => {
if let Err(e) = self.tm_tx.send(packet) {
log::warn!("error sending event TM packet: {:?}", e);
log::warn!("error sending TM packet: {:?}", e);
}
}
Err(e) => {
log::warn!("error packing event TM packet: {:?}", e);
log::warn!("error packing TM packet: {:?}", e);
}
}
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc::{self, TryRecvError};
use arbitrary_int::u11;
use satrs::spacepackets::SpacePacketHeader;
use types::{Apid, MessageType, TcHeader, acs::mgm};
use super::*;
struct Testbench {
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
mgm_event_tx: mpsc::SyncSender<(ComponentId, mgm::Event)>,
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
event_manager: EventManager,
}
impl Testbench {
fn new() -> Self {
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
let (_ctrl_tx, ctrl_rx) = mpsc::sync_channel(5);
let (mgm_event_tx, mgm_rx) = mpsc::sync_channel(5);
let (_mgm_assembly_tx, mgm_assembly_rx) = mpsc::sync_channel(5);
let (_pcdu_tx, pcdu_rx) = mpsc::sync_channel(5);
let (_tc_source_tx, tc_source_rx) = mpsc::sync_channel(5);
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
Self {
tc_tx,
mgm_event_tx,
tm_rx,
event_manager: EventManager::new(
tc_rx,
ctrl_rx,
mgm_rx,
mgm_assembly_rx,
pcdu_rx,
tc_source_rx,
tm_tx,
),
}
}
fn send_request(&self, request: Request) {
self.tc_tx
.send(CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(ComponentId::EventManager, MessageType::Event),
request,
))
.unwrap();
}
}
#[test]
fn test_events_enabled_by_default() {
let mut tb = Testbench::new();
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disabled_component_silences_all_its_events() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm0);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.mgm_event_tx
.send((
ComponentId::AcsMgm0,
mgm::Event::ModeChanged(types::DeviceMode::Normal),
))
.unwrap();
tb.event_manager.periodic_operation();
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_disabled_component_does_not_affect_other_components() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm1);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disabled_event_only_silences_that_specific_event() {
let mut tb = Testbench::new();
tb.event_manager.disable_event(
ComponentId::AcsMgm0,
mgm::Event::SpiFaultThresholdExceeded.event_id(),
);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.mgm_event_tx
.send((
ComponentId::AcsMgm0,
mgm::Event::ModeChanged(types::DeviceMode::Normal),
))
.unwrap();
tb.event_manager.periodic_operation();
let tm = tb.tm_rx.try_recv().expect("expected event TM");
let event: mgm::Event = postcard::from_bytes(&tm.payload).unwrap();
assert!(matches!(event, mgm::Event::ModeChanged(_)));
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_re_enabling_a_component_restores_its_events() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm0);
tb.event_manager.enable_component(ComponentId::AcsMgm0);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disable_component_request() {
let mut tb = Testbench::new();
tb.send_request(Request::DisableComponent(ComponentId::AcsMgm0));
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
let tm = tb.tm_rx.try_recv().expect("expected request response TM");
assert_eq!(tm.tm_header.sender_id, ComponentId::EventManager);
assert!(tm.tm_header.tc_id.is_some());
let response: Response = postcard::from_bytes(&tm.payload).unwrap();
assert_eq!(response, Response::Ok);
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_disable_event_request() {
let mut tb = Testbench::new();
tb.send_request(Request::DisableEvent {
sender_id: ComponentId::AcsMgm0,
event_id: mgm::Event::SpiFaultThresholdExceeded.event_id(),
});
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected request response TM");
tb.send_request(Request::EnableEvent {
sender_id: ComponentId::AcsMgm0,
event_id: mgm::Event::SpiFaultThresholdExceeded.event_id(),
});
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected request response TM");
tb.tm_rx.try_recv().expect("expected event TM");
}
}
+11 -8
View File
@@ -81,6 +81,7 @@ fn main() {
let (acs_subsystem_tc_tx, acs_subsystem_tc_rx) = mpsc::sync_channel(10);
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 (event_manager_tc_tx, event_manager_tc_rx) = mpsc::sync_channel(10);
let (mgt_request_tx, mgt_request_rx) = mpsc::sync_channel(5);
let (mgt_report_tx, mgt_report_rx) = mpsc::sync_channel(5);
@@ -105,14 +106,15 @@ fn main() {
let (mgm_assembly_event_tx, mgm_assembly_event_rx) = mpsc::sync_channel(10);
let (pcdu_event_tx, pcdu_event_rx) = mpsc::sync_channel(10);
let (tc_source_event_tx, tc_source_event_rx) = mpsc::sync_channel(10);
let mut event_manager = EventManager {
ctrl_rx: event_ctrl_rx,
mgm_rx: mgm_event_rx,
mgm_assembly_rx: mgm_assembly_event_rx,
pcdu_rx: pcdu_event_rx,
tc_source_rx: tc_source_event_rx,
tm_tx: tm_sink_tx.clone(),
};
let mut event_manager = EventManager::new(
event_manager_tc_rx,
event_ctrl_rx,
mgm_event_rx,
mgm_assembly_event_rx,
pcdu_event_rx,
tc_source_event_rx,
tm_sink_tx.clone(),
);
let mut controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx);
@@ -124,6 +126,7 @@ fn main() {
tc_source.add_target(ComponentId::AcsMgm1, mgm_1_handler_tc_tx);
tc_source.add_target(ComponentId::AcsMgmAssembly, mgm_assembly_tc_tx);
tc_source.add_target(ComponentId::AcsSubsystem, acs_subsystem_tc_tx);
tc_source.add_target(ComponentId::EventManager, event_manager_tc_tx);
let tc_sender = TmTcSender::Normal(tc_source_tx.clone());
let udp_tm_handler = UdpTmHandlerWithChannel {