Merge pull request 'more events' (#281) from more-events into main
Reviewed-on: #281
This commit was merged in pull request #281.
This commit is contained in:
@@ -170,6 +170,7 @@ pub struct MgmHandlerLis3Mdl {
|
||||
mode_leaf_helper: ModeLeafHelper,
|
||||
spi_fault_counter: FaultCounterStd,
|
||||
health_table: HealthTableMapSync,
|
||||
event_tx: mpsc::SyncSender<(ComponentId, mgm::Event)>,
|
||||
}
|
||||
|
||||
impl MgmHandlerLis3Mdl {
|
||||
@@ -183,6 +184,7 @@ impl MgmHandlerLis3Mdl {
|
||||
mode_leaf_helper: ModeLeafHelper,
|
||||
mode_timeout: Duration,
|
||||
health_table: HealthTableMapSync,
|
||||
event_tx: mpsc::SyncSender<(ComponentId, mgm::Event)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@@ -201,6 +203,7 @@ impl MgmHandlerLis3Mdl {
|
||||
mode_leaf_helper,
|
||||
spi_fault_counter: FaultCounterStd::new(SPI_FAULT_THRESHOLD, SPI_FAULT_DECREMENT_AFTER),
|
||||
health_table,
|
||||
event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,9 +445,12 @@ impl MgmHandlerLis3Mdl {
|
||||
);
|
||||
self.health_table
|
||||
.set_health(component_id, HealthState::Faulty);
|
||||
// TODO: Event? Health-table changes are currently invisible to the ground
|
||||
// except through this log line. Likely applies to other health/mode
|
||||
// transitions across the example app too, not just this one.
|
||||
if let Err(e) = self.event_tx.send((
|
||||
self.id.component_id(),
|
||||
mgm::Event::SpiFaultThresholdExceeded,
|
||||
)) {
|
||||
log::warn!("{}: failed to send fault event: {}", self.id.str(), e);
|
||||
}
|
||||
// Do not restart an already pending Off transition: poll_sensor still calls
|
||||
// this every cycle the fault persists, and current stays Normal until the
|
||||
// transition completes, so re-triggering here would keep resetting the
|
||||
@@ -496,7 +502,16 @@ impl MgmHandlerLis3Mdl {
|
||||
|
||||
fn announce_mode(&self) {
|
||||
log::info!("{} announcing mode: {:?}", self.id.str(), self.mode());
|
||||
// TODO: Event?
|
||||
if let Err(e) = self
|
||||
.event_tx
|
||||
.send((self.id.component_id(), mgm::Event::ModeChanged(self.mode())))
|
||||
{
|
||||
log::warn!(
|
||||
"{}: failed to send mode changed event: {}",
|
||||
self.id.str(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_mode_to_parent(&self) {
|
||||
@@ -567,6 +582,7 @@ mod tests {
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
pub switch_rx: mpsc::Receiver<SwitchRequest>,
|
||||
pub health_table: HealthTableMapSync,
|
||||
pub event_rx: mpsc::Receiver<(ComponentId, mgm::Event)>,
|
||||
pub handler: MgmHandlerLis3Mdl,
|
||||
}
|
||||
|
||||
@@ -587,6 +603,7 @@ mod tests {
|
||||
let switch_map = SwitchSet::new(switch_map);
|
||||
let shared_switch_set = SharedSwitchSet::new(Mutex::new(switch_map));
|
||||
let health_table = HealthTableMapSync::default();
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(5);
|
||||
let handler = MgmHandlerLis3Mdl::new(
|
||||
MgmId::_0,
|
||||
TmtcQueues { tc_rx, tm_tx },
|
||||
@@ -596,6 +613,7 @@ mod tests {
|
||||
mode_leaf_helper,
|
||||
Duration::from_millis(100),
|
||||
health_table.clone(),
|
||||
event_tx,
|
||||
);
|
||||
Self {
|
||||
assembly_mode_request_tx,
|
||||
@@ -603,6 +621,7 @@ mod tests {
|
||||
shared_switch_set,
|
||||
switch_rx,
|
||||
health_table,
|
||||
event_rx,
|
||||
handler,
|
||||
tm_rx,
|
||||
tc_tx,
|
||||
@@ -685,6 +704,13 @@ mod tests {
|
||||
postcard::from_bytes::<types::acs::mgm::response::Response>(&tm_packet.payload)
|
||||
.expect("failed to deserialize mode reply");
|
||||
matches!(response, types::acs::mgm::response::Response::Ok);
|
||||
|
||||
let (sender_id, event) = testbench
|
||||
.event_rx
|
||||
.try_recv()
|
||||
.expect("expected mode changed event");
|
||||
assert_eq!(sender_id, ComponentId::AcsMgm0);
|
||||
assert!(matches!(event, mgm::Event::ModeChanged(DeviceMode::Normal)));
|
||||
// The device should have been polled once.
|
||||
assert_eq!(testbench.test_spi_interface().call_count, 1);
|
||||
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
||||
@@ -871,6 +897,11 @@ mod tests {
|
||||
fn test_spi_fault_above_threshold_marks_component_faulty() {
|
||||
let mut testbench = MgmTestbench::new();
|
||||
testbench.switch_to_normal();
|
||||
// Drain the mode changed event emitted by switch_to_normal().
|
||||
testbench
|
||||
.event_rx
|
||||
.try_recv()
|
||||
.expect("expected mode changed event");
|
||||
testbench.test_spi_interface().next_mgm_data = MgmLis3RawValues {
|
||||
x: -1,
|
||||
y: -1,
|
||||
@@ -885,6 +916,9 @@ mod tests {
|
||||
Some(HealthState::Faulty)
|
||||
);
|
||||
assert!(!testbench.handler.shared_mgm_set.lock().unwrap().valid);
|
||||
let (sender_id, event) = testbench.event_rx.try_recv().expect("expected fault event");
|
||||
assert_eq!(sender_id, ComponentId::AcsMgm0);
|
||||
assert!(matches!(event, mgm::Event::SpiFaultThresholdExceeded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -44,6 +44,7 @@ pub struct Assembly {
|
||||
mgm_modes: [MgmInfo; 2],
|
||||
parent_queues: ParentQueueHelper,
|
||||
pub(crate) children_queues: ChildrenQueueHelper,
|
||||
event_tx: mpsc::SyncSender<types::acs::mgm_assembly::Event>,
|
||||
}
|
||||
|
||||
impl Assembly {
|
||||
@@ -54,6 +55,7 @@ impl Assembly {
|
||||
children_queues: ChildrenQueueHelper,
|
||||
tmtc_queues: TmtcQueues,
|
||||
mode_timeout: Duration,
|
||||
event_tx: mpsc::SyncSender<types::acs::mgm_assembly::Event>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mode_helper: ModeHelper::new(Mode::NoModeKeeping, mode_timeout),
|
||||
@@ -62,6 +64,7 @@ impl Assembly {
|
||||
mgm_modes: [MgmInfo::default(); 2],
|
||||
parent_queues,
|
||||
children_queues,
|
||||
event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,12 +298,19 @@ impl Assembly {
|
||||
}
|
||||
|
||||
fn announce_mode(&self) {
|
||||
// TODO: Event?
|
||||
log::info!(
|
||||
"{:?} announcing mode: {:?}",
|
||||
Self::ID,
|
||||
self.mode_helper.current
|
||||
);
|
||||
if let Err(e) = self
|
||||
.event_tx
|
||||
.send(types::acs::mgm_assembly::Event::ModeChanged(
|
||||
self.mode_helper.current,
|
||||
))
|
||||
{
|
||||
log::warn!("{:?}: failed to send mode changed event: {}", Self::ID, e);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -336,6 +346,7 @@ mod tests {
|
||||
mgm_report_tx: [mpsc::SyncSender<types::acs::mgm::response::ModeResponse>; 2],
|
||||
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
event_rx: mpsc::Receiver<mgm_assembly::Event>,
|
||||
assembly: Assembly,
|
||||
}
|
||||
|
||||
@@ -351,6 +362,7 @@ mod tests {
|
||||
|
||||
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
|
||||
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(5);
|
||||
|
||||
Self {
|
||||
subsystem_req_tx,
|
||||
@@ -359,6 +371,7 @@ mod tests {
|
||||
mgm_report_tx: [mgm_0_mode_report_tx, mgm_1_mode_report_tx],
|
||||
tc_tx,
|
||||
tm_rx,
|
||||
event_rx,
|
||||
assembly: Assembly::new(
|
||||
ParentQueueHelper {
|
||||
request_rx: subsystem_req_rx,
|
||||
@@ -370,6 +383,7 @@ mod tests {
|
||||
},
|
||||
TmtcQueues { tc_rx, tm_tx },
|
||||
Duration::from_millis(20),
|
||||
event_tx,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -450,6 +464,12 @@ mod tests {
|
||||
assert_eq!(response.tm_header.message_type, MessageType::Verification);
|
||||
let response: response::Response = postcard::from_bytes(&response.payload).unwrap();
|
||||
assert_eq!(response, response::Response::Ok);
|
||||
|
||||
let event = tb.event_rx.try_recv().expect("expected mode changed event");
|
||||
assert!(matches!(
|
||||
event,
|
||||
mgm_assembly::Event::ModeChanged(Mode::Device(DeviceMode::Normal))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -271,6 +271,7 @@ pub struct PcduHandler<ComInterface: SerialInterface> {
|
||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||
mode: DeviceMode,
|
||||
stamp_helper: TimestampHelper,
|
||||
event_tx: mpsc::SyncSender<pcdu::Event>,
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
@@ -281,6 +282,7 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
com_interface: ComInterface,
|
||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||
init_mode: DeviceMode,
|
||||
event_tx: mpsc::SyncSender<pcdu::Event>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dev_str: "PCDU",
|
||||
@@ -292,6 +294,7 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
stamp_helper: TimestampHelper::default(),
|
||||
// Start in normal mode by default. Assume that the PCDU itself is on by default.
|
||||
mode: init_mode,
|
||||
event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,6 +440,9 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
|
||||
if let Err(_e) = self.com_interface.send(pcdu_req_ser.as_bytes()) {
|
||||
log::warn!("polling PCDU switch info failed");
|
||||
if let Err(e) = self.event_tx.send(pcdu::Event::SerialCommError) {
|
||||
log::warn!("failed to send comm error event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,12 +557,17 @@ mod tests {
|
||||
pub inner: SerialInterfaceDummy,
|
||||
pub send_queue: RefCell<VecDeque<Vec<u8>>>,
|
||||
pub reply_queue: RefCell<VecDeque<String>>,
|
||||
/// Makes the next `send` call fail, to exercise comm-error handling.
|
||||
pub fail_next_send: RefCell<bool>,
|
||||
}
|
||||
|
||||
impl SerialInterface for SerialInterfaceTest {
|
||||
type Error = ();
|
||||
|
||||
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
if self.fail_next_send.replace(false) {
|
||||
return Err(());
|
||||
}
|
||||
let mut send_queue_mut = self.send_queue.borrow_mut();
|
||||
send_queue_mut.push_back(data.to_vec());
|
||||
self.inner.send(data)
|
||||
@@ -588,6 +599,7 @@ mod tests {
|
||||
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
pub switch_request_tx: mpsc::Sender<SwitchRequest>,
|
||||
pub event_rx: mpsc::Receiver<pcdu::Event>,
|
||||
pub handler: PcduHandler<SerialInterfaceTest>,
|
||||
}
|
||||
|
||||
@@ -598,6 +610,7 @@ mod tests {
|
||||
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 (event_tx, event_rx) = mpsc::sync_channel(5);
|
||||
let shared_switch_map =
|
||||
Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown()));
|
||||
let handler = PcduHandler::new(
|
||||
@@ -607,6 +620,7 @@ mod tests {
|
||||
SerialInterfaceTest::default(),
|
||||
shared_switch_map,
|
||||
DeviceMode::Off,
|
||||
event_tx,
|
||||
);
|
||||
Self {
|
||||
mode_request_tx,
|
||||
@@ -614,6 +628,7 @@ mod tests {
|
||||
tc_tx,
|
||||
tm_rx,
|
||||
switch_request_tx,
|
||||
event_rx,
|
||||
handler,
|
||||
}
|
||||
}
|
||||
@@ -664,6 +679,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_periodic_command_send_failure_sends_event() {
|
||||
let testbench = PcduTestbench::new();
|
||||
*testbench.handler.com_interface.fail_next_send.borrow_mut() = true;
|
||||
testbench.handler.handle_periodic_commands();
|
||||
let event = testbench
|
||||
.event_rx
|
||||
.try_recv()
|
||||
.expect("expected comm error event");
|
||||
assert!(matches!(event, pcdu::Event::SerialCommError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_handler() {
|
||||
let mut testbench = PcduTestbench::new();
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use types::{ComponentId, Event, Message, ccsds::CcsdsTmPacketOwned, control};
|
||||
use types::{
|
||||
ComponentId, Event, Message,
|
||||
acs::{mgm, mgm_assembly},
|
||||
ccsds::CcsdsTmPacketOwned,
|
||||
control, pcdu, tmtc,
|
||||
};
|
||||
|
||||
use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||
|
||||
@@ -6,14 +11,32 @@ use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||
// event groups as well.
|
||||
pub struct EventManager {
|
||||
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)>,
|
||||
pub mgm_assembly_rx: std::sync::mpsc::Receiver<mgm_assembly::Event>,
|
||||
pub pcdu_rx: std::sync::mpsc::Receiver<pcdu::Event>,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl EventManager {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
if let Ok(event) = self.ctrl_rx.try_recv() {
|
||||
while let Ok(event) = self.ctrl_rx.try_recv() {
|
||||
self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event));
|
||||
}
|
||||
while let Ok((sender_id, event)) = self.mgm_rx.try_recv() {
|
||||
self.event_to_tm(sender_id, &event);
|
||||
}
|
||||
while let Ok(event) = self.mgm_assembly_rx.try_recv() {
|
||||
self.event_to_tm(ComponentId::AcsMgmAssembly, &event);
|
||||
}
|
||||
while let Ok(event) = self.pcdu_rx.try_recv() {
|
||||
self.event_to_tm(ComponentId::EpsPcdu, &event);
|
||||
}
|
||||
while let Ok((sender_id, event)) = self.tc_source_rx.try_recv() {
|
||||
self.event_to_tm(sender_id, &event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_to_tm(
|
||||
|
||||
@@ -101,15 +101,23 @@ fn main() {
|
||||
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 (mgm_event_tx, mgm_event_rx) = mpsc::sync_channel(10);
|
||||
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 controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx);
|
||||
|
||||
let ccsds_distributor = CcsdsDistributor::default();
|
||||
let mut tc_source = TcSourceTask::new(tc_source_rx, ccsds_distributor);
|
||||
let mut tc_source = TcSourceTask::new(tc_source_rx, ccsds_distributor, tc_source_event_tx);
|
||||
tc_source.add_target(ComponentId::EpsPcdu, pcdu_handler_tc_tx);
|
||||
tc_source.add_target(ComponentId::Controller, controller_tc_tx);
|
||||
tc_source.add_target(ComponentId::AcsMgm0, mgm_0_handler_tc_tx);
|
||||
@@ -199,6 +207,7 @@ fn main() {
|
||||
},
|
||||
Duration::from_millis(1000),
|
||||
health_table.clone(),
|
||||
mgm_event_tx.clone(),
|
||||
);
|
||||
let mut mgm_1_handler = mgm::MgmHandlerLis3Mdl::new(
|
||||
mgm::MgmId::_1,
|
||||
@@ -215,6 +224,7 @@ fn main() {
|
||||
},
|
||||
Duration::from_millis(1000),
|
||||
health_table.clone(),
|
||||
mgm_event_tx,
|
||||
);
|
||||
let mut mgm_assembly = mgm_assembly::Assembly::new(
|
||||
mgm_assembly::ParentQueueHelper {
|
||||
@@ -230,6 +240,7 @@ fn main() {
|
||||
tm_tx: tm_sink_tx.clone(),
|
||||
},
|
||||
Duration::from_millis(2000),
|
||||
mgm_assembly_event_tx,
|
||||
);
|
||||
|
||||
let mut acs_controller = ctrl::Controller::new(ctrl::ModeLeafHelper {
|
||||
@@ -275,6 +286,7 @@ fn main() {
|
||||
pcdu_serial_interface,
|
||||
shared_switch_set,
|
||||
DeviceMode::Normal,
|
||||
pcdu_event_tx,
|
||||
);
|
||||
|
||||
// The PCDU is a critical component which should be in normal mode immediately.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use satrs::{
|
||||
HandlingStatus,
|
||||
ComponentId as RawComponentId, HandlingStatus,
|
||||
spacepackets::{CcsdsPacketReader, ChecksumType},
|
||||
tmtc::PacketAsVec,
|
||||
};
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
sync::mpsc::{self, TryRecvError},
|
||||
};
|
||||
use types::{ComponentId, TcHeader, ccsds::CcsdsTcPacketOwned};
|
||||
use types::{ComponentId, TcHeader, ccsds::CcsdsTcPacketOwned, tmtc};
|
||||
|
||||
pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<CcsdsTcPacketOwned>>;
|
||||
|
||||
@@ -15,16 +15,19 @@ pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<Ccs
|
||||
pub struct TcSourceTask {
|
||||
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
ccsds_distributor: CcsdsDistributor,
|
||||
event_tx: mpsc::SyncSender<(ComponentId, tmtc::Event)>,
|
||||
}
|
||||
|
||||
impl TcSourceTask {
|
||||
pub fn new(
|
||||
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
ccsds_distributor: CcsdsDistributor,
|
||||
event_tx: mpsc::SyncSender<(ComponentId, tmtc::Event)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
ccsds_distributor,
|
||||
event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +58,7 @@ impl TcSourceTask {
|
||||
"received invalid CCSDS TC packet: {:?}",
|
||||
ccsds_tc_reader_result.err()
|
||||
);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
self.send_event(packet.sender_id, tmtc::Event::InvalidTcPacket);
|
||||
return HandlingStatus::HandledOne;
|
||||
}
|
||||
let ccsds_tc_reader = ccsds_tc_reader_result.unwrap();
|
||||
@@ -66,7 +69,7 @@ impl TcSourceTask {
|
||||
"received CCSDS TC packet with invalid TC header: {:?}",
|
||||
tc_header_result.err()
|
||||
);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
self.send_event(packet.sender_id, tmtc::Event::InvalidTcHeader);
|
||||
return HandlingStatus::HandledOne;
|
||||
}
|
||||
let (tc_header, payload) = tc_header_result.unwrap();
|
||||
@@ -81,7 +84,10 @@ impl TcSourceTask {
|
||||
.ok();
|
||||
} else {
|
||||
log::warn!("no TC handler for target ID {:?}", tc_header.target_id);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
self.send_event(
|
||||
packet.sender_id,
|
||||
tmtc::Event::UnknownTargetId(tc_header.target_id),
|
||||
);
|
||||
}
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
@@ -94,4 +100,141 @@ impl TcSourceTask {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `sender_id` is the raw ID tagged on the received packet, which is not necessarily a
|
||||
/// known [ComponentId] (e.g. a spoofed or garbled packet). Falls back to [ComponentId::Ground]
|
||||
/// as the event sender in that case.
|
||||
fn send_event(&self, sender_id: RawComponentId, event: tmtc::Event) {
|
||||
let sender_id = ComponentId::try_from(sender_id).unwrap_or_else(|_| {
|
||||
log::warn!("TC source event for unknown raw sender ID {}", sender_id);
|
||||
ComponentId::Ground
|
||||
});
|
||||
if let Err(e) = self.event_tx.send((sender_id, event)) {
|
||||
log::warn!("failed to send TC source event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
use arbitrary_int::u11;
|
||||
use satrs::spacepackets::{
|
||||
CcsdsPacketCreatorOwned, ChecksumType, PacketType, SpacePacketHeader,
|
||||
};
|
||||
use types::{Apid, MessageType, ccsds::CcsdsTcPacketOwned};
|
||||
|
||||
use super::*;
|
||||
|
||||
struct Testbench {
|
||||
tc_tx: mpsc::SyncSender<PacketAsVec>,
|
||||
target_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||
event_rx: mpsc::Receiver<(ComponentId, tmtc::Event)>,
|
||||
tc_source: TcSourceTask,
|
||||
}
|
||||
|
||||
impl Testbench {
|
||||
fn new() -> Self {
|
||||
let (tc_tx, tc_source_rx) = mpsc::sync_channel(5);
|
||||
let (target_tx, target_rx) = mpsc::sync_channel(5);
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(5);
|
||||
let mut tc_source =
|
||||
TcSourceTask::new(tc_source_rx, CcsdsDistributor::default(), event_tx);
|
||||
tc_source.add_target(ComponentId::EpsPcdu, target_tx);
|
||||
Self {
|
||||
tc_tx,
|
||||
target_rx,
|
||||
event_rx,
|
||||
tc_source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_tc_raw(target_id: ComponentId) -> Vec<u8> {
|
||||
CcsdsTcPacketOwned::new_with_request(
|
||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||
TcHeader::new(target_id, MessageType::Ping),
|
||||
(),
|
||||
)
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// A structurally valid CCSDS packet (correct length and CRC), but with arbitrary user data
|
||||
/// instead of a postcard-encoded [TcHeader].
|
||||
fn raw_ccsds_tc_with_user_data(user_data: &[u8]) -> Vec<u8> {
|
||||
CcsdsPacketCreatorOwned::new(
|
||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||
PacketType::Tc,
|
||||
user_data,
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
.unwrap()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_tc_is_routed_without_event() {
|
||||
let mut tb = Testbench::new();
|
||||
tb.tc_tx
|
||||
.send(PacketAsVec::new(
|
||||
ComponentId::UdpServer as u32,
|
||||
valid_tc_raw(ComponentId::EpsPcdu),
|
||||
))
|
||||
.unwrap();
|
||||
tb.tc_source.periodic_operation();
|
||||
tb.target_rx.try_recv().expect("TC was not routed");
|
||||
assert!(matches!(tb.event_rx.try_recv(), Err(TryRecvError::Empty)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_ccsds_packet_sends_event() {
|
||||
let mut tb = Testbench::new();
|
||||
tb.tc_tx
|
||||
.send(PacketAsVec::new(
|
||||
ComponentId::UdpServer as u32,
|
||||
vec![1, 2, 3],
|
||||
))
|
||||
.unwrap();
|
||||
tb.tc_source.periodic_operation();
|
||||
let (sender_id, event) = tb.event_rx.try_recv().expect("expected event");
|
||||
assert_eq!(sender_id, ComponentId::UdpServer);
|
||||
assert!(matches!(event, tmtc::Event::InvalidTcPacket));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_tc_header_sends_event() {
|
||||
let mut tb = Testbench::new();
|
||||
tb.tc_tx
|
||||
.send(PacketAsVec::new(
|
||||
ComponentId::UdpServer as u32,
|
||||
// A single byte is enough to decode the `ComponentId` discriminant, but not
|
||||
// enough for the trailing `MessageType`, so `TcHeader` deserialization fails
|
||||
// while the CCSDS packet itself stays valid.
|
||||
raw_ccsds_tc_with_user_data(&[0]),
|
||||
))
|
||||
.unwrap();
|
||||
tb.tc_source.periodic_operation();
|
||||
let (sender_id, event) = tb.event_rx.try_recv().expect("expected event");
|
||||
assert_eq!(sender_id, ComponentId::UdpServer);
|
||||
assert!(matches!(event, tmtc::Event::InvalidTcHeader));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_target_id_sends_event() {
|
||||
let mut tb = Testbench::new();
|
||||
tb.tc_tx
|
||||
.send(PacketAsVec::new(
|
||||
ComponentId::UdpServer as u32,
|
||||
valid_tc_raw(ComponentId::Ground),
|
||||
))
|
||||
.unwrap();
|
||||
tb.tc_source.periodic_operation();
|
||||
let (sender_id, event) = tb.event_rx.try_recv().expect("expected event");
|
||||
assert_eq!(sender_id, ComponentId::UdpServer);
|
||||
assert!(matches!(
|
||||
event,
|
||||
tmtc::Event::UnknownTargetId(ComponentId::Ground)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,21 @@ pub struct SensorData {
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
/// The SPI fault counter exceeded its threshold, the component was marked faulty and
|
||||
/// commanded off.
|
||||
SpiFaultThresholdExceeded,
|
||||
/// A commanded or autonomous mode transition completed.
|
||||
ModeChanged(crate::DeviceMode),
|
||||
}
|
||||
|
||||
impl crate::Message for Event {
|
||||
fn message_type(&self) -> crate::MessageType {
|
||||
crate::MessageType::Event
|
||||
}
|
||||
}
|
||||
|
||||
pub mod response {
|
||||
use crate::{DeviceMode, Message, acs::mgm::SensorData};
|
||||
|
||||
|
||||
@@ -136,3 +136,15 @@ pub mod response {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
/// A commanded or autonomous mode transition completed.
|
||||
ModeChanged(Mode),
|
||||
}
|
||||
|
||||
impl crate::Message for Event {
|
||||
fn message_type(&self) -> crate::MessageType {
|
||||
crate::MessageType::Event
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod acs;
|
||||
pub mod ccsds;
|
||||
pub mod control;
|
||||
pub mod pcdu;
|
||||
pub mod tmtc;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
@@ -56,6 +56,18 @@ pub enum SwitchStateBinary {
|
||||
On = 1,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
/// Sending a request to the PCDU over the serial/simulator link failed.
|
||||
SerialCommError,
|
||||
}
|
||||
|
||||
impl crate::Message for Event {
|
||||
fn message_type(&self) -> crate::MessageType {
|
||||
crate::MessageType::Event
|
||||
}
|
||||
}
|
||||
|
||||
pub type SwitchMapBinary = HashMap<SwitchId, SwitchStateBinary>;
|
||||
|
||||
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::{ComponentId, Message};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
/// A received CCSDS packet failed CRC or basic format validation.
|
||||
InvalidTcPacket,
|
||||
/// The CCSDS packet was valid, but its embedded TC header could not be decoded.
|
||||
InvalidTcHeader,
|
||||
/// The TC header decoded fine, but no handler is registered for its target ID.
|
||||
UnknownTargetId(ComponentId),
|
||||
}
|
||||
|
||||
impl Message for Event {
|
||||
fn message_type(&self) -> crate::MessageType {
|
||||
crate::MessageType::Event
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user