Merge pull request 'Move to CCSDS + serde, rip out PUS' (#259) from move-to-ccsds-and-serde into main
Reviewed-on: #259
This commit was merged in pull request #259.
This commit is contained in:
+3
-1
@@ -4,7 +4,9 @@ members = [
|
|||||||
"satrs",
|
"satrs",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
"satrs-example",
|
"satrs-example",
|
||||||
"satrs-minisim",
|
"satrs-example/models",
|
||||||
|
"satrs-example/client",
|
||||||
|
"satrs-example/minisim",
|
||||||
"satrs-shared",
|
"satrs-shared",
|
||||||
"embedded-examples/embedded-client",
|
"embedded-examples/embedded-client",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -23,23 +23,18 @@ derive-new = "0.7"
|
|||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
arbitrary-int = "2"
|
arbitrary-int = "2"
|
||||||
bitbybit = "1.4"
|
bitbybit = "1.4"
|
||||||
|
postcard = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
[dependencies.satrs]
|
satrs = { path = "../satrs", features = ["test_util"] }
|
||||||
path = "../satrs"
|
models = { path = "./models" }
|
||||||
features = ["test_util"]
|
satrs-minisim = { path = "./minisim" }
|
||||||
|
satrs-mib = { path = "../satrs-mib" }
|
||||||
[dependencies.satrs-minisim]
|
|
||||||
path = "../satrs-minisim"
|
|
||||||
|
|
||||||
[dependencies.satrs-mib]
|
|
||||||
version = "0.1.1"
|
|
||||||
path = "../satrs-mib"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["heap_tmtc"]
|
# default = ["heap_tmtc"]
|
||||||
heap_tmtc = []
|
# heap_tmtc = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
log = "0.4"
|
||||||
|
fern = "0.7"
|
||||||
|
humantime = "2"
|
||||||
|
serde = { version = "1" }
|
||||||
|
satrs-example = { path = ".." }
|
||||||
|
models = { path = "../models" }
|
||||||
|
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
|
||||||
|
bitbybit = "1.4"
|
||||||
|
arbitrary-int = "2"
|
||||||
|
ctrlc = { version = "3.5" }
|
||||||
|
postcard = { version = "1" }
|
||||||
|
anyhow = "1"
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
use arbitrary_int::u11;
|
||||||
|
use clap::Parser as _;
|
||||||
|
use models::{Apid, MessageType, TcHeader};
|
||||||
|
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
||||||
|
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
|
||||||
|
use std::{
|
||||||
|
net::{IpAddr, SocketAddr, UdpSocket},
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[arg(short, long)]
|
||||||
|
ping: bool,
|
||||||
|
#[arg(short, long)]
|
||||||
|
test_event: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> {
|
||||||
|
fern::Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{} {} {}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
record.level(),
|
||||||
|
record.target(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.level(level)
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file("output.log")?)
|
||||||
|
.apply()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
setup_logger(log::LevelFilter::Debug).unwrap();
|
||||||
|
let kill_signal = Arc::new(AtomicBool::new(false));
|
||||||
|
let ctrl_kill_signal = kill_signal.clone();
|
||||||
|
ctrlc::set_handler(move || ctrl_kill_signal.store(true, Ordering::Relaxed)).unwrap();
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
|
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
||||||
|
client.set_nonblocking(true)?;
|
||||||
|
client.set_read_timeout(Some(Duration::from_millis(200)))?;
|
||||||
|
|
||||||
|
if cli.ping {
|
||||||
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
|
||||||
|
TcHeader::new(models::ComponentId::Controller, models::MessageType::Ping),
|
||||||
|
models::control::request::Request::Ping,
|
||||||
|
);
|
||||||
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
|
log::info!("sending ping request with TC ID {:#010x}", sent_tc_id.raw());
|
||||||
|
let request_packet = request.to_vec();
|
||||||
|
client.send_to(&request_packet, addr).unwrap();
|
||||||
|
}
|
||||||
|
if cli.test_event {
|
||||||
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
|
||||||
|
TcHeader::new(models::ComponentId::Controller, models::MessageType::Event),
|
||||||
|
models::control::request::Request::TestEvent,
|
||||||
|
);
|
||||||
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
|
log::info!(
|
||||||
|
"sending event request with TC ID {:#010x}",
|
||||||
|
sent_tc_id.raw()
|
||||||
|
);
|
||||||
|
let request_packet = request.to_vec();
|
||||||
|
client.send_to(&request_packet, addr).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut recv_buf: Box<[u8; 2048]> = Box::new([0; 2048]);
|
||||||
|
log::info!("entering listening loop");
|
||||||
|
loop {
|
||||||
|
if kill_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
log::info!("received kill signal, exiting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match client.recv(recv_buf.as_mut_slice()) {
|
||||||
|
Ok(received_bytes) => handle_raw_tm_packet(&recv_buf.as_slice()[0..received_bytes])?,
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == std::io::ErrorKind::WouldBlock
|
||||||
|
|| e.kind() == std::io::ErrorKind::TimedOut
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log::warn!("UDP reception error: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
|
||||||
|
match spacepackets::CcsdsPacketReader::new_with_checksum(data) {
|
||||||
|
Ok(packet) => {
|
||||||
|
//let (tm_header, response, remainder) = unpack_tm_header_and_response(&packet)?;
|
||||||
|
let tm_header_result =
|
||||||
|
postcard::take_from_bytes::<models::TmHeader>(packet.user_data());
|
||||||
|
if let Err(e) = tm_header_result {
|
||||||
|
bail!("Failed to deserialize TM header: {}", e);
|
||||||
|
}
|
||||||
|
let (tm_header, remainder) = tm_header_result.unwrap();
|
||||||
|
if let Some(tc_id) = tm_header.tc_id {
|
||||||
|
log::info!(
|
||||||
|
"Received TM with APID {} and from sender {:?} for TC ID {:#010x}",
|
||||||
|
packet.apid(),
|
||||||
|
tm_header.sender_id,
|
||||||
|
tc_id.raw()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"Received unsolicited TM with APID {} and from sender {:?}",
|
||||||
|
packet.apid(),
|
||||||
|
tm_header.sender_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if tm_header.message_type == MessageType::Event {
|
||||||
|
let response = postcard::from_bytes::<models::Event>(remainder);
|
||||||
|
log::info!(
|
||||||
|
"Received event from {:?}: {:?}",
|
||||||
|
tm_header.sender_id,
|
||||||
|
response.unwrap()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
match tm_header.sender_id {
|
||||||
|
models::ComponentId::EpsPcdu => {
|
||||||
|
let response =
|
||||||
|
postcard::from_bytes::<models::pcdu::response::Response>(remainder);
|
||||||
|
log::info!("Received response from PCDU: {:?}", response.unwrap());
|
||||||
|
}
|
||||||
|
models::ComponentId::Controller => {
|
||||||
|
let response =
|
||||||
|
postcard::from_bytes::<models::control::response::Response>(remainder);
|
||||||
|
log::info!("Received response from controller: {:?}", response.unwrap());
|
||||||
|
}
|
||||||
|
models::ComponentId::AcsSubsystem => todo!(),
|
||||||
|
models::ComponentId::AcsMgmAssembly => todo!(),
|
||||||
|
models::ComponentId::AcsMgm0 => todo!(),
|
||||||
|
models::ComponentId::AcsMgm1 => todo!(),
|
||||||
|
models::ComponentId::EpsSubsystem => todo!(),
|
||||||
|
models::ComponentId::UdpServer => todo!(),
|
||||||
|
models::ComponentId::TcpServer => todo!(),
|
||||||
|
models::ComponentId::Ground => todo!(),
|
||||||
|
models::ComponentId::EventManager => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => todo!(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -15,12 +15,10 @@ strum = { version = "0.27", features = ["derive"] }
|
|||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
humantime = "2"
|
humantime = "2"
|
||||||
tai-time = { version = "0.3", features = ["serde"] }
|
tai-time = { version = "0.3", features = ["serde"] }
|
||||||
|
nexosim = { version = "0.3.1" }
|
||||||
|
|
||||||
[dependencies.nexosim]
|
satrs = { path = "../../satrs" }
|
||||||
version = "0.3.1"
|
models = { path = "../models" }
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
path = "../satrs"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
delegate = "0.13"
|
delegate = "0.13"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::{f32::consts::PI, sync::mpsc, time::Duration};
|
use std::{f32::consts::PI, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
use models::pcdu::SwitchStateBinary;
|
||||||
use nexosim::{
|
use nexosim::{
|
||||||
model::{Context, Model},
|
model::{Context, Model},
|
||||||
ports::Output,
|
ports::Output,
|
||||||
};
|
};
|
||||||
use satrs::power::SwitchStateBinary;
|
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{
|
acs::{
|
||||||
lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla,
|
lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla,
|
||||||
@@ -179,13 +179,12 @@ impl Model for MagnetorquerModel {}
|
|||||||
pub mod tests {
|
pub mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use satrs::power::SwitchStateBinary;
|
use models::pcdu::{SwitchId, SwitchStateBinary};
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{
|
acs::{
|
||||||
lis3mdl::{self, MgmLis3MdlReply},
|
lis3mdl::{self, MgmLis3MdlReply},
|
||||||
MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest,
|
MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest,
|
||||||
},
|
},
|
||||||
eps::PcduSwitch,
|
|
||||||
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic_mgm_request_switched_on() {
|
fn test_basic_mgm_request_switched_on() {
|
||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
switch_device_on(&mut sim_testbench, PcduSwitch::Mgm);
|
switch_device_on(&mut sim_testbench, SwitchId::Mgm0);
|
||||||
|
|
||||||
let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
||||||
sim_testbench
|
sim_testbench
|
||||||
@@ -279,7 +278,7 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic_mgt_request_is_on() {
|
fn test_basic_mgt_request_is_on() {
|
||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
switch_device_on(&mut sim_testbench, PcduSwitch::Mgt);
|
switch_device_on(&mut sim_testbench, SwitchId::Mgt);
|
||||||
let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk);
|
let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk);
|
||||||
|
|
||||||
sim_testbench
|
sim_testbench
|
||||||
@@ -324,7 +323,7 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic_mgt_request_is_on_and_torquing() {
|
fn test_basic_mgt_request_is_on_and_torquing() {
|
||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
switch_device_on(&mut sim_testbench, PcduSwitch::Mgt);
|
switch_device_on(&mut sim_testbench, SwitchId::Mgt);
|
||||||
let commanded_dipole = MgtDipole {
|
let commanded_dipole = MgtDipole {
|
||||||
x: -200,
|
x: -200,
|
||||||
y: 200,
|
y: 200,
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
use std::{sync::mpsc, time::Duration};
|
use std::{sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
use models::pcdu::{SwitchId, SwitchMapBinaryWrapper, SwitchStateBinary};
|
||||||
use nexosim::{
|
use nexosim::{
|
||||||
model::{Context, Model},
|
model::{Context, Model},
|
||||||
ports::Output,
|
ports::Output,
|
||||||
};
|
};
|
||||||
use satrs::power::SwitchStateBinary;
|
use satrs_minisim::{eps::PcduReply, SimReply};
|
||||||
use satrs_minisim::{
|
|
||||||
eps::{PcduReply, PcduSwitch, SwitchMapBinaryWrapper},
|
|
||||||
SimReply,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
||||||
|
|
||||||
@@ -45,10 +42,7 @@ impl PcduModel {
|
|||||||
self.reply_sender.send(reply).unwrap();
|
self.reply_sender.send(reply).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_device(
|
pub async fn switch_device(&mut self, switch_and_target_state: (SwitchId, SwitchStateBinary)) {
|
||||||
&mut self,
|
|
||||||
switch_and_target_state: (PcduSwitch, SwitchStateBinary),
|
|
||||||
) {
|
|
||||||
let val = self
|
let val = self
|
||||||
.switcher_map
|
.switcher_map
|
||||||
.0
|
.0
|
||||||
@@ -56,12 +50,13 @@ impl PcduModel {
|
|||||||
.unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0));
|
.unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0));
|
||||||
*val = switch_and_target_state.1;
|
*val = switch_and_target_state.1;
|
||||||
match switch_and_target_state.0 {
|
match switch_and_target_state.0 {
|
||||||
PcduSwitch::Mgm => {
|
SwitchId::Mgm0 => {
|
||||||
self.mgm_0_switch.send(switch_and_target_state.1).await;
|
self.mgm_0_switch.send(switch_and_target_state.1).await;
|
||||||
}
|
}
|
||||||
PcduSwitch::Mgt => {
|
SwitchId::Mgt => {
|
||||||
self.mgt_switch.send(switch_and_target_state.1).await;
|
self.mgt_switch.send(switch_and_target_state.1).await;
|
||||||
}
|
}
|
||||||
|
SwitchId::Mgm1 => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,16 +68,16 @@ pub(crate) mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use models::pcdu::SwitchMapBinary;
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
eps::{PcduRequest, SwitchMapBinary},
|
eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
||||||
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::test_helpers::SimTestbench;
|
use crate::test_helpers::SimTestbench;
|
||||||
|
|
||||||
fn switch_device(
|
fn switch_device(
|
||||||
sim_testbench: &mut SimTestbench,
|
sim_testbench: &mut SimTestbench,
|
||||||
switch: PcduSwitch,
|
switch: SwitchId,
|
||||||
target: SwitchStateBinary,
|
target: SwitchStateBinary,
|
||||||
) {
|
) {
|
||||||
let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice {
|
let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice {
|
||||||
@@ -97,10 +92,10 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: PcduSwitch) {
|
pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: SwitchId) {
|
||||||
switch_device(sim_testbench, switch, SwitchStateBinary::Off);
|
switch_device(sim_testbench, switch, SwitchStateBinary::Off);
|
||||||
}
|
}
|
||||||
pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: PcduSwitch) {
|
pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: SwitchId) {
|
||||||
switch_device(sim_testbench, switch, SwitchStateBinary::On);
|
switch_device(sim_testbench, switch, SwitchStateBinary::On);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +123,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_pcdu_switching_single_switch(switch: PcduSwitch, target: SwitchStateBinary) {
|
fn test_pcdu_switching_single_switch(switch: SwitchId, target: SwitchStateBinary) {
|
||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
switch_device(&mut sim_testbench, switch, target);
|
switch_device(&mut sim_testbench, switch, target);
|
||||||
let mut switcher_map = get_all_off_switch_map();
|
let mut switcher_map = get_all_off_switch_map();
|
||||||
@@ -165,17 +160,17 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pcdu_switching_mgm_on() {
|
fn test_pcdu_switching_mgm_on() {
|
||||||
test_pcdu_switching_single_switch(PcduSwitch::Mgm, SwitchStateBinary::On);
|
test_pcdu_switching_single_switch(SwitchId::Mgm0, SwitchStateBinary::On);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pcdu_switching_mgt_on() {
|
fn test_pcdu_switching_mgt_on() {
|
||||||
test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On);
|
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pcdu_switching_mgt_off() {
|
fn test_pcdu_switching_mgt_off() {
|
||||||
test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On);
|
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On);
|
||||||
test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::Off);
|
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::Off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
use nexosim::time::MonotonicTime;
|
use nexosim::time::MonotonicTime;
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
@@ -162,73 +161,7 @@ impl From<SimRequestError> for SimCtrlReply {
|
|||||||
|
|
||||||
pub mod eps {
|
pub mod eps {
|
||||||
use super::*;
|
use super::*;
|
||||||
use satrs::power::{SwitchState, SwitchStateBinary};
|
use models::pcdu::{SwitchId, SwitchMapBinary, SwitchStateBinary};
|
||||||
use std::collections::HashMap;
|
|
||||||
use strum::{EnumIter, IntoEnumIterator};
|
|
||||||
|
|
||||||
pub type SwitchMap = HashMap<PcduSwitch, SwitchState>;
|
|
||||||
pub type SwitchMapBinary = HashMap<PcduSwitch, SwitchStateBinary>;
|
|
||||||
|
|
||||||
pub struct SwitchMapWrapper(pub SwitchMap);
|
|
||||||
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Debug,
|
|
||||||
Copy,
|
|
||||||
Clone,
|
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
Hash,
|
|
||||||
EnumIter,
|
|
||||||
IntoPrimitive,
|
|
||||||
TryFromPrimitive,
|
|
||||||
)]
|
|
||||||
#[repr(u16)]
|
|
||||||
pub enum PcduSwitch {
|
|
||||||
Mgm = 0,
|
|
||||||
Mgt = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SwitchMapBinaryWrapper {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut switch_map = SwitchMapBinary::default();
|
|
||||||
for entry in PcduSwitch::iter() {
|
|
||||||
switch_map.insert(entry, SwitchStateBinary::Off);
|
|
||||||
}
|
|
||||||
Self(switch_map)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SwitchMapWrapper {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut switch_map = SwitchMap::default();
|
|
||||||
for entry in PcduSwitch::iter() {
|
|
||||||
switch_map.insert(entry, SwitchState::Unknown);
|
|
||||||
}
|
|
||||||
Self(switch_map)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwitchMapWrapper {
|
|
||||||
pub fn new_with_init_switches_off() -> Self {
|
|
||||||
let mut switch_map = SwitchMap::default();
|
|
||||||
for entry in PcduSwitch::iter() {
|
|
||||||
switch_map.insert(entry, SwitchState::Off);
|
|
||||||
}
|
|
||||||
Self(switch_map)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self {
|
|
||||||
Self(
|
|
||||||
switch_map
|
|
||||||
.iter()
|
|
||||||
.map(|(key, value)| (*key, SwitchState::from(*value)))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -240,7 +173,7 @@ pub mod eps {
|
|||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PcduRequest {
|
pub enum PcduRequest {
|
||||||
SwitchDevice {
|
SwitchDevice {
|
||||||
switch: PcduSwitch,
|
switch: SwitchId,
|
||||||
state: SwitchStateBinary,
|
state: SwitchStateBinary,
|
||||||
},
|
},
|
||||||
RequestSwitchInfo,
|
RequestSwitchInfo,
|
||||||
@@ -264,7 +197,7 @@ pub mod eps {
|
|||||||
pub mod acs {
|
pub mod acs {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use satrs::power::SwitchStateBinary;
|
use models::pcdu::SwitchStateBinary;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -91,11 +91,8 @@ impl SimUdpServer {
|
|||||||
self.sender_addr = Some(src);
|
self.sender_addr = Some(src);
|
||||||
|
|
||||||
let sim_req = SimRequest::from_raw_data(&self.req_buf[..bytes_read]);
|
let sim_req = SimRequest::from_raw_data(&self.req_buf[..bytes_read]);
|
||||||
if sim_req.is_err() {
|
if let Err(e) = sim_req {
|
||||||
log::warn!(
|
log::warn!("received UDP request with invalid format: {}", e);
|
||||||
"received UDP request with invalid format: {}",
|
|
||||||
sim_req.unwrap_err()
|
|
||||||
);
|
|
||||||
return processed_requests;
|
return processed_requests;
|
||||||
}
|
}
|
||||||
self.request_sender.send(sim_req.unwrap()).unwrap();
|
self.request_sender.send(sim_req.unwrap()).unwrap();
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "models"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
|
||||||
|
satrs = { path = "../../satrs" }
|
||||||
|
num_enum = { version = "0.7" }
|
||||||
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
|
postcard = { version = "1" }
|
||||||
|
thiserror = { version = "2" }
|
||||||
|
bitbybit = "1.4"
|
||||||
|
arbitrary-int = "2"
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
use crate::TmHeader;
|
||||||
|
use serde::Serialize;
|
||||||
|
use spacepackets::{
|
||||||
|
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpHeader, SpacePacketHeader,
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::TcHeader;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CcsdsTcPacketOwned {
|
||||||
|
pub sp_header: SpacePacketHeader,
|
||||||
|
pub tc_header: TcHeader,
|
||||||
|
pub payload: alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CcsdsTcPacketOwned {
|
||||||
|
pub fn new_with_request<R: serde::Serialize>(
|
||||||
|
sp_header: SpacePacketHeader,
|
||||||
|
tc_header: TcHeader,
|
||||||
|
request: R,
|
||||||
|
) -> Self {
|
||||||
|
let request_serialized = postcard::to_allocvec(&request).unwrap();
|
||||||
|
Self::new(sp_header, tc_header, request_serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
sp_header: SpacePacketHeader,
|
||||||
|
tc_header: TcHeader,
|
||||||
|
payload: alloc::vec::Vec<u8>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
sp_header,
|
||||||
|
tc_header,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
|
||||||
|
let response_len =
|
||||||
|
postcard::experimental::serialized_size(&self.tc_header)? + self.payload.len();
|
||||||
|
let mut ccsds_tc = CcsdsPacketCreatorWithReservedData::new_tc_with_checksum(
|
||||||
|
self.sp_header,
|
||||||
|
response_len,
|
||||||
|
buf,
|
||||||
|
)?;
|
||||||
|
let user_data = ccsds_tc.packet_data_mut();
|
||||||
|
let ser_len = postcard::to_slice(&self.tc_header, user_data)?.len();
|
||||||
|
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
|
||||||
|
let ccsds_packet_len = ccsds_tc.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.tc_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, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CcsdsTmPacketOwned {
|
||||||
|
pub fn new_with_serde_payload(
|
||||||
|
sp_header: SpHeader,
|
||||||
|
tm_header: &TmHeader,
|
||||||
|
payload: &impl Serialize,
|
||||||
|
) -> Result<Self, postcard::Error> {
|
||||||
|
Ok(CcsdsTmPacketOwned {
|
||||||
|
sp_header,
|
||||||
|
tm_header: *tm_header,
|
||||||
|
payload: postcard::to_allocvec(&payload)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use crate::Message;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
TestEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for Event {
|
||||||
|
fn message_type(&self) -> crate::MessageType {
|
||||||
|
crate::MessageType::Event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod request {
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Request {
|
||||||
|
Ping,
|
||||||
|
TestEvent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod response {
|
||||||
|
use crate::Message;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Response {
|
||||||
|
Ok,
|
||||||
|
Event(super::Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for Response {
|
||||||
|
fn message_type(&self) -> crate::MessageType {
|
||||||
|
match self {
|
||||||
|
Response::Ok => crate::MessageType::Verification,
|
||||||
|
Response::Event(_event) => crate::MessageType::Event,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
extern crate alloc;
|
||||||
|
use spacepackets::{
|
||||||
|
CcsdsPacketIdAndPsc,
|
||||||
|
time::cds::{CdsTime, MIN_CDS_FIELD_LEN},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod ccsds;
|
||||||
|
pub mod control;
|
||||||
|
pub mod mgm;
|
||||||
|
pub mod pcdu;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
serde::Serialize,
|
||||||
|
serde::Deserialize,
|
||||||
|
num_enum::TryFromPrimitive,
|
||||||
|
num_enum::IntoPrimitive,
|
||||||
|
)]
|
||||||
|
#[repr(u64)]
|
||||||
|
pub enum ComponentId {
|
||||||
|
Controller,
|
||||||
|
|
||||||
|
AcsSubsystem,
|
||||||
|
AcsMgmAssembly,
|
||||||
|
AcsMgm0,
|
||||||
|
AcsMgm1,
|
||||||
|
|
||||||
|
EpsSubsystem,
|
||||||
|
EpsPcdu,
|
||||||
|
|
||||||
|
UdpServer,
|
||||||
|
TcpServer,
|
||||||
|
EventManager,
|
||||||
|
|
||||||
|
Ground,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
|
||||||
|
#[bitbybit::bitenum(u11)]
|
||||||
|
pub enum Apid {
|
||||||
|
Tmtc = 1,
|
||||||
|
Cfdp = 2,
|
||||||
|
|
||||||
|
Acs = 3,
|
||||||
|
Eps = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
ControllerEvent(control::Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for Event {
|
||||||
|
fn message_type(&self) -> MessageType {
|
||||||
|
MessageType::Event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TmHeader {
|
||||||
|
pub fn new(
|
||||||
|
sender_id: ComponentId,
|
||||||
|
target_id: ComponentId,
|
||||||
|
message_type: MessageType,
|
||||||
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
cds_timestamp: &CdsTime,
|
||||||
|
) -> Self {
|
||||||
|
// Can not fail, CDS short always requires 7 bytes.
|
||||||
|
let mut stamp_buf: [u8; MIN_CDS_FIELD_LEN] = [0; MIN_CDS_FIELD_LEN];
|
||||||
|
cds_timestamp.write_to_bytes(&mut stamp_buf).unwrap();
|
||||||
|
Self {
|
||||||
|
sender_id,
|
||||||
|
target_id,
|
||||||
|
tc_id,
|
||||||
|
message_type,
|
||||||
|
timestamp: Some(stamp_buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new_for_unsolicited_tm(
|
||||||
|
sender_id: ComponentId,
|
||||||
|
target_id: ComponentId,
|
||||||
|
message_type: MessageType,
|
||||||
|
cds_timestamp: &CdsTime,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(sender_id, target_id, message_type, None, cds_timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_for_tc_response(
|
||||||
|
sender_id: ComponentId,
|
||||||
|
target_id: ComponentId,
|
||||||
|
message_type: MessageType,
|
||||||
|
tc_id: CcsdsPacketIdAndPsc,
|
||||||
|
cds_timestamp: &CdsTime,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(
|
||||||
|
sender_id,
|
||||||
|
target_id,
|
||||||
|
message_type,
|
||||||
|
Some(tc_id),
|
||||||
|
cds_timestamp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes_postcard(data: &[u8]) -> Result<(Self, &[u8]), postcard::Error> {
|
||||||
|
postcard::take_from_bytes::<TmHeader>(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct TcHeader {
|
||||||
|
pub target_id: ComponentId,
|
||||||
|
pub request_type: MessageType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcHeader {
|
||||||
|
pub fn new(target_id: ComponentId, request_type: MessageType) -> Self {
|
||||||
|
Self {
|
||||||
|
target_id,
|
||||||
|
request_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum MessageType {
|
||||||
|
Ping,
|
||||||
|
Mode,
|
||||||
|
Hk,
|
||||||
|
Action,
|
||||||
|
Event,
|
||||||
|
Verification,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Message {
|
||||||
|
fn message_type(&self) -> MessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum DeviceMode {
|
||||||
|
Off = 0,
|
||||||
|
On = 1,
|
||||||
|
Normal = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum HkRequestType {
|
||||||
|
OneShot,
|
||||||
|
EnablePeriodic(core::time::Duration),
|
||||||
|
DisablePeriodic,
|
||||||
|
ModifyInterval(core::time::Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
pub mod request {
|
||||||
|
use crate::HkRequestType;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum HkId {
|
||||||
|
Sensor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct HkRequest {
|
||||||
|
pub id: HkId,
|
||||||
|
pub req_type: HkRequestType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Request {
|
||||||
|
Ping,
|
||||||
|
Hk(HkRequest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MgmData {
|
||||||
|
pub valid: bool,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod response {
|
||||||
|
use crate::{Message, mgm::MgmData};
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum HkResponse {
|
||||||
|
MgmData(MgmData),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Response {
|
||||||
|
Ok,
|
||||||
|
Hk(HkResponse),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for Response {
|
||||||
|
fn message_type(&self) -> crate::MessageType {
|
||||||
|
match self {
|
||||||
|
Response::Ok => crate::MessageType::Verification,
|
||||||
|
Response::Hk(_hk_response) => crate::MessageType::Hk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use strum::IntoEnumIterator as _;
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u16, debug, default = 0x0)]
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct SwitchesBitfield {
|
||||||
|
#[bit(2, rw)]
|
||||||
|
magnetorquer: bool,
|
||||||
|
#[bit(1, rw)]
|
||||||
|
mgm1: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
mgm0: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
serde::Serialize,
|
||||||
|
serde::Deserialize,
|
||||||
|
Hash,
|
||||||
|
strum::EnumIter,
|
||||||
|
num_enum::IntoPrimitive,
|
||||||
|
num_enum::TryFromPrimitive,
|
||||||
|
)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum SwitchId {
|
||||||
|
Mgm0 = 0,
|
||||||
|
Mgm1 = 1,
|
||||||
|
Mgt = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum SwitchState {
|
||||||
|
Off = 0,
|
||||||
|
On = 1,
|
||||||
|
Unknown = 2,
|
||||||
|
Faulty = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SwitchStateBinary> for SwitchState {
|
||||||
|
fn from(value: SwitchStateBinary) -> Self {
|
||||||
|
match value {
|
||||||
|
SwitchStateBinary::Off => SwitchState::Off,
|
||||||
|
SwitchStateBinary::On => SwitchState::On,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum SwitchStateBinary {
|
||||||
|
Off = 0,
|
||||||
|
On = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SwitchMapBinary = HashMap<SwitchId, SwitchStateBinary>;
|
||||||
|
|
||||||
|
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
|
||||||
|
|
||||||
|
impl Default for SwitchMapBinaryWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut switch_map = SwitchMapBinary::default();
|
||||||
|
for entry in SwitchId::iter() {
|
||||||
|
switch_map.insert(entry, SwitchStateBinary::Off);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SwitchRequest {
|
||||||
|
pub switch_id: SwitchId,
|
||||||
|
pub target_state: SwitchStateBinary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchRequest {
|
||||||
|
pub fn new(switch_id: SwitchId, target_state: SwitchStateBinary) -> Self {
|
||||||
|
Self {
|
||||||
|
switch_id,
|
||||||
|
target_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch_id(&self) -> SwitchId {
|
||||||
|
self.switch_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_state(&self) -> SwitchStateBinary {
|
||||||
|
self.target_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod request {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Request {
|
||||||
|
Ping,
|
||||||
|
GetSwitches,
|
||||||
|
EnableSwitches(SwitchesBitfield),
|
||||||
|
DisableSwitches(SwitchesBitfield),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod response {
|
||||||
|
use super::*;
|
||||||
|
use crate::Message;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum Response {
|
||||||
|
Ok,
|
||||||
|
Switches(SwitchesBitfield),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for Response {
|
||||||
|
fn message_type(&self) -> crate::MessageType {
|
||||||
|
match self {
|
||||||
|
Response::Ok => crate::MessageType::Verification,
|
||||||
|
Response::Switches(_switches) => crate::MessageType::Action,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+169
-179
@@ -1,14 +1,14 @@
|
|||||||
use derive_new::new;
|
use models::ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned};
|
||||||
use satrs::hk::{HkRequest, HkRequestVariant};
|
use models::mgm::MgmData;
|
||||||
|
use models::pcdu::SwitchId;
|
||||||
|
use models::{mgm, ComponentId, HkRequestType};
|
||||||
use satrs::mode_tree::{ModeChild, ModeNode};
|
use satrs::mode_tree::{ModeChild, ModeNode};
|
||||||
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
|
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
||||||
use satrs_example::ids::generic_pus::PUS_MODE;
|
|
||||||
use satrs_example::{DeviceMode, TimestampHelper};
|
use satrs_example::{DeviceMode, TimestampHelper};
|
||||||
use satrs_minisim::acs::lis3mdl::{
|
use satrs_minisim::acs::lis3mdl::{
|
||||||
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
|
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
|
||||||
};
|
};
|
||||||
use satrs_minisim::acs::MgmRequestLis3Mdl;
|
use satrs_minisim::acs::MgmRequestLis3Mdl;
|
||||||
use satrs_minisim::eps::PcduSwitch;
|
|
||||||
use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest};
|
use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::mpsc::{self};
|
use std::sync::mpsc::{self};
|
||||||
@@ -19,17 +19,12 @@ use satrs::mode::{
|
|||||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||||
ModeRequestHandlerMpscBounded,
|
ModeRequestHandlerMpscBounded,
|
||||||
};
|
};
|
||||||
use satrs::pus::{EcssTmSender, PusTmVariant};
|
use satrs::request::{GenericMessage, MessageMetadata};
|
||||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
|
||||||
use satrs_example::config::components::NO_SENDER;
|
use satrs_example::config::components::NO_SENDER;
|
||||||
|
|
||||||
use crate::hk::PusHkHelper;
|
use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||||
use crate::pus::hk::{HkReply, HkReplyVariant};
|
use crate::eps::PowerSwitchHelper;
|
||||||
use crate::requests::CompositeRequest;
|
|
||||||
use crate::spi::SpiInterface;
|
use crate::spi::SpiInterface;
|
||||||
use crate::tmtc::sender::TmTcSender;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14;
|
pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14;
|
||||||
|
|
||||||
@@ -38,12 +33,6 @@ pub const X_LOWBYTE_IDX: usize = 9;
|
|||||||
pub const Y_LOWBYTE_IDX: usize = 11;
|
pub const Y_LOWBYTE_IDX: usize = 11;
|
||||||
pub const Z_LOWBYTE_IDX: usize = 13;
|
pub const Z_LOWBYTE_IDX: usize = 13;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum SetId {
|
|
||||||
SensorData = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Eq)]
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
pub enum TransitionState {
|
pub enum TransitionState {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -120,19 +109,10 @@ impl SpiInterface for SpiSimInterfaceWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct MgmData {
|
|
||||||
pub valid: bool,
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BufWrapper {
|
pub struct BufWrapper {
|
||||||
tx_buf: [u8; 32],
|
tx_buf: [u8; 32],
|
||||||
rx_buf: [u8; 32],
|
rx_buf: [u8; 32],
|
||||||
tm_buf: [u8; 32],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ModeHelpers {
|
pub struct ModeHelpers {
|
||||||
@@ -154,40 +134,52 @@ impl Default for ModeHelpers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
||||||
#[derive(new)]
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub struct MgmHandlerLis3Mdl<
|
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface> {
|
||||||
ComInterface: SpiInterface,
|
id: ComponentId,
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
|
||||||
> {
|
|
||||||
id: UniqueApidTargetId,
|
|
||||||
dev_str: &'static str,
|
dev_str: &'static str,
|
||||||
mode_node: ModeRequestHandlerMpscBounded,
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
tc_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
switch_helper: SwitchHelper,
|
switch_helper: PowerSwitchHelper,
|
||||||
tm_sender: TmTcSender,
|
|
||||||
pub com_interface: ComInterface,
|
pub com_interface: ComInterface,
|
||||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
#[new(value = "PusHkHelper::new(id)")]
|
//hk_helper: PusHkHelper,
|
||||||
hk_helper: PusHkHelper,
|
|
||||||
#[new(default)]
|
|
||||||
mode_helpers: ModeHelpers,
|
mode_helpers: ModeHelpers,
|
||||||
#[new(default)]
|
|
||||||
bufs: BufWrapper,
|
bufs: BufWrapper,
|
||||||
#[new(default)]
|
|
||||||
stamp_helper: TimestampHelper,
|
stamp_helper: TimestampHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<ComInterface: SpiInterface> MgmHandlerLis3Mdl<ComInterface> {
|
||||||
ComInterface: SpiInterface,
|
#[allow(clippy::too_many_arguments)]
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
pub fn new(
|
||||||
> MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
id: ComponentId,
|
||||||
{
|
dev_str: &'static str,
|
||||||
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
|
tc_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
|
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
switch_helper: PowerSwitchHelper,
|
||||||
|
com_interface: ComInterface,
|
||||||
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
dev_str,
|
||||||
|
mode_node,
|
||||||
|
tc_rx,
|
||||||
|
tm_tx,
|
||||||
|
switch_helper,
|
||||||
|
com_interface,
|
||||||
|
shared_mgm_set,
|
||||||
|
mode_helpers: ModeHelpers::default(),
|
||||||
|
bufs: BufWrapper::default(),
|
||||||
|
stamp_helper: TimestampHelper::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
self.stamp_helper.update_from_now();
|
self.stamp_helper.update_from_now();
|
||||||
// Handle requests.
|
// Handle requests.
|
||||||
self.handle_composite_requests();
|
self.handle_telecommands();
|
||||||
self.handle_mode_requests();
|
self.handle_mode_requests();
|
||||||
if let Some(target_mode_submode) = self.mode_helpers.target {
|
if let Some(target_mode_submode) = self.mode_helpers.target {
|
||||||
self.handle_mode_transition(target_mode_submode);
|
self.handle_mode_transition(target_mode_submode);
|
||||||
@@ -198,67 +190,76 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_composite_requests(&mut self) {
|
pub fn handle_telecommands(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
match self.composite_request_rx.try_recv() {
|
match self.tc_rx.try_recv() {
|
||||||
Ok(ref msg) => match &msg.message {
|
Ok(packet) => {
|
||||||
CompositeRequest::Hk(hk_request) => {
|
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
|
||||||
self.handle_hk_request(&msg.requestor_info, hk_request)
|
match postcard::from_bytes::<mgm::request::Request>(&packet.payload) {
|
||||||
}
|
Ok(request) => {
|
||||||
// TODO: This object does not have actions (yet).. Still send back completion failure
|
log::info!(
|
||||||
// reply.
|
"received request {:?} with TC ID {:#010x}",
|
||||||
CompositeRequest::Action(_action_req) => {}
|
request,
|
||||||
},
|
tc_id.raw()
|
||||||
|
);
|
||||||
Err(e) => {
|
match request {
|
||||||
if e != mpsc::TryRecvError::Empty {
|
mgm::request::Request::Ping => {
|
||||||
log::warn!(
|
self.send_telemetry(Some(tc_id), mgm::response::Response::Ok)
|
||||||
"{}: failed to receive composite request: {:?}",
|
}
|
||||||
self.dev_str,
|
mgm::request::Request::Hk(hk_request) => {
|
||||||
e
|
self.handle_hk_request(Some(tc_id), &hk_request)
|
||||||
);
|
} //mgm::request::Request::Mo
|
||||||
} else {
|
}
|
||||||
break;
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to deserialize request: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
std::sync::mpsc::TryRecvError::Empty => break,
|
||||||
|
std::sync::mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("packet sender disconnected")
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
pub fn send_telemetry(
|
||||||
match hk_request.variant {
|
&self,
|
||||||
HkRequestVariant::OneShot => {
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
|
response: mgm::response::Response,
|
||||||
if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
|
) {
|
||||||
self.stamp_helper.stamp(),
|
match pack_ccsds_tm_packet_for_now(self.id, tc_id, &response) {
|
||||||
SetId::SensorData as u32,
|
Ok(packet) => {
|
||||||
&mut |hk_buf| {
|
if let Err(e) = self.tm_tx.send(packet) {
|
||||||
hk_buf[0] = mgm_snapshot.valid as u8;
|
log::warn!("failed to send TM packet: {}", e);
|
||||||
hk_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
|
|
||||||
hk_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
|
|
||||||
hk_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
|
|
||||||
Ok(13)
|
|
||||||
},
|
|
||||||
&mut self.bufs.tm_buf,
|
|
||||||
) {
|
|
||||||
// TODO: If sending the TM fails, we should also send a failure reply.
|
|
||||||
self.tm_sender
|
|
||||||
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
|
||||||
.expect("failed to send HK TM");
|
|
||||||
self.hk_reply_tx
|
|
||||||
.send(GenericMessage::new(
|
|
||||||
*requestor_info,
|
|
||||||
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
|
||||||
))
|
|
||||||
.expect("failed to send HK reply");
|
|
||||||
} else {
|
|
||||||
// TODO: Send back failure reply. Need result code for this.
|
|
||||||
log::error!("TM buffer too small to generate HK data");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HkRequestVariant::EnablePeriodic => todo!(),
|
Err(e) => {
|
||||||
HkRequestVariant::DisablePeriodic => todo!(),
|
log::warn!("failed to pack TM packet: {}", e);
|
||||||
HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_hk_request(
|
||||||
|
&mut self,
|
||||||
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
hk_request: &models::mgm::request::HkRequest,
|
||||||
|
) {
|
||||||
|
match hk_request.req_type {
|
||||||
|
HkRequestType::OneShot => {
|
||||||
|
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
|
||||||
|
self.send_telemetry(
|
||||||
|
tc_id,
|
||||||
|
mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(mgm_snapshot)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HkRequestType::EnablePeriodic(_duration) => todo!(),
|
||||||
|
HkRequestType::DisablePeriodic => todo!(),
|
||||||
|
HkRequestType::ModifyInterval(_duration) => todo!(),
|
||||||
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +332,7 @@ impl<
|
|||||||
if self.mode_helpers.transition_state == TransitionState::Idle {
|
if self.mode_helpers.transition_state == TransitionState::Idle {
|
||||||
let result = self
|
let result = self
|
||||||
.switch_helper
|
.switch_helper
|
||||||
.send_switch_on_cmd(MessageMetadata::new(0, self.id.id()), PcduSwitch::Mgm);
|
.send_switch_on_cmd(MessageMetadata::new(0, self.id as u32), SwitchId::Mgm0);
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
// Could not send switch command.. still continue with transition.
|
// Could not send switch command.. still continue with transition.
|
||||||
log::error!("failed to send switch on command");
|
log::error!("failed to send switch on command");
|
||||||
@@ -339,10 +340,7 @@ impl<
|
|||||||
self.mode_helpers.transition_state = TransitionState::PowerSwitching;
|
self.mode_helpers.transition_state = TransitionState::PowerSwitching;
|
||||||
}
|
}
|
||||||
if self.mode_helpers.transition_state == TransitionState::PowerSwitching
|
if self.mode_helpers.transition_state == TransitionState::PowerSwitching
|
||||||
&& self
|
&& self.switch_helper.is_switch_on(SwitchId::Mgm0)
|
||||||
.switch_helper
|
|
||||||
.is_switch_on(PcduSwitch::Mgm)
|
|
||||||
.expect("switch info error")
|
|
||||||
{
|
{
|
||||||
self.mode_helpers.transition_state = TransitionState::Done;
|
self.mode_helpers.transition_state = TransitionState::Done;
|
||||||
}
|
}
|
||||||
@@ -356,21 +354,13 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<ComInterface: SpiInterface> ModeProvider for MgmHandlerLis3Mdl<ComInterface> {
|
||||||
ComInterface: SpiInterface,
|
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
|
||||||
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
|
||||||
{
|
|
||||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||||
self.mode_helpers.current
|
self.mode_helpers.current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<ComInterface: SpiInterface> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface> {
|
||||||
ComInterface: SpiInterface,
|
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
|
||||||
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
|
||||||
{
|
|
||||||
type Error = ModeError;
|
type Error = ModeError;
|
||||||
|
|
||||||
fn start_transition(
|
fn start_transition(
|
||||||
@@ -417,7 +407,7 @@ impl<
|
|||||||
if requestor.sender_id() == NO_SENDER {
|
if requestor.sender_id() == NO_SENDER {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if requestor.sender_id() != PUS_MODE.id() {
|
if requestor.sender_id() != ComponentId::Ground as u32 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {:x}",
|
"can not send back mode reply to sender {:x}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
@@ -434,7 +424,7 @@ impl<
|
|||||||
requestor: MessageMetadata,
|
requestor: MessageMetadata,
|
||||||
reply: ModeReply,
|
reply: ModeReply,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
if requestor.sender_id() != PUS_MODE.id() {
|
if requestor.sender_id() != ComponentId::Ground as u32 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {}",
|
"can not send back mode reply to sender {}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
@@ -455,21 +445,13 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<ComInterface: SpiInterface> ModeNode for MgmHandlerLis3Mdl<ComInterface> {
|
||||||
ComInterface: SpiInterface,
|
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
|
||||||
> ModeNode for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
|
||||||
{
|
|
||||||
fn id(&self) -> satrs::ComponentId {
|
fn id(&self) -> satrs::ComponentId {
|
||||||
self.id.into()
|
self.id as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<ComInterface: SpiInterface> ModeChild for MgmHandlerLis3Mdl<ComInterface> {
|
||||||
ComInterface: SpiInterface,
|
|
||||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
|
||||||
> ModeChild for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
|
||||||
{
|
|
||||||
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
||||||
|
|
||||||
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
|
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
|
||||||
@@ -484,19 +466,19 @@ mod tests {
|
|||||||
sync::{mpsc, Arc},
|
sync::{mpsc, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use arbitrary_int::u21;
|
use models::{
|
||||||
|
pcdu::{SwitchRequest, SwitchState, SwitchStateBinary},
|
||||||
|
ComponentId,
|
||||||
|
};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
mode::{ModeReply, ModeRequest},
|
mode::{ModeReply, ModeRequest},
|
||||||
mode_tree::ModeParent,
|
mode_tree::ModeParent,
|
||||||
power::SwitchStateBinary,
|
request::GenericMessage,
|
||||||
request::{GenericMessage, UniqueApidTargetId},
|
|
||||||
tmtc::PacketAsVec,
|
tmtc::PacketAsVec,
|
||||||
ComponentId,
|
|
||||||
};
|
};
|
||||||
use satrs_example::ids::{acs::ASSEMBLY, Apid};
|
|
||||||
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
|
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
|
||||||
|
|
||||||
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
|
use crate::eps::pcdu::{SharedSwitchSet, SwitchMap, SwitchSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -524,22 +506,24 @@ mod tests {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct MgmTestbench {
|
pub struct MgmTestbench {
|
||||||
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
||||||
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
|
pub mode_reply_rx_to_ground: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
pub shared_switch_set: SharedSwitchSet,
|
||||||
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||||
pub handler: MgmHandlerLis3Mdl<TestSpiInterface, TestSwitchHelper>,
|
pub switch_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||||
|
pub handler: MgmHandlerLis3Mdl<TestSpiInterface>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct MgmAssemblyMock(
|
pub struct MgmAssemblyMock(
|
||||||
pub HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
pub HashMap<satrs::ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ModeNode for MgmAssemblyMock {
|
impl ModeNode for MgmAssemblyMock {
|
||||||
fn id(&self) -> satrs::ComponentId {
|
fn id(&self) -> satrs::ComponentId {
|
||||||
PUS_MODE.into()
|
ComponentId::AcsMgmAssembly as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,17 +536,19 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PusMock {
|
#[allow(dead_code)]
|
||||||
pub request_sender_map: HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
pub struct GroundMock {
|
||||||
|
pub request_sender_map:
|
||||||
|
HashMap<satrs::ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModeNode for PusMock {
|
impl ModeNode for GroundMock {
|
||||||
fn id(&self) -> satrs::ComponentId {
|
fn id(&self) -> satrs::ComponentId {
|
||||||
PUS_MODE.into()
|
ComponentId::Ground as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModeParent for PusMock {
|
impl ModeParent for GroundMock {
|
||||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||||
|
|
||||||
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
|
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
|
||||||
@@ -573,36 +559,40 @@ mod tests {
|
|||||||
impl MgmTestbench {
|
impl MgmTestbench {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (request_tx, request_rx) = mpsc::sync_channel(5);
|
let (request_tx, request_rx) = mpsc::sync_channel(5);
|
||||||
let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5);
|
let (reply_tx_to_ground, reply_rx_to_ground) = mpsc::sync_channel(5);
|
||||||
let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
|
let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
|
||||||
let id = UniqueApidTargetId::new(Apid::Acs.raw_value(), u21::new(1));
|
let mode_node =
|
||||||
let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx);
|
ModeRequestHandlerMpscBounded::new(ComponentId::Ground as u32, request_rx);
|
||||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
let (tc_tx, tc_rx) = mpsc::sync_channel(10);
|
||||||
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
|
let (hk_reply_tx, _hk_reply_rx) = mpsc::sync_channel(10);
|
||||||
let (tm_tx, tm_rx) = mpsc::sync_channel(10);
|
let (_tm_tx, tm_rx) = mpsc::sync_channel(10);
|
||||||
let tm_sender = TmTcSender::Heap(tm_tx);
|
let (switcher_tx, switch_rx) = mpsc::sync_channel(10);
|
||||||
let shared_mgm_set = Arc::default();
|
let shared_mgm_set = Arc::default();
|
||||||
|
let mut switch_map = SwitchMap::new();
|
||||||
|
switch_map.insert(SwitchId::Mgm0, SwitchState::Off);
|
||||||
|
let switch_map = SwitchSet::new(switch_map);
|
||||||
|
let shared_switch_set = SharedSwitchSet::new(Mutex::new(switch_map));
|
||||||
let mut handler = MgmHandlerLis3Mdl::new(
|
let mut handler = MgmHandlerLis3Mdl::new(
|
||||||
id,
|
ComponentId::AcsMgm0,
|
||||||
"TEST_MGM",
|
"TEST_MGM",
|
||||||
mode_node,
|
mode_node,
|
||||||
composite_request_rx,
|
tc_rx,
|
||||||
hk_reply_tx,
|
hk_reply_tx,
|
||||||
TestSwitchHelper::default(),
|
PowerSwitchHelper::new(switcher_tx, shared_switch_set.clone()),
|
||||||
tm_sender,
|
|
||||||
TestSpiInterface::default(),
|
TestSpiInterface::default(),
|
||||||
shared_mgm_set,
|
shared_mgm_set,
|
||||||
);
|
);
|
||||||
handler.add_mode_parent(PUS_MODE.into(), reply_tx_to_pus);
|
handler.add_mode_parent(ComponentId::Ground as u32, reply_tx_to_ground);
|
||||||
handler.add_mode_parent(ASSEMBLY.into(), reply_tx_to_parent);
|
handler.add_mode_parent(ComponentId::AcsMgmAssembly as u32, reply_tx_to_parent);
|
||||||
Self {
|
Self {
|
||||||
mode_request_tx: request_tx,
|
mode_request_tx: request_tx,
|
||||||
mode_reply_rx_to_pus: reply_rx_to_pus,
|
mode_reply_rx_to_ground: reply_rx_to_ground,
|
||||||
mode_reply_rx_to_parent: reply_rx_to_parent,
|
mode_reply_rx_to_parent: reply_rx_to_parent,
|
||||||
composite_request_tx,
|
shared_switch_set,
|
||||||
|
switch_rx,
|
||||||
handler,
|
handler,
|
||||||
tm_rx,
|
tm_rx,
|
||||||
hk_reply_rx,
|
tc_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,7 +622,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.mode_request_tx
|
.mode_request_tx
|
||||||
.send(GenericMessage::new(
|
.send(GenericMessage::new(
|
||||||
MessageMetadata::new(0, PUS_MODE.id()),
|
MessageMetadata::new(0, ComponentId::Ground as u32),
|
||||||
ModeRequest::SetMode {
|
ModeRequest::SetMode {
|
||||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||||
forced: false,
|
forced: false,
|
||||||
@@ -647,22 +637,22 @@ mod tests {
|
|||||||
assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
|
||||||
|
|
||||||
// Verify power switch handling.
|
// Verify power switch handling.
|
||||||
let mut switch_requests = testbench.handler.switch_helper.switch_requests.borrow_mut();
|
let switch_req = testbench.switch_rx.try_recv().expect("no switch request");
|
||||||
assert_eq!(switch_requests.len(), 1);
|
assert_eq!(switch_req.message.switch_id, SwitchId::Mgm0);
|
||||||
let switch_req = switch_requests.pop_front().expect("no switch request");
|
assert_eq!(switch_req.message.target_state, SwitchStateBinary::On);
|
||||||
assert_eq!(switch_req.target_state, SwitchStateBinary::On);
|
|
||||||
assert_eq!(switch_req.switch_id, PcduSwitch::Mgm);
|
// This simulates one cycle for the power switch to update.
|
||||||
let mut switch_info_requests = testbench
|
testbench
|
||||||
.handler
|
.shared_switch_set
|
||||||
.switch_helper
|
.lock()
|
||||||
.switch_info_requests
|
.unwrap()
|
||||||
.borrow_mut();
|
.set_switch_state(SwitchId::Mgm0, SwitchState::On);
|
||||||
assert_eq!(switch_info_requests.len(), 1);
|
|
||||||
let switch_info_req = switch_info_requests.pop_front().expect("no switch request");
|
// Now the power switch is updated and the mode request should be completed.
|
||||||
assert_eq!(switch_info_req, PcduSwitch::Mgm);
|
testbench.handler.periodic_operation();
|
||||||
|
|
||||||
let mode_reply = testbench
|
let mode_reply = testbench
|
||||||
.mode_reply_rx_to_pus
|
.mode_reply_rx_to_ground
|
||||||
.try_recv()
|
.try_recv()
|
||||||
.expect("no mode reply generated");
|
.expect("no mode reply generated");
|
||||||
match mode_reply.message {
|
match mode_reply.message {
|
||||||
@@ -673,7 +663,7 @@ mod tests {
|
|||||||
_ => panic!("unexpected mode reply"),
|
_ => panic!("unexpected mode reply"),
|
||||||
}
|
}
|
||||||
// The device should have been polled once.
|
// The device should have been polled once.
|
||||||
assert_eq!(testbench.handler.com_interface.call_count, 1);
|
assert_eq!(testbench.handler.com_interface.call_count, 2);
|
||||||
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
||||||
assert!(mgm_set.x < 0.001);
|
assert!(mgm_set.x < 0.001);
|
||||||
assert!(mgm_set.y < 0.001);
|
assert!(mgm_set.y < 0.001);
|
||||||
@@ -693,7 +683,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.mode_request_tx
|
.mode_request_tx
|
||||||
.send(GenericMessage::new(
|
.send(GenericMessage::new(
|
||||||
MessageMetadata::new(0, PUS_MODE.id()),
|
MessageMetadata::new(0, ComponentId::Ground as u32),
|
||||||
ModeRequest::SetMode {
|
ModeRequest::SetMode {
|
||||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||||
forced: false,
|
forced: false,
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
use arbitrary_int::u11;
|
|
||||||
use satrs::pus::verification::RequestId;
|
|
||||||
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
|
||||||
use satrs::spacepackets::ecss::tm::PusTmReader;
|
|
||||||
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
|
||||||
use satrs::spacepackets::SpHeader;
|
|
||||||
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
|
||||||
use std::net::{IpAddr, SocketAddr, UdpSocket};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut buf = [0; 32];
|
|
||||||
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
|
||||||
let pus_tc = PusTcCreator::new_simple(
|
|
||||||
SpHeader::new_from_apid(u11::new(0x02)),
|
|
||||||
MessageTypeId::new(17, 1),
|
|
||||||
&[],
|
|
||||||
CreatorConfig::default(),
|
|
||||||
);
|
|
||||||
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
|
||||||
let tc_req_id = RequestId::new(&pus_tc);
|
|
||||||
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
|
|
||||||
let size = pus_tc
|
|
||||||
.write_to_bytes(&mut buf)
|
|
||||||
.expect("Creating PUS TC failed");
|
|
||||||
client
|
|
||||||
.send_to(&buf[0..size], addr)
|
|
||||||
.unwrap_or_else(|_| panic!("Sending to {addr:?} failed"));
|
|
||||||
client
|
|
||||||
.set_read_timeout(Some(Duration::from_secs(2)))
|
|
||||||
.expect("Setting read timeout failed");
|
|
||||||
loop {
|
|
||||||
let res = client.recv(&mut buf);
|
|
||||||
match res {
|
|
||||||
Ok(_len) => {
|
|
||||||
let pus_tm = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
|
|
||||||
if pus_tm.service_type_id() == 17 && pus_tm.message_subtype_id() == 2 {
|
|
||||||
println!("Received PUS Ping Reply TM[17,2]")
|
|
||||||
} else if pus_tm.service_type_id() == 1 {
|
|
||||||
if pus_tm.source_data().is_empty() {
|
|
||||||
println!("Invalid verification TM, no source data");
|
|
||||||
}
|
|
||||||
let src_data = pus_tm.source_data();
|
|
||||||
if src_data.len() < 4 {
|
|
||||||
println!("Invalid verification TM source data, less than 4 bytes")
|
|
||||||
}
|
|
||||||
let req_id = RequestId::from_bytes(src_data).unwrap();
|
|
||||||
let subtype_id = pus_tm.message_subtype_id();
|
|
||||||
if subtype_id == 1 {
|
|
||||||
println!("Received TM[1,1] acceptance success for request ID {req_id}")
|
|
||||||
} else if subtype_id == 2 {
|
|
||||||
println!("Received TM[1,2] acceptance failure for request ID {req_id}")
|
|
||||||
} else if subtype_id == 3 {
|
|
||||||
println!("Received TM[1,3] start success for request ID {req_id}")
|
|
||||||
} else if subtype_id == 4 {
|
|
||||||
println!("Received TM[1,2] start failure for request ID {req_id}")
|
|
||||||
} else if subtype_id == 5 {
|
|
||||||
println!("Received TM[1,5] step success for request ID {req_id}")
|
|
||||||
} else if subtype_id == 6 {
|
|
||||||
println!("Received TM[1,6] step failure for request ID {req_id}")
|
|
||||||
} else if subtype_id == 7 {
|
|
||||||
println!("Received TM[1,7] completion success for request ID {req_id}")
|
|
||||||
} else if subtype_id == 8 {
|
|
||||||
println!("Received TM[1,8] completion failure for request ID {req_id}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"Received TM[{}, {}] with {} bytes",
|
|
||||||
pus_tm.service_type_id(),
|
|
||||||
pus_tm.message_subtype_id(),
|
|
||||||
size
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ref e)
|
|
||||||
if e.kind() == std::io::ErrorKind::WouldBlock
|
|
||||||
|| e.kind() == std::io::ErrorKind::TimedOut =>
|
|
||||||
{
|
|
||||||
println!("No reply received for 2 seconds");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("UDP receive error {:?}", res.unwrap_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
|
||||||
use std::thread;
|
|
||||||
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
|
|
||||||
|
|
||||||
trait FieldDataProvider: Send {
|
|
||||||
fn get_data(&self) -> &[u8];
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FixedFieldDataWrapper {
|
|
||||||
data: [u8; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedFieldDataWrapper {
|
|
||||||
pub fn from_two_u32(p0: u32, p1: u32) -> Self {
|
|
||||||
let mut data = [0; 8];
|
|
||||||
data[0..4].copy_from_slice(p0.to_be_bytes().as_slice());
|
|
||||||
data[4..8].copy_from_slice(p1.to_be_bytes().as_slice());
|
|
||||||
Self { data }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FieldDataProvider for FixedFieldDataWrapper {
|
|
||||||
fn get_data(&self) -> &[u8] {
|
|
||||||
self.data.as_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FieldDataTraitObj = Box<dyn FieldDataProvider>;
|
|
||||||
|
|
||||||
struct ExampleMgmSet {
|
|
||||||
mgm_vec: [f32; 3],
|
|
||||||
temperature: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct ExampleMgmSetZc {
|
|
||||||
mgm_vec: [u8; 12],
|
|
||||||
temperatur: U16<NetworkEndian>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (s0, r0): (Sender<FieldDataTraitObj>, Receiver<FieldDataTraitObj>) = bounded(5);
|
|
||||||
let data_wrapper = FixedFieldDataWrapper::from_two_u32(2, 3);
|
|
||||||
s0.send(Box::new(data_wrapper)).unwrap();
|
|
||||||
let jh0 = thread::spawn(move || {
|
|
||||||
let data = r0.recv().unwrap();
|
|
||||||
let raw = data.get_data();
|
|
||||||
println!("Received data {raw:?}");
|
|
||||||
});
|
|
||||||
let jh1 = thread::spawn(|| {});
|
|
||||||
jh0.join().unwrap();
|
|
||||||
jh1.join().unwrap();
|
|
||||||
//let mut max_val: u16 = u16::MAX;
|
|
||||||
//max_val += 1;
|
|
||||||
//println!("Max val: {}", max_val);
|
|
||||||
let atomic_u16: AtomicU16 = AtomicU16::new(u16::MAX);
|
|
||||||
atomic_u16.fetch_add(1, Ordering::SeqCst);
|
|
||||||
println!(
|
|
||||||
"atomic after overflow: {}",
|
|
||||||
atomic_u16.load(Ordering::SeqCst)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use arbitrary_int::u11;
|
||||||
|
use models::{ccsds::CcsdsTmPacketOwned, Apid, ComponentId, Message, TmHeader};
|
||||||
|
use satrs::spacepackets::{
|
||||||
|
time::{cds::CdsTime, StdTimestampError},
|
||||||
|
CcsdsPacketIdAndPsc, SpHeader,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CcsdsTmCreationError {
|
||||||
|
#[error("postcard error: {0}")]
|
||||||
|
Postcard(#[from] postcard::Error),
|
||||||
|
#[error("timestamp error: {0}")]
|
||||||
|
Time(#[from] StdTimestampError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pack_ccsds_tm_packet_for_now(
|
||||||
|
sender_id: ComponentId,
|
||||||
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
payload: &(impl Serialize + Message),
|
||||||
|
) -> Result<CcsdsTmPacketOwned, CcsdsTmCreationError> {
|
||||||
|
let now = CdsTime::now_with_u16_days()?;
|
||||||
|
let sp_header = SpHeader::new_from_apid(u11::new(Apid::Tmtc as u16));
|
||||||
|
let tm_header = TmHeader::new(
|
||||||
|
sender_id,
|
||||||
|
ComponentId::Ground,
|
||||||
|
payload.message_type(),
|
||||||
|
tc_id,
|
||||||
|
&now,
|
||||||
|
);
|
||||||
|
Ok(CcsdsTmPacketOwned::new_with_serde_payload(
|
||||||
|
sp_header, &tm_header, payload,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
@@ -7,13 +7,10 @@ use satrs::{
|
|||||||
use satrs_mib::res_code::ResultU16Info;
|
use satrs_mib::res_code::ResultU16Info;
|
||||||
use satrs_mib::resultcode;
|
use satrs_mib::resultcode;
|
||||||
use std::{collections::HashSet, net::Ipv4Addr};
|
use std::{collections::HashSet, net::Ipv4Addr};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator as _;
|
||||||
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use satrs::{
|
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
||||||
events_legacy::{EventU32TypedSev, SeverityInfo},
|
|
||||||
pool::{StaticMemoryPool, StaticPoolConfig},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -39,19 +36,17 @@ pub enum GroupId {
|
|||||||
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 TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(0, 0);
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for id in crate::ids::Apid::iter() {
|
for id in models::Apid::iter() {
|
||||||
set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16)));
|
set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16)));
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
};
|
};
|
||||||
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for id in crate::ids::Apid::iter() {
|
for id in models::Apid::iter() {
|
||||||
set.insert(id as u16);
|
set.insert(id as u16);
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -175,6 +170,6 @@ pub mod pool {
|
|||||||
pub mod tasks {
|
pub mod tasks {
|
||||||
pub const FREQ_MS_UDP_TMTC: u64 = 200;
|
pub const FREQ_MS_UDP_TMTC: u64 = 200;
|
||||||
pub const FREQ_MS_AOCS: u64 = 500;
|
pub const FREQ_MS_AOCS: u64 = 500;
|
||||||
pub const FREQ_MS_PUS_STACK: u64 = 200;
|
pub const FREQ_MS_CONTROLLER: u64 = 200;
|
||||||
pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5;
|
pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use models::{
|
||||||
|
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
||||||
|
control, ComponentId,
|
||||||
|
};
|
||||||
|
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
||||||
|
|
||||||
|
use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||||
|
|
||||||
|
pub struct Controller {
|
||||||
|
pub tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
|
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
pub event_ctrl_tx: std::sync::mpsc::SyncSender<control::Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
pub fn new(
|
||||||
|
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
|
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
event_ctrl_tx: std::sync::mpsc::SyncSender<control::Event>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tc_rx,
|
||||||
|
tm_tx,
|
||||||
|
event_ctrl_tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.handle_telecommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_telecommands(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.tc_rx.try_recv() {
|
||||||
|
Ok(packet) => {
|
||||||
|
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
|
||||||
|
match postcard::from_bytes::<control::request::Request>(&packet.payload) {
|
||||||
|
Ok(request) => {
|
||||||
|
log::info!(
|
||||||
|
"received request {:?} with TC ID {:#010x}",
|
||||||
|
request,
|
||||||
|
tc_id.raw()
|
||||||
|
);
|
||||||
|
match request {
|
||||||
|
control::request::Request::Ping => self
|
||||||
|
.send_telemetry(Some(tc_id), control::response::Response::Ok),
|
||||||
|
control::request::Request::TestEvent => {
|
||||||
|
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to deserialize request: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
std::sync::mpsc::TryRecvError::Empty => break,
|
||||||
|
std::sync::mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("packet sender disconnected")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_telemetry(
|
||||||
|
&self,
|
||||||
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
response: control::response::Response,
|
||||||
|
) {
|
||||||
|
match pack_ccsds_tm_packet_for_now(ComponentId::Controller, tc_id, &response) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if let Err(e) = self.tm_tx.send(packet) {
|
||||||
|
log::warn!("failed to send TM packet: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to pack TM packet: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
|
use models::pcdu::{SwitchId, SwitchRequest, SwitchState, SwitchStateBinary};
|
||||||
use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration};
|
use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
use satrs::{
|
use satrs::{
|
||||||
power::{
|
|
||||||
PowerSwitchInfo, PowerSwitcherCommandSender, SwitchRequest, SwitchState, SwitchStateBinary,
|
|
||||||
},
|
|
||||||
queue::GenericSendError,
|
queue::GenericSendError,
|
||||||
request::{GenericMessage, MessageMetadata},
|
request::{GenericMessage, MessageMetadata},
|
||||||
};
|
};
|
||||||
use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::eps::pcdu::SwitchMapWrapper;
|
||||||
|
|
||||||
use self::pcdu::SharedSwitchSet;
|
use self::pcdu::SharedSwitchSet;
|
||||||
|
|
||||||
pub mod pcdu;
|
pub mod pcdu;
|
||||||
@@ -22,6 +21,7 @@ pub struct PowerSwitchHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum SwitchCommandingError {
|
pub enum SwitchCommandingError {
|
||||||
#[error("send error: {0}")]
|
#[error("send error: {0}")]
|
||||||
Send(#[from] GenericSendError),
|
Send(#[from] GenericSendError),
|
||||||
@@ -31,18 +31,72 @@ pub enum SwitchCommandingError {
|
|||||||
pub enum SwitchInfoError {
|
pub enum SwitchInfoError {
|
||||||
/// This is a configuration error which should not occur.
|
/// This is a configuration error which should not occur.
|
||||||
#[error("switch ID not in map")]
|
#[error("switch ID not in map")]
|
||||||
SwitchIdNotInMap(PcduSwitch),
|
SwitchIdNotInMap(SwitchId),
|
||||||
#[error("switch set invalid")]
|
#[error("switch set invalid")]
|
||||||
SwitchSetInvalid,
|
SwitchSetInvalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
|
impl PowerSwitchHelper {
|
||||||
|
pub fn send_switch_on_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
|
switch_id: SwitchId,
|
||||||
|
) -> Result<(), GenericSendError> {
|
||||||
|
self.switcher_tx.send(GenericMessage::new(
|
||||||
|
requestor_info,
|
||||||
|
SwitchRequest::new(switch_id, SwitchStateBinary::On),
|
||||||
|
))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn send_switch_off_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
|
switch_id: SwitchId,
|
||||||
|
) -> Result<(), GenericSendError> {
|
||||||
|
self.switcher_tx.send(GenericMessage::new(
|
||||||
|
requestor_info,
|
||||||
|
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
|
||||||
|
))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch_state(&self, switch_id: SwitchId) -> Result<SwitchState, SwitchInfoError> {
|
||||||
|
let switch_set = self
|
||||||
|
.shared_switch_set
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock switch set");
|
||||||
|
if !switch_set.valid {
|
||||||
|
return Err(SwitchInfoError::SwitchSetInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(state) = switch_set.switch_map.get(&switch_id) {
|
||||||
|
return Ok(*state);
|
||||||
|
}
|
||||||
|
Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn switch_delay_ms(&self) -> Duration {
|
||||||
|
// Here, we could set device specific switch delays theoretically. Set it to this value
|
||||||
|
// for now.
|
||||||
|
Duration::from_millis(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_switch_on(&self, switch_id: SwitchId) -> bool {
|
||||||
|
if let Ok(state) = self.switch_state(switch_id) {
|
||||||
|
state == SwitchState::On
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
impl PowerSwitchInfo<SwitchId> for PowerSwitchHelper {
|
||||||
type Error = SwitchInfoError;
|
type Error = SwitchInfoError;
|
||||||
|
|
||||||
fn switch_state(
|
fn switch_state(&self, switch_id: SwitchId) -> Result<SwitchState, Self::Error> {
|
||||||
&self,
|
|
||||||
switch_id: PcduSwitch,
|
|
||||||
) -> Result<satrs::power::SwitchState, Self::Error> {
|
|
||||||
let switch_set = self
|
let switch_set = self
|
||||||
.shared_switch_set
|
.shared_switch_set
|
||||||
.lock()
|
.lock()
|
||||||
@@ -63,43 +117,51 @@ impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
|
|||||||
Duration::from_millis(1000)
|
Duration::from_millis(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
|
/*
|
||||||
|
impl PowerSwitcherCommandSender<SwitchId> for PowerSwitchHelper {
|
||||||
type Error = SwitchCommandingError;
|
type Error = SwitchCommandingError;
|
||||||
|
|
||||||
fn send_switch_on_cmd(
|
fn send_switch_on_cmd(
|
||||||
&self,
|
&self,
|
||||||
requestor_info: satrs::request::MessageMetadata,
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
switch_id: PcduSwitch,
|
switch_id: SwitchId,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
self.switcher_tx
|
self.switcher_tx.send(GenericMessage::new(
|
||||||
.send_switch_on_cmd(requestor_info, switch_id)?;
|
requestor_info,
|
||||||
|
SwitchRequest::new(switch_id, SwitchStateBinary::On),
|
||||||
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_switch_off_cmd(
|
fn send_switch_off_cmd(
|
||||||
&self,
|
&self,
|
||||||
requestor_info: satrs::request::MessageMetadata,
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
switch_id: PcduSwitch,
|
switch_id: SwitchId,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
self.switcher_tx
|
self.switcher_tx.send(GenericMessage::new(
|
||||||
.send_switch_off_cmd(requestor_info, switch_id)?;
|
requestor_info,
|
||||||
|
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
|
||||||
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct SwitchRequestInfo {
|
pub struct SwitchRequestInfo {
|
||||||
pub requestor_info: MessageMetadata,
|
pub requestor_info: MessageMetadata,
|
||||||
pub switch_id: PcduSwitch,
|
pub switch_id: SwitchId,
|
||||||
pub target_state: satrs::power::SwitchStateBinary,
|
pub target_state: SwitchStateBinary,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test switch helper which can be used for unittests.
|
// Test switch helper which can be used for unittests.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct TestSwitchHelper {
|
pub struct TestSwitchHelper {
|
||||||
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
|
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
|
||||||
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
|
pub switch_info_requests: RefCell<VecDeque<SwitchId>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub switch_delay_request_count: u32,
|
pub switch_delay_request_count: u32,
|
||||||
pub next_switch_delay: Duration,
|
pub next_switch_delay: Duration,
|
||||||
@@ -120,13 +182,11 @@ impl Default for TestSwitchHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
|
/*
|
||||||
|
impl PowerSwitchInfo<SwitchId> for TestSwitchHelper {
|
||||||
type Error = SwitchInfoError;
|
type Error = SwitchInfoError;
|
||||||
|
|
||||||
fn switch_state(
|
fn switch_state(&self, switch_id: SwitchId) -> Result<satrs::power::SwitchState, Self::Error> {
|
||||||
&self,
|
|
||||||
switch_id: PcduSwitch,
|
|
||||||
) -> Result<satrs::power::SwitchState, Self::Error> {
|
|
||||||
let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut();
|
let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut();
|
||||||
switch_info_requests_mut.push_back(switch_id);
|
switch_info_requests_mut.push_back(switch_id);
|
||||||
if !self.switch_map_valid {
|
if !self.switch_map_valid {
|
||||||
@@ -144,13 +204,13 @@ impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
|
impl PowerSwitcherCommandSender<SwitchId> for TestSwitchHelper {
|
||||||
type Error = SwitchCommandingError;
|
type Error = SwitchCommandingError;
|
||||||
|
|
||||||
fn send_switch_on_cmd(
|
fn send_switch_on_cmd(
|
||||||
&self,
|
&self,
|
||||||
requestor_info: MessageMetadata,
|
requestor_info: MessageMetadata,
|
||||||
switch_id: PcduSwitch,
|
switch_id: SwitchId,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
||||||
switch_requests_mut.push_back(SwitchRequestInfo {
|
switch_requests_mut.push_back(SwitchRequestInfo {
|
||||||
@@ -170,7 +230,7 @@ impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
|
|||||||
fn send_switch_off_cmd(
|
fn send_switch_off_cmd(
|
||||||
&self,
|
&self,
|
||||||
requestor_info: MessageMetadata,
|
requestor_info: MessageMetadata,
|
||||||
switch_id: PcduSwitch,
|
switch_id: SwitchId,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
||||||
switch_requests_mut.push_back(SwitchRequestInfo {
|
switch_requests_mut.push_back(SwitchRequestInfo {
|
||||||
@@ -187,11 +247,12 @@ impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl TestSwitchHelper {
|
impl TestSwitchHelper {
|
||||||
// Helper function which can be used to force a switch to another state for test purposes.
|
// Helper function which can be used to force a switch to another state for test purposes.
|
||||||
pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) {
|
pub fn set_switch_state(&mut self, switch: SwitchId, state: SwitchState) {
|
||||||
self.switch_map.get_mut().0.insert(switch, state);
|
self.switch_map.get_mut().0.insert(switch, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+235
-148
@@ -1,43 +1,118 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::VecDeque,
|
collections::{HashMap, VecDeque},
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::{mpsc, Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
|
use models::{
|
||||||
|
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
||||||
|
pcdu::{
|
||||||
|
self, SwitchId, SwitchMapBinary, SwitchMapBinaryWrapper, SwitchRequest, SwitchState,
|
||||||
|
SwitchStateBinary, SwitchesBitfield,
|
||||||
|
},
|
||||||
|
ComponentId,
|
||||||
|
};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
hk::{HkRequest, HkRequestVariant},
|
|
||||||
mode::{
|
mode::{
|
||||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||||
ModeRequestHandlerMpscBounded,
|
ModeRequestHandlerMpscBounded,
|
||||||
},
|
},
|
||||||
mode_tree::{ModeChild, ModeNode},
|
mode_tree::{ModeChild, ModeNode},
|
||||||
power::SwitchRequest,
|
|
||||||
pus::{EcssTmSender, PusTmVariant},
|
|
||||||
queue::GenericSendError,
|
queue::GenericSendError,
|
||||||
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
|
request::{GenericMessage, MessageMetadata},
|
||||||
spacepackets::ByteConversionError,
|
spacepackets::CcsdsPacketIdAndPsc,
|
||||||
};
|
|
||||||
use satrs_example::{
|
|
||||||
config::components::NO_SENDER,
|
|
||||||
ids::{eps::PCDU, generic_pus::PUS_MODE},
|
|
||||||
DeviceMode, TimestampHelper,
|
|
||||||
};
|
};
|
||||||
|
use satrs_example::{config::components::NO_SENDER, DeviceMode, TimestampHelper};
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
eps::{
|
eps::{PcduReply, PcduRequest},
|
||||||
PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper,
|
|
||||||
},
|
|
||||||
SerializableSimMsgPayload, SimReply, SimRequest,
|
SerializableSimMsgPayload, SimReply, SimRequest,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::IntoEnumIterator as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||||
hk::PusHkHelper,
|
|
||||||
pus::hk::{HkReply, HkReplyVariant},
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
requests::CompositeRequest,
|
pub struct SwitchSet {
|
||||||
tmtc::sender::TmTcSender,
|
pub valid: bool,
|
||||||
};
|
pub switch_map: SwitchMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchSet {
|
||||||
|
pub fn new(switch_map: SwitchMap) -> Self {
|
||||||
|
Self {
|
||||||
|
valid: true,
|
||||||
|
switch_map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_init_switches_unknown() -> Self {
|
||||||
|
let wrapper = SwitchMapWrapper::default();
|
||||||
|
Self::new(wrapper.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bitfield(&self) -> Option<SwitchesBitfield> {
|
||||||
|
for entry in SwitchId::iter() {
|
||||||
|
if !self.switch_map.contains_key(&entry) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
SwitchesBitfield::builder()
|
||||||
|
.with_magnetorquer(*self.switch_map.get(&SwitchId::Mgt).unwrap() == SwitchState::On)
|
||||||
|
.with_mgm1(*self.switch_map.get(&SwitchId::Mgm1).unwrap() == SwitchState::On)
|
||||||
|
.with_mgm0(*self.switch_map.get(&SwitchId::Mgm0).unwrap() == SwitchState::On)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_switch_state(&mut self, switch_id: SwitchId, state: SwitchState) -> bool {
|
||||||
|
if !self.switch_map.contains_key(&switch_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*self.switch_map.get_mut(&switch_id).unwrap() = state;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SwitchMap = HashMap<SwitchId, SwitchState>;
|
||||||
|
|
||||||
|
pub struct SwitchMapWrapper(pub SwitchMap);
|
||||||
|
|
||||||
|
impl Default for SwitchMapWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut switch_map = SwitchMap::default();
|
||||||
|
for entry in SwitchId::iter() {
|
||||||
|
switch_map.insert(entry, SwitchState::Unknown);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchMapWrapper {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new_with_init_switches_off() -> Self {
|
||||||
|
let mut switch_map = SwitchMap::default();
|
||||||
|
for entry in SwitchId::iter() {
|
||||||
|
switch_map.insert(entry, SwitchState::Off);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self {
|
||||||
|
Self(
|
||||||
|
switch_map
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| (*key, SwitchState::from(*value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
|
||||||
|
|
||||||
pub trait SerialInterface {
|
pub trait SerialInterface {
|
||||||
type Error: core::fmt::Debug;
|
type Error: core::fmt::Debug;
|
||||||
@@ -194,44 +269,47 @@ pub enum OpCode {
|
|||||||
PollAndRecvReplies = 1,
|
PollAndRecvReplies = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
||||||
pub struct SwitchSet {
|
|
||||||
pub valid: bool,
|
|
||||||
pub switch_map: SwitchMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
|
|
||||||
|
|
||||||
/// Example PCDU device handler.
|
/// Example PCDU device handler.
|
||||||
#[derive(new)]
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub struct PcduHandler<ComInterface: SerialInterface> {
|
pub struct PcduHandler<ComInterface: SerialInterface> {
|
||||||
id: UniqueApidTargetId,
|
|
||||||
dev_str: &'static str,
|
dev_str: &'static str,
|
||||||
mode_node: ModeRequestHandlerMpscBounded,
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
|
||||||
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
|
||||||
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||||
tm_sender: TmTcSender,
|
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
|
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
pub com_interface: ComInterface,
|
pub com_interface: ComInterface,
|
||||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||||
#[new(value = "PusHkHelper::new(id)")]
|
|
||||||
hk_helper: PusHkHelper,
|
|
||||||
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
|
|
||||||
mode_and_submode: ModeAndSubmode,
|
mode_and_submode: ModeAndSubmode,
|
||||||
#[new(default)]
|
|
||||||
stamp_helper: TimestampHelper,
|
stamp_helper: TimestampHelper,
|
||||||
#[new(value = "[0; 256]")]
|
|
||||||
tm_buf: [u8; 256],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||||
|
pub fn new(
|
||||||
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
|
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
|
||||||
|
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||||
|
com_interface: ComInterface,
|
||||||
|
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
dev_str: "PCDU",
|
||||||
|
mode_node,
|
||||||
|
tc_rx,
|
||||||
|
switch_request_rx,
|
||||||
|
tm_tx,
|
||||||
|
com_interface,
|
||||||
|
shared_switch_map,
|
||||||
|
mode_and_submode: ModeAndSubmode::new(0, 0),
|
||||||
|
stamp_helper: TimestampHelper::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn periodic_operation(&mut self, op_code: OpCode) {
|
pub fn periodic_operation(&mut self, op_code: OpCode) {
|
||||||
match op_code {
|
match op_code {
|
||||||
OpCode::RegularOp => {
|
OpCode::RegularOp => {
|
||||||
self.stamp_helper.update_from_now();
|
self.stamp_helper.update_from_now();
|
||||||
// Handle requests.
|
// Handle requests.
|
||||||
self.handle_composite_requests();
|
self.handle_telecommands();
|
||||||
self.handle_mode_requests();
|
self.handle_mode_requests();
|
||||||
self.handle_switch_requests();
|
self.handle_switch_requests();
|
||||||
// Poll the switch states and/or telemetry regularly here.
|
// Poll the switch states and/or telemetry regularly here.
|
||||||
@@ -246,75 +324,87 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_composite_requests(&mut self) {
|
pub fn handle_telecommands(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
match self.composite_request_rx.try_recv() {
|
match self.tc_rx.try_recv() {
|
||||||
Ok(ref msg) => match &msg.message {
|
Ok(packet) => {
|
||||||
CompositeRequest::Hk(hk_request) => {
|
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
|
||||||
self.handle_hk_request(&msg.requestor_info, hk_request)
|
match postcard::from_bytes::<pcdu::request::Request>(&packet.payload) {
|
||||||
}
|
Ok(request) => {
|
||||||
// TODO: This object does not have actions (yet).. Still send back completion failure
|
log::info!(
|
||||||
// reply.
|
"received request {:?} with TC ID {:#010x}",
|
||||||
CompositeRequest::Action(_action_req) => {}
|
request,
|
||||||
},
|
tc_id.raw()
|
||||||
|
);
|
||||||
Err(e) => {
|
match request {
|
||||||
if e != mpsc::TryRecvError::Empty {
|
pcdu::request::Request::Ping => {
|
||||||
log::warn!(
|
self.send_tm(Some(tc_id), pcdu::response::Response::Ok)
|
||||||
"{}: failed to receive composite request: {:?}",
|
}
|
||||||
self.dev_str,
|
pcdu::request::Request::GetSwitches => self.send_tm(
|
||||||
e
|
Some(tc_id),
|
||||||
);
|
pcdu::response::Response::Switches(
|
||||||
} else {
|
self.shared_switch_map
|
||||||
break;
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_bitfield()
|
||||||
|
.expect("could not build switches response"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pcdu::request::Request::EnableSwitches(switches) => {
|
||||||
|
self.handle_switches_bitfield_request(
|
||||||
|
switches,
|
||||||
|
SwitchStateBinary::On,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pcdu::request::Request::DisableSwitches(switches) => {
|
||||||
|
self.handle_switches_bitfield_request(
|
||||||
|
switches,
|
||||||
|
SwitchStateBinary::Off,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to deserialize request: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
std::sync::mpsc::TryRecvError::Empty => break,
|
||||||
|
std::sync::mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("packet sender disconnected")
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
pub fn handle_switches_bitfield_request(
|
||||||
match hk_request.variant {
|
&mut self,
|
||||||
HkRequestVariant::OneShot => {
|
switches: SwitchesBitfield,
|
||||||
if hk_request.unique_id == SetId::SwitcherSet as u32 {
|
state: SwitchStateBinary,
|
||||||
if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
|
) {
|
||||||
self.stamp_helper.stamp(),
|
if switches.mgm0() {
|
||||||
SetId::SwitcherSet as u32,
|
self.handle_device_switching(SwitchId::Mgm0, state);
|
||||||
&mut |hk_buf| {
|
}
|
||||||
// Send TM down as JSON.
|
if switches.mgm1() {
|
||||||
let switch_map_snapshot = self
|
self.handle_device_switching(SwitchId::Mgm1, state);
|
||||||
.shared_switch_map
|
}
|
||||||
.lock()
|
if switches.magnetorquer() {
|
||||||
.expect("failed to lock switch map")
|
self.handle_device_switching(SwitchId::Mgt, state);
|
||||||
.clone();
|
}
|
||||||
let switch_map_json = serde_json::to_string(&switch_map_snapshot)
|
}
|
||||||
.expect("failed to serialize switch map");
|
|
||||||
if switch_map_json.len() > hk_buf.len() {
|
pub fn send_tm(&self, tc_id: Option<CcsdsPacketIdAndPsc>, response: pcdu::response::Response) {
|
||||||
log::error!("switch map JSON too large for HK buffer");
|
match pack_ccsds_tm_packet_for_now(ComponentId::EpsPcdu, tc_id, &response) {
|
||||||
return Err(ByteConversionError::ToSliceTooSmall {
|
Ok(packet) => {
|
||||||
found: hk_buf.len(),
|
if let Err(e) = self.tm_tx.send(packet) {
|
||||||
expected: switch_map_json.len(),
|
log::warn!("failed to send TM packet: {}", e);
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(switch_map_json.len())
|
|
||||||
},
|
|
||||||
&mut self.tm_buf,
|
|
||||||
) {
|
|
||||||
self.tm_sender
|
|
||||||
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
|
||||||
.expect("failed to send HK TM");
|
|
||||||
self.hk_reply_tx
|
|
||||||
.send(GenericMessage::new(
|
|
||||||
*requestor_info,
|
|
||||||
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
|
||||||
))
|
|
||||||
.expect("failed to send HK reply");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HkRequestVariant::EnablePeriodic => todo!(),
|
Err(e) => {
|
||||||
HkRequestVariant::DisablePeriodic => todo!(),
|
log::warn!("failed to pack TM packet: {}", e);
|
||||||
HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,22 +447,26 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_device_switching(&mut self, switch_id: SwitchId, state: SwitchStateBinary) {
|
||||||
|
let pcdu_req = PcduRequest::SwitchDevice {
|
||||||
|
switch: switch_id,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
|
||||||
|
self.com_interface
|
||||||
|
.send(pcdu_req_ser.as_bytes())
|
||||||
|
.expect("failed to send switch request to PCDU");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_switch_requests(&mut self) {
|
pub fn handle_switch_requests(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
match self.switch_request_rx.try_recv() {
|
match self.switch_request_rx.try_recv() {
|
||||||
Ok(switch_req) => match PcduSwitch::try_from(switch_req.message.switch_id()) {
|
Ok(switch_req) => {
|
||||||
Ok(pcdu_switch) => {
|
self.handle_device_switching(
|
||||||
let pcdu_req = PcduRequest::SwitchDevice {
|
switch_req.message.switch_id(),
|
||||||
switch: pcdu_switch,
|
switch_req.message.target_state(),
|
||||||
state: switch_req.message.target_state(),
|
);
|
||||||
};
|
}
|
||||||
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
|
|
||||||
self.com_interface
|
|
||||||
.send(pcdu_req_ser.as_bytes())
|
|
||||||
.expect("failed to send switch request to PCDU");
|
|
||||||
}
|
|
||||||
Err(e) => todo!("failed to convert switch ID {:?} to typed PCDU switch", e),
|
|
||||||
},
|
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
mpsc::TryRecvError::Empty => break,
|
mpsc::TryRecvError::Empty => break,
|
||||||
mpsc::TryRecvError::Disconnected => {
|
mpsc::TryRecvError::Disconnected => {
|
||||||
@@ -450,7 +544,7 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
|
|||||||
if requestor.sender_id() == NO_SENDER {
|
if requestor.sender_id() == NO_SENDER {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if requestor.sender_id() != PUS_MODE.id() {
|
if requestor.sender_id() != ComponentId::Ground as u32 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {}",
|
"can not send back mode reply to sender {}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
@@ -467,7 +561,7 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
|
|||||||
requestor: MessageMetadata,
|
requestor: MessageMetadata,
|
||||||
reply: ModeReply,
|
reply: ModeReply,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
if requestor.sender_id() != PUS_MODE.id() {
|
if requestor.sender_id() != ComponentId::Ground as u32 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {}",
|
"can not send back mode reply to sender {}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
@@ -490,7 +584,7 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
|
|||||||
|
|
||||||
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
|
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
|
||||||
fn id(&self) -> satrs::ComponentId {
|
fn id(&self) -> satrs::ComponentId {
|
||||||
PCDU.into()
|
ComponentId::EpsPcdu as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,12 +600,8 @@ impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use arbitrary_int::u21;
|
use models::pcdu::{SwitchMapBinary, SwitchStateBinary};
|
||||||
use satrs::{
|
use satrs::{mode::ModeRequest, request::GenericMessage};
|
||||||
mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
|
|
||||||
};
|
|
||||||
use satrs_example::ids::{self, Apid};
|
|
||||||
use satrs_minisim::eps::SwitchMapBinary;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -550,13 +640,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct PcduTestbench {
|
pub struct PcduTestbench {
|
||||||
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
||||||
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
|
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||||
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
|
||||||
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
|
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
|
||||||
pub handler: PcduHandler<SerialInterfaceTest>,
|
pub handler: PcduHandler<SerialInterfaceTest>,
|
||||||
}
|
}
|
||||||
@@ -564,33 +654,30 @@ mod tests {
|
|||||||
impl PcduTestbench {
|
impl PcduTestbench {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5);
|
let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5);
|
||||||
let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5);
|
let (_mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5);
|
||||||
let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
|
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 mode_node =
|
||||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, mode_request_rx);
|
||||||
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
|
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
|
||||||
let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
|
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
|
||||||
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
|
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
|
||||||
let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
|
let shared_switch_map =
|
||||||
|
Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown()));
|
||||||
let mut handler = PcduHandler::new(
|
let mut handler = PcduHandler::new(
|
||||||
UniqueApidTargetId::new(Apid::Eps.raw_value(), u21::new(0)),
|
|
||||||
"TEST_PCDU",
|
|
||||||
mode_node,
|
mode_node,
|
||||||
composite_request_rx,
|
tc_rx,
|
||||||
hk_reply_tx,
|
tm_tx.clone(),
|
||||||
switch_reqest_rx,
|
switch_reqest_rx,
|
||||||
TmTcSender::Heap(tm_tx.clone()),
|
|
||||||
SerialInterfaceTest::default(),
|
SerialInterfaceTest::default(),
|
||||||
shared_switch_map,
|
shared_switch_map,
|
||||||
);
|
);
|
||||||
handler.add_mode_parent(ids::eps::SUBSYSTEM.into(), mode_reply_tx_to_parent);
|
handler.add_mode_parent(ComponentId::EpsSubsystem as u32, mode_reply_tx_to_parent);
|
||||||
handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus);
|
//handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus);
|
||||||
Self {
|
Self {
|
||||||
mode_request_tx,
|
mode_request_tx,
|
||||||
mode_reply_rx_to_pus,
|
mode_reply_rx_to_pus,
|
||||||
mode_reply_rx_to_parent,
|
mode_reply_rx_to_parent,
|
||||||
composite_request_tx,
|
tc_tx,
|
||||||
hk_reply_rx,
|
|
||||||
tm_rx,
|
tm_rx,
|
||||||
switch_request_tx,
|
switch_request_tx,
|
||||||
handler,
|
handler,
|
||||||
@@ -610,7 +697,7 @@ mod tests {
|
|||||||
pub fn verify_switch_req_was_sent(
|
pub fn verify_switch_req_was_sent(
|
||||||
&self,
|
&self,
|
||||||
expected_queue_len: usize,
|
expected_queue_len: usize,
|
||||||
switch_id: PcduSwitch,
|
switch_id: SwitchId,
|
||||||
target_state: SwitchStateBinary,
|
target_state: SwitchStateBinary,
|
||||||
) {
|
) {
|
||||||
// Check that there is now communication happening.
|
// Check that there is now communication happening.
|
||||||
@@ -679,7 +766,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.mode_request_tx
|
.mode_request_tx
|
||||||
.send(GenericMessage::new(
|
.send(GenericMessage::new(
|
||||||
MessageMetadata::new(0, PUS_MODE.id()),
|
MessageMetadata::new(0, ComponentId::Ground as u32),
|
||||||
ModeRequest::SetMode {
|
ModeRequest::SetMode {
|
||||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||||
forced: false,
|
forced: false,
|
||||||
@@ -687,7 +774,7 @@ mod tests {
|
|||||||
))
|
))
|
||||||
.expect("failed to send mode request");
|
.expect("failed to send mode request");
|
||||||
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
|
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
|
||||||
assert!(!switch_map_shared.valid);
|
assert!(switch_map_shared.valid);
|
||||||
drop(switch_map_shared);
|
drop(switch_map_shared);
|
||||||
testbench.handler.periodic_operation(OpCode::RegularOp);
|
testbench.handler.periodic_operation(OpCode::RegularOp);
|
||||||
testbench
|
testbench
|
||||||
@@ -714,7 +801,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.mode_request_tx
|
.mode_request_tx
|
||||||
.send(GenericMessage::new(
|
.send(GenericMessage::new(
|
||||||
MessageMetadata::new(0, PUS_MODE.id()),
|
MessageMetadata::new(0, ComponentId::Ground as u32),
|
||||||
ModeRequest::SetMode {
|
ModeRequest::SetMode {
|
||||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||||
forced: false,
|
forced: false,
|
||||||
@@ -724,8 +811,8 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.switch_request_tx
|
.switch_request_tx
|
||||||
.send(GenericMessage::new(
|
.send(GenericMessage::new(
|
||||||
MessageMetadata::new(0, ids::acs::MGM0.id()),
|
MessageMetadata::new(0, ComponentId::AcsMgm0 as u32),
|
||||||
SwitchRequest::new(0, SwitchStateBinary::On),
|
SwitchRequest::new(SwitchId::Mgm0, SwitchStateBinary::On),
|
||||||
))
|
))
|
||||||
.expect("failed to send switch request");
|
.expect("failed to send switch request");
|
||||||
testbench.handler.periodic_operation(OpCode::RegularOp);
|
testbench.handler.periodic_operation(OpCode::RegularOp);
|
||||||
@@ -733,11 +820,11 @@ mod tests {
|
|||||||
.handler
|
.handler
|
||||||
.periodic_operation(OpCode::PollAndRecvReplies);
|
.periodic_operation(OpCode::PollAndRecvReplies);
|
||||||
|
|
||||||
testbench.verify_switch_req_was_sent(2, PcduSwitch::Mgm, SwitchStateBinary::On);
|
testbench.verify_switch_req_was_sent(2, SwitchId::Mgm0, SwitchStateBinary::On);
|
||||||
testbench.verify_switch_info_req_was_sent(1);
|
testbench.verify_switch_info_req_was_sent(1);
|
||||||
let mut switch_map = SwitchMapBinaryWrapper::default().0;
|
let mut switch_map = SwitchMapBinaryWrapper::default().0;
|
||||||
*switch_map
|
*switch_map
|
||||||
.get_mut(&PcduSwitch::Mgm)
|
.get_mut(&SwitchId::Mgm0)
|
||||||
.expect("switch state setting failed") = SwitchStateBinary::On;
|
.expect("switch state setting failed") = SwitchStateBinary::On;
|
||||||
testbench.verify_switch_reply_received(1, switch_map);
|
testbench.verify_switch_reply_received(1, switch_map);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use models::{ccsds::CcsdsTmPacketOwned, control, ComponentId, Event, Message};
|
||||||
|
|
||||||
|
use crate::ccsds::pack_ccsds_tm_packet_for_now;
|
||||||
|
|
||||||
|
pub struct EventManager {
|
||||||
|
pub ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
|
||||||
|
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventManager {
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
if let Ok(event) = self.ctrl_rx.try_recv() {
|
||||||
|
self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_to_tm(
|
||||||
|
&mut self,
|
||||||
|
sender_id: ComponentId,
|
||||||
|
event: &(impl serde::Serialize + Message),
|
||||||
|
) {
|
||||||
|
match pack_ccsds_tm_packet_for_now(sender_id, None, event) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if let Err(e) = self.tm_tx.send(packet) {
|
||||||
|
log::warn!("error sending event TM packet: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("error packing event TM packet: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
//! This is an auto-generated configuration module.
|
|
||||||
use satrs::request::UniqueApidTargetId;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
|
|
||||||
#[bitbybit::bitenum(u11)]
|
|
||||||
pub enum Apid {
|
|
||||||
Sched = 1,
|
|
||||||
GenericPus = 2,
|
|
||||||
Acs = 3,
|
|
||||||
Cfdp = 4,
|
|
||||||
Tmtc = 5,
|
|
||||||
Eps = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod acs {
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u21, exhaustive = false)]
|
|
||||||
pub enum Id {
|
|
||||||
Subsystem = 1,
|
|
||||||
Assembly = 2,
|
|
||||||
Mgm0 = 3,
|
|
||||||
Mgm1 = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Subsystem.raw_value());
|
|
||||||
pub const ASSEMBLY: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Assembly.raw_value());
|
|
||||||
pub const MGM0: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm0.raw_value());
|
|
||||||
pub const MGM1: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm1.raw_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod eps {
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u21, exhaustive = false)]
|
|
||||||
pub enum Id {
|
|
||||||
Pcdu = 0,
|
|
||||||
Subsystem = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PCDU: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Pcdu.raw_value());
|
|
||||||
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Subsystem.raw_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod generic_pus {
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u21, exhaustive = false)]
|
|
||||||
pub enum Id {
|
|
||||||
PusEventManagement = 0,
|
|
||||||
PusRouting = 1,
|
|
||||||
PusTest = 2,
|
|
||||||
PusAction = 3,
|
|
||||||
PusMode = 4,
|
|
||||||
PusHk = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PUS_EVENT_MANAGEMENT: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
|
||||||
super::Apid::GenericPus.raw_value(),
|
|
||||||
Id::PusEventManagement.raw_value(),
|
|
||||||
);
|
|
||||||
pub const PUS_ROUTING: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
|
||||||
super::Apid::GenericPus.raw_value(),
|
|
||||||
Id::PusRouting.raw_value(),
|
|
||||||
);
|
|
||||||
pub const PUS_TEST: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
|
||||||
super::Apid::GenericPus.raw_value(),
|
|
||||||
Id::PusTest.raw_value(),
|
|
||||||
);
|
|
||||||
pub const PUS_ACTION: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
|
||||||
super::Apid::GenericPus.raw_value(),
|
|
||||||
Id::PusAction.raw_value(),
|
|
||||||
);
|
|
||||||
pub const PUS_MODE: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
|
||||||
super::Apid::GenericPus.raw_value(),
|
|
||||||
Id::PusMode.raw_value(),
|
|
||||||
);
|
|
||||||
pub const PUS_HK: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::GenericPus.raw_value(), Id::PusHk.raw_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod sched {
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u21, exhaustive = false)]
|
|
||||||
pub enum Id {
|
|
||||||
PusSched = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PUS_SCHED: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Sched.raw_value(), Id::PusSched.raw_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod tmtc {
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u21, exhaustive = false)]
|
|
||||||
pub enum Id {
|
|
||||||
UdpServer = 0,
|
|
||||||
TcpServer = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const UDP_SERVER: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::UdpServer.raw_value());
|
|
||||||
pub const TCP_SERVER: super::UniqueApidTargetId =
|
|
||||||
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::TcpServer.raw_value());
|
|
||||||
}
|
|
||||||
@@ -1,66 +1,29 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
use std::sync::mpsc;
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::warn;
|
||||||
|
use models::ccsds::CcsdsTmPacketOwned;
|
||||||
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||||
use satrs::pus::HandlingStatus;
|
use satrs::pus::HandlingStatus;
|
||||||
use satrs::queue::GenericSendError;
|
use satrs::queue::GenericSendError;
|
||||||
use satrs::tmtc::PacketAsVec;
|
|
||||||
|
|
||||||
use satrs::pool::{PoolProviderWithGuards, SharedStaticMemoryPool};
|
|
||||||
use satrs::tmtc::PacketInPool;
|
|
||||||
|
|
||||||
use crate::tmtc::sender::TmTcSender;
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
pub trait UdpTmHandler {
|
pub trait UdpTmHandlerProvider {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StaticUdpTmHandler {
|
pub struct UdpTmHandlerWithChannel {
|
||||||
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
pub tm_store: SharedStaticMemoryPool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpTmHandler for StaticUdpTmHandler {
|
impl UdpTmHandlerProvider for UdpTmHandlerWithChannel {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, &recv_addr: &SocketAddr) {
|
|
||||||
while let Ok(pus_tm_in_pool) = self.tm_rx.try_recv() {
|
|
||||||
let store_lock = self.tm_store.write();
|
|
||||||
if store_lock.is_err() {
|
|
||||||
warn!("Locking TM store failed");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut store_lock = store_lock.unwrap();
|
|
||||||
let pg = store_lock.read_with_guard(pus_tm_in_pool.store_addr);
|
|
||||||
let read_res = pg.read_as_vec();
|
|
||||||
if read_res.is_err() {
|
|
||||||
warn!("Error reading TM pool data");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let buf = read_res.unwrap();
|
|
||||||
let result = socket.send_to(&buf, recv_addr);
|
|
||||||
if let Err(e) = result {
|
|
||||||
warn!("Sending TM with UDP socket failed: {e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DynamicUdpTmHandler {
|
|
||||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpTmHandler for DynamicUdpTmHandler {
|
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||||
while let Ok(tm) = self.tm_rx.try_recv() {
|
while let Ok(tm) = self.tm_rx.try_recv() {
|
||||||
if tm.packet.len() > 9 {
|
log::debug!("sending TM from sender {:?}", tm.tm_header.sender_id);
|
||||||
let service = tm.packet[7];
|
let result = socket.send_to(&tm.to_vec(), recv_addr);
|
||||||
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);
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
warn!("Sending TM with UDP socket failed: {e}")
|
warn!("Sending TM with UDP socket failed: {e}")
|
||||||
}
|
}
|
||||||
@@ -68,12 +31,49 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> {
|
#[derive(Default, Debug, Clone)]
|
||||||
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>,
|
pub struct TestTmHandler {
|
||||||
pub tm_handler: TmHandler,
|
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) {
|
pub fn periodic_operation(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if self.poll_tc_server() == HandlingStatus::Empty {
|
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||||
@@ -107,15 +107,12 @@ impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
net::IpAddr,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use arbitrary_int::traits::Integer as _;
|
use arbitrary_int::traits::Integer as _;
|
||||||
use arbitrary_int::u14;
|
use arbitrary_int::u14;
|
||||||
|
use models::Apid;
|
||||||
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
@@ -125,25 +122,13 @@ mod tests {
|
|||||||
ComponentId,
|
ComponentId,
|
||||||
};
|
};
|
||||||
use satrs_example::config::OBSW_SERVER_ADDR;
|
use satrs_example::config::OBSW_SERVER_ADDR;
|
||||||
use satrs_example::ids;
|
|
||||||
|
|
||||||
use crate::tmtc::sender::{MockSender, TmTcSender};
|
use crate::tmtc::sender::MockSender;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
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]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||||
@@ -154,7 +139,7 @@ mod tests {
|
|||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
let mut udp_dyn_server = UdpTmtcServer {
|
let mut udp_dyn_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler,
|
tm_handler: tm_handler.into(),
|
||||||
};
|
};
|
||||||
udp_dyn_server.periodic_operation();
|
udp_dyn_server.periodic_operation();
|
||||||
let queue = udp_dyn_server
|
let queue = udp_dyn_server
|
||||||
@@ -179,9 +164,9 @@ mod tests {
|
|||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
let mut udp_dyn_server = UdpTmtcServer {
|
let mut udp_dyn_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
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 sph = SpHeader::new_for_unseg_tc(Apid::Tmtc.raw_value(), u14::ZERO, 0);
|
||||||
let ping_tc = PusTcCreator::new_simple(
|
let ping_tc = PusTcCreator::new_simple(
|
||||||
sph,
|
sph,
|
||||||
MessageTypeId::new(17, 1),
|
MessageTypeId::new(17, 1),
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
use std::sync::mpsc::{self};
|
|
||||||
|
|
||||||
use crate::pus::create_verification_reporter;
|
|
||||||
use arbitrary_int::traits::Integer as _;
|
use arbitrary_int::traits::Integer as _;
|
||||||
use arbitrary_int::u11;
|
use arbitrary_int::u11;
|
||||||
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
|
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
|
||||||
@@ -34,6 +31,7 @@ impl EventTmHook for EventApidSetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
|
/// 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.
|
/// packets. It also handles the verification completion of PUS event service requests.
|
||||||
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
||||||
@@ -292,3 +290,4 @@ mod tests {
|
|||||||
// TODO: Add test.
|
// TODO: Add test.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@@ -39,6 +39,7 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi
|
|||||||
VerificationReporter::new(owner_id, &verif_cfg)
|
VerificationReporter::new(owner_id, &verif_cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
||||||
pub struct PusTcMpscRouter {
|
pub struct PusTcMpscRouter {
|
||||||
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||||
@@ -187,6 +188,7 @@ impl PusTcDistributor {
|
|||||||
Ok(HandlingStatus::HandledOne)
|
Ok(HandlingStatus::HandledOne)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub trait TargetedPusService {
|
pub trait TargetedPusService {
|
||||||
const SERVICE_ID: u8;
|
const SERVICE_ID: u8;
|
||||||
@@ -15,8 +15,8 @@ use satrs::spacepackets::ecss::tc::PusTcReader;
|
|||||||
use satrs::spacepackets::ecss::PusPacket;
|
use satrs::spacepackets::ecss::PusPacket;
|
||||||
use satrs::ComponentId;
|
use satrs::ComponentId;
|
||||||
use satrs_example::config::tmtc_err;
|
use satrs_example::config::tmtc_err;
|
||||||
use satrs_example::ids;
|
|
||||||
|
|
||||||
|
/*
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CompositeRequest {
|
pub enum CompositeRequest {
|
||||||
@@ -153,3 +153,4 @@ impl PusRequestRouter<ModeRequest> for GenericRequestRouter {
|
|||||||
Err(GenericRoutingError::UnknownTargetId(target_id))
|
Err(GenericRoutingError::UnknownTargetId(target_id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@@ -1,7 +1,23 @@
|
|||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub use models::ComponentId;
|
||||||
use satrs::spacepackets::time::cds::CdsTime;
|
use satrs::spacepackets::time::cds::CdsTime;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod ids;
|
|
||||||
|
/// 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, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum DeviceMode {
|
pub enum DeviceMode {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
|
|||||||
message
|
message
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.level(log::LevelFilter::Debug)
|
.level(log::LevelFilter::Info)
|
||||||
.chain(std::io::stdout())
|
.chain(std::io::stdout())
|
||||||
.chain(fern::log_file("output.log")?)
|
.chain(fern::log_file("output.log")?)
|
||||||
.apply()?;
|
.apply()?;
|
||||||
|
|||||||
+83
-251
@@ -5,12 +5,10 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper};
|
|
||||||
use eps::{
|
use eps::{
|
||||||
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
|
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
|
||||||
PowerSwitchHelper,
|
PowerSwitchHelper,
|
||||||
};
|
};
|
||||||
use events::EventHandler;
|
|
||||||
use interface::{
|
use interface::{
|
||||||
sim_client_udp::create_sim_client,
|
sim_client_udp::create_sim_client,
|
||||||
tcp::{SyncTcpTmSource, TcpTask},
|
tcp::{SyncTcpTmSource, TcpTask},
|
||||||
@@ -18,256 +16,102 @@ use interface::{
|
|||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use logger::setup_logger;
|
use logger::setup_logger;
|
||||||
use pus::{
|
use models::ComponentId;
|
||||||
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::{
|
use satrs::{
|
||||||
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
||||||
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
|
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
|
||||||
mode_tree::connect_mode_nodes,
|
pus::HandlingStatus,
|
||||||
pus::{event_man::EventRequestWithToken, EcssTcCacher, HandlingStatus},
|
|
||||||
request::{GenericMessage, MessageMetadata},
|
request::{GenericMessage, MessageMetadata},
|
||||||
spacepackets::time::cds::CdsTime,
|
spacepackets::time::cds::CdsTime,
|
||||||
};
|
};
|
||||||
use satrs_example::{
|
use satrs_example::{
|
||||||
config::{
|
config::{
|
||||||
components::NO_SENDER,
|
components::NO_SENDER,
|
||||||
pool::create_sched_tc_pool,
|
tasks::{FREQ_MS_AOCS, FREQ_MS_CONTROLLER, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS},
|
||||||
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,
|
OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
|
||||||
},
|
},
|
||||||
ids::{
|
|
||||||
acs::*,
|
|
||||||
eps::*,
|
|
||||||
tmtc::{TCP_SERVER, UDP_SERVER},
|
|
||||||
},
|
|
||||||
DeviceMode,
|
DeviceMode,
|
||||||
};
|
};
|
||||||
use tmtc::sender::TmTcSender;
|
use tmtc::sender::TmTcSender;
|
||||||
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
|
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
use crate::{
|
||||||
if #[cfg(feature = "heap_tmtc")] {
|
acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper},
|
||||||
use interface::udp::DynamicUdpTmHandler;
|
control::Controller,
|
||||||
use satrs::pus::EcssTcVecCacher;
|
eps::pcdu::SwitchSet,
|
||||||
use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic};
|
event_manager::EventManager,
|
||||||
} else {
|
interface::udp::UdpTmHandlerWithChannel,
|
||||||
use std::sync::RwLock;
|
tmtc::tc_source::CcsdsDistributor,
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod acs;
|
mod acs;
|
||||||
|
mod ccsds;
|
||||||
|
mod control;
|
||||||
mod eps;
|
mod eps;
|
||||||
mod events;
|
mod event_manager;
|
||||||
mod hk;
|
|
||||||
mod interface;
|
mod interface;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod pus;
|
|
||||||
mod requests;
|
|
||||||
mod spi;
|
mod spi;
|
||||||
mod tmtc;
|
mod tmtc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
setup_logger().expect("setting up logging with fern failed");
|
setup_logger().expect("setting up logging with fern failed");
|
||||||
println!("Running OBSW example");
|
println!("Runng 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 (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||||
let (tm_sink_tx, tm_sink_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);
|
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 (sim_request_tx, sim_request_rx) = mpsc::channel();
|
||||||
let (mgm_0_sim_reply_tx, mgm_0_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 (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
|
||||||
let (pcdu_sim_reply_tx, pcdu_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);
|
let mut opt_sim_client = create_sim_client(sim_request_rx);
|
||||||
|
|
||||||
let (mgm_0_handler_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10);
|
let (mgm_0_handler_tc_tx, mgm_0_handler_tc_rx) = mpsc::sync_channel(10);
|
||||||
let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10);
|
let (mgm_1_handler_tc_tx, mgm_1_handler_tc_rx) = mpsc::sync_channel(10);
|
||||||
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30);
|
let (pcdu_handler_tc_tx, pcdu_handler_tc_rx) = mpsc::sync_channel(30);
|
||||||
let (mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5);
|
let (controller_tc_tx, controller_tc_rx) = mpsc::sync_channel(10);
|
||||||
let (mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5);
|
|
||||||
|
let (_mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5);
|
||||||
|
let (_mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5);
|
||||||
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5);
|
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5);
|
||||||
|
|
||||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
let (event_ctrl_tx, event_ctrl_rx) = mpsc::sync_channel(10);
|
||||||
let mut request_map = GenericRequestRouter::default();
|
let mut event_manager = EventManager {
|
||||||
request_map
|
ctrl_rx: event_ctrl_rx,
|
||||||
.composite_router_map
|
tm_tx: tm_sink_tx.clone(),
|
||||||
.insert(MGM0.id(), mgm_0_handler_composite_tx);
|
|
||||||
request_map
|
|
||||||
.composite_router_map
|
|
||||||
.insert(MGM1.id(), mgm_1_handler_composite_tx);
|
|
||||||
request_map
|
|
||||||
.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_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 (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_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50);
|
|
||||||
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 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! {
|
let mut controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx);
|
||||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
|
||||||
let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new(
|
let ccsds_distributor = CcsdsDistributor::default();
|
||||||
shared_tc_pool_wrapper.clone(),
|
let mut tc_source = TcSourceTask::new(tc_source_rx, ccsds_distributor);
|
||||||
tc_source_rx,
|
tc_source.add_target(ComponentId::EpsPcdu, pcdu_handler_tc_tx);
|
||||||
PusTcDistributor::new(tm_sender.clone(), pus_router),
|
tc_source.add_target(ComponentId::Controller, controller_tc_tx);
|
||||||
));
|
tc_source.add_target(ComponentId::AcsMgm0, mgm_0_handler_tc_tx);
|
||||||
let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool);
|
tc_source.add_target(ComponentId::AcsMgm1, mgm_1_handler_tc_tx);
|
||||||
let udp_tm_handler = StaticUdpTmHandler {
|
|
||||||
tm_rx: tm_server_rx,
|
let tc_sender = TmTcSender::Normal(tc_source_tx.clone());
|
||||||
tm_store: shared_tm_pool.clone(),
|
let udp_tm_handler = UdpTmHandlerWithChannel {
|
||||||
};
|
tm_rx: tm_server_rx,
|
||||||
} 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 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())
|
let udp_tc_server = UdpTcServer::new(
|
||||||
.expect("creating UDP TMTC server failed");
|
ComponentId::UdpServer as u32,
|
||||||
|
sock_addr,
|
||||||
|
2048,
|
||||||
|
tc_sender.clone(),
|
||||||
|
)
|
||||||
|
.expect("creating UDP TMTC server failed");
|
||||||
let mut udp_tmtc_server = UdpTmtcServer {
|
let mut udp_tmtc_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler: udp_tm_handler,
|
tm_handler: udp_tm_handler.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tcp_server_cfg = ServerConfig::new(
|
let tcp_server_cfg = ServerConfig::new(
|
||||||
TCP_SERVER.id(),
|
ComponentId::TcpServer as u32,
|
||||||
sock_addr,
|
sock_addr,
|
||||||
Duration::from_millis(400),
|
Duration::from_millis(400),
|
||||||
4096,
|
4096,
|
||||||
@@ -282,31 +126,18 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.expect("tcp server creation failed");
|
.expect("tcp server creation failed");
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
let mut tm_sink = TmSink::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
|
||||||
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 shared_switch_set = Arc::new(Mutex::default());
|
let shared_switch_set = Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown()));
|
||||||
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
||||||
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
||||||
|
|
||||||
let shared_mgm_0_set = Arc::default();
|
let shared_mgm_0_set = Arc::default();
|
||||||
let shared_mgm_1_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_0_mode_node =
|
||||||
let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), mgm_1_handler_mode_rx);
|
ModeRequestHandlerMpscBounded::new(ComponentId::AcsMgm0 as u32, mgm_0_handler_mode_rx);
|
||||||
|
let mgm_1_mode_node =
|
||||||
|
ModeRequestHandlerMpscBounded::new(ComponentId::AcsMgm1 as u32, mgm_1_handler_mode_rx);
|
||||||
let (mgm_0_spi_interface, mgm_1_spi_interface) =
|
let (mgm_0_spi_interface, mgm_1_spi_interface) =
|
||||||
if let Some(sim_client) = opt_sim_client.as_mut() {
|
if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||||
sim_client
|
sim_client
|
||||||
@@ -330,28 +161,27 @@ fn main() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
||||||
MGM0,
|
ComponentId::AcsMgm0,
|
||||||
"MGM_0",
|
"MGM_0",
|
||||||
mgm_0_mode_node,
|
mgm_0_mode_node,
|
||||||
mgm_0_handler_composite_rx,
|
mgm_0_handler_tc_rx,
|
||||||
pus_hk_reply_tx.clone(),
|
tm_sink_tx.clone(),
|
||||||
switch_helper.clone(),
|
switch_helper.clone(),
|
||||||
tm_sender.clone(),
|
|
||||||
mgm_0_spi_interface,
|
mgm_0_spi_interface,
|
||||||
shared_mgm_0_set,
|
shared_mgm_0_set,
|
||||||
);
|
);
|
||||||
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
||||||
MGM1,
|
ComponentId::AcsMgm1,
|
||||||
"MGM_1",
|
"MGM_1",
|
||||||
mgm_1_mode_node,
|
mgm_1_mode_node,
|
||||||
mgm_1_handler_composite_rx,
|
mgm_1_handler_tc_rx,
|
||||||
pus_hk_reply_tx.clone(),
|
tm_sink_tx.clone(),
|
||||||
switch_helper.clone(),
|
switch_helper.clone(),
|
||||||
tm_sender.clone(),
|
|
||||||
mgm_1_spi_interface,
|
mgm_1_spi_interface,
|
||||||
shared_mgm_1_set,
|
shared_mgm_1_set,
|
||||||
);
|
);
|
||||||
// Connect PUS service to device handlers.
|
// Connect PUS service to device handlers.
|
||||||
|
/*
|
||||||
connect_mode_nodes(
|
connect_mode_nodes(
|
||||||
&mut pus_stack.mode_srv,
|
&mut pus_stack.mode_srv,
|
||||||
mgm_0_handler_mode_tx,
|
mgm_0_handler_mode_tx,
|
||||||
@@ -364,6 +194,7 @@ fn main() {
|
|||||||
&mut mgm_1_handler,
|
&mut mgm_1_handler,
|
||||||
pus_mode_reply_tx.clone(),
|
pus_mode_reply_tx.clone(),
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
|
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);
|
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
|
||||||
@@ -374,24 +205,24 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
|
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
|
||||||
};
|
};
|
||||||
let pcdu_mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), pcdu_handler_mode_rx);
|
let pcdu_mode_node =
|
||||||
|
ModeRequestHandlerMpscBounded::new(ComponentId::EpsPcdu as u32, pcdu_handler_mode_rx);
|
||||||
let mut pcdu_handler = PcduHandler::new(
|
let mut pcdu_handler = PcduHandler::new(
|
||||||
PCDU,
|
|
||||||
"PCDU",
|
|
||||||
pcdu_mode_node,
|
pcdu_mode_node,
|
||||||
pcdu_handler_composite_rx,
|
pcdu_handler_tc_rx,
|
||||||
pus_hk_reply_tx,
|
tm_sink_tx.clone(),
|
||||||
switch_request_rx,
|
switch_request_rx,
|
||||||
tm_sender.clone(),
|
|
||||||
pcdu_serial_interface,
|
pcdu_serial_interface,
|
||||||
shared_switch_set,
|
shared_switch_set,
|
||||||
);
|
);
|
||||||
|
/*
|
||||||
connect_mode_nodes(
|
connect_mode_nodes(
|
||||||
&mut pus_stack.mode_srv,
|
&mut pus_stack.mode_srv,
|
||||||
pcdu_handler_mode_tx.clone(),
|
pcdu_handler_mode_tx.clone(),
|
||||||
&mut pcdu_handler,
|
&mut pcdu_handler,
|
||||||
pus_mode_reply_tx,
|
pus_mode_reply_tx,
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
// The PCDU is a critical component which should be in normal mode immediately.
|
// The PCDU is a critical component which should be in normal mode immediately.
|
||||||
pcdu_handler_mode_tx
|
pcdu_handler_mode_tx
|
||||||
@@ -406,12 +237,12 @@ fn main() {
|
|||||||
|
|
||||||
info!("Starting TMTC and UDP task");
|
info!("Starting TMTC and UDP task");
|
||||||
let jh_udp_tmtc = thread::Builder::new()
|
let jh_udp_tmtc = thread::Builder::new()
|
||||||
.name("SATRS tmtc-udp".to_string())
|
.name("TMTC & UDP".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running UDP server on port {SERVER_PORT}");
|
info!("Running UDP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
udp_tmtc_server.periodic_operation();
|
udp_tmtc_server.periodic_operation();
|
||||||
tmtc_task.periodic_operation();
|
tc_source.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
|
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -419,7 +250,7 @@ fn main() {
|
|||||||
|
|
||||||
info!("Starting TCP task");
|
info!("Starting TCP task");
|
||||||
let jh_tcp = thread::Builder::new()
|
let jh_tcp = thread::Builder::new()
|
||||||
.name("sat-rs tcp".to_string())
|
.name("TCP".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running TCP server on port {SERVER_PORT}");
|
info!("Running TCP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
@@ -430,7 +261,7 @@ fn main() {
|
|||||||
|
|
||||||
info!("Starting TM funnel task");
|
info!("Starting TM funnel task");
|
||||||
let jh_tm_funnel = thread::Builder::new()
|
let jh_tm_funnel = thread::Builder::new()
|
||||||
.name("tm sink".to_string())
|
.name("TM SINK".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
tm_sink.operation();
|
tm_sink.operation();
|
||||||
})
|
})
|
||||||
@@ -441,7 +272,7 @@ fn main() {
|
|||||||
info!("Starting UDP sim client task");
|
info!("Starting UDP sim client task");
|
||||||
opt_jh_sim_client = Some(
|
opt_jh_sim_client = Some(
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("sat-rs sim adapter".to_string())
|
.name("SIM ADAPTER".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
if sim_client.operation() == HandlingStatus::Empty {
|
if sim_client.operation() == HandlingStatus::Empty {
|
||||||
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
|
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
|
||||||
@@ -453,7 +284,7 @@ fn main() {
|
|||||||
|
|
||||||
info!("Starting AOCS thread");
|
info!("Starting AOCS thread");
|
||||||
let jh_aocs = thread::Builder::new()
|
let jh_aocs = thread::Builder::new()
|
||||||
.name("sat-rs aocs".to_string())
|
.name("AOCS".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
mgm_0_handler.periodic_operation();
|
mgm_0_handler.periodic_operation();
|
||||||
mgm_1_handler.periodic_operation();
|
mgm_1_handler.periodic_operation();
|
||||||
@@ -463,12 +294,13 @@ fn main() {
|
|||||||
|
|
||||||
info!("Starting EPS thread");
|
info!("Starting EPS thread");
|
||||||
let jh_eps = thread::Builder::new()
|
let jh_eps = thread::Builder::new()
|
||||||
.name("sat-rs eps".to_string())
|
.name("EPS".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
// TODO: We should introduce something like a fixed timeslot helper to allow a more
|
// TODO: We should introduce something like a fixed timeslot helper to allow a more
|
||||||
// declarative API. It would also be very useful for the AOCS task.
|
// declarative API. It would also be very useful for the AOCS task.
|
||||||
//
|
//
|
||||||
// TODO: The fixed timeslot handler exists.. use it.
|
// TODO: The fixed timeslot handler exists.. use it.
|
||||||
|
// TODO: Why not just use sync code in the PCDU handler, and fully delay there?
|
||||||
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp);
|
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp);
|
||||||
thread::sleep(Duration::from_millis(50));
|
thread::sleep(Duration::from_millis(50));
|
||||||
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
||||||
@@ -478,13 +310,13 @@ fn main() {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting PUS handler thread");
|
info!("Starting controller thread");
|
||||||
let jh_pus_handler = thread::Builder::new()
|
let jh_controller_thread = thread::Builder::new()
|
||||||
.name("sat-rs pus".to_string())
|
.name("CTRL".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
event_handler.periodic_operation();
|
controller.periodic_operation();
|
||||||
pus_stack.periodic_operation();
|
event_manager.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
thread::sleep(Duration::from_millis(FREQ_MS_CONTROLLER));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -504,7 +336,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
jh_aocs.join().expect("Joining AOCS thread failed");
|
jh_aocs.join().expect("Joining AOCS thread failed");
|
||||||
jh_eps.join().expect("Joining EPS thread failed");
|
jh_eps.join().expect("Joining EPS thread failed");
|
||||||
jh_pus_handler
|
jh_controller_thread
|
||||||
.join()
|
.join()
|
||||||
.expect("Joining PUS handler thread failed");
|
.expect("Joining PUS handler thread failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
|
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
|
||||||
|
|
||||||
use satrs::{
|
use satrs::{
|
||||||
pus::EcssTmSender,
|
|
||||||
queue::GenericSendError,
|
queue::GenericSendError,
|
||||||
spacepackets::ecss::WritablePusPacket,
|
tmtc::{PacketAsVec, PacketHandler},
|
||||||
tmtc::{PacketAsVec, PacketHandler, PacketSenderWithSharedPool},
|
|
||||||
ComponentId,
|
ComponentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,8 +12,7 @@ pub struct MockSender(pub RefCell<VecDeque<PacketAsVec>>);
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TmTcSender {
|
pub enum TmTcSender {
|
||||||
Static(PacketSenderWithSharedPool),
|
Normal(mpsc::SyncSender<PacketAsVec>),
|
||||||
Heap(mpsc::SyncSender<PacketAsVec>),
|
|
||||||
Mock(MockSender),
|
Mock(MockSender),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,37 +26,13 @@ impl TmTcSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EcssTmSender for TmTcSender {
|
|
||||||
fn send_tm(
|
|
||||||
&self,
|
|
||||||
sender_id: satrs::ComponentId,
|
|
||||||
tm: satrs::pus::PusTmVariant,
|
|
||||||
) -> Result<(), satrs::pus::EcssTmtcError> {
|
|
||||||
match self {
|
|
||||||
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
|
|
||||||
.send(PacketAsVec::new(sender_id, pus_tm_creator.to_vec()?))
|
|
||||||
.map_err(|_| GenericSendError::RxDisconnected.into()),
|
|
||||||
},
|
|
||||||
TmTcSender::Mock(_) => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketHandler for TmTcSender {
|
impl PacketHandler for TmTcSender {
|
||||||
type Error = GenericSendError;
|
type Error = GenericSendError;
|
||||||
|
|
||||||
fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
||||||
match self {
|
match self {
|
||||||
TmTcSender::Static(packet_sender_with_shared_pool) => {
|
TmTcSender::Normal(sync_sender) => {
|
||||||
if let Err(e) = packet_sender_with_shared_pool.handle_packet(sender_id, packet) {
|
if let Err(e) = sync_sender.send(PacketAsVec::new(sender_id, packet.to_vec())) {
|
||||||
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) {
|
|
||||||
log::error!("Error sending packet via Heap TM/TC sender: {:?}", e);
|
log::error!("Error sending packet via Heap TM/TC sender: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,97 @@
|
|||||||
|
use models::{ccsds::CcsdsTcPacketOwned, ComponentId, TcHeader};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
pool::PoolProvider,
|
|
||||||
pus::HandlingStatus,
|
pus::HandlingStatus,
|
||||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
spacepackets::{CcsdsPacketReader, ChecksumType},
|
||||||
|
tmtc::PacketAsVec,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::mpsc::{self, TryRecvError},
|
||||||
};
|
};
|
||||||
use std::sync::mpsc::{self, TryRecvError};
|
|
||||||
|
|
||||||
use crate::pus::PusTcDistributor;
|
pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<CcsdsTcPacketOwned>>;
|
||||||
|
|
||||||
// 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>,
|
|
||||||
/// We allocate this buffer from the heap to avoid a clippy warning on large enum variant
|
|
||||||
/// differences.
|
|
||||||
tc_buf: Box<[u8; 4096]>,
|
|
||||||
pus_distributor: PusTcDistributor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TcSourceTaskStatic {
|
|
||||||
pub fn new(
|
|
||||||
shared_tc_pool: SharedPacketPool,
|
|
||||||
tc_receiver: mpsc::Receiver<PacketInPool>,
|
|
||||||
pus_receiver: PusTcDistributor,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
shared_tc_pool,
|
|
||||||
tc_receiver,
|
|
||||||
tc_buf: Box::new([0; 4096]),
|
|
||||||
pus_distributor: pus_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
self.poll_tc();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_tc(&mut self) -> HandlingStatus {
|
|
||||||
// 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_in_pool) => {
|
|
||||||
let pool = self
|
|
||||||
.shared_tc_pool
|
|
||||||
.0
|
|
||||||
.read()
|
|
||||||
.expect("locking tc pool failed");
|
|
||||||
pool.read(&packet_in_pool.store_addr, self.tc_buf.as_mut_slice())
|
|
||||||
.expect("reading pool failed");
|
|
||||||
drop(pool);
|
|
||||||
self.pus_distributor
|
|
||||||
.handle_tc_packet_in_store(packet_in_pool, self.tc_buf.as_slice())
|
|
||||||
.ok();
|
|
||||||
HandlingStatus::HandledOne
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => HandlingStatus::Empty,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
log::warn!("tmtc thread: sender disconnected");
|
|
||||||
HandlingStatus::Empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TC source components where the heap is the backing memory of the received telecommands.
|
// 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>,
|
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 {
|
|
||||||
Self {
|
|
||||||
tc_receiver,
|
|
||||||
pus_distributor: pus_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
self.poll_tc();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_tc(&mut self) -> HandlingStatus {
|
|
||||||
// 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();
|
|
||||||
HandlingStatus::HandledOne
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => HandlingStatus::Empty,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
log::warn!("tmtc thread: sender disconnected");
|
|
||||||
HandlingStatus::Empty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum TcSourceTask {
|
|
||||||
Static(TcSourceTaskStatic),
|
|
||||||
Heap(TcSourceTaskDynamic),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcSourceTask {
|
impl TcSourceTask {
|
||||||
|
pub fn new(
|
||||||
|
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
|
ccsds_distributor: CcsdsDistributor,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tc_receiver,
|
||||||
|
ccsds_distributor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_target(
|
||||||
|
&mut self,
|
||||||
|
target_id: ComponentId,
|
||||||
|
sender: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||||
|
) {
|
||||||
|
self.ccsds_distributor.insert(target_id, sender);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
match self {
|
loop {
|
||||||
TcSourceTask::Static(task) => task.periodic_operation(),
|
if self.poll_tc() == HandlingStatus::Empty {
|
||||||
TcSourceTask::Heap(task) => task.periodic_operation(),
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_tc(&mut self) -> HandlingStatus {
|
||||||
|
match self.tc_receiver.try_recv() {
|
||||||
|
Ok(packet) => {
|
||||||
|
log::debug!("received raw packet: {:?}", 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) {
|
||||||
|
log::debug!("sending TC packet to target ID: {:?}", 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 {
|
||||||
|
TryRecvError::Empty => HandlingStatus::Empty,
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
log::warn!("tmtc thread: sender disconnected");
|
||||||
|
HandlingStatus::Empty
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use arbitrary_int::{u11, u14};
|
use arbitrary_int::{u11, u14};
|
||||||
use log::info;
|
use models::ccsds::CcsdsTmPacketOwned;
|
||||||
use satrs::{
|
use satrs::spacepackets::seq_count::{SequenceCounter, SequenceCounterCcsdsSimple};
|
||||||
pool::PoolProvider,
|
|
||||||
spacepackets::{
|
|
||||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
|
||||||
seq_count::SequenceCounter,
|
|
||||||
seq_count::SequenceCounterCcsdsSimple,
|
|
||||||
time::cds::MIN_CDS_FIELD_LEN,
|
|
||||||
CcsdsPacket,
|
|
||||||
},
|
|
||||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::interface::tcp::SyncTcpTmSource;
|
use crate::interface::tcp::SyncTcpTmSource;
|
||||||
|
|
||||||
@@ -34,118 +24,22 @@ impl CcsdsSeqCounterMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TmFunnelCommon {
|
pub struct TmSink {
|
||||||
seq_counter_map: CcsdsSeqCounterMap,
|
seq_counter_map: CcsdsSeqCounterMap,
|
||||||
msg_counter_map: HashMap<u8, u16>,
|
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
sync_tm_tcp_source: SyncTcpTmSource,
|
||||||
|
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
|
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TmFunnelCommon {
|
impl TmSink {
|
||||||
pub fn new(sync_tm_tcp_source: SyncTcpTmSource) -> Self {
|
pub fn new(
|
||||||
|
sync_tm_tcp_source: SyncTcpTmSource,
|
||||||
|
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
|
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
seq_counter_map: Default::default(),
|
seq_counter_map: Default::default(),
|
||||||
msg_counter_map: Default::default(),
|
|
||||||
sync_tm_tcp_source,
|
sync_tm_tcp_source,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applies common packet processing operations for PUS TM packets. This includes setting
|
|
||||||
// a sequence counter
|
|
||||||
fn apply_packet_processing(&mut self, mut zero_copy_writer: PusTmZeroCopyWriter) {
|
|
||||||
// zero_copy_writer.set_apid(PUS_APID);
|
|
||||||
zero_copy_writer.set_seq_count(
|
|
||||||
self.seq_counter_map
|
|
||||||
.get_and_increment(zero_copy_writer.apid()),
|
|
||||||
);
|
|
||||||
let entry = self
|
|
||||||
.msg_counter_map
|
|
||||||
.entry(zero_copy_writer.service_type_id())
|
|
||||||
.or_insert(0);
|
|
||||||
zero_copy_writer.set_msg_count(*entry);
|
|
||||||
if *entry == u16::MAX {
|
|
||||||
*entry = 0;
|
|
||||||
} else {
|
|
||||||
*entry += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::packet_printout(&zero_copy_writer);
|
|
||||||
// This operation has to come last!
|
|
||||||
zero_copy_writer.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packet_printout(tm: &PusTmZeroCopyWriter) {
|
|
||||||
info!(
|
|
||||||
"Sending PUS TM[{},{}] with APID {}",
|
|
||||||
tm.service_type_id(),
|
|
||||||
tm.message_subtype_id(),
|
|
||||||
tm.apid()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TmSinkStatic {
|
|
||||||
common: TmFunnelCommon,
|
|
||||||
shared_tm_store: SharedPacketPool,
|
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
|
|
||||||
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TmSinkStatic {
|
|
||||||
pub fn new(
|
|
||||||
shared_tm_store: SharedPacketPool,
|
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
|
|
||||||
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
|
||||||
shared_tm_store,
|
|
||||||
tm_funnel_rx,
|
|
||||||
tm_server_tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn operation(&mut self) {
|
|
||||||
if let Ok(pus_tm_in_pool) = self.tm_funnel_rx.recv() {
|
|
||||||
// Read the TM, set sequence counter and message counter, and finally update
|
|
||||||
// the CRC.
|
|
||||||
let shared_pool = self.shared_tm_store.0.clone();
|
|
||||||
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
|
|
||||||
let mut tm_copy = Vec::new();
|
|
||||||
pool_guard
|
|
||||||
.modify(&pus_tm_in_pool.store_addr, |buf| {
|
|
||||||
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN, true)
|
|
||||||
.expect("Creating TM zero copy writer failed");
|
|
||||||
self.common.apply_packet_processing(zero_copy_writer);
|
|
||||||
tm_copy = buf.to_vec()
|
|
||||||
})
|
|
||||||
.expect("Reading TM from pool failed");
|
|
||||||
self.tm_server_tx
|
|
||||||
.send(pus_tm_in_pool)
|
|
||||||
.expect("Sending TM to server failed");
|
|
||||||
// We could also do this step in the update closure, but I'd rather avoid this, could
|
|
||||||
// lead to nested locking.
|
|
||||||
self.common.sync_tm_tcp_source.add_tm(&tm_copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TmSinkDynamic {
|
|
||||||
common: TmFunnelCommon,
|
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
|
||||||
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TmSinkDynamic {
|
|
||||||
pub fn new(
|
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
|
||||||
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
|
||||||
tm_funnel_rx,
|
tm_funnel_rx,
|
||||||
tm_server_tx,
|
tm_server_tx,
|
||||||
}
|
}
|
||||||
@@ -153,31 +47,12 @@ impl TmSinkDynamic {
|
|||||||
|
|
||||||
pub fn operation(&mut self) {
|
pub fn operation(&mut self) {
|
||||||
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
||||||
// Read the TM, set sequence counter and message counter, and finally update
|
tm.sp_header
|
||||||
// the CRC.
|
.set_seq_count(self.seq_counter_map.get_and_increment(tm.sp_header.apid()));
|
||||||
let zero_copy_writer =
|
self.sync_tm_tcp_source.add_tm(&tm.to_vec());
|
||||||
PusTmZeroCopyWriter::new(&mut tm.packet, 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.tm_server_tx
|
self.tm_server_tx
|
||||||
.send(tm)
|
.send(tm)
|
||||||
.expect("Sending TM to server failed");
|
.expect("Sending TM to server failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum TmSink {
|
|
||||||
Static(TmSinkStatic),
|
|
||||||
Heap(TmSinkDynamic),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TmSink {
|
|
||||||
pub fn operation(&mut self) {
|
|
||||||
match self {
|
|
||||||
TmSink::Static(static_sink) => static_sink.operation(),
|
|
||||||
TmSink::Heap(dynamic_sink) => dynamic_sink.operation(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -78,8 +78,3 @@ test_util = []
|
|||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "event_test"
|
|
||||||
path = "tests/pus_events.rs"
|
|
||||||
required-features = ["test_util"]
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
//! show how the event management modules can be integrated into a more complex software.
|
//! show how the event management modules can be integrated into a more complex software.
|
||||||
use crate::{
|
use crate::{
|
||||||
ComponentId,
|
ComponentId,
|
||||||
events::{Event, EventId, GroupId},
|
legacy::events::{Event, EventId, GroupId},
|
||||||
queue::GenericSendError,
|
queue::GenericSendError,
|
||||||
};
|
};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
@@ -96,6 +96,7 @@ impl<EventInstance: Event> EventMessage<EventInstance> {
|
|||||||
pub trait EventSender<EventInstance: Event> {
|
pub trait EventSender<EventInstance: Event> {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Destination component ID.
|
||||||
fn target_id(&self) -> ComponentId;
|
fn target_id(&self) -> ComponentId;
|
||||||
|
|
||||||
fn send(&self, message: EventMessage<EventInstance>) -> Result<(), Self::Error>;
|
fn send(&self, message: EventMessage<EventInstance>) -> Result<(), Self::Error>;
|
||||||
@@ -317,7 +318,7 @@ pub mod alloc_mod {
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
use crate::events::EventErasedAlloc;
|
use crate::legacy::events::EventErasedAlloc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -460,7 +461,7 @@ pub mod alloc_mod {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod std_mod {
|
pub mod std_mod {
|
||||||
use crate::{
|
use crate::{
|
||||||
events::{EventErasedAlloc, EventErasedHeapless},
|
legacy::events::{EventErasedAlloc, EventErasedHeapless},
|
||||||
queue::GenericReceiveError,
|
queue::GenericReceiveError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -577,7 +578,7 @@ mod tests {
|
|||||||
use arbitrary_int::u14;
|
use arbitrary_int::u14;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::events::{EventErasedAlloc, Severity};
|
use crate::legacy::events::{EventErasedAlloc, Severity};
|
||||||
use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1};
|
use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
//! The [PUS event](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/pus/event.rs)
|
//! The [PUS event](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/pus/event.rs)
|
||||||
//! module and the generic [events module](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/events.rs)
|
//! module and the generic [events module](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/events.rs)
|
||||||
//! show how the event management modules can be integrated into a more complex software.
|
//! show how the event management modules can be integrated into a more complex software.
|
||||||
use crate::events_legacy::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
|
use crate::legacy::events_legacy::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
|
||||||
use crate::params::Params;
|
use crate::params::Params;
|
||||||
use crate::queue::GenericSendError;
|
use crate::queue::GenericSendError;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
@@ -588,8 +588,8 @@ pub mod std_mod {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::event_man_legacy::EventManager;
|
use crate::legacy::event_man_legacy::EventManager;
|
||||||
use crate::events_legacy::{EventU32, GenericEvent, Severity};
|
use crate::legacy::events_legacy::{EventU32, GenericEvent, Severity};
|
||||||
use crate::params::{ParamsHeapless, ParamsRaw};
|
use crate::params::{ParamsHeapless, ParamsRaw};
|
||||||
use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1};
|
use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1};
|
||||||
use std::format;
|
use std::format;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod event_man;
|
||||||
|
pub mod events;
|
||||||
|
|
||||||
|
pub mod pus;
|
||||||
@@ -267,217 +267,4 @@ mod alloc_mod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {}
|
||||||
use super::*;
|
|
||||||
use crate::ComponentId;
|
|
||||||
use crate::events_legacy::{EventU32, Severity};
|
|
||||||
use crate::pus::test_util::TEST_COMPONENT_ID_0;
|
|
||||||
use crate::pus::tests::CommonTmInfo;
|
|
||||||
use crate::pus::{ChannelWithId, EcssTmSender, EcssTmtcError, PusTmVariant};
|
|
||||||
use spacepackets::ByteConversionError;
|
|
||||||
use spacepackets::ecss::PusError;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
const EXAMPLE_APID: u11 = u11::new(0xee);
|
|
||||||
const EXAMPLE_GROUP_ID: u16 = 2;
|
|
||||||
const EXAMPLE_EVENT_ID_0: u16 = 1;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const EXAMPLE_EVENT_ID_1: u16 = 2;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
|
||||||
struct TmInfo {
|
|
||||||
pub sender_id: ComponentId,
|
|
||||||
pub common: CommonTmInfo,
|
|
||||||
pub event: EventU32,
|
|
||||||
pub aux_data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct TestSender {
|
|
||||||
pub service_queue: RefCell<VecDeque<TmInfo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelWithId for TestSender {
|
|
||||||
fn id(&self) -> ComponentId {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EcssTmSender for TestSender {
|
|
||||||
fn send_tm(&self, sender_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> {
|
|
||||||
match tm {
|
|
||||||
PusTmVariant::InStore(_) => {
|
|
||||||
panic!("TestSender: unexpected call with address");
|
|
||||||
}
|
|
||||||
PusTmVariant::Direct(tm) => {
|
|
||||||
assert!(!tm.source_data().is_empty());
|
|
||||||
let src_data = tm.source_data();
|
|
||||||
assert!(src_data.len() >= 4);
|
|
||||||
let event =
|
|
||||||
EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
|
||||||
let mut aux_data = Vec::new();
|
|
||||||
if src_data.len() > 4 {
|
|
||||||
aux_data.extend_from_slice(&src_data[4..]);
|
|
||||||
}
|
|
||||||
self.service_queue.borrow_mut().push_back(TmInfo {
|
|
||||||
sender_id,
|
|
||||||
common: CommonTmInfo::new_from_tm(&tm),
|
|
||||||
event,
|
|
||||||
aux_data,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn severity_to_subservice(severity: Severity) -> MessageSubtypeId {
|
|
||||||
match severity {
|
|
||||||
Severity::Info => MessageSubtypeId::TmInfoReport,
|
|
||||||
Severity::Low => MessageSubtypeId::TmLowSeverityReport,
|
|
||||||
Severity::Medium => MessageSubtypeId::TmMediumSeverityReport,
|
|
||||||
Severity::High => MessageSubtypeId::TmHighSeverityReport,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_basic_event(
|
|
||||||
reporter: &mut EventReporter,
|
|
||||||
sender: &mut TestSender,
|
|
||||||
time_stamp: &[u8],
|
|
||||||
event: EventU32,
|
|
||||||
severity: Severity,
|
|
||||||
aux_data: Option<&[u8]>,
|
|
||||||
) {
|
|
||||||
match severity {
|
|
||||||
Severity::Info => {
|
|
||||||
reporter
|
|
||||||
.event_info(sender, time_stamp, event, aux_data)
|
|
||||||
.expect("Error reporting info event");
|
|
||||||
}
|
|
||||||
Severity::Low => {
|
|
||||||
reporter
|
|
||||||
.event_low_severity(sender, time_stamp, event, aux_data)
|
|
||||||
.expect("Error reporting low event");
|
|
||||||
}
|
|
||||||
Severity::Medium => {
|
|
||||||
reporter
|
|
||||||
.event_medium_severity(sender, time_stamp, event, aux_data)
|
|
||||||
.expect("Error reporting medium event");
|
|
||||||
}
|
|
||||||
Severity::High => {
|
|
||||||
reporter
|
|
||||||
.event_high_severity(sender, time_stamp, event, aux_data)
|
|
||||||
.expect("Error reporting high event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn basic_event_test(
|
|
||||||
max_event_aux_data_buf: usize,
|
|
||||||
severity: Severity,
|
|
||||||
error_data: Option<&[u8]>,
|
|
||||||
) {
|
|
||||||
let mut sender = TestSender::default();
|
|
||||||
let mut reporter = EventReporter::new(
|
|
||||||
TEST_COMPONENT_ID_0.id(),
|
|
||||||
EXAMPLE_APID,
|
|
||||||
0,
|
|
||||||
max_event_aux_data_buf,
|
|
||||||
);
|
|
||||||
let time_stamp_empty: [u8; 7] = [0; 7];
|
|
||||||
let mut error_copy = Vec::new();
|
|
||||||
if let Some(err_data) = error_data {
|
|
||||||
error_copy.extend_from_slice(err_data);
|
|
||||||
}
|
|
||||||
let event = EventU32::new_checked(severity, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
|
|
||||||
.expect("Error creating example event");
|
|
||||||
report_basic_event(
|
|
||||||
&mut reporter,
|
|
||||||
&mut sender,
|
|
||||||
&time_stamp_empty,
|
|
||||||
event,
|
|
||||||
severity,
|
|
||||||
error_data,
|
|
||||||
);
|
|
||||||
let mut service_queue = sender.service_queue.borrow_mut();
|
|
||||||
assert_eq!(service_queue.len(), 1);
|
|
||||||
let tm_info = service_queue.pop_front().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
tm_info.common.subservice,
|
|
||||||
severity_to_subservice(severity) as u8
|
|
||||||
);
|
|
||||||
assert_eq!(tm_info.common.dest_id, 0);
|
|
||||||
assert_eq!(tm_info.common.timestamp, time_stamp_empty);
|
|
||||||
assert_eq!(tm_info.common.msg_counter, 0);
|
|
||||||
assert_eq!(tm_info.common.apid, EXAMPLE_APID);
|
|
||||||
assert_eq!(tm_info.event, event);
|
|
||||||
assert_eq!(tm_info.sender_id, TEST_COMPONENT_ID_0.id());
|
|
||||||
assert_eq!(tm_info.aux_data, error_copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_info_event_generation() {
|
|
||||||
basic_event_test(4, Severity::Info, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_low_severity_event() {
|
|
||||||
basic_event_test(4, Severity::Low, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_medium_severity_event() {
|
|
||||||
basic_event_test(4, Severity::Medium, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_high_severity_event() {
|
|
||||||
basic_event_test(4, Severity::High, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn event_with_info_string() {
|
|
||||||
let info_string = "Test Information";
|
|
||||||
basic_event_test(32, Severity::Info, Some(info_string.as_bytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn low_severity_with_raw_err_data() {
|
|
||||||
let raw_err_param: i32 = -1;
|
|
||||||
let raw_err = raw_err_param.to_be_bytes();
|
|
||||||
basic_event_test(8, Severity::Low, Some(&raw_err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_buf_too_small(
|
|
||||||
reporter: &mut EventReporter,
|
|
||||||
sender: &mut TestSender,
|
|
||||||
expected_found_len: usize,
|
|
||||||
) {
|
|
||||||
let time_stamp_empty: [u8; 7] = [0; 7];
|
|
||||||
let event = EventU32::new_checked(Severity::Info, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
|
|
||||||
.expect("Error creating example event");
|
|
||||||
let err = reporter.event_info(sender, &time_stamp_empty, event, None);
|
|
||||||
assert!(err.is_err());
|
|
||||||
let err = err.unwrap_err();
|
|
||||||
if let EcssTmtcError::Pus(PusError::ByteConversion(
|
|
||||||
ByteConversionError::ToSliceTooSmall { found, expected },
|
|
||||||
)) = err
|
|
||||||
{
|
|
||||||
assert_eq!(expected, 4);
|
|
||||||
assert_eq!(found, expected_found_len);
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected error {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insufficient_buffer() {
|
|
||||||
let mut sender = TestSender::default();
|
|
||||||
for i in 0..3 {
|
|
||||||
let mut reporter = EventReporter::new(0, EXAMPLE_APID, 0, i);
|
|
||||||
check_buf_too_small(&mut reporter, &mut sender, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod event;
|
||||||
+1
-6
@@ -9,8 +9,6 @@
|
|||||||
//!
|
//!
|
||||||
//! The core modules of this crate include
|
//! The core modules of this crate include
|
||||||
//!
|
//!
|
||||||
//! - The [event manager][event_man] module which provides a publish and
|
|
||||||
//! and subscribe to route events.
|
|
||||||
//! - The [pus] module which provides special support for projects using
|
//! - The [pus] module which provides special support for projects using
|
||||||
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||||
#![no_std]
|
#![no_std]
|
||||||
@@ -27,15 +25,12 @@ pub mod ccsds;
|
|||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub mod dev_mgmt;
|
pub mod dev_mgmt;
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
pub mod event_man;
|
|
||||||
pub mod event_man_legacy;
|
|
||||||
pub mod events;
|
|
||||||
pub mod events_legacy;
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod executable;
|
pub mod executable;
|
||||||
pub mod hal;
|
pub mod hal;
|
||||||
pub mod health;
|
pub mod health;
|
||||||
pub mod hk;
|
pub mod hk;
|
||||||
|
pub mod legacy;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod mode_tree;
|
pub mod mode_tree;
|
||||||
|
|||||||
@@ -1,464 +0,0 @@
|
|||||||
use crate::events_legacy::{EventU32, GenericEvent, Severity};
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use crate::events_legacy::{EventU32TypedSev, HasSeverity};
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use core::hash::Hash;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use crate::pus::EcssTmSender;
|
|
||||||
use crate::pus::EcssTmtcError;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use crate::pus::event::EventReporter;
|
|
||||||
use crate::pus::verification::TcStateToken;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use alloc_mod::*;
|
|
||||||
#[cfg(feature = "heapless")]
|
|
||||||
pub use heapless_mod::*;
|
|
||||||
|
|
||||||
/// This trait allows the PUS event manager implementation to stay generic over various types
|
|
||||||
/// of backend containers.
|
|
||||||
///
|
|
||||||
/// These backend containers keep track on whether a particular event is enabled or disabled for
|
|
||||||
/// reporting and also expose a simple API to enable or disable the event reporting.
|
|
||||||
///
|
|
||||||
/// For example, a straight forward implementation for host systems could use a
|
|
||||||
/// [hash set](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html)
|
|
||||||
/// structure to track disabled events. A more primitive and embedded friendly
|
|
||||||
/// solution could track this information in a static or pre-allocated list which contains
|
|
||||||
/// the disabled events.
|
|
||||||
pub trait PusEventReportingMapProvider<Event: GenericEvent> {
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn event_enabled(&self, event: &Event) -> bool;
|
|
||||||
fn enable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
|
|
||||||
fn disable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "heapless")]
|
|
||||||
pub mod heapless_mod {
|
|
||||||
use super::*;
|
|
||||||
use crate::events::LargestEventRaw;
|
|
||||||
use core::marker::PhantomData;
|
|
||||||
|
|
||||||
// TODO: After a new version of heapless is released which uses hash32 version 0.3, try using
|
|
||||||
// regular Event type again.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct HeaplessPusMgmtBackendProvider<const N: usize, Provider: GenericEvent> {
|
|
||||||
disabled: heapless::index_set::FnvIndexSet<LargestEventRaw, N>,
|
|
||||||
phantom: PhantomData<Provider>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize, Provider: GenericEvent> PusEventReportingMapProvider<Provider>
|
|
||||||
for HeaplessPusMgmtBackendProvider<N, Provider>
|
|
||||||
{
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn event_enabled(&self, event: &Provider) -> bool {
|
|
||||||
self.disabled.contains(&event.raw_as_largest_type())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
|
||||||
self.disabled
|
|
||||||
.insert(event.raw_as_largest_type())
|
|
||||||
.map_err(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
|
||||||
Ok(self.disabled.remove(&event.raw_as_largest_type()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum EventRequest<Event: GenericEvent = EventU32> {
|
|
||||||
Enable(Event),
|
|
||||||
Disable(Event),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EventRequestWithToken<Event: GenericEvent = EventU32> {
|
|
||||||
pub request: EventRequest<Event>,
|
|
||||||
pub token: TcStateToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventManError {
|
|
||||||
EcssTmtcError(EcssTmtcError),
|
|
||||||
SeverityMissmatch(Severity, Severity),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcssTmtcError> for EventManError {
|
|
||||||
fn from(v: EcssTmtcError) -> Self {
|
|
||||||
Self::EcssTmtcError(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub mod alloc_mod {
|
|
||||||
use core::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
events_legacy::EventU16,
|
|
||||||
params::{Params, WritableToBeBytes},
|
|
||||||
pus::event::{DummyEventHook, EventTmHook},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Default backend provider which uses a hash set as the event reporting status container
|
|
||||||
/// like mentioned in the example of the [PusEventReportingMapProvider] documentation.
|
|
||||||
///
|
|
||||||
/// This provider is a good option for host systems or larger embedded systems where
|
|
||||||
/// the expected occasional memory allocation performed by the [HashSet] is not an issue.
|
|
||||||
pub struct DefaultPusEventReportingMap<Event: GenericEvent = EventU32> {
|
|
||||||
disabled: HashSet<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent> Default for DefaultPusEventReportingMap<Event> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
disabled: HashSet::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent + PartialEq + Eq + Hash + Copy + Clone>
|
|
||||||
PusEventReportingMapProvider<Event> for DefaultPusEventReportingMap<Event>
|
|
||||||
{
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn event_enabled(&self, event: &Event) -> bool {
|
|
||||||
!self.disabled.contains(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error> {
|
|
||||||
Ok(self.disabled.remove(event))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error> {
|
|
||||||
Ok(self.disabled.insert(*event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct EventGenerationResult {
|
|
||||||
pub event_was_enabled: bool,
|
|
||||||
pub params_were_propagated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PusEventTmCreatorWithMap<
|
|
||||||
ReportingMap: PusEventReportingMapProvider<Event>,
|
|
||||||
Event: GenericEvent,
|
|
||||||
EventTmHookInstance: EventTmHook = DummyEventHook,
|
|
||||||
> {
|
|
||||||
pub reporter: EventReporter<EventTmHookInstance>,
|
|
||||||
reporting_map: ReportingMap,
|
|
||||||
phantom: PhantomData<Event>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
ReportingMap: PusEventReportingMapProvider<Event>,
|
|
||||||
Event: GenericEvent,
|
|
||||||
EventTmHookInstance: EventTmHook,
|
|
||||||
> PusEventTmCreatorWithMap<ReportingMap, Event, EventTmHookInstance>
|
|
||||||
{
|
|
||||||
pub fn new(reporter: EventReporter<EventTmHookInstance>, backend: ReportingMap) -> Self {
|
|
||||||
Self {
|
|
||||||
reporter,
|
|
||||||
reporting_map: backend,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_tm_for_event(&mut self, event: &Event) -> Result<bool, ReportingMap::Error> {
|
|
||||||
self.reporting_map.enable_event_reporting(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_tm_for_event(&mut self, event: &Event) -> Result<bool, ReportingMap::Error> {
|
|
||||||
self.reporting_map.disable_event_reporting(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_pus_event_tm_generic(
|
|
||||||
&self,
|
|
||||||
sender: &(impl EcssTmSender + ?Sized),
|
|
||||||
time_stamp: &[u8],
|
|
||||||
event: Event,
|
|
||||||
params: Option<&[u8]>,
|
|
||||||
) -> Result<bool, EventManError> {
|
|
||||||
if !self.reporting_map.event_enabled(&event) {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
match event.severity() {
|
|
||||||
Severity::Info => self
|
|
||||||
.reporter
|
|
||||||
.event_info(sender, time_stamp, event, params)
|
|
||||||
.map(|_| true)
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
Severity::Low => self
|
|
||||||
.reporter
|
|
||||||
.event_low_severity(sender, time_stamp, event, params)
|
|
||||||
.map(|_| true)
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
Severity::Medium => self
|
|
||||||
.reporter
|
|
||||||
.event_medium_severity(sender, time_stamp, event, params)
|
|
||||||
.map(|_| true)
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
Severity::High => self
|
|
||||||
.reporter
|
|
||||||
.event_high_severity(sender, time_stamp, event, params)
|
|
||||||
.map(|_| true)
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_pus_event_tm_generic_with_generic_params(
|
|
||||||
&self,
|
|
||||||
sender: &(impl EcssTmSender + ?Sized),
|
|
||||||
time_stamp: &[u8],
|
|
||||||
event: Event,
|
|
||||||
small_data_buf: &mut [u8],
|
|
||||||
params: Option<&Params>,
|
|
||||||
) -> Result<EventGenerationResult, EventManError> {
|
|
||||||
let mut result = EventGenerationResult {
|
|
||||||
event_was_enabled: false,
|
|
||||||
params_were_propagated: true,
|
|
||||||
};
|
|
||||||
if params.is_none() {
|
|
||||||
result.event_was_enabled =
|
|
||||||
self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?;
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
let params = params.unwrap();
|
|
||||||
result.event_was_enabled = match params {
|
|
||||||
Params::Heapless(heapless_param) => {
|
|
||||||
heapless_param
|
|
||||||
.write_to_be_bytes(&mut small_data_buf[..heapless_param.written_len()])
|
|
||||||
.map_err(EcssTmtcError::ByteConversion)?;
|
|
||||||
self.generate_pus_event_tm_generic(
|
|
||||||
sender,
|
|
||||||
time_stamp,
|
|
||||||
event,
|
|
||||||
Some(small_data_buf),
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
Params::Vec(vec) => {
|
|
||||||
self.generate_pus_event_tm_generic(sender, time_stamp, event, Some(vec))?
|
|
||||||
}
|
|
||||||
Params::String(string) => self.generate_pus_event_tm_generic(
|
|
||||||
sender,
|
|
||||||
time_stamp,
|
|
||||||
event,
|
|
||||||
Some(string.as_bytes()),
|
|
||||||
)?,
|
|
||||||
_ => {
|
|
||||||
result.params_were_propagated = false;
|
|
||||||
self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent + Copy + PartialEq + Eq + Hash, EventTmHookInstance: EventTmHook>
|
|
||||||
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<Event>, Event, EventTmHookInstance>
|
|
||||||
{
|
|
||||||
pub fn new_with_default_backend(reporter: EventReporter<EventTmHookInstance>) -> Self {
|
|
||||||
Self {
|
|
||||||
reporter,
|
|
||||||
reporting_map: DefaultPusEventReportingMap::default(),
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<ReportingMap: PusEventReportingMapProvider<EventU32>>
|
|
||||||
PusEventTmCreatorWithMap<ReportingMap, EventU32>
|
|
||||||
{
|
|
||||||
pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>(
|
|
||||||
&mut self,
|
|
||||||
event: &EventU32TypedSev<Severity>,
|
|
||||||
) -> Result<bool, ReportingMap::Error> {
|
|
||||||
self.reporting_map.enable_event_reporting(event.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>(
|
|
||||||
&mut self,
|
|
||||||
event: &EventU32TypedSev<Severity>,
|
|
||||||
) -> Result<bool, ReportingMap::Error> {
|
|
||||||
self.reporting_map.disable_event_reporting(event.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_pus_event_tm<Severity: HasSeverity>(
|
|
||||||
&self,
|
|
||||||
sender: &(impl EcssTmSender + ?Sized),
|
|
||||||
time_stamp: &[u8],
|
|
||||||
event: EventU32TypedSev<Severity>,
|
|
||||||
aux_data: Option<&[u8]>,
|
|
||||||
) -> Result<bool, EventManError> {
|
|
||||||
self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DefaultPusEventU16TmCreator<EventTmHook = DummyEventHook> =
|
|
||||||
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<EventU16>, EventU16, EventTmHook>;
|
|
||||||
pub type DefaultPusEventU32TmCreator<EventTmHook = DummyEventHook> =
|
|
||||||
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<EventU32>, EventU32, EventTmHook>;
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use alloc::string::{String, ToString};
|
|
||||||
use alloc::vec;
|
|
||||||
use arbitrary_int::{u11, u21};
|
|
||||||
use spacepackets::ecss::PusPacket;
|
|
||||||
use spacepackets::ecss::event::MessageSubtypeId;
|
|
||||||
use spacepackets::ecss::tm::PusTmReader;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::request::UniqueApidTargetId;
|
|
||||||
use crate::{events_legacy::SeverityInfo, tmtc::PacketAsVec};
|
|
||||||
use std::sync::mpsc::{self, TryRecvError};
|
|
||||||
|
|
||||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(1, 0);
|
|
||||||
const LOW_SEV_EVENT: EventU32 = EventU32::new(Severity::Low, 1, 5);
|
|
||||||
const EMPTY_STAMP: [u8; 7] = [0; 7];
|
|
||||||
const TEST_APID: u11 = u11::new(0x02);
|
|
||||||
const TEST_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, u21::new(0x05));
|
|
||||||
|
|
||||||
fn create_basic_man_1() -> DefaultPusEventU32TmCreator {
|
|
||||||
let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128);
|
|
||||||
PusEventTmCreatorWithMap::new_with_default_backend(reporter)
|
|
||||||
}
|
|
||||||
fn create_basic_man_2() -> DefaultPusEventU32TmCreator {
|
|
||||||
let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128);
|
|
||||||
let backend = DefaultPusEventReportingMap::default();
|
|
||||||
PusEventTmCreatorWithMap::new(reporter, backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic() {
|
|
||||||
let event_man = create_basic_man_1();
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
let event_sent = event_man
|
|
||||||
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
|
|
||||||
.expect("Sending info event failed");
|
|
||||||
|
|
||||||
assert!(event_sent);
|
|
||||||
// Will not check packet here, correctness of packet was tested somewhere else
|
|
||||||
event_rx.try_recv().expect("Receiving event TM failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_disable_event() {
|
|
||||||
let mut event_man = create_basic_man_2();
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
// let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
|
|
||||||
let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert!(res.unwrap());
|
|
||||||
let mut event_sent = event_man
|
|
||||||
.generate_pus_event_tm_generic(&event_tx, &EMPTY_STAMP, LOW_SEV_EVENT, None)
|
|
||||||
.expect("Sending low severity event failed");
|
|
||||||
assert!(!event_sent);
|
|
||||||
let res = event_rx.try_recv();
|
|
||||||
assert!(res.is_err());
|
|
||||||
assert!(matches!(res.unwrap_err(), TryRecvError::Empty));
|
|
||||||
// Check that only the low severity event was disabled
|
|
||||||
event_sent = event_man
|
|
||||||
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
|
|
||||||
.expect("Sending info event failed");
|
|
||||||
assert!(event_sent);
|
|
||||||
event_rx.try_recv().expect("No info event received");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reenable_event() {
|
|
||||||
let mut event_man = create_basic_man_1();
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert!(res.unwrap());
|
|
||||||
res = event_man.enable_tm_for_event_with_sev(&INFO_EVENT);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert!(res.unwrap());
|
|
||||||
let event_sent = event_man
|
|
||||||
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
|
|
||||||
.expect("Sending info event failed");
|
|
||||||
assert!(event_sent);
|
|
||||||
event_rx.try_recv().expect("No info event received");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_event_with_generic_string_param() {
|
|
||||||
let event_man = create_basic_man_1();
|
|
||||||
let mut small_data_buf = [0; 128];
|
|
||||||
let param_data = "hello world";
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
let res = event_man.generate_pus_event_tm_generic_with_generic_params(
|
|
||||||
&event_tx,
|
|
||||||
&EMPTY_STAMP,
|
|
||||||
INFO_EVENT.into(),
|
|
||||||
&mut small_data_buf,
|
|
||||||
Some(¶m_data.to_string().into()),
|
|
||||||
);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
assert!(res.event_was_enabled);
|
|
||||||
assert!(res.params_were_propagated);
|
|
||||||
let event_tm = event_rx.try_recv().expect("no event received");
|
|
||||||
let tm = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed");
|
|
||||||
assert_eq!(tm.service_type_id(), 5);
|
|
||||||
assert_eq!(
|
|
||||||
tm.message_subtype_id(),
|
|
||||||
MessageSubtypeId::TmInfoReport as u8
|
|
||||||
);
|
|
||||||
assert_eq!(tm.user_data().len(), 4 + param_data.len());
|
|
||||||
let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap());
|
|
||||||
assert_eq!(u32_event, INFO_EVENT.raw());
|
|
||||||
let string_data = String::from_utf8_lossy(&tm.user_data()[4..]);
|
|
||||||
assert_eq!(string_data, param_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_event_with_generic_vec_param() {
|
|
||||||
let event_man = create_basic_man_1();
|
|
||||||
let mut small_data_buf = [0; 128];
|
|
||||||
let param_data = vec![1, 2, 3, 4];
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
let res = event_man.generate_pus_event_tm_generic_with_generic_params(
|
|
||||||
&event_tx,
|
|
||||||
&EMPTY_STAMP,
|
|
||||||
INFO_EVENT.into(),
|
|
||||||
&mut small_data_buf,
|
|
||||||
Some(¶m_data.clone().into()),
|
|
||||||
);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
assert!(res.event_was_enabled);
|
|
||||||
assert!(res.params_were_propagated);
|
|
||||||
let event_tm = event_rx.try_recv().expect("no event received");
|
|
||||||
let tm = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed");
|
|
||||||
assert_eq!(tm.service_type_id(), 5);
|
|
||||||
assert_eq!(
|
|
||||||
tm.message_subtype_id(),
|
|
||||||
MessageSubtypeId::TmInfoReport as u8
|
|
||||||
);
|
|
||||||
assert_eq!(tm.user_data().len(), 4 + param_data.len());
|
|
||||||
let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap());
|
|
||||||
assert_eq!(u32_event, INFO_EVENT.raw());
|
|
||||||
let vec_data = tm.user_data()[4..].to_vec();
|
|
||||||
assert_eq!(vec_data, param_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_event_with_generic_store_param_not_propagated() {
|
|
||||||
// TODO: Test this.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_event_with_generic_heapless_param() {
|
|
||||||
// TODO: Test this.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
use crate::events_legacy::EventU32;
|
|
||||||
use crate::pus::event_man::{EventRequest, EventRequestWithToken};
|
|
||||||
use crate::pus::verification::TcStateToken;
|
|
||||||
use crate::pus::{DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError};
|
|
||||||
use crate::queue::GenericSendError;
|
|
||||||
use spacepackets::ecss::PusPacket;
|
|
||||||
use spacepackets::ecss::event::MessageSubtypeId;
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
|
|
||||||
use super::verification::VerificationReportingProvider;
|
|
||||||
use super::{
|
|
||||||
CacheAndReadRawEcssTc, EcssTcReceiver, EcssTmSender, GenericConversionError,
|
|
||||||
GenericRoutingError, HandlingStatus, PusServiceHelper,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct PusEventServiceHandler<
|
|
||||||
TcReceiver: EcssTcReceiver,
|
|
||||||
TmSender: EcssTmSender,
|
|
||||||
TcInMemConverter: CacheAndReadRawEcssTc,
|
|
||||||
VerificationReporter: VerificationReportingProvider,
|
|
||||||
> {
|
|
||||||
pub service_helper:
|
|
||||||
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
|
||||||
event_request_tx: Sender<EventRequestWithToken>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
TcReceiver: EcssTcReceiver,
|
|
||||||
TmSender: EcssTmSender,
|
|
||||||
TcInMemConverter: CacheAndReadRawEcssTc,
|
|
||||||
VerificationReporter: VerificationReportingProvider,
|
|
||||||
> PusEventServiceHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
|
|
||||||
{
|
|
||||||
pub fn new(
|
|
||||||
service_helper: PusServiceHelper<
|
|
||||||
TcReceiver,
|
|
||||||
TmSender,
|
|
||||||
TcInMemConverter,
|
|
||||||
VerificationReporter,
|
|
||||||
>,
|
|
||||||
event_request_tx: Sender<EventRequestWithToken>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
service_helper,
|
|
||||||
event_request_tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_and_handle_next_tc<ErrorCb: FnMut(&PartialPusHandlingError)>(
|
|
||||||
&mut self,
|
|
||||||
mut error_callback: ErrorCb,
|
|
||||||
time_stamp: &[u8],
|
|
||||||
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
|
|
||||||
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
|
|
||||||
if possible_packet.is_none() {
|
|
||||||
return Ok(HandlingStatus::Empty.into());
|
|
||||||
}
|
|
||||||
let ecss_tc_and_token = possible_packet.unwrap();
|
|
||||||
self.service_helper
|
|
||||||
.tc_in_mem_converter_mut()
|
|
||||||
.cache(&ecss_tc_and_token.tc_in_memory)?;
|
|
||||||
let tc = self.service_helper.tc_in_mem_converter().convert()?;
|
|
||||||
let subservice = tc.message_subtype_id();
|
|
||||||
let srv = MessageSubtypeId::try_from(subservice);
|
|
||||||
if srv.is_err() {
|
|
||||||
return Ok(DirectPusPacketHandlerResult::CustomSubservice(
|
|
||||||
tc.message_subtype_id(),
|
|
||||||
ecss_tc_and_token.token,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut handle_enable_disable_request =
|
|
||||||
|enable: bool| -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
|
|
||||||
if tc.user_data().len() < 4 {
|
|
||||||
return Err(GenericConversionError::NotEnoughAppData {
|
|
||||||
expected: 4,
|
|
||||||
found: tc.user_data().len(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
let user_data = tc.user_data();
|
|
||||||
let event_u32 =
|
|
||||||
EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap()));
|
|
||||||
let mut token: TcStateToken = ecss_tc_and_token.token.into();
|
|
||||||
match self.service_helper.common.verif_reporter.start_success(
|
|
||||||
&self.service_helper.common.tm_sender,
|
|
||||||
ecss_tc_and_token.token,
|
|
||||||
time_stamp,
|
|
||||||
) {
|
|
||||||
Ok(start_token) => {
|
|
||||||
token = start_token.into();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error_callback(&PartialPusHandlingError::Verification(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_req_with_token = if enable {
|
|
||||||
EventRequestWithToken {
|
|
||||||
request: EventRequest::Enable(event_u32),
|
|
||||||
token,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EventRequestWithToken {
|
|
||||||
request: EventRequest::Disable(event_u32),
|
|
||||||
token,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.event_request_tx
|
|
||||||
.send(event_req_with_token)
|
|
||||||
.map_err(|_| {
|
|
||||||
PusPacketHandlingError::RequestRouting(GenericRoutingError::Send(
|
|
||||||
GenericSendError::RxDisconnected,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
Ok(HandlingStatus::HandledOne.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
match srv.unwrap() {
|
|
||||||
MessageSubtypeId::TmInfoReport
|
|
||||||
| MessageSubtypeId::TmLowSeverityReport
|
|
||||||
| MessageSubtypeId::TmMediumSeverityReport
|
|
||||||
| MessageSubtypeId::TmHighSeverityReport => {
|
|
||||||
return Err(PusPacketHandlingError::RequestConversion(
|
|
||||||
GenericConversionError::WrongService(tc.message_subtype_id()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
MessageSubtypeId::TcEnableEventGeneration => {
|
|
||||||
handle_enable_disable_request(true)?;
|
|
||||||
}
|
|
||||||
MessageSubtypeId::TcDisableEventGeneration => {
|
|
||||||
handle_enable_disable_request(false)?;
|
|
||||||
}
|
|
||||||
MessageSubtypeId::TcReportDisabledList | MessageSubtypeId::TmDisabledEventsReport => {
|
|
||||||
return Ok(DirectPusPacketHandlerResult::SubserviceNotImplemented(
|
|
||||||
subservice,
|
|
||||||
ecss_tc_and_token.token,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HandlingStatus::HandledOne.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use arbitrary_int::traits::Integer as _;
|
|
||||||
use arbitrary_int::u14;
|
|
||||||
use delegate::delegate;
|
|
||||||
use spacepackets::ecss::event::MessageSubtypeId;
|
|
||||||
use spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
|
||||||
use spacepackets::time::{TimeWriter, cds};
|
|
||||||
use spacepackets::util::UnsignedEnum;
|
|
||||||
use spacepackets::{
|
|
||||||
SpHeader,
|
|
||||||
ecss::{
|
|
||||||
tc::{PusTcCreator, PusTcSecondaryHeader},
|
|
||||||
tm::PusTmReader,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use std::sync::mpsc::{self, Sender};
|
|
||||||
|
|
||||||
use crate::pus::event_man::EventRequest;
|
|
||||||
use crate::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID};
|
|
||||||
use crate::pus::verification::{
|
|
||||||
RequestId, VerificationReporter, VerificationReportingProvider,
|
|
||||||
};
|
|
||||||
use crate::pus::{GenericConversionError, HandlingStatus, MpscTcReceiver};
|
|
||||||
use crate::tmtc::PacketSenderWithSharedPool;
|
|
||||||
use crate::{
|
|
||||||
events_legacy::EventU32,
|
|
||||||
pus::{
|
|
||||||
DirectPusPacketHandlerResult, EcssTcInSharedPoolCacher, PusPacketHandlingError,
|
|
||||||
event_man::EventRequestWithToken,
|
|
||||||
tests::PusServiceHandlerWithSharedStoreCommon,
|
|
||||||
verification::{TcStateAccepted, VerificationToken},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::PusEventServiceHandler;
|
|
||||||
|
|
||||||
const TEST_EVENT_0: EventU32 = EventU32::new(crate::events_legacy::Severity::Info, 5, 25);
|
|
||||||
|
|
||||||
struct Pus5HandlerWithStoreTester {
|
|
||||||
common: PusServiceHandlerWithSharedStoreCommon,
|
|
||||||
handler: PusEventServiceHandler<
|
|
||||||
MpscTcReceiver,
|
|
||||||
PacketSenderWithSharedPool,
|
|
||||||
EcssTcInSharedPoolCacher,
|
|
||||||
VerificationReporter,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pus5HandlerWithStoreTester {
|
|
||||||
pub fn new(event_request_tx: Sender<EventRequestWithToken>) -> Self {
|
|
||||||
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(0);
|
|
||||||
Self {
|
|
||||||
common,
|
|
||||||
handler: PusEventServiceHandler::new(srv_handler, event_request_tx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusTestHarness for Pus5HandlerWithStoreTester {
|
|
||||||
fn start_verification(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
|
|
||||||
let init_token = self
|
|
||||||
.handler
|
|
||||||
.service_helper
|
|
||||||
.verif_reporter_mut()
|
|
||||||
.start_verification(tc);
|
|
||||||
self.handler
|
|
||||||
.service_helper
|
|
||||||
.verif_reporter()
|
|
||||||
.acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7])
|
|
||||||
.expect("acceptance success failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_tc(&self, token: &VerificationToken<TcStateAccepted>, tc: &PusTcCreator) {
|
|
||||||
self.common
|
|
||||||
.send_tc(self.handler.service_helper.id(), token, tc);
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate! {
|
|
||||||
to self.common {
|
|
||||||
fn read_next_tm(&mut self) -> PusTmReader<'_>;
|
|
||||||
fn check_no_tm_available(&self) -> bool;
|
|
||||||
fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimplePusPacketHandler for Pus5HandlerWithStoreTester {
|
|
||||||
fn handle_one_tc(
|
|
||||||
&mut self,
|
|
||||||
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
|
|
||||||
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
|
|
||||||
self.handler.poll_and_handle_next_tc(|_| {}, &time_stamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_test(
|
|
||||||
test_harness: &mut (impl PusTestHarness + SimplePusPacketHandler),
|
|
||||||
subservice: MessageSubtypeId,
|
|
||||||
expected_event_req: EventRequest,
|
|
||||||
event_req_receiver: mpsc::Receiver<EventRequestWithToken>,
|
|
||||||
) {
|
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(5, subservice as u8));
|
|
||||||
let mut app_data = [0; 4];
|
|
||||||
TEST_EVENT_0
|
|
||||||
.write_to_be_bytes(&mut app_data)
|
|
||||||
.expect("writing test event failed");
|
|
||||||
let ping_tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
|
||||||
let token = test_harness.start_verification(&ping_tc);
|
|
||||||
test_harness.send_tc(&token, &ping_tc);
|
|
||||||
let request_id = token.request_id();
|
|
||||||
test_harness.handle_one_tc().unwrap();
|
|
||||||
test_harness.check_next_verification_tm(1, request_id);
|
|
||||||
test_harness.check_next_verification_tm(3, request_id);
|
|
||||||
// Completion TM is not generated for us.
|
|
||||||
assert!(test_harness.check_no_tm_available());
|
|
||||||
let event_request = event_req_receiver
|
|
||||||
.try_recv()
|
|
||||||
.expect("no event request received");
|
|
||||||
assert_eq!(expected_event_req, event_request.request);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_enabling_event_reporting() {
|
|
||||||
let (event_request_tx, event_request_rx) = mpsc::channel();
|
|
||||||
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
|
|
||||||
event_test(
|
|
||||||
&mut test_harness,
|
|
||||||
MessageSubtypeId::TcEnableEventGeneration,
|
|
||||||
EventRequest::Enable(TEST_EVENT_0),
|
|
||||||
event_request_rx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_disabling_event_reporting() {
|
|
||||||
let (event_request_tx, event_request_rx) = mpsc::channel();
|
|
||||||
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
|
|
||||||
event_test(
|
|
||||||
&mut test_harness,
|
|
||||||
MessageSubtypeId::TcDisableEventGeneration,
|
|
||||||
EventRequest::Disable(TEST_EVENT_0),
|
|
||||||
event_request_rx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_tc_queue() {
|
|
||||||
let (event_request_tx, _) = mpsc::channel();
|
|
||||||
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let result = result.unwrap();
|
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
result,
|
|
||||||
DirectPusPacketHandlerResult::Handled(HandlingStatus::Empty)
|
|
||||||
),
|
|
||||||
"unexpected result type {result:?}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sending_custom_subservice() {
|
|
||||||
let (event_request_tx, _) = mpsc::channel();
|
|
||||||
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
|
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(5, 200));
|
|
||||||
let ping_tc =
|
|
||||||
PusTcCreator::new_no_app_data(sp_header, sec_header, CreatorConfig::default());
|
|
||||||
let token = test_harness.start_verification(&ping_tc);
|
|
||||||
test_harness.send_tc(&token, &ping_tc);
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let result = result.unwrap();
|
|
||||||
if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result {
|
|
||||||
assert_eq!(subservice, 200);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected result type {result:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sending_invalid_app_data() {
|
|
||||||
let (event_request_tx, _) = mpsc::channel();
|
|
||||||
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
|
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(
|
|
||||||
5,
|
|
||||||
MessageSubtypeId::TcEnableEventGeneration as u8,
|
|
||||||
));
|
|
||||||
let ping_tc =
|
|
||||||
PusTcCreator::new(sp_header, sec_header, &[0, 1, 2], CreatorConfig::default());
|
|
||||||
let token = test_harness.start_verification(&ping_tc);
|
|
||||||
test_harness.send_tc(&token, &ping_tc);
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_err());
|
|
||||||
let result = result.unwrap_err();
|
|
||||||
if let PusPacketHandlingError::RequestConversion(
|
|
||||||
GenericConversionError::NotEnoughAppData { expected, found },
|
|
||||||
) = result
|
|
||||||
{
|
|
||||||
assert_eq!(expected, 4);
|
|
||||||
assert_eq!(found, 3);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected result type {result:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,10 +25,6 @@ use spacepackets::ecss::tm::PusTmCreator;
|
|||||||
use spacepackets::{ByteConversionError, SpHeader};
|
use spacepackets::{ByteConversionError, SpHeader};
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod event;
|
|
||||||
pub mod event_man;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub mod event_srv;
|
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use std::sync::mpsc;
|
|||||||
|
|
||||||
/// This is a helper class for [std] environments to handle generic PUS 11 (scheduling service)
|
/// This is a helper class for [std] environments to handle generic PUS 11 (scheduling service)
|
||||||
/// packets. This handler is able to handle the most important PUS requests for a scheduling
|
/// packets. This handler is able to handle the most important PUS requests for a scheduling
|
||||||
/// service which provides the [PusSchedulerProvider].
|
/// service which provides the [PusScheduler].
|
||||||
///
|
///
|
||||||
/// Please note that this class does not do the regular periodic handling like releasing any
|
/// Please note that this class does not do the regular periodic handling like releasing any
|
||||||
/// telecommands inside the scheduler. The user can retrieve the wrapped scheduler via the
|
/// telecommands inside the scheduler. The user can retrieve the wrapped scheduler via the
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
use arbitrary_int::{u11, u21};
|
|
||||||
use satrs::event_man_legacy::{
|
|
||||||
EventManagerWithMpsc, EventMessage, EventMessageU32, EventRoutingError, EventSendProvider,
|
|
||||||
EventU32SenderMpsc,
|
|
||||||
};
|
|
||||||
use satrs::events_legacy::{EventU32, EventU32TypedSev, Severity, SeverityInfo};
|
|
||||||
use satrs::params::U32Pair;
|
|
||||||
use satrs::params::{Params, ParamsHeapless, WritableToBeBytes};
|
|
||||||
use satrs::pus::event_man::{DefaultPusEventReportingMap, EventReporter, PusEventTmCreatorWithMap};
|
|
||||||
use satrs::request::UniqueApidTargetId;
|
|
||||||
use satrs::tmtc::PacketAsVec;
|
|
||||||
use spacepackets::ecss::PusError;
|
|
||||||
use spacepackets::ecss::tm::PusTmReader;
|
|
||||||
use std::sync::mpsc::{self, SendError, TryRecvError};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(1, 0);
|
|
||||||
const LOW_SEV_EVENT: EventU32 = EventU32::new(Severity::Low, 1, 5);
|
|
||||||
const EMPTY_STAMP: [u8; 7] = [0; 7];
|
|
||||||
const TEST_APID: u11 = u11::new(0x02);
|
|
||||||
const TEST_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, u21::new(0x05));
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CustomTmSenderError {
|
|
||||||
SendError(SendError<Vec<u8>>),
|
|
||||||
PusError(PusError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_threaded_usage() {
|
|
||||||
let (event_tx, event_rx) = mpsc::sync_channel(100);
|
|
||||||
let mut event_man = EventManagerWithMpsc::new(event_rx);
|
|
||||||
|
|
||||||
let (pus_event_man_tx, pus_event_man_rx) = mpsc::channel();
|
|
||||||
let pus_event_man_send_provider = EventU32SenderMpsc::new(1, pus_event_man_tx);
|
|
||||||
event_man.subscribe_all(pus_event_man_send_provider.target_id());
|
|
||||||
event_man.add_sender(pus_event_man_send_provider);
|
|
||||||
let (event_packet_tx, event_packet_rx) = mpsc::channel::<PacketAsVec>();
|
|
||||||
let reporter = EventReporter::new(TEST_ID.raw(), u11::new(0x02), 0, 128);
|
|
||||||
let pus_event_man =
|
|
||||||
PusEventTmCreatorWithMap::new(reporter, DefaultPusEventReportingMap::default());
|
|
||||||
let error_handler = |event_msg: &EventMessageU32, error: EventRoutingError| {
|
|
||||||
panic!("received routing error for event {event_msg:?}: {error:?}");
|
|
||||||
};
|
|
||||||
// PUS + Generic event manager thread
|
|
||||||
let jh0 = thread::spawn(move || {
|
|
||||||
let mut event_cnt = 0;
|
|
||||||
let mut params_array: [u8; 128] = [0; 128];
|
|
||||||
loop {
|
|
||||||
event_man.try_event_handling(error_handler);
|
|
||||||
match pus_event_man_rx.try_recv() {
|
|
||||||
Ok(event_msg) => {
|
|
||||||
let gen_event = |aux_data| {
|
|
||||||
pus_event_man.generate_pus_event_tm_generic(
|
|
||||||
&event_packet_tx,
|
|
||||||
&EMPTY_STAMP,
|
|
||||||
event_msg.event(),
|
|
||||||
aux_data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let res = if let Some(aux_data) = event_msg.params() {
|
|
||||||
match aux_data {
|
|
||||||
Params::Heapless(heapless) => match heapless {
|
|
||||||
ParamsHeapless::Raw(raw) => {
|
|
||||||
raw.write_to_be_bytes(&mut params_array)
|
|
||||||
.expect("Writing raw parameter failed");
|
|
||||||
gen_event(Some(¶ms_array[0..raw.written_len()]))
|
|
||||||
}
|
|
||||||
ParamsHeapless::EcssEnum(e) => {
|
|
||||||
e.write_to_be_bytes(&mut params_array)
|
|
||||||
.expect("Writing ECSS enum failed");
|
|
||||||
gen_event(Some(¶ms_array[0..e.written_len()]))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Params::Vec(vec) => gen_event(Some(vec.as_slice())),
|
|
||||||
Params::String(str) => gen_event(Some(str.as_bytes())),
|
|
||||||
Params::Store(_) => gen_event(None),
|
|
||||||
_ => panic!("unsupported parameter type"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gen_event(None)
|
|
||||||
};
|
|
||||||
event_cnt += 1;
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert!(res.unwrap());
|
|
||||||
if event_cnt == 2 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if let TryRecvError::Disconnected = e {
|
|
||||||
panic!("Event receiver disconnected!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event sender and TM checker thread
|
|
||||||
let jh1 = thread::spawn(move || {
|
|
||||||
event_tx
|
|
||||||
.send(EventMessage::new(TEST_ID.id(), INFO_EVENT.into()))
|
|
||||||
.expect("Sending info event failed");
|
|
||||||
loop {
|
|
||||||
match event_packet_rx.try_recv() {
|
|
||||||
// Event TM received successfully
|
|
||||||
Ok(event_tm) => {
|
|
||||||
let tm = PusTmReader::new(event_tm.packet.as_slice(), 7)
|
|
||||||
.expect("Deserializing TM failed");
|
|
||||||
assert_eq!(tm.service_type_id(), 5);
|
|
||||||
assert_eq!(tm.message_subtype_id(), 1);
|
|
||||||
let src_data = tm.source_data();
|
|
||||||
assert!(!src_data.is_empty());
|
|
||||||
assert_eq!(src_data.len(), 4);
|
|
||||||
let event =
|
|
||||||
EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
|
||||||
assert_eq!(event, INFO_EVENT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if let TryRecvError::Disconnected = e {
|
|
||||||
panic!("Event sender disconnected!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event_tx
|
|
||||||
.send(EventMessage::new_with_params(
|
|
||||||
TEST_ID.id(),
|
|
||||||
LOW_SEV_EVENT,
|
|
||||||
&Params::Heapless((2_u32, 3_u32).into()),
|
|
||||||
))
|
|
||||||
.expect("Sending low severity event failed");
|
|
||||||
loop {
|
|
||||||
match event_packet_rx.try_recv() {
|
|
||||||
// Event TM received successfully
|
|
||||||
Ok(event_tm) => {
|
|
||||||
let tm = PusTmReader::new(event_tm.packet.as_slice(), 7)
|
|
||||||
.expect("Deserializing TM failed");
|
|
||||||
assert_eq!(tm.service_type_id(), 5);
|
|
||||||
assert_eq!(tm.message_subtype_id(), 2);
|
|
||||||
let src_data = tm.source_data();
|
|
||||||
assert!(!src_data.is_empty());
|
|
||||||
assert_eq!(src_data.len(), 12);
|
|
||||||
let event =
|
|
||||||
EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
|
||||||
assert_eq!(event, LOW_SEV_EVENT);
|
|
||||||
let u32_pair: U32Pair =
|
|
||||||
src_data[4..].try_into().expect("Creating U32Pair failed");
|
|
||||||
assert_eq!(u32_pair.0, 2);
|
|
||||||
assert_eq!(u32_pair.1, 3);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if let TryRecvError::Disconnected = e {
|
|
||||||
panic!("Event sender disconnected!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
jh0.join().expect("Joining manager thread failed");
|
|
||||||
jh1.join().expect("Joining creator thread failed");
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user