MIO TCP client #6
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -483,6 +483,7 @@ dependencies = [
|
|||||||
"fern",
|
"fern",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
"mio",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"satrs",
|
"satrs",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
|
@ -14,6 +14,7 @@ strum = { version = "0.26", features = ["derive"] }
|
|||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
derive-new = "0.6"
|
derive-new = "0.6"
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
|
mio = "0.8"
|
||||||
|
|
||||||
[dependencies.satrs]
|
[dependencies.satrs]
|
||||||
version = "0.2.0-rc.0"
|
version = "0.2.0-rc.0"
|
||||||
|
@ -34,6 +34,7 @@ appears to be a useful source for documentation.
|
|||||||
|
|
||||||
- [OBSW documents](https://opssat1.esoc.esa.int/projects/experimenter-information/dmsf?folder_id=7)
|
- [OBSW documents](https://opssat1.esoc.esa.int/projects/experimenter-information/dmsf?folder_id=7)
|
||||||
- [Software Integration Process](https://opssat1.esoc.esa.int/dmsf/files/34/view)
|
- [Software Integration Process](https://opssat1.esoc.esa.int/dmsf/files/34/view)
|
||||||
|
- [SPP/TCP bridge](https://opssat1.esoc.esa.int/dmsf/files/65/view)
|
||||||
- [Cross-compiling SEPP](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Cross-compiling_SEPP_application)
|
- [Cross-compiling SEPP](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Cross-compiling_SEPP_application)
|
||||||
- [TMTC infrastructure](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Live_TM_TC_data)
|
- [TMTC infrastructure](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Live_TM_TC_data)
|
||||||
- [Submitting an Experiment](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Building_and_submitting_your_application_to_ESOC)
|
- [Submitting an Experiment](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Building_and_submitting_your_application_to_ESOC)
|
||||||
|
@ -11,6 +11,7 @@ pub const HOME_FOLER_EXPERIMENT: &str = "/home/exp278";
|
|||||||
|
|
||||||
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
||||||
pub const SERVER_PORT: u16 = 7301;
|
pub const SERVER_PORT: u16 = 7301;
|
||||||
|
pub const TCP_SPP_SERVER_PORT: u16 = 4096;
|
||||||
pub const EXPERIMENT_ID: u32 = 278;
|
pub const EXPERIMENT_ID: u32 = 278;
|
||||||
pub const EXPERIMENT_APID: u16 = 1024 + EXPERIMENT_ID as u16;
|
pub const EXPERIMENT_APID: u16 = 1024 + EXPERIMENT_ID as u16;
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub mod can;
|
pub mod can;
|
||||||
pub mod tcp;
|
pub mod tcp_server;
|
||||||
pub mod udp;
|
pub mod tcp_spp_client;
|
||||||
|
pub mod udp_server;
|
||||||
|
83
src/interface/tcp_spp_client.rs
Normal file
83
src/interface/tcp_spp_client.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use std::io::{self, Read};
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use mio::net::TcpStream;
|
||||||
|
use mio::{Events, Interest, Poll, Token};
|
||||||
|
use ops_sat_rs::config::TCP_SPP_SERVER_PORT;
|
||||||
|
use satrs::spacepackets::ecss::CCSDS_HEADER_LEN;
|
||||||
|
use satrs::spacepackets::{CcsdsPacket, SpHeader};
|
||||||
|
|
||||||
|
pub struct TcpSppClient {
|
||||||
|
poll: Poll,
|
||||||
|
events: Events,
|
||||||
|
client: TcpStream,
|
||||||
|
read_buf: [u8; 4096],
|
||||||
|
tc_source_tx: mpsc::Sender<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpSppClient {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let poll = Poll::new()?;
|
||||||
|
let events = Events::with_capacity(128);
|
||||||
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 1)), TCP_SPP_SERVER_PORT);
|
||||||
|
let mut client = TcpStream::connect(addr)?;
|
||||||
|
poll.registry().register(
|
||||||
|
&mut client,
|
||||||
|
Token(0),
|
||||||
|
Interest::READABLE | Interest::WRITABLE,
|
||||||
|
)?;
|
||||||
|
Ok(Self {
|
||||||
|
poll,
|
||||||
|
events,
|
||||||
|
client,
|
||||||
|
read_buf: [0; 4096],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) -> io::Result<()> {
|
||||||
|
self.poll.poll(&mut self.events, None)?;
|
||||||
|
let events: Vec<mio::event::Event> = self.events.iter().cloned().collect();
|
||||||
|
for event in events {
|
||||||
|
if event.token() == Token(0) {
|
||||||
|
if event.is_readable() {
|
||||||
|
self.read_from_server()?;
|
||||||
|
}
|
||||||
|
if event.is_writable() {
|
||||||
|
// Read packets from a queue and send them here..
|
||||||
|
// self.client.write_all(b"hello")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from_server(&mut self) -> io::Result<()> {
|
||||||
|
match self.client.read(&mut self.read_buf) {
|
||||||
|
Ok(0) => return Err(io::Error::from(io::ErrorKind::BrokenPipe)),
|
||||||
|
Ok(n) => {
|
||||||
|
if n < CCSDS_HEADER_LEN + 1 {
|
||||||
|
log::warn!("received packet smaller than minimum expected packet size.");
|
||||||
|
log::debug!("{:?}", &self.read_buf[..n]);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// We already checked that the received size has the minimum expected size.
|
||||||
|
let (sp_header, data) =
|
||||||
|
SpHeader::from_be_bytes(&self.read_buf[..n]).expect("parsing SP header failed");
|
||||||
|
let full_expected_packet_len = sp_header.total_len();
|
||||||
|
// We received an incomplete frame?
|
||||||
|
if n < full_expected_packet_len {
|
||||||
|
log::warn!(
|
||||||
|
"received incomplete SPP, with detected packet length {} but read buffer length {}",
|
||||||
|
full_expected_packet_len, n
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.tc_source_tx
|
||||||
|
.send(self.read_buf[0..full_expected_packet_len].to_vec());
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -23,8 +23,8 @@ use crate::tmtc::tm_funnel::TmFunnelDynamic;
|
|||||||
use crate::tmtc::TcSourceTaskDynamic;
|
use crate::tmtc::TcSourceTaskDynamic;
|
||||||
use crate::{controller::ExperimentController, pus::test::create_test_service};
|
use crate::{controller::ExperimentController, pus::test::create_test_service};
|
||||||
use crate::{
|
use crate::{
|
||||||
interface::tcp::{SyncTcpTmSource, TcpTask},
|
interface::tcp_server::{SyncTcpTmSource, TcpTask},
|
||||||
interface::udp::{DynamicUdpTmHandler, UdpTmtcServer},
|
interface::udp_server::{DynamicUdpTmHandler, UdpTmtcServer},
|
||||||
logger::setup_logger,
|
logger::setup_logger,
|
||||||
tmtc::ccsds::CcsdsReceiver,
|
tmtc::ccsds::CcsdsReceiver,
|
||||||
tmtc::PusTcSourceProviderDynamic,
|
tmtc::PusTcSourceProviderDynamic,
|
||||||
|
@ -59,7 +59,7 @@ pub struct PusTcMpscRouter {
|
|||||||
// pub mode_tc_sender: Sender<EcssTcAndToken>,
|
// pub mode_tc_sender: Sender<EcssTcAndToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PusReceiver<TmSender: EcssTmSenderCore> {
|
pub struct PusTcDistributor<TmSender: EcssTmSenderCore> {
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
pub tm_sender: TmSender,
|
pub tm_sender: TmSender,
|
||||||
pub verif_reporter: VerificationReporter,
|
pub verif_reporter: VerificationReporter,
|
||||||
@ -67,7 +67,7 @@ pub struct PusReceiver<TmSender: EcssTmSenderCore> {
|
|||||||
stamp_helper: TimeStampHelper,
|
stamp_helper: TimeStampHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSenderCore> PusReceiver<TmSender> {
|
impl<TmSender: EcssTmSenderCore> PusTcDistributor<TmSender> {
|
||||||
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PUS_ROUTING_SERVICE.raw(),
|
id: PUS_ROUTING_SERVICE.raw(),
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
use ops_sat_rs::config::EXPERIMENT_APID;
|
|
||||||
use satrs::pus::ReceivesEcssPusTc;
|
|
||||||
use satrs::spacepackets::{CcsdsPacket, SpHeader};
|
|
||||||
use satrs::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
|
|
||||||
use satrs::ValidatorU16Id;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CcsdsReceiver<
|
|
||||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone,
|
|
||||||
E,
|
|
||||||
> {
|
|
||||||
pub tc_source: TcSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
|
|
||||||
E: 'static,
|
|
||||||
> ValidatorU16Id for CcsdsReceiver<TcSource, E>
|
|
||||||
{
|
|
||||||
fn validate(&self, apid: u16) -> bool {
|
|
||||||
apid == EXPERIMENT_APID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
|
|
||||||
E: 'static,
|
|
||||||
> CcsdsPacketHandler for CcsdsReceiver<TcSource, E>
|
|
||||||
{
|
|
||||||
type Error = E;
|
|
||||||
|
|
||||||
fn handle_packet_with_valid_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
self.tc_source.pass_ccsds(sp_header, tc_raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_packet_with_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
_tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
// TODO: Log event as telemetry or something similar?
|
|
||||||
log::warn!("unknown APID 0x{:x?} detected", sp_header.apid());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +1,2 @@
|
|||||||
use crate::pus::PusReceiver;
|
pub mod tc_source;
|
||||||
use satrs::pool::{StoreAddr, StoreError};
|
pub mod tm_sink;
|
||||||
use satrs::pus::{EcssTcAndToken, MpscTmAsVecSender};
|
|
||||||
use satrs::spacepackets::ecss::PusPacket;
|
|
||||||
use satrs::{
|
|
||||||
pus::ReceivesEcssPusTc,
|
|
||||||
spacepackets::{ecss::tc::PusTcReader, SpHeader},
|
|
||||||
tmtc::ReceivesCcsdsTc,
|
|
||||||
};
|
|
||||||
use std::sync::mpsc::{self, SendError, Sender, TryRecvError};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub mod ccsds;
|
|
||||||
pub mod tm_funnel;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
|
||||||
pub enum MpscStoreAndSendError {
|
|
||||||
#[error("Store error: {0}")]
|
|
||||||
Store(#[from] StoreError),
|
|
||||||
#[error("TC send error: {0}")]
|
|
||||||
TcSend(#[from] SendError<EcssTcAndToken>),
|
|
||||||
#[error("TMTC send error: {0}")]
|
|
||||||
TmTcSend(#[from] SendError<StoreAddr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newtype, can not implement necessary traits on MPSC sender directly because of orphan rules.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PusTcSourceProviderDynamic(pub Sender<Vec<u8>>);
|
|
||||||
|
|
||||||
impl ReceivesEcssPusTc for PusTcSourceProviderDynamic {
|
|
||||||
type Error = SendError<Vec<u8>>;
|
|
||||||
|
|
||||||
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
|
||||||
self.0.send(pus_tc.raw_data().to_vec())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReceivesCcsdsTc for PusTcSourceProviderDynamic {
|
|
||||||
type Error = mpsc::SendError<Vec<u8>>;
|
|
||||||
|
|
||||||
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
self.0.send(tc_raw.to_vec())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TC source components where the heap is the backing memory of the received telecommands.
|
|
||||||
pub struct TcSourceTaskDynamic {
|
|
||||||
pub tc_receiver: mpsc::Receiver<Vec<u8>>,
|
|
||||||
pus_receiver: PusReceiver<MpscTmAsVecSender>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcSourceTaskDynamic {
|
|
||||||
pub fn new(
|
|
||||||
tc_receiver: mpsc::Receiver<Vec<u8>>,
|
|
||||||
pus_receiver: PusReceiver<MpscTmAsVecSender>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
tc_receiver,
|
|
||||||
pus_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
self.poll_tc();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_tc(&mut self) -> bool {
|
|
||||||
match self.tc_receiver.try_recv() {
|
|
||||||
Ok(tc) => match PusTcReader::new(&tc) {
|
|
||||||
Ok((pus_tc, _)) => {
|
|
||||||
self.pus_receiver
|
|
||||||
.handle_tc_packet(
|
|
||||||
satrs::pus::TcInMemory::Vec(tc.clone()),
|
|
||||||
pus_tc.service(),
|
|
||||||
&pus_tc,
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("error creating PUS TC from raw data: {e}");
|
|
||||||
log::warn!("raw data: {:x?}", tc);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => false,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
log::warn!("tmtc thread: sender disconnected");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
128
src/tmtc/tc_source.rs
Normal file
128
src/tmtc/tc_source.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use satrs::{pool::PoolProvider, tmtc::tc_helper::SharedTcPool};
|
||||||
|
use std::sync::mpsc::{self, TryRecvError};
|
||||||
|
|
||||||
|
use satrs::{
|
||||||
|
pool::StoreAddr,
|
||||||
|
pus::{MpscTmAsVecSender, MpscTmInSharedPoolSenderBounded},
|
||||||
|
spacepackets::ecss::{tc::PusTcReader, PusPacket},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::pus::PusTcDistributor;
|
||||||
|
|
||||||
|
// TC source components where static pools are the backing memory of the received telecommands.
|
||||||
|
pub struct TcSourceTaskStatic {
|
||||||
|
shared_tc_pool: SharedTcPool,
|
||||||
|
tc_receiver: mpsc::Receiver<StoreAddr>,
|
||||||
|
tc_buf: [u8; 4096],
|
||||||
|
pus_receiver: PusTcDistributor<MpscTmInSharedPoolSenderBounded>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcSourceTaskStatic {
|
||||||
|
pub fn new(
|
||||||
|
shared_tc_pool: SharedTcPool,
|
||||||
|
tc_receiver: mpsc::Receiver<StoreAddr>,
|
||||||
|
pus_receiver: PusTcDistributor<MpscTmInSharedPoolSenderBounded>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
shared_tc_pool,
|
||||||
|
tc_receiver,
|
||||||
|
tc_buf: [0; 4096],
|
||||||
|
pus_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.poll_tc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_tc(&mut self) -> bool {
|
||||||
|
match self.tc_receiver.try_recv() {
|
||||||
|
Ok(addr) => {
|
||||||
|
let pool = self
|
||||||
|
.shared_tc_pool
|
||||||
|
.0
|
||||||
|
.read()
|
||||||
|
.expect("locking tc pool failed");
|
||||||
|
pool.read(&addr, &mut self.tc_buf)
|
||||||
|
.expect("reading pool failed");
|
||||||
|
drop(pool);
|
||||||
|
match PusTcReader::new(&self.tc_buf) {
|
||||||
|
Ok((pus_tc, _)) => {
|
||||||
|
self.pus_receiver
|
||||||
|
.handle_tc_packet(
|
||||||
|
satrs::pus::TcInMemory::StoreAddr(addr),
|
||||||
|
pus_tc.service(),
|
||||||
|
&pus_tc,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("error creating PUS TC from raw data: {e}");
|
||||||
|
log::warn!("raw data: {:x?}", self.tc_buf);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
TryRecvError::Empty => false,
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
log::warn!("tmtc thread: sender disconnected");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TC source components where the heap is the backing memory of the received telecommands.
|
||||||
|
pub struct TcSourceTaskDynamic {
|
||||||
|
pub tc_receiver: mpsc::Receiver<Vec<u8>>,
|
||||||
|
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcSourceTaskDynamic {
|
||||||
|
pub fn new(
|
||||||
|
tc_receiver: mpsc::Receiver<Vec<u8>>,
|
||||||
|
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tc_receiver,
|
||||||
|
pus_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.poll_tc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_tc(&mut self) -> bool {
|
||||||
|
// Right now, we only expect PUS packets.
|
||||||
|
match self.tc_receiver.try_recv() {
|
||||||
|
Ok(tc) => match PusTcReader::new(&tc) {
|
||||||
|
Ok((pus_tc, _)) => {
|
||||||
|
self.pus_receiver
|
||||||
|
.handle_tc_packet(
|
||||||
|
satrs::pus::TcInMemory::Vec(tc.clone()),
|
||||||
|
pus_tc.service(),
|
||||||
|
&pus_tc,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("error creating PUS TC from raw data: {e}");
|
||||||
|
log::warn!("raw data: {:x?}", tc);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => match e {
|
||||||
|
TryRecvError::Empty => false,
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
log::warn!("tmtc thread: sender disconnected");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ use satrs::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::interface::tcp::SyncTcpTmSource;
|
use crate::interface::tcp_server::SyncTcpTmSource;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CcsdsSeqCounterMap {
|
pub struct CcsdsSeqCounterMap {
|
Loading…
Reference in New Issue
Block a user