Compare commits
6 Commits
satrs-v0.3
...
move-to-cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f4c0e7d51 | ||
|
|
b3a7f3f9d2 | ||
|
|
e1bf81e804 | ||
| d7e6732888 | |||
|
|
c27569a526 | ||
| fef8d6a792 |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -11,6 +11,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install libudev-dev on Ubuntu
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: sudo apt update && sudo apt install -y libudev-dev
|
||||
- run: cargo check
|
||||
# Check example with static pool configuration
|
||||
- run: cargo check -p satrs-example --no-default-features
|
||||
@@ -23,6 +26,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
- run: sudo apt update && sudo apt install -y libudev-dev
|
||||
- run: cargo nextest run --all-features
|
||||
- run: cargo test --doc --all-features
|
||||
|
||||
@@ -57,7 +61,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features
|
||||
- run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features --no-deps
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
@@ -67,4 +71,5 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- run: sudo apt update && sudo apt install -y libudev-dev
|
||||
- run: cargo clippy -- -D warnings
|
||||
|
||||
6
justfile
6
justfile
@@ -14,12 +14,12 @@ test:
|
||||
embedded:
|
||||
cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
|
||||
|
||||
fmt:
|
||||
cargo fmt --all
|
||||
|
||||
check-fmt:
|
||||
cargo fmt --all -- --check
|
||||
|
||||
fmt:
|
||||
cargo fmt --all
|
||||
|
||||
clippy:
|
||||
cargo clippy -- -D warnings
|
||||
|
||||
|
||||
@@ -23,23 +23,17 @@ derive-new = "0.7"
|
||||
cfg-if = "1"
|
||||
arbitrary-int = "2"
|
||||
bitbybit = "1.4"
|
||||
postcard = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
[dependencies.satrs]
|
||||
path = "../satrs"
|
||||
features = ["test_util"]
|
||||
|
||||
[dependencies.satrs-minisim]
|
||||
path = "../satrs-minisim"
|
||||
|
||||
[dependencies.satrs-mib]
|
||||
version = "0.1.1"
|
||||
path = "../satrs-mib"
|
||||
satrs = { path = "../satrs", features = ["test_util"] }
|
||||
satrs-minisim = { path = "../satrs-minisim" }
|
||||
satrs-mib = { path = "../satrs-mib" }
|
||||
|
||||
[features]
|
||||
default = ["heap_tmtc"]
|
||||
heap_tmtc = []
|
||||
# default = ["heap_tmtc"]
|
||||
# heap_tmtc = []
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
use derive_new::new;
|
||||
use satrs::hk::{HkRequest, HkRequestVariant};
|
||||
use satrs::mode_tree::{ModeChild, ModeNode};
|
||||
@@ -717,3 +718,4 @@ mod tests {
|
||||
assert!(mgm_set.valid);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,7 @@ use satrs::{
|
||||
use satrs_example::{
|
||||
config::components::NO_SENDER,
|
||||
ids::{eps::PCDU, generic_pus::PUS_MODE},
|
||||
DeviceMode, TimestampHelper,
|
||||
CcsdsTmPacketOwned, DeviceMode, TimestampHelper,
|
||||
};
|
||||
use satrs_minisim::{
|
||||
eps::{
|
||||
@@ -32,12 +32,7 @@ use satrs_minisim::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
hk::PusHkHelper,
|
||||
pus::hk::{HkReply, HkReplyVariant},
|
||||
requests::CompositeRequest,
|
||||
tmtc::sender::TmTcSender,
|
||||
};
|
||||
use crate::{hk::PusHkHelper, requests::CompositeRequest};
|
||||
|
||||
pub trait SerialInterface {
|
||||
type Error: core::fmt::Debug;
|
||||
@@ -210,9 +205,10 @@ pub struct PcduHandler<ComInterface: SerialInterface> {
|
||||
dev_str: &'static str,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||
//hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||
hk_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||
tm_sender: TmTcSender,
|
||||
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
pub com_interface: ComInterface,
|
||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||
#[new(value = "PusHkHelper::new(id)")]
|
||||
@@ -273,7 +269,7 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
||||
pub fn handle_hk_request(&mut self, _requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
||||
match hk_request.variant {
|
||||
HkRequestVariant::OneShot => {
|
||||
if hk_request.unique_id == SetId::SwitcherSet as u32 {
|
||||
@@ -300,8 +296,10 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
},
|
||||
&mut self.tm_buf,
|
||||
) {
|
||||
// TODO: Fix
|
||||
/*
|
||||
self.tm_sender
|
||||
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
||||
.send(self.id.id(), PusTmVariant::Direct(hk_tm))
|
||||
.expect("failed to send HK TM");
|
||||
self.hk_reply_tx
|
||||
.send(GenericMessage::new(
|
||||
@@ -309,6 +307,7 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
||||
))
|
||||
.expect("failed to send HK reply");
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,9 +506,7 @@ mod tests {
|
||||
use std::sync::mpsc;
|
||||
|
||||
use arbitrary_int::u21;
|
||||
use satrs::{
|
||||
mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
|
||||
};
|
||||
use satrs::{mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage};
|
||||
use satrs_example::ids::{self, Apid};
|
||||
use satrs_minisim::eps::SwitchMapBinary;
|
||||
|
||||
@@ -555,8 +552,9 @@ mod tests {
|
||||
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
||||
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
//pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
pub hk_rx: std::sync::mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
|
||||
pub handler: PcduHandler<SerialInterfaceTest>,
|
||||
}
|
||||
@@ -568,8 +566,8 @@ mod tests {
|
||||
let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
|
||||
let mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx);
|
||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
||||
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
|
||||
let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
|
||||
let (hk_tx, hk_rx) = mpsc::sync_channel(10);
|
||||
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::default()));
|
||||
let mut handler = PcduHandler::new(
|
||||
@@ -577,9 +575,10 @@ mod tests {
|
||||
"TEST_PCDU",
|
||||
mode_node,
|
||||
composite_request_rx,
|
||||
hk_reply_tx,
|
||||
hk_tx,
|
||||
switch_reqest_rx,
|
||||
TmTcSender::Heap(tm_tx.clone()),
|
||||
tm_tx.clone(),
|
||||
//TmTcSender::Normal(tm_tx.clone()),
|
||||
SerialInterfaceTest::default(),
|
||||
shared_switch_map,
|
||||
);
|
||||
@@ -590,7 +589,7 @@ mod tests {
|
||||
mode_reply_rx_to_pus,
|
||||
mode_reply_rx_to_parent,
|
||||
composite_request_tx,
|
||||
hk_reply_rx,
|
||||
hk_rx,
|
||||
tm_rx,
|
||||
switch_request_tx,
|
||||
handler,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::mpsc::{self};
|
||||
|
||||
use crate::pus::create_verification_reporter;
|
||||
//use crate::pus::create_verification_reporter;
|
||||
use arbitrary_int::traits::Integer as _;
|
||||
use arbitrary_int::u11;
|
||||
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
|
||||
@@ -34,6 +34,7 @@ impl EventTmHook for EventApidSetter {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
|
||||
/// packets. It also handles the verification completion of PUS event service requests.
|
||||
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
||||
@@ -292,3 +293,4 @@ mod tests {
|
||||
// TODO: Add test.
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#![allow(dead_code)]
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||
use satrs::pus::HandlingStatus;
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
|
||||
use satrs::pool::{PoolProviderWithGuards, SharedStaticMemoryPool};
|
||||
use satrs::tmtc::PacketInPool;
|
||||
use satrs_example::CcsdsTmPacketOwned;
|
||||
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
pub trait UdpTmHandler {
|
||||
pub trait UdpTmHandlerProvider {
|
||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||
}
|
||||
|
||||
/*
|
||||
pub struct StaticUdpTmHandler {
|
||||
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
||||
pub tm_store: SharedStaticMemoryPool,
|
||||
@@ -45,22 +45,17 @@ impl UdpTmHandler for StaticUdpTmHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct DynamicUdpTmHandler {
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
pub struct UdpTmHandlerWithChannel {
|
||||
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
}
|
||||
|
||||
impl UdpTmHandler for DynamicUdpTmHandler {
|
||||
impl UdpTmHandlerProvider for UdpTmHandlerWithChannel {
|
||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||
while let Ok(tm) = self.tm_rx.try_recv() {
|
||||
if tm.packet.len() > 9 {
|
||||
let service = tm.packet[7];
|
||||
let subservice = tm.packet[8];
|
||||
info!("Sending PUS TM[{service},{subservice}]")
|
||||
} else {
|
||||
info!("Sending PUS TM");
|
||||
}
|
||||
let result = socket.send_to(&tm.packet, recv_addr);
|
||||
info!("Sending PUS TM with header {:?}", tm.tm_header);
|
||||
let result = socket.send_to(&tm.to_vec(), recv_addr);
|
||||
if let Err(e) = result {
|
||||
warn!("Sending TM with UDP socket failed: {e}")
|
||||
}
|
||||
@@ -68,12 +63,49 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> {
|
||||
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>,
|
||||
pub tm_handler: TmHandler,
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct TestTmHandler {
|
||||
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
|
||||
}
|
||||
|
||||
impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
|
||||
impl UdpTmHandlerProvider for TestTmHandler {
|
||||
fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||
self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UdpTmHandler {
|
||||
Normal(UdpTmHandlerWithChannel),
|
||||
Test(TestTmHandler),
|
||||
}
|
||||
|
||||
impl From<UdpTmHandlerWithChannel> for UdpTmHandler {
|
||||
fn from(handler: UdpTmHandlerWithChannel) -> Self {
|
||||
UdpTmHandler::Normal(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestTmHandler> for UdpTmHandler {
|
||||
fn from(handler: TestTmHandler) -> Self {
|
||||
UdpTmHandler::Test(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl UdpTmHandlerProvider for UdpTmHandler {
|
||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||
match self {
|
||||
UdpTmHandler::Normal(handler) => handler.send_tm_to_udp_client(socket, recv_addr),
|
||||
UdpTmHandler::Test(handler) => handler.send_tm_to_udp_client(socket, recv_addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UdpTmtcServer {
|
||||
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>,
|
||||
pub tm_handler: UdpTmHandler,
|
||||
}
|
||||
|
||||
impl UdpTmtcServer {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
loop {
|
||||
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||
@@ -107,12 +139,8 @@ impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
net::IpAddr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use arbitrary_int::traits::Integer as _;
|
||||
use arbitrary_int::u14;
|
||||
@@ -127,23 +155,12 @@ mod tests {
|
||||
use satrs_example::config::OBSW_SERVER_ADDR;
|
||||
use satrs_example::ids;
|
||||
|
||||
use crate::tmtc::sender::{MockSender, TmTcSender};
|
||||
use crate::tmtc::sender::MockSender;
|
||||
|
||||
use super::*;
|
||||
|
||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct TestTmHandler {
|
||||
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
|
||||
}
|
||||
|
||||
impl UdpTmHandler for TestTmHandler {
|
||||
fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||
self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||
@@ -154,7 +171,7 @@ mod tests {
|
||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||
let mut udp_dyn_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler,
|
||||
tm_handler: tm_handler.into(),
|
||||
};
|
||||
udp_dyn_server.periodic_operation();
|
||||
let queue = udp_dyn_server
|
||||
@@ -179,7 +196,7 @@ mod tests {
|
||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||
let mut udp_dyn_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler,
|
||||
tm_handler: tm_handler.into(),
|
||||
};
|
||||
let sph = SpHeader::new_for_unseg_tc(ids::Apid::GenericPus.raw_value(), u14::ZERO, 0);
|
||||
let ping_tc = PusTcCreator::new_simple(
|
||||
|
||||
@@ -39,6 +39,7 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi
|
||||
VerificationReporter::new(owner_id, &verif_cfg)
|
||||
}
|
||||
|
||||
/*
|
||||
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
||||
pub struct PusTcMpscRouter {
|
||||
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
@@ -187,6 +188,7 @@ impl PusTcDistributor {
|
||||
Ok(HandlingStatus::HandledOne)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub trait TargetedPusService {
|
||||
const SERVICE_ID: u8;
|
||||
@@ -5,7 +5,7 @@ use crate::pus::create_verification_reporter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use log::info;
|
||||
use satrs::pool::{PoolProvider, StaticMemoryPool};
|
||||
use satrs::pus::scheduler::{PusScheduler, TcInfo};
|
||||
use satrs::pus::scheduler::{PusSchedulerAlloc, TcInfo};
|
||||
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
|
||||
use satrs::pus::verification::VerificationReporter;
|
||||
use satrs::pus::{
|
||||
@@ -86,7 +86,7 @@ pub struct SchedulingServiceWrapper {
|
||||
TmTcSender,
|
||||
EcssTcCacher,
|
||||
VerificationReporter,
|
||||
PusScheduler,
|
||||
PusSchedulerAlloc,
|
||||
>,
|
||||
pub sched_tc_pool: StaticMemoryPool,
|
||||
pub releaser_buf: [u8; 4096],
|
||||
@@ -179,7 +179,7 @@ pub fn create_scheduler_service(
|
||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
sched_tc_pool: StaticMemoryPool,
|
||||
) -> SchedulingServiceWrapper {
|
||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||
let scheduler = PusSchedulerAlloc::new_with_current_init_time(Duration::from_secs(5))
|
||||
.expect("Creating PUS Scheduler failed");
|
||||
let pus_11_handler = PusSchedServiceHandler::new(
|
||||
PusServiceHelper::new(
|
||||
@@ -1,8 +1,129 @@
|
||||
use satrs::spacepackets::time::cds::CdsTime;
|
||||
extern crate alloc;
|
||||
|
||||
use satrs::spacepackets::{
|
||||
ccsds_packet_len_for_user_data_len_with_checksum, time::cds::CdsTime, CcsdsPacketCreationError,
|
||||
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
pub mod ids;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
num_enum::TryFromPrimitive,
|
||||
num_enum::IntoPrimitive,
|
||||
)]
|
||||
#[repr(u64)]
|
||||
pub enum ComponentId {
|
||||
Pcdu,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum MessageType {
|
||||
Ping,
|
||||
Mode,
|
||||
Hk,
|
||||
Action,
|
||||
Verification,
|
||||
}
|
||||
|
||||
/// Unserialized owned TM packet which can be cloned and sent around.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CcsdsTmPacketOwned {
|
||||
pub sp_header: SpacePacketHeader,
|
||||
pub tm_header: TmHeader,
|
||||
pub payload: alloc::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
/// Simple type modelling packet stored in the heap. This structure is intended to
|
||||
/// be used when sending a packet via a message queue, so it also contains the sender ID.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct PacketAsVec {
|
||||
pub sender_id: ComponentId,
|
||||
pub packet: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PacketAsVec {
|
||||
pub fn new(sender_id: ComponentId, packet: Vec<u8>) -> Self {
|
||||
Self { sender_id, packet }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CcsdsCreationError {
|
||||
#[error("CCSDS packet creation error: {0}")]
|
||||
CcsdsPacketCreation(#[from] CcsdsPacketCreationError),
|
||||
#[error("postcard error: {0}")]
|
||||
Postcard(#[from] postcard::Error),
|
||||
#[error("timestamp generation error")]
|
||||
Time,
|
||||
}
|
||||
|
||||
impl CcsdsTmPacketOwned {
|
||||
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
|
||||
let response_len =
|
||||
postcard::experimental::serialized_size(&self.tm_header)? + self.payload.len();
|
||||
let mut ccsds_tm = CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(
|
||||
self.sp_header,
|
||||
response_len,
|
||||
buf,
|
||||
)?;
|
||||
let user_data = ccsds_tm.packet_data_mut();
|
||||
let ser_len = postcard::to_slice(&self.tm_header, user_data)?.len();
|
||||
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
|
||||
let ccsds_packet_len = ccsds_tm.finish();
|
||||
Ok(ccsds_packet_len)
|
||||
}
|
||||
|
||||
pub fn len_written(&self) -> usize {
|
||||
ccsds_packet_len_for_user_data_len_with_checksum(
|
||||
postcard::experimental::serialized_size(&self.tm_header).unwrap() as usize
|
||||
+ postcard::experimental::serialized_size(&self.payload).unwrap() as usize,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
|
||||
let mut buf = alloc::vec![0u8; self.len_written()];
|
||||
let len = self.write_to_bytes(&mut buf).unwrap();
|
||||
buf.truncate(len);
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct TmHeader {
|
||||
pub sender_id: ComponentId,
|
||||
pub target_id: ComponentId,
|
||||
pub message_type: MessageType,
|
||||
/// Telemetry can either be sent unsolicited, or as a response to telecommands.
|
||||
pub tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||
/// Raw CDS short timestamp.
|
||||
pub timestamp: Option<[u8; 7]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct TcHeader {
|
||||
pub target_id: ComponentId,
|
||||
pub request_type: MessageType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CcsdsTcPacketOwned {
|
||||
pub sp_header: SpacePacketHeader,
|
||||
pub tc_header: TcHeader,
|
||||
pub payload: alloc::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum DeviceMode {
|
||||
Off = 0,
|
||||
|
||||
@@ -5,12 +5,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper};
|
||||
use eps::{
|
||||
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
|
||||
PowerSwitchHelper,
|
||||
};
|
||||
use events::EventHandler;
|
||||
use interface::{
|
||||
sim_client_udp::create_sim_client,
|
||||
tcp::{SyncTcpTmSource, TcpTask},
|
||||
@@ -18,29 +16,17 @@ use interface::{
|
||||
};
|
||||
use log::info;
|
||||
use logger::setup_logger;
|
||||
use pus::{
|
||||
action::create_action_service,
|
||||
event::create_event_service,
|
||||
hk::create_hk_service,
|
||||
mode::create_mode_service,
|
||||
scheduler::{create_scheduler_service, TcReleaser},
|
||||
stack::PusStack,
|
||||
test::create_test_service,
|
||||
PusTcDistributor, PusTcMpscRouter,
|
||||
};
|
||||
use requests::GenericRequestRouter;
|
||||
use satrs::{
|
||||
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
||||
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
|
||||
mode_tree::connect_mode_nodes,
|
||||
pus::{event_man::EventRequestWithToken, EcssTcCacher, HandlingStatus},
|
||||
pus::{event_man::EventRequestWithToken, HandlingStatus},
|
||||
request::{GenericMessage, MessageMetadata},
|
||||
spacepackets::time::cds::CdsTime,
|
||||
};
|
||||
use satrs_example::{
|
||||
config::{
|
||||
components::NO_SENDER,
|
||||
pool::create_sched_tc_pool,
|
||||
tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS},
|
||||
OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
|
||||
},
|
||||
@@ -54,23 +40,7 @@ use satrs_example::{
|
||||
use tmtc::sender::TmTcSender;
|
||||
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "heap_tmtc")] {
|
||||
use interface::udp::DynamicUdpTmHandler;
|
||||
use satrs::pus::EcssTcVecCacher;
|
||||
use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic};
|
||||
} else {
|
||||
use std::sync::RwLock;
|
||||
use interface::udp::StaticUdpTmHandler;
|
||||
use satrs::pus::EcssTcInSharedPoolCacher;
|
||||
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
||||
use satrs_example::config::pool::create_static_pools;
|
||||
use tmtc::{
|
||||
tc_source::TcSourceTaskStatic,
|
||||
tm_sink::TmSinkStatic,
|
||||
};
|
||||
}
|
||||
}
|
||||
use crate::{interface::udp::UdpTmHandlerWithChannel, tmtc::tc_source::CcsdsDistributor};
|
||||
|
||||
mod acs;
|
||||
mod eps;
|
||||
@@ -78,7 +48,7 @@ mod events;
|
||||
mod hk;
|
||||
mod interface;
|
||||
mod logger;
|
||||
mod pus;
|
||||
//mod pus;
|
||||
mod requests;
|
||||
mod spi;
|
||||
mod tmtc;
|
||||
@@ -87,33 +57,13 @@ fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
println!("Running OBSW example");
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let (tm_pool, tc_pool) = create_static_pools();
|
||||
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
|
||||
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
|
||||
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
|
||||
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
|
||||
}
|
||||
}
|
||||
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
|
||||
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let tm_sender = TmTcSender::Static(
|
||||
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone())
|
||||
);
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let tm_sender = TmTcSender::Heap(tm_sink_tx.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let (sim_request_tx, sim_request_rx) = mpsc::channel();
|
||||
let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel();
|
||||
let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
|
||||
//let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel();
|
||||
//let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
|
||||
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
|
||||
let mut opt_sim_client = create_sim_client(sim_request_rx);
|
||||
|
||||
@@ -136,134 +86,44 @@ fn main() {
|
||||
.composite_router_map
|
||||
.insert(PCDU.id(), pcdu_handler_composite_tx);
|
||||
|
||||
// This helper structure is used by all telecommand providers which need to send telecommands
|
||||
// to the TC source.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let tc_sender_with_shared_pool =
|
||||
PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
|
||||
let tc_in_mem_converter =
|
||||
EcssTcCacher::Static(EcssTcInSharedPoolCacher::new(shared_tc_pool, 4096));
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let tc_in_mem_converter = EcssTcCacher::Heap(EcssTcVecCacher::default());
|
||||
}
|
||||
}
|
||||
|
||||
// Create event handling components
|
||||
// These sender handles are used to send event requests, for example to enable or disable
|
||||
// certain events.
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(100);
|
||||
//let (event_tx, event_rx) = mpsc::sync_channel(100);
|
||||
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
|
||||
|
||||
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||
// in the sat-rs documentation.
|
||||
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
|
||||
//let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
|
||||
|
||||
let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20);
|
||||
let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10);
|
||||
let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50);
|
||||
let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50);
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50);
|
||||
let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50);
|
||||
//let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20);
|
||||
//let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10);
|
||||
//let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50);
|
||||
//let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50);
|
||||
//let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50);
|
||||
//let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50);
|
||||
|
||||
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||
//let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50);
|
||||
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30);
|
||||
//let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let tc_releaser = TcReleaser::Static(tc_sender_with_shared_pool.clone());
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let tc_releaser = TcReleaser::Heap(tc_source_tx.clone());
|
||||
}
|
||||
}
|
||||
let pus_router = PusTcMpscRouter {
|
||||
test_tc_sender: pus_test_tx,
|
||||
event_tc_sender: pus_event_tx,
|
||||
sched_tc_sender: pus_sched_tx,
|
||||
hk_tc_sender: pus_hk_tx,
|
||||
action_tc_sender: pus_action_tx,
|
||||
mode_tc_sender: pus_mode_tx,
|
||||
let mut ccsds_distributor = CcsdsDistributor::default();
|
||||
let mut tmtc_task = TcSourceTask::new(
|
||||
tc_source_rx,
|
||||
ccsds_distributor,
|
||||
//PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||
);
|
||||
let tc_sender = TmTcSender::Normal(tc_source_tx.clone());
|
||||
let udp_tm_handler = UdpTmHandlerWithChannel {
|
||||
tm_rx: tm_server_rx,
|
||||
};
|
||||
let pus_test_service = create_test_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
event_tx.clone(),
|
||||
pus_test_rx,
|
||||
);
|
||||
let pus_scheduler_service = create_scheduler_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
tc_releaser,
|
||||
pus_sched_rx,
|
||||
create_sched_tc_pool(),
|
||||
);
|
||||
let pus_event_service = create_event_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
pus_event_rx,
|
||||
event_request_tx,
|
||||
);
|
||||
let pus_action_service = create_action_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
pus_action_rx,
|
||||
request_map.clone(),
|
||||
pus_action_reply_rx,
|
||||
);
|
||||
let pus_hk_service = create_hk_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
pus_hk_rx,
|
||||
request_map.clone(),
|
||||
pus_hk_reply_rx,
|
||||
);
|
||||
let pus_mode_service = create_mode_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
pus_mode_rx,
|
||||
request_map,
|
||||
pus_mode_reply_rx,
|
||||
);
|
||||
let mut pus_stack = PusStack::new(
|
||||
pus_test_service,
|
||||
pus_hk_service,
|
||||
pus_event_service,
|
||||
pus_action_service,
|
||||
pus_scheduler_service,
|
||||
pus_mode_service,
|
||||
);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new(
|
||||
shared_tc_pool_wrapper.clone(),
|
||||
tc_source_rx,
|
||||
PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||
));
|
||||
let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool);
|
||||
let udp_tm_handler = StaticUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
tm_store: shared_tm_pool.clone(),
|
||||
};
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new(
|
||||
tc_source_rx,
|
||||
PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||
));
|
||||
let tc_sender = TmTcSender::Heap(tc_source_tx.clone());
|
||||
let udp_tm_handler = DynamicUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone())
|
||||
.expect("creating UDP TMTC server failed");
|
||||
let mut udp_tmtc_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler: udp_tm_handler,
|
||||
tm_handler: udp_tm_handler.into(),
|
||||
};
|
||||
|
||||
let tcp_server_cfg = ServerConfig::new(
|
||||
@@ -282,88 +142,75 @@ fn main() {
|
||||
)
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let mut tm_sink = TmSink::Static(TmSinkStatic::new(
|
||||
shared_tm_pool_wrapper,
|
||||
sync_tm_tcp_source,
|
||||
tm_sink_rx,
|
||||
tm_server_tx,
|
||||
));
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let mut tm_sink = TmSink::Heap(TmSinkDynamic::new(
|
||||
sync_tm_tcp_source,
|
||||
tm_sink_rx,
|
||||
tm_server_tx,
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut tm_sink = TmSink::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
|
||||
|
||||
let shared_switch_set = Arc::new(Mutex::default());
|
||||
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
||||
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
||||
|
||||
let shared_mgm_0_set = Arc::default();
|
||||
let shared_mgm_1_set = Arc::default();
|
||||
//let shared_mgm_0_set = Arc::default();
|
||||
//let shared_mgm_1_set = Arc::default();
|
||||
let mgm_0_mode_node = ModeRequestHandlerMpscBounded::new(MGM0.into(), mgm_0_handler_mode_rx);
|
||||
let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), 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
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
|
||||
sim_client
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
|
||||
(
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_0_sim_reply_rx,
|
||||
}),
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_1_sim_reply_rx,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
)
|
||||
};
|
||||
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM0,
|
||||
"MGM_0",
|
||||
mgm_0_mode_node,
|
||||
mgm_0_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sender.clone(),
|
||||
mgm_0_spi_interface,
|
||||
shared_mgm_0_set,
|
||||
);
|
||||
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM1,
|
||||
"MGM_1",
|
||||
mgm_1_mode_node,
|
||||
mgm_1_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sender.clone(),
|
||||
mgm_1_spi_interface,
|
||||
shared_mgm_1_set,
|
||||
);
|
||||
// Connect PUS service to device handlers.
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_0_handler_mode_tx,
|
||||
&mut mgm_0_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_1_handler_mode_tx,
|
||||
&mut mgm_1_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
/*
|
||||
let (mgm_0_spi_interface, mgm_1_spi_interface) =
|
||||
if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||
sim_client
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
|
||||
sim_client
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
|
||||
(
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_0_sim_reply_rx,
|
||||
}),
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_1_sim_reply_rx,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
)
|
||||
};
|
||||
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM0,
|
||||
"MGM_0",
|
||||
mgm_0_mode_node,
|
||||
mgm_0_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sender.clone(),
|
||||
mgm_0_spi_interface,
|
||||
shared_mgm_0_set,
|
||||
);
|
||||
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM1,
|
||||
"MGM_1",
|
||||
mgm_1_mode_node,
|
||||
mgm_1_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sender.clone(),
|
||||
mgm_1_spi_interface,
|
||||
shared_mgm_1_set,
|
||||
);
|
||||
// Connect PUS service to device handlers.
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_0_handler_mode_tx,
|
||||
&mut mgm_0_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_1_handler_mode_tx,
|
||||
&mut mgm_1_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
*/
|
||||
|
||||
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
|
||||
@@ -382,16 +229,18 @@ fn main() {
|
||||
pcdu_handler_composite_rx,
|
||||
pus_hk_reply_tx,
|
||||
switch_request_rx,
|
||||
tm_sender.clone(),
|
||||
tm_sink_tx.clone(),
|
||||
pcdu_serial_interface,
|
||||
shared_switch_set,
|
||||
);
|
||||
/*
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
pcdu_handler_mode_tx.clone(),
|
||||
&mut pcdu_handler,
|
||||
pus_mode_reply_tx,
|
||||
);
|
||||
*/
|
||||
|
||||
// The PCDU is a critical component which should be in normal mode immediately.
|
||||
pcdu_handler_mode_tx
|
||||
@@ -455,8 +304,8 @@ fn main() {
|
||||
let jh_aocs = thread::Builder::new()
|
||||
.name("sat-rs aocs".to_string())
|
||||
.spawn(move || loop {
|
||||
mgm_0_handler.periodic_operation();
|
||||
mgm_1_handler.periodic_operation();
|
||||
//mgm_0_handler.periodic_operation();
|
||||
//mgm_1_handler.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||
})
|
||||
.unwrap();
|
||||
@@ -482,8 +331,8 @@ fn main() {
|
||||
let jh_pus_handler = thread::Builder::new()
|
||||
.name("sat-rs pus".to_string())
|
||||
.spawn(move || loop {
|
||||
event_handler.periodic_operation();
|
||||
pus_stack.periodic_operation();
|
||||
//event_handler.periodic_operation();
|
||||
//pus_stack.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
|
||||
|
||||
use satrs::{
|
||||
pus::EcssTmSender,
|
||||
queue::GenericSendError,
|
||||
spacepackets::ecss::WritablePusPacket,
|
||||
tmtc::{PacketAsVec, PacketHandler, PacketSenderWithSharedPool},
|
||||
tmtc::{PacketAsVec, PacketHandler},
|
||||
ComponentId,
|
||||
};
|
||||
|
||||
@@ -14,8 +12,7 @@ pub struct MockSender(pub RefCell<VecDeque<PacketAsVec>>);
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TmTcSender {
|
||||
Static(PacketSenderWithSharedPool),
|
||||
Heap(mpsc::SyncSender<PacketAsVec>),
|
||||
Normal(mpsc::SyncSender<PacketAsVec>),
|
||||
Mock(MockSender),
|
||||
}
|
||||
|
||||
@@ -29,6 +26,7 @@ impl TmTcSender {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl EcssTmSender for TmTcSender {
|
||||
fn send_tm(
|
||||
&self,
|
||||
@@ -36,7 +34,7 @@ impl EcssTmSender for TmTcSender {
|
||||
tm: satrs::pus::PusTmVariant,
|
||||
) -> Result<(), satrs::pus::EcssTmtcError> {
|
||||
match self {
|
||||
TmTcSender::Static(sync_sender) => sync_sender.send_tm(sender_id, tm),
|
||||
//TmTcSender::Static(sync_sender) => sync_sender.send_tm(sender_id, tm),
|
||||
TmTcSender::Heap(sync_sender) => match tm {
|
||||
satrs::pus::PusTmVariant::InStore(_) => panic!("can not send TM in store"),
|
||||
satrs::pus::PusTmVariant::Direct(pus_tm_creator) => sync_sender
|
||||
@@ -47,19 +45,15 @@ impl EcssTmSender for TmTcSender {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl PacketHandler for TmTcSender {
|
||||
type Error = GenericSendError;
|
||||
|
||||
fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
||||
match self {
|
||||
TmTcSender::Static(packet_sender_with_shared_pool) => {
|
||||
if let Err(e) = packet_sender_with_shared_pool.handle_packet(sender_id, packet) {
|
||||
log::error!("Error sending packet via Static TM/TC sender: {:?}", e);
|
||||
}
|
||||
}
|
||||
TmTcSender::Heap(sync_sender) => {
|
||||
if let Err(e) = sync_sender.handle_packet(sender_id, packet) {
|
||||
TmTcSender::Normal(sync_sender) => {
|
||||
if let Err(e) = sync_sender.send(PacketAsVec::new(sender_id, packet.to_vec())) {
|
||||
log::error!("Error sending packet via Heap TM/TC sender: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use satrs::{
|
||||
pool::PoolProvider,
|
||||
pus::HandlingStatus,
|
||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
||||
spacepackets::{CcsdsPacketReader, ChecksumType},
|
||||
tmtc::PacketAsVec,
|
||||
};
|
||||
use satrs_example::{CcsdsTcPacketOwned, ComponentId, TcHeader};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::mpsc::{self, TryRecvError},
|
||||
};
|
||||
use std::sync::mpsc::{self, TryRecvError};
|
||||
|
||||
use crate::pus::PusTcDistributor;
|
||||
|
||||
// TC source components where static pools are the backing memory of the received telecommands.
|
||||
/*
|
||||
pub struct TcSourceTaskStatic {
|
||||
shared_tc_pool: SharedPacketPool,
|
||||
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||
@@ -64,19 +67,25 @@ impl TcSourceTaskStatic {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<CcsdsTcPacketOwned>>;
|
||||
|
||||
// TC source components where the heap is the backing memory of the received telecommands.
|
||||
pub struct TcSourceTaskDynamic {
|
||||
pub struct TcSourceTask {
|
||||
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pus_distributor: PusTcDistributor,
|
||||
ccsds_distributor: CcsdsDistributor,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TcSourceTaskDynamic {
|
||||
pub fn new(tc_receiver: mpsc::Receiver<PacketAsVec>, pus_receiver: PusTcDistributor) -> Self {
|
||||
//#[allow(dead_code)]
|
||||
impl TcSourceTask {
|
||||
pub fn new(
|
||||
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
ccsds_distributor: CcsdsDistributor,
|
||||
) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
pus_distributor: pus_receiver,
|
||||
ccsds_distributor,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +97,41 @@ impl TcSourceTaskDynamic {
|
||||
// Right now, we only expect ECSS PUS packets.
|
||||
// If packets like CFDP are expected, we might have to check the APID first.
|
||||
match self.tc_receiver.try_recv() {
|
||||
Ok(packet_as_vec) => {
|
||||
self.pus_distributor
|
||||
.handle_tc_packet_vec(packet_as_vec)
|
||||
.ok();
|
||||
Ok(packet) => {
|
||||
let ccsds_tc_reader_result =
|
||||
CcsdsPacketReader::new(&packet.packet, Some(ChecksumType::WithCrc16));
|
||||
if ccsds_tc_reader_result.is_err() {
|
||||
log::warn!(
|
||||
"received invalid CCSDS TC packet: {:?}",
|
||||
ccsds_tc_reader_result.err()
|
||||
);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
return HandlingStatus::HandledOne;
|
||||
}
|
||||
let ccsds_tc_reader = ccsds_tc_reader_result.unwrap();
|
||||
let tc_header_result =
|
||||
postcard::take_from_bytes::<TcHeader>(ccsds_tc_reader.user_data());
|
||||
if tc_header_result.is_err() {
|
||||
log::warn!(
|
||||
"received CCSDS TC packet with invalid TC header: {:?}",
|
||||
tc_header_result.err()
|
||||
);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
return HandlingStatus::HandledOne;
|
||||
}
|
||||
let (tc_header, payload) = tc_header_result.unwrap();
|
||||
if let Some(sender) = self.ccsds_distributor.get(&tc_header.target_id) {
|
||||
sender
|
||||
.send(CcsdsTcPacketOwned {
|
||||
sp_header: *ccsds_tc_reader.sp_header(),
|
||||
tc_header,
|
||||
payload: payload.to_vec(),
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
log::warn!("no TC handler for target ID {:?}", tc_header.target_id);
|
||||
// TODO: Send a dedicated TM packet.
|
||||
}
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
Err(e) => match e {
|
||||
@@ -104,18 +144,3 @@ impl TcSourceTaskDynamic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum TcSourceTask {
|
||||
Static(TcSourceTaskStatic),
|
||||
Heap(TcSourceTaskDynamic),
|
||||
}
|
||||
|
||||
impl TcSourceTask {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
match self {
|
||||
TcSourceTask::Static(task) => task.periodic_operation(),
|
||||
TcSourceTask::Heap(task) => task.periodic_operation(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ use std::{
|
||||
use arbitrary_int::{u11, u14};
|
||||
use log::info;
|
||||
use satrs::{
|
||||
pool::PoolProvider,
|
||||
spacepackets::{
|
||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||
seq_count::SequenceCounter,
|
||||
seq_count::SequenceCounterCcsdsSimple,
|
||||
time::cds::MIN_CDS_FIELD_LEN,
|
||||
seq_count::{SequenceCounter, SequenceCounterCcsdsSimple},
|
||||
CcsdsPacket,
|
||||
},
|
||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
||||
tmtc::PacketAsVec,
|
||||
};
|
||||
use satrs_example::CcsdsTmPacketOwned;
|
||||
|
||||
use crate::interface::tcp::SyncTcpTmSource;
|
||||
|
||||
@@ -83,6 +81,7 @@ impl TmFunnelCommon {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub struct TmSinkStatic {
|
||||
common: TmFunnelCommon,
|
||||
shared_tm_store: SharedPacketPool,
|
||||
@@ -130,19 +129,20 @@ impl TmSinkStatic {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct TmSinkDynamic {
|
||||
pub struct TmSink {
|
||||
common: TmFunnelCommon,
|
||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
|
||||
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TmSinkDynamic {
|
||||
//#[allow(dead_code)]
|
||||
impl TmSink {
|
||||
pub fn new(
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
|
||||
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
@@ -153,13 +153,21 @@ impl TmSinkDynamic {
|
||||
|
||||
pub fn operation(&mut self) {
|
||||
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
||||
//let tm_raw = tm.to_vec();
|
||||
tm.sp_header.set_seq_count(
|
||||
self.common
|
||||
.seq_counter_map
|
||||
.get_and_increment(tm.sp_header.apid()),
|
||||
);
|
||||
// Read the TM, set sequence counter and message counter, and finally update
|
||||
// the CRC.
|
||||
/*
|
||||
let zero_copy_writer =
|
||||
PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN, true)
|
||||
PusTmZeroCopyWriter::new(&mut tm_raw, MIN_CDS_FIELD_LEN, true)
|
||||
.expect("Creating TM zero copy writer failed");
|
||||
self.common.apply_packet_processing(zero_copy_writer);
|
||||
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
|
||||
*/
|
||||
self.common.sync_tm_tcp_source.add_tm(&tm.to_vec());
|
||||
self.tm_server_tx
|
||||
.send(tm)
|
||||
.expect("Sending TM to server failed");
|
||||
@@ -167,12 +175,13 @@ impl TmSinkDynamic {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum TmSink {
|
||||
Static(TmSinkStatic),
|
||||
Heap(TmSinkDynamic),
|
||||
}
|
||||
//#[allow(dead_code)]
|
||||
//pub enum TmSink {
|
||||
//Static(TmSinkStatic),
|
||||
//Heap(TmSinkDynamic),
|
||||
//}
|
||||
|
||||
/*
|
||||
impl TmSink {
|
||||
pub fn operation(&mut self) {
|
||||
match self {
|
||||
@@ -181,3 +190,4 @@ impl TmSink {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,8 @@ license = "Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
spacepackets = { version = "0.17", default-features = false }
|
||||
# spacepackets = { version = "0.17", default-features = false }
|
||||
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
|
||||
serde = { version = "1", default-features = false, optional = true }
|
||||
defmt = {version = "1", optional = true }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
|
||||
|
||||
[dependencies]
|
||||
satrs-shared = { version = "0.2", path = "../satrs-shared" }
|
||||
spacepackets = { version = "0.17", default-features = false }
|
||||
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
|
||||
|
||||
delegate = "0.13"
|
||||
paste = "1"
|
||||
|
||||
1
satrs/src/ccsds/mod.rs
Normal file
1
satrs/src/ccsds/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod scheduler;
|
||||
926
satrs/src/ccsds/scheduler.rs
Normal file
926
satrs/src/ccsds/scheduler.rs
Normal file
@@ -0,0 +1,926 @@
|
||||
//! # CCSDS Telecommand Scheduler.
|
||||
#![deny(missing_docs)]
|
||||
use core::{hash::Hash, time::Duration};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
use spacepackets::{
|
||||
CcsdsPacketIdAndPsc,
|
||||
time::{TimestampError, UnixTime},
|
||||
};
|
||||
|
||||
/// Generic CCSDS scheduling errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ScheduleError {
|
||||
/// The release time is within the time-margin added on top of the current time.
|
||||
/// The first parameter is the current time, the second one the time margin, and the third one
|
||||
/// the release time.
|
||||
#[error("release time in margin")]
|
||||
ReleaseTimeInTimeMargin {
|
||||
/// Current time.
|
||||
current_time: UnixTime,
|
||||
/// Configured time margin.
|
||||
time_margin: Duration,
|
||||
/// Release time.
|
||||
release_time: UnixTime,
|
||||
},
|
||||
/// Nested time-tagged commands are not allowed.
|
||||
#[error("nested scheduled tc")]
|
||||
NestedScheduledTc,
|
||||
/// TC data is empty.
|
||||
#[error("tc data empty")]
|
||||
TcDataEmpty,
|
||||
/// Scheduler is full, packet number limit reached.
|
||||
#[error("scheduler is full, packet number limit reached")]
|
||||
PacketLimitReached,
|
||||
/// Scheduler is full, numver of bytes limit reached.
|
||||
#[error("scheduler is full, number of bytes limit reached")]
|
||||
ByteLimitReached,
|
||||
/// Timestamp error.
|
||||
#[error("timestamp error: {0}")]
|
||||
TimestampError(#[from] TimestampError),
|
||||
}
|
||||
|
||||
/// Packet ID used for identifying scheduled packets.
|
||||
///
|
||||
/// Right now, this ID can be determined from the packet without requiring external input
|
||||
/// or custom data fields in the CCSDS space pacekt.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CcsdsSchedulePacketId {
|
||||
/// Base ID.
|
||||
pub base: CcsdsPacketIdAndPsc,
|
||||
/// Optional checksum of the packet.
|
||||
pub crc16: Option<u16>,
|
||||
}
|
||||
|
||||
impl CcsdsSchedulePacketId {
|
||||
/// Create a new CCSDS scheduling packet ID.
|
||||
pub const fn new(base: CcsdsPacketIdAndPsc, checksum: Option<u16>) -> Self {
|
||||
Self {
|
||||
base,
|
||||
crc16: checksum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CcsdsSchedulePacketId {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.base.hash(state);
|
||||
self.crc16.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Modules requiring [alloc] support.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use core::time::Duration;
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::SystemTimeError;
|
||||
|
||||
use alloc::collections::btree_map;
|
||||
use spacepackets::{CcsdsPacketIdAndPsc, CcsdsPacketReader, time::UnixTime};
|
||||
|
||||
use crate::ccsds::scheduler::CcsdsSchedulePacketId;
|
||||
|
||||
/// The scheduler can be configured to have bounds for both the number of packets
|
||||
/// and the total number of bytes used by scheduled packets.
|
||||
///
|
||||
/// This can be used to avoid memory exhaustion in systems with limited resources or under
|
||||
/// heavy workloads.
|
||||
#[derive(Default, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Limits {
|
||||
/// Maximum number of scheduled packets.
|
||||
pub packets: Option<usize>,
|
||||
/// Maximum total number of bytes used by scheduled packets.
|
||||
pub bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// Create new limits for the CCSDS scheduler.
|
||||
pub const fn new(packets: Option<usize>, bytes: Option<usize>) -> Self {
|
||||
Self { packets, bytes }
|
||||
}
|
||||
|
||||
/// Check if no limits are set.
|
||||
pub fn has_no_limits(&self) -> bool {
|
||||
self.packets.is_none() && self.bytes.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill count of the scheduler.
|
||||
#[derive(Default, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FillCount {
|
||||
/// Number of scheduled packets.
|
||||
pub packets: usize,
|
||||
/// Total number of bytes used by scheduled packets.
|
||||
pub bytes: usize,
|
||||
}
|
||||
|
||||
/// Simple CCSDS scheduler implementation.
|
||||
///
|
||||
/// Relies of [alloc] support but limits the number of scheduled packets.
|
||||
#[derive(Debug)]
|
||||
pub struct CcsdsScheduler {
|
||||
tc_map: alloc::collections::BTreeMap<
|
||||
UnixTime,
|
||||
alloc::vec::Vec<(CcsdsSchedulePacketId, alloc::vec::Vec<u8>)>,
|
||||
>,
|
||||
limits: Limits,
|
||||
pub(crate) current_time: UnixTime,
|
||||
time_margin: Duration,
|
||||
}
|
||||
|
||||
impl CcsdsScheduler {
|
||||
/// Create a new CCSDS scheduler.
|
||||
pub fn new(current_time: UnixTime, limits: Limits, time_margin: Duration) -> Self {
|
||||
Self {
|
||||
tc_map: alloc::collections::BTreeMap::new(),
|
||||
limits,
|
||||
current_time,
|
||||
time_margin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [Self::new], but sets the `init_current_time` parameter to the current system time.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new_with_current_init_time(
|
||||
limits: Limits,
|
||||
time_margin: Duration,
|
||||
) -> Result<Self, SystemTimeError> {
|
||||
Ok(Self::new(UnixTime::now()?, limits, time_margin))
|
||||
}
|
||||
|
||||
/// Current fill count: number of scheduled packets and total number of bytes.
|
||||
///
|
||||
/// The first returned value is the number of scheduled packets, the second one is the
|
||||
/// byte count.
|
||||
pub fn current_fill_count(&self) -> FillCount {
|
||||
let mut fill_count = FillCount::default();
|
||||
for value in self.tc_map.values() {
|
||||
for (_, raw_scheduled_tc) in value {
|
||||
fill_count.packets += 1;
|
||||
fill_count.bytes += raw_scheduled_tc.len();
|
||||
}
|
||||
}
|
||||
fill_count
|
||||
}
|
||||
|
||||
/// Current number of scheduled entries.
|
||||
pub fn num_of_entries(&self) -> usize {
|
||||
self.current_fill_count().packets
|
||||
}
|
||||
|
||||
/// Update the current time.
|
||||
#[inline]
|
||||
pub fn update_time(&mut self, current_time: UnixTime) {
|
||||
self.current_time = current_time;
|
||||
}
|
||||
|
||||
/// Current time.
|
||||
#[inline]
|
||||
pub fn current_time(&self) -> &UnixTime {
|
||||
&self.current_time
|
||||
}
|
||||
|
||||
fn common_check(
|
||||
&mut self,
|
||||
release_time: UnixTime,
|
||||
packet_size: usize,
|
||||
) -> Result<(), super::ScheduleError> {
|
||||
if !self.limits.has_no_limits() {
|
||||
let fill_count = self.current_fill_count();
|
||||
if let Some(max_bytes) = self.limits.bytes {
|
||||
if fill_count.bytes + packet_size > max_bytes {
|
||||
return Err(super::ScheduleError::ByteLimitReached);
|
||||
}
|
||||
}
|
||||
if let Some(max_packets) = self.limits.packets {
|
||||
if fill_count.packets + 1 > max_packets {
|
||||
return Err(super::ScheduleError::PacketLimitReached);
|
||||
}
|
||||
}
|
||||
}
|
||||
if release_time < self.current_time + self.time_margin {
|
||||
return Err(super::ScheduleError::ReleaseTimeInTimeMargin {
|
||||
current_time: self.current_time,
|
||||
time_margin: self.time_margin,
|
||||
release_time,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert a telecommand using an existing [CcsdsPacketReader].
|
||||
pub fn insert_telecommand_with_reader(
|
||||
&mut self,
|
||||
reader: &CcsdsPacketReader,
|
||||
release_time: UnixTime,
|
||||
) -> Result<(), super::ScheduleError> {
|
||||
self.common_check(release_time, reader.packet_len())?;
|
||||
let base_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(reader);
|
||||
let checksum = reader.checksum();
|
||||
let packet_id_scheduling = CcsdsSchedulePacketId {
|
||||
base: base_id,
|
||||
crc16: checksum,
|
||||
};
|
||||
self.insert_telecommand(packet_id_scheduling, reader.raw_data(), release_time)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert a raw telecommand, assuming the user has already extracted the
|
||||
/// [CcsdsSchedulePacketId]
|
||||
pub fn insert_telecommand(
|
||||
&mut self,
|
||||
packet_id_scheduling: CcsdsSchedulePacketId,
|
||||
raw_packet: &[u8],
|
||||
release_time: UnixTime,
|
||||
) -> Result<(), super::ScheduleError> {
|
||||
self.common_check(release_time, raw_packet.len())?;
|
||||
match self.tc_map.entry(release_time) {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
e.insert(alloc::vec![(packet_id_scheduling, raw_packet.to_vec())]);
|
||||
}
|
||||
btree_map::Entry::Occupied(mut v) => {
|
||||
v.get_mut()
|
||||
.push((packet_id_scheduling, raw_packet.to_vec()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Release all telecommands which should be released based on the current time.
|
||||
pub fn release_telecommands<R: FnMut(&CcsdsSchedulePacketId, &[u8])>(
|
||||
&mut self,
|
||||
mut releaser: R,
|
||||
) {
|
||||
let tcs_to_release = self.telecommands_to_release();
|
||||
for tc_group in tcs_to_release {
|
||||
for (packet_id, raw_tc) in tc_group.1 {
|
||||
releaser(packet_id, raw_tc);
|
||||
}
|
||||
}
|
||||
self.tc_map.retain(|k, _| k > &self.current_time);
|
||||
}
|
||||
|
||||
/// Retrieve all telecommands which should be released based on the current time.
|
||||
pub fn telecommands_to_release(
|
||||
&self,
|
||||
) -> btree_map::Range<
|
||||
'_,
|
||||
UnixTime,
|
||||
alloc::vec::Vec<(CcsdsSchedulePacketId, alloc::vec::Vec<u8>)>,
|
||||
> {
|
||||
self.tc_map.range(..=self.current_time)
|
||||
}
|
||||
|
||||
/// Delete scheduled telecommand by their packet ID.
|
||||
///
|
||||
/// Returns whether any telecommand was deleted. This function might have to be called
|
||||
/// multiple times if multiple identical CCSDS packet IDs are possible.
|
||||
pub fn delete_by_id(&mut self, packet_id: &CcsdsSchedulePacketId) -> bool {
|
||||
let mut was_removed = false;
|
||||
self.tc_map.retain(|_, v| {
|
||||
let len_before = v.len();
|
||||
v.retain(|(stored_id, _)| stored_id != packet_id);
|
||||
let has_remaining = !v.is_empty();
|
||||
if v.len() < len_before {
|
||||
was_removed = true;
|
||||
}
|
||||
has_remaining
|
||||
});
|
||||
was_removed
|
||||
}
|
||||
|
||||
/// Delete all telecommands scheduled in a time window.
|
||||
///
|
||||
/// The range includes the start time but excludes the end time. Returns whether any
|
||||
/// telecommands were deleted.
|
||||
pub fn delete_time_window(&mut self, start_time: UnixTime, end_time: UnixTime) -> bool {
|
||||
let len_before = self.tc_map.len();
|
||||
self.tc_map.retain(|k, _| k < &start_time || k >= &end_time);
|
||||
self.tc_map.len() < len_before
|
||||
}
|
||||
|
||||
/// Delete all scheduled telecommands scheduled after or at a given time.
|
||||
///
|
||||
/// Returns whether any telecommands were deleted.
|
||||
pub fn delete_starting_at(&mut self, start_time: UnixTime) -> bool {
|
||||
let len_before = self.tc_map.len();
|
||||
self.tc_map.retain(|k, _| k < &start_time);
|
||||
self.tc_map.len() < len_before
|
||||
}
|
||||
|
||||
/// Delete all scheduled telecommands scheduled before but not equal to a given time.
|
||||
///
|
||||
/// Returns whether any telecommands were deleted.
|
||||
pub fn delete_before(&mut self, end_time: UnixTime) -> bool {
|
||||
let len_before = self.tc_map.len();
|
||||
self.tc_map.retain(|k, _| k >= &end_time);
|
||||
self.tc_map.len() < len_before
|
||||
}
|
||||
|
||||
/// Completely clear the scheduler.
|
||||
pub fn clear(&mut self) {
|
||||
self.tc_map.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use arbitrary_int::{traits::Integer, u11, u14};
|
||||
use spacepackets::{
|
||||
CcsdsPacketCreatorOwned, CcsdsPacketReader, ChecksumType, SpacePacketHeader,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_tc(app_data: &[u8], seq_count: u14) -> CcsdsPacketCreatorOwned {
|
||||
CcsdsPacketCreatorOwned::new(
|
||||
SpacePacketHeader::new_for_tc(
|
||||
u11::new(0x1),
|
||||
spacepackets::SequenceFlags::Unsegmented,
|
||||
seq_count,
|
||||
0,
|
||||
),
|
||||
spacepackets::PacketType::Tc,
|
||||
app_data,
|
||||
Some(ChecksumType::WithCrc16),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(5000),
|
||||
);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
assert_eq!(scheduler.num_of_entries(), 0);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(scheduler.current_time(), &unix_time);
|
||||
scheduler.release_telecommands(|_, _| {
|
||||
panic!("should not be called");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mutable_closure() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(5000),
|
||||
);
|
||||
let mut some_flag = false;
|
||||
// We should be able to manipulate the boolean inside the closure.
|
||||
scheduler.release_telecommands(|_, _| {
|
||||
some_flag = true;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc = test_tc(&[1, 2, 3], u14::ZERO);
|
||||
let test_tc_raw = test_tc.to_vec();
|
||||
let reader = CcsdsPacketReader::new(&test_tc_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_raw.len());
|
||||
assert_eq!(scheduler.num_of_entries(), 1);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
scheduler.clear();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
assert_eq!(scheduler.num_of_entries(), 0);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_and_release_one() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[1, 2, 3], u14::ZERO);
|
||||
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_0);
|
||||
let test_tc_raw = test_tc_0.to_vec();
|
||||
let reader = CcsdsPacketReader::new(&test_tc_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let checksum = reader.checksum();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_raw.len());
|
||||
assert_eq!(scheduler.num_of_entries(), 1);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
scheduler.release_telecommands(|_, _| {
|
||||
panic!("should not be called");
|
||||
});
|
||||
scheduler.update_time(UnixTime::new(3, 0));
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
scheduler.release_telecommands(|tc_id_scheduled, tc_raw| {
|
||||
assert_eq!(tc_id, tc_id_scheduled.base);
|
||||
assert_eq!(checksum, tc_id_scheduled.crc16);
|
||||
assert_eq!(tc_raw, test_tc_raw);
|
||||
});
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_and_release_multi_0() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let tc_id_0 = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_0);
|
||||
let tc_id_1 = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_1);
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand(
|
||||
CcsdsSchedulePacketId::new(tc_id_1, reader_1.checksum()),
|
||||
&test_tc_1_raw,
|
||||
UnixTime::new(5, 0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 2);
|
||||
assert_eq!(
|
||||
scheduler.current_fill_count().bytes,
|
||||
test_tc_0_raw.len() + test_tc_1_raw.len()
|
||||
);
|
||||
assert_eq!(scheduler.num_of_entries(), 2);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
scheduler.release_telecommands(|_, _| {
|
||||
panic!("should not be called");
|
||||
});
|
||||
|
||||
// Release first TC.
|
||||
scheduler.update_time(UnixTime::new(3, 0));
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
scheduler.release_telecommands(|tc_id_scheduled, tc_raw| {
|
||||
assert_eq!(tc_id_0, tc_id_scheduled.base);
|
||||
assert_eq!(reader_0.checksum(), tc_id_scheduled.crc16);
|
||||
assert_eq!(tc_raw, test_tc_0_raw);
|
||||
});
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_1_raw.len());
|
||||
assert_eq!(scheduler.num_of_entries(), 1);
|
||||
|
||||
// Release second TC.
|
||||
scheduler.update_time(UnixTime::new(6, 0));
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
scheduler.release_telecommands(|tc_id_scheduled, tc_raw| {
|
||||
assert_eq!(tc_id_1, tc_id_scheduled.base);
|
||||
assert_eq!(reader_1.checksum(), tc_id_scheduled.crc16);
|
||||
assert_eq!(tc_raw, test_tc_1_raw);
|
||||
});
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
assert_eq!(scheduler.num_of_entries(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_and_release_multi_1() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let tc_id_0 = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_0);
|
||||
let tc_id_1 = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_1);
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand(
|
||||
CcsdsSchedulePacketId::new(tc_id_1, reader_1.checksum()),
|
||||
&test_tc_1_raw,
|
||||
UnixTime::new(5, 0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 2);
|
||||
assert_eq!(
|
||||
scheduler.current_fill_count().bytes,
|
||||
test_tc_0_raw.len() + test_tc_1_raw.len()
|
||||
);
|
||||
assert_eq!(scheduler.num_of_entries(), 2);
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
scheduler.release_telecommands(|_, _| {
|
||||
panic!("should not be called");
|
||||
});
|
||||
|
||||
// Release first TC.
|
||||
scheduler.update_time(UnixTime::new(6, 0));
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
2
|
||||
);
|
||||
let mut index = 0;
|
||||
scheduler.release_telecommands(|tc_id_scheduled, tc_raw| {
|
||||
if index == 0 {
|
||||
assert_eq!(tc_id_0, tc_id_scheduled.base);
|
||||
assert_eq!(reader_0.checksum(), tc_id_scheduled.crc16);
|
||||
assert_eq!(tc_raw, test_tc_0_raw);
|
||||
} else {
|
||||
assert_eq!(tc_id_1, tc_id_scheduled.base);
|
||||
assert_eq!(reader_1.checksum(), tc_id_scheduled.crc16);
|
||||
assert_eq!(tc_raw, test_tc_1_raw);
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
assert_eq!(
|
||||
scheduler
|
||||
.telecommands_to_release()
|
||||
.collect::<alloc::vec::Vec<_>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
assert_eq!(scheduler.num_of_entries(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packet_limit_reached() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(3), None),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let reader = CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_0);
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 3);
|
||||
assert_eq!(
|
||||
scheduler.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0)),
|
||||
Err(ScheduleError::PacketLimitReached)
|
||||
);
|
||||
assert_eq!(
|
||||
scheduler.insert_telecommand(
|
||||
CcsdsSchedulePacketId::new(tc_id, reader.checksum()),
|
||||
&test_tc_0_raw,
|
||||
UnixTime::new(2, 0)
|
||||
),
|
||||
Err(ScheduleError::PacketLimitReached)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_byte_limit_reached() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(None, Some(test_tc_0.len_written() * 3)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let reader = CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc_0);
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
assert_eq!(scheduler.current_fill_count().packets, 3);
|
||||
assert_eq!(
|
||||
scheduler.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0)),
|
||||
Err(ScheduleError::ByteLimitReached)
|
||||
);
|
||||
assert_eq!(
|
||||
scheduler.insert_telecommand(
|
||||
CcsdsSchedulePacketId::new(tc_id, reader.checksum()),
|
||||
&test_tc_0_raw,
|
||||
UnixTime::new(2, 0)
|
||||
),
|
||||
Err(ScheduleError::ByteLimitReached)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deletion_by_id() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc = test_tc(&[1, 2, 3], u14::ZERO);
|
||||
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&test_tc);
|
||||
let test_tc_raw = test_tc.to_vec();
|
||||
let reader = CcsdsPacketReader::new(&test_tc_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let checksum = reader.checksum();
|
||||
let id = CcsdsSchedulePacketId::new(tc_id, checksum);
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler.delete_by_id(&id);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deletion_by_window_0() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let test_tc_2 = test_tc(&[1, 2, 3], u14::new(2));
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let test_tc_2_raw = test_tc_2.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_2 =
|
||||
CcsdsPacketReader::new(&test_tc_2_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_1, UnixTime::new(5, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_2, UnixTime::new(7, 0))
|
||||
.unwrap();
|
||||
let deleted = scheduler.delete_time_window(UnixTime::new(3, 0), UnixTime::new(6, 0));
|
||||
assert!(deleted);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 2);
|
||||
assert_eq!(
|
||||
scheduler.current_fill_count().bytes,
|
||||
test_tc_0_raw.len() + test_tc_2_raw.len()
|
||||
);
|
||||
scheduler.update_time(UnixTime::new(10, 0));
|
||||
let mut index = 0;
|
||||
scheduler.release_telecommands(|_id, packet| {
|
||||
if index == 0 {
|
||||
assert_eq!(packet, test_tc_0_raw);
|
||||
} else {
|
||||
assert_eq!(packet, test_tc_2_raw);
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
assert_eq!(scheduler.current_fill_count().packets, 0);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deletion_by_window_1() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let test_tc_2 = test_tc(&[1, 2, 3], u14::new(2));
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let test_tc_2_raw = test_tc_2.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_2 =
|
||||
CcsdsPacketReader::new(&test_tc_2_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_1, UnixTime::new(5, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_2, UnixTime::new(7, 0))
|
||||
.unwrap();
|
||||
// This only deletes the first 2 TCs.
|
||||
let deleted = scheduler.delete_time_window(UnixTime::new(2, 0), UnixTime::new(7, 0));
|
||||
assert!(deleted);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_2_raw.len());
|
||||
scheduler.update_time(UnixTime::new(10, 0));
|
||||
scheduler.release_telecommands(|_id, packet| {
|
||||
assert_eq!(packet, test_tc_2_raw);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deletion_from_start() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let test_tc_2 = test_tc(&[1, 2, 3], u14::new(2));
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let test_tc_2_raw = test_tc_2.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_2 =
|
||||
CcsdsPacketReader::new(&test_tc_2_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_1, UnixTime::new(5, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_2, UnixTime::new(7, 0))
|
||||
.unwrap();
|
||||
// This only deletes the first 2 TCs.
|
||||
let deleted = scheduler.delete_starting_at(UnixTime::new(5, 0));
|
||||
assert!(deleted);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_0_raw.len());
|
||||
scheduler.update_time(UnixTime::new(10, 0));
|
||||
scheduler.release_telecommands(|_id, packet| {
|
||||
assert_eq!(packet, test_tc_0_raw);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deletion_until_end() {
|
||||
let unix_time = UnixTime::new(0, 0);
|
||||
let mut scheduler = CcsdsScheduler::new(
|
||||
unix_time,
|
||||
Limits::new(Some(100), Some(1024)),
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let test_tc_0 = test_tc(&[42], u14::ZERO);
|
||||
let test_tc_1 = test_tc(&[1, 2, 3], u14::new(1));
|
||||
let test_tc_2 = test_tc(&[1, 2, 3], u14::new(2));
|
||||
let test_tc_0_raw = test_tc_0.to_vec();
|
||||
let test_tc_1_raw = test_tc_1.to_vec();
|
||||
let test_tc_2_raw = test_tc_2.to_vec();
|
||||
let reader_0 =
|
||||
CcsdsPacketReader::new(&test_tc_0_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_1 =
|
||||
CcsdsPacketReader::new(&test_tc_1_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
let reader_2 =
|
||||
CcsdsPacketReader::new(&test_tc_2_raw, Some(ChecksumType::WithCrc16)).unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_0, UnixTime::new(2, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_1, UnixTime::new(5, 0))
|
||||
.unwrap();
|
||||
scheduler
|
||||
.insert_telecommand_with_reader(&reader_2, UnixTime::new(7, 0))
|
||||
.unwrap();
|
||||
// This only deletes the first 2 TCs.
|
||||
let deleted = scheduler.delete_before(UnixTime::new(7, 0));
|
||||
assert!(deleted);
|
||||
assert_eq!(scheduler.current_fill_count().packets, 1);
|
||||
assert_eq!(scheduler.current_fill_count().bytes, test_tc_2_raw.len());
|
||||
scheduler.update_time(UnixTime::new(10, 0));
|
||||
scheduler.release_telecommands(|_id, packet| {
|
||||
assert_eq!(packet, test_tc_2_raw);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ extern crate downcast_rs;
|
||||
extern crate std;
|
||||
|
||||
pub mod action;
|
||||
pub mod ccsds;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod dev_mgmt;
|
||||
pub mod encoding;
|
||||
|
||||
@@ -155,73 +155,34 @@ impl Display for StoreIdError {
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for StoreIdError {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PoolError {
|
||||
/// Requested data block is too large
|
||||
#[error("data to store with size {0} is too large")]
|
||||
DataTooLarge(usize),
|
||||
/// The store is full. Contains the index of the full subpool
|
||||
#[error("store does not have any capacity")]
|
||||
StoreFull(u16),
|
||||
/// The store can not hold any data.
|
||||
#[error("store does not have any capacity")]
|
||||
NoCapacity,
|
||||
/// Store ID is invalid. This also includes partial errors where only the subpool is invalid
|
||||
#[error("invalid store ID: {0}, address: {1:?}")]
|
||||
InvalidStoreId(StoreIdError, Option<PoolAddr>),
|
||||
/// Valid subpool and packet index, but no data is stored at the given address
|
||||
#[error("no data exists at address {0:?}")]
|
||||
DataDoesNotExist(PoolAddr),
|
||||
ByteConversionError(spacepackets::ByteConversionError),
|
||||
#[error("byte conversion error: {0}")]
|
||||
ByteConversion(#[from] ByteConversionError),
|
||||
#[error("lock error")]
|
||||
LockError,
|
||||
/// Internal or configuration errors
|
||||
#[error("lock error")]
|
||||
InternalError(u32),
|
||||
}
|
||||
|
||||
impl Display for PoolError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
PoolError::DataTooLarge(size) => {
|
||||
write!(f, "data to store with size {size} is too large")
|
||||
}
|
||||
PoolError::NoCapacity => {
|
||||
write!(f, "store does not have any capacity")
|
||||
}
|
||||
PoolError::StoreFull(u16) => {
|
||||
write!(f, "store is too full. index for full subpool: {u16}")
|
||||
}
|
||||
PoolError::InvalidStoreId(id_e, addr) => {
|
||||
write!(f, "invalid store ID: {id_e}, address: {addr:?}")
|
||||
}
|
||||
PoolError::DataDoesNotExist(addr) => {
|
||||
write!(f, "no data exists at address {addr:?}")
|
||||
}
|
||||
PoolError::InternalError(e) => {
|
||||
write!(f, "internal error: {e}")
|
||||
}
|
||||
PoolError::ByteConversionError(e) => {
|
||||
write!(f, "store error: {e}")
|
||||
}
|
||||
PoolError::LockError => {
|
||||
write!(f, "lock error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteConversionError> for PoolError {
|
||||
fn from(value: ByteConversionError) -> Self {
|
||||
Self::ByteConversionError(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for PoolError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
if let PoolError::InvalidStoreId(e, _) = self {
|
||||
return Some(e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic trait for pool providers which provide memory pools for variable sized packet data.
|
||||
///
|
||||
/// It specifies a basic API to [Self::add], [Self::modify], [Self::read] and [Self::delete] data
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! The core data structure of this module is the [PusScheduler]. This structure can be used
|
||||
//! to perform the scheduling of telecommands like specified in the ECSS standard.
|
||||
use arbitrary_int::{u11, u14};
|
||||
use core::fmt::{Debug, Display, Formatter};
|
||||
use core::fmt::Debug;
|
||||
use core::time::Duration;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -12,8 +12,6 @@ use spacepackets::ecss::tc::{GenericPusTcSecondaryHeader, IsPusTelecommand, PusT
|
||||
use spacepackets::ecss::{PusError, PusPacket, WritablePusPacket};
|
||||
use spacepackets::time::{CcsdsTimeProvider, TimeReader, TimeWriter, TimestampError, UnixTime};
|
||||
use spacepackets::{ByteConversionError, CcsdsPacket};
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
use crate::pool::{PoolError, PoolProvider};
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -144,107 +142,39 @@ impl<TimeProvider: CcsdsTimeProvider + Clone> TimeWindow<TimeProvider> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ScheduleError {
|
||||
PusError(PusError),
|
||||
#[error("pus error: {0}")]
|
||||
PusError(#[from] PusError),
|
||||
/// The release time is within the time-margin added on top of the current time.
|
||||
/// The first parameter is the current time, the second one the time margin, and the third one
|
||||
/// the release time.
|
||||
#[error("release time in margin")]
|
||||
ReleaseTimeInTimeMargin {
|
||||
current_time: UnixTime,
|
||||
time_margin: Duration,
|
||||
release_time: UnixTime,
|
||||
},
|
||||
/// Nested time-tagged commands are not allowed.
|
||||
#[error("nested scheduled tc")]
|
||||
NestedScheduledTc,
|
||||
StoreError(PoolError),
|
||||
#[error("store error")]
|
||||
Pool(#[from] PoolError),
|
||||
#[error("tc data empty")]
|
||||
TcDataEmpty,
|
||||
TimestampError(TimestampError),
|
||||
#[error("timestamp error: {0}")]
|
||||
TimestampError(#[from] TimestampError),
|
||||
#[error("wrong subservice number {0}")]
|
||||
WrongSubservice(u8),
|
||||
#[error("wrong service number {0}")]
|
||||
WrongService(u8),
|
||||
ByteConversionError(ByteConversionError),
|
||||
}
|
||||
|
||||
impl Display for ScheduleError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ScheduleError::PusError(e) => {
|
||||
write!(f, "Pus Error: {e}")
|
||||
}
|
||||
ScheduleError::ReleaseTimeInTimeMargin {
|
||||
current_time,
|
||||
time_margin,
|
||||
release_time,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"time margin too short, current time: {current_time:?}, time margin: {time_margin:?}, release time: {release_time:?}"
|
||||
)
|
||||
}
|
||||
ScheduleError::NestedScheduledTc => {
|
||||
write!(f, "nested scheduling is not allowed")
|
||||
}
|
||||
ScheduleError::StoreError(e) => {
|
||||
write!(f, "pus scheduling: {e}")
|
||||
}
|
||||
ScheduleError::TcDataEmpty => {
|
||||
write!(f, "empty TC data field")
|
||||
}
|
||||
ScheduleError::TimestampError(e) => {
|
||||
write!(f, "pus scheduling: {e}")
|
||||
}
|
||||
ScheduleError::WrongService(srv) => {
|
||||
write!(f, "pus scheduling: wrong service number {srv}")
|
||||
}
|
||||
ScheduleError::WrongSubservice(subsrv) => {
|
||||
write!(f, "pus scheduling: wrong subservice number {subsrv}")
|
||||
}
|
||||
ScheduleError::ByteConversionError(e) => {
|
||||
write!(f, "pus scheduling: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PusError> for ScheduleError {
|
||||
fn from(e: PusError) -> Self {
|
||||
Self::PusError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PoolError> for ScheduleError {
|
||||
fn from(e: PoolError) -> Self {
|
||||
Self::StoreError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimestampError> for ScheduleError {
|
||||
fn from(e: TimestampError) -> Self {
|
||||
Self::TimestampError(e)
|
||||
}
|
||||
}
|
||||
impl From<ByteConversionError> for ScheduleError {
|
||||
fn from(e: ByteConversionError) -> Self {
|
||||
Self::ByteConversionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for ScheduleError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
ScheduleError::PusError(e) => Some(e),
|
||||
ScheduleError::StoreError(e) => Some(e),
|
||||
ScheduleError::TimestampError(e) => Some(e),
|
||||
ScheduleError::ByteConversionError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[error("byte conversion error: {0}")]
|
||||
ByteConversionError(#[from] ByteConversionError),
|
||||
}
|
||||
|
||||
/// Generic trait for scheduler objects which are able to schedule ECSS PUS C packets.
|
||||
pub trait PusSchedulerProvider {
|
||||
pub trait PusScheduler {
|
||||
type TimeProvider: CcsdsTimeProvider + TimeReader;
|
||||
|
||||
fn reset(&mut self, store: &mut (impl PoolProvider + ?Sized)) -> Result<(), PoolError>;
|
||||
@@ -405,7 +335,7 @@ pub mod alloc_mod {
|
||||
///
|
||||
/// Currently, sub-schedules and groups are not supported.
|
||||
#[derive(Debug)]
|
||||
pub struct PusScheduler {
|
||||
pub struct PusSchedulerAlloc {
|
||||
// TODO: Use MonotonicTime from tai-time crate instead of UnixTime and cache leap seconds.
|
||||
// TODO: Introduce optional limit of commands stored in the TC map. If a limit is set,
|
||||
// there will be a check for each insertion whether the map is full, making the memory
|
||||
@@ -415,7 +345,8 @@ pub mod alloc_mod {
|
||||
time_margin: Duration,
|
||||
enabled: bool,
|
||||
}
|
||||
impl PusScheduler {
|
||||
|
||||
impl PusSchedulerAlloc {
|
||||
/// Create a new PUS scheduler.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -427,7 +358,7 @@ pub mod alloc_mod {
|
||||
/// * `tc_buf_size` - Buffer for temporary storage of telecommand packets. This buffer
|
||||
/// should be large enough to accomodate the largest expected TC packets.
|
||||
pub fn new(init_current_time: UnixTime, time_margin: Duration) -> Self {
|
||||
PusScheduler {
|
||||
PusSchedulerAlloc {
|
||||
tc_map: Default::default(),
|
||||
current_time: init_current_time,
|
||||
time_margin,
|
||||
@@ -449,10 +380,12 @@ pub mod alloc_mod {
|
||||
num_entries
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn update_time(&mut self, current_time: UnixTime) {
|
||||
self.current_time = current_time;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_time(&self) -> &UnixTime {
|
||||
&self.current_time
|
||||
}
|
||||
@@ -798,7 +731,7 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
impl PusSchedulerProvider for PusScheduler {
|
||||
impl PusScheduler for PusSchedulerAlloc {
|
||||
type TimeProvider = cds::CdsTime;
|
||||
|
||||
/// This will disable the scheduler and clear the schedule as specified in 6.11.4.4.
|
||||
@@ -982,7 +915,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_enable_api() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
assert!(scheduler.is_enabled());
|
||||
scheduler.disable();
|
||||
assert!(!scheduler.is_enabled());
|
||||
@@ -996,7 +930,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::new(0), &[]);
|
||||
@@ -1038,7 +973,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_multi_with_same_time() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
scheduler
|
||||
.insert_unwrapped_and_stored_tc(
|
||||
@@ -1097,7 +1033,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_time_update() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let time = UnixTime::new(1, 2_000_000);
|
||||
scheduler.update_time(time);
|
||||
assert_eq!(scheduler.current_time(), &time);
|
||||
@@ -1151,7 +1088,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
@@ -1219,7 +1157,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
@@ -1279,7 +1218,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
scheduler.disable();
|
||||
|
||||
@@ -1344,7 +1284,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_unwrapped_tc() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
@@ -1394,7 +1335,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_wrapped_tc() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
@@ -1446,7 +1388,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_wrong_service() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
@@ -1471,7 +1414,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_wrong_subservice() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
@@ -1496,7 +1440,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_wrapped_tc_faulty_app_data() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
@@ -1513,7 +1458,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_doubly_wrapped_time_tagged_cmd() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
@@ -1531,7 +1477,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_ctor_from_current() {
|
||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||
let scheduler = PusSchedulerAlloc::new_with_current_init_time(Duration::from_secs(5))
|
||||
.expect("creation from current time failed");
|
||||
let current_time = scheduler.current_time;
|
||||
assert!(current_time.as_secs() > 0);
|
||||
@@ -1539,7 +1485,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_update_from_current() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
assert_eq!(scheduler.current_time.as_secs(), 0);
|
||||
scheduler
|
||||
.update_time_from_now()
|
||||
@@ -1549,7 +1496,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn release_time_within_time_margin() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(10, 32), (5, 64)],
|
||||
@@ -1582,7 +1530,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
scheduler
|
||||
@@ -1619,7 +1568,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
scheduler
|
||||
@@ -1645,7 +1595,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
scheduler
|
||||
@@ -1666,7 +1617,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
scheduler
|
||||
@@ -1687,7 +1639,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
scheduler
|
||||
@@ -1729,7 +1682,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_full_store_test() {
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
|
||||
vec![(1, 64)],
|
||||
@@ -1745,7 +1699,7 @@ mod tests {
|
||||
assert!(insert_res.is_err());
|
||||
let err = insert_res.unwrap_err();
|
||||
match err {
|
||||
ScheduleError::StoreError(e) => match e {
|
||||
ScheduleError::Pool(e) => match e {
|
||||
PoolError::StoreFull(_) => {}
|
||||
_ => panic!("unexpected store error {e}"),
|
||||
},
|
||||
@@ -1755,7 +1709,7 @@ mod tests {
|
||||
|
||||
fn insert_command_with_release_time(
|
||||
pool: &mut StaticMemoryPool,
|
||||
scheduler: &mut PusScheduler,
|
||||
scheduler: &mut PusSchedulerAlloc,
|
||||
seq_count: u14,
|
||||
release_secs: u64,
|
||||
) -> TcInfo {
|
||||
@@ -1774,7 +1728,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
assert_eq!(scheduler.num_scheduled_telecommands(), 2);
|
||||
@@ -1806,7 +1761,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let _ = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
let tc_info_2 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 150);
|
||||
@@ -1841,7 +1797,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
let _ = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 150);
|
||||
@@ -1876,7 +1833,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let _ = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
let tc_info_2 = insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 150);
|
||||
@@ -1917,7 +1875,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
assert_eq!(scheduler.num_scheduled_telecommands(), 2);
|
||||
@@ -1946,7 +1905,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let cmd_0_to_delete =
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 100);
|
||||
@@ -1973,7 +1933,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let cmd_0_to_delete =
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let cmd_1_to_delete =
|
||||
@@ -2001,7 +1962,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let cmd_out_of_range_0 =
|
||||
insert_command_with_release_time(&mut pool, &mut scheduler, u14::ZERO, 50);
|
||||
let cmd_0_to_delete =
|
||||
@@ -2039,7 +2001,8 @@ mod tests {
|
||||
vec![(10, 32), (5, 64)],
|
||||
false,
|
||||
));
|
||||
let mut scheduler = PusScheduler::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
let mut scheduler =
|
||||
PusSchedulerAlloc::new(UnixTime::new_only_secs(0), Duration::from_secs(5));
|
||||
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, u14::ZERO, &[]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::scheduler::PusSchedulerProvider;
|
||||
use super::scheduler::PusScheduler;
|
||||
use super::verification::{VerificationReporter, VerificationReportingProvider};
|
||||
use super::{
|
||||
CacheAndReadRawEcssTc, DirectPusPacketHandlerResult, EcssTcInSharedPoolCacher, EcssTcReceiver,
|
||||
@@ -26,11 +26,11 @@ pub struct PusSchedServiceHandler<
|
||||
TmSender: EcssTmSender,
|
||||
TcInMemConverter: CacheAndReadRawEcssTc,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
PusScheduler: PusSchedulerProvider,
|
||||
PusSchedulerInstance: PusScheduler,
|
||||
> {
|
||||
pub service_helper:
|
||||
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
scheduler: PusScheduler,
|
||||
scheduler: PusSchedulerInstance,
|
||||
}
|
||||
|
||||
impl<
|
||||
@@ -38,7 +38,7 @@ impl<
|
||||
TmSender: EcssTmSender,
|
||||
TcInMemConverter: CacheAndReadRawEcssTc,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
Scheduler: PusSchedulerProvider,
|
||||
Scheduler: PusScheduler,
|
||||
> PusSchedServiceHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter, Scheduler>
|
||||
{
|
||||
pub fn new(
|
||||
@@ -254,7 +254,7 @@ mod tests {
|
||||
use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError};
|
||||
use crate::pus::{
|
||||
EcssTcInSharedPoolCacher,
|
||||
scheduler::{self, PusSchedulerProvider, TcInfo},
|
||||
scheduler::{self, PusScheduler, TcInfo},
|
||||
tests::PusServiceHandlerWithSharedStoreCommon,
|
||||
verification::{RequestId, TcStateAccepted, VerificationToken},
|
||||
};
|
||||
@@ -349,7 +349,7 @@ mod tests {
|
||||
inserted_tcs: VecDeque<TcInfo>,
|
||||
}
|
||||
|
||||
impl PusSchedulerProvider for TestScheduler {
|
||||
impl PusScheduler for TestScheduler {
|
||||
type TimeProvider = cds::CdsTime;
|
||||
|
||||
fn reset(
|
||||
|
||||
Reference in New Issue
Block a user