Compare commits
99 Commits
satrs-shar
...
update-emb
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e2fbc08545
|
||
2e58a311c8 | |||
![]() |
62d64e692a
|
||
3784f47b66 | |||
![]() |
e1911f1b6e | ||
2e53ce1871 | |||
![]() |
a6c460129b
|
||
d5ecefd683 | |||
2f9f3b8183 | |||
b28cff85de | |||
da3de9f22c | |||
aefdf0987d | |||
![]() |
7ae9e62d66 | ||
fea1a9028b | |||
![]() |
abc145fb2d
|
||
490635dfcf | |||
de3d22a022 | |||
cbe211fe8b | |||
![]() |
e379bc3fd7 | ||
a61ee85796 | |||
![]() |
81473b30f9
|
||
22675a73f9 | |||
![]() |
c68e3d4f75
|
||
3deedfba17 | |||
![]() |
533caea0fe
|
||
848fe6f207 | |||
![]() |
cf64fea7d9 | ||
4948db3fa5 | |||
![]() |
1920e4878c
|
||
f46bc94b04 | |||
fb45da1890
|
|||
f600ee499a | |||
3f28f60c59 | |||
44b1f2b037 | |||
4b22958b34 | |||
a711c6acd9 | |||
99a954a1f5
|
|||
4a4fd7ac2c
|
|||
9bf08849a2
|
|||
b8f7fefe26 | |||
a501832698
|
|||
c7284d3f1c | |||
18263d4568 | |||
95519c1363
|
|||
2ec32717d0 | |||
bfdd777685
|
|||
b54c2b7863 | |||
19f3355283 | |||
4aeb28d2f1
|
|||
ddc4544456 | |||
9ab36c0362 | |||
b95769c177 | |||
bb20533ae1 | |||
27cacd0f43
|
|||
bd6488e87b | |||
52ec0d44aa | |||
4fa9f8d685 | |||
767cf6b1a5 | |||
5bf5518591
|
|||
f3e0609910 | |||
05106354e3 | |||
719b70d834 | |||
d4082fa098 | |||
c9c9e4f735 | |||
2c92a95184
|
|||
1a1d330814 | |||
ef7b5d66fe
|
|||
b3497e0592 | |||
73286e36c2
|
|||
ed266a11f6 | |||
af972e174f
|
|||
3d12083c16
|
|||
e8d2c020fa | |||
69e172b633 | |||
e1dda751bc
|
|||
b01628d8ef | |||
31844e4fe2 | |||
738872f421 | |||
309e39999f
|
|||
1c43c3adf9 | |||
d9e0abffa7 | |||
abec9dd448
|
|||
c18fbb59ad | |||
c91ddcd658
|
|||
c5fa1955d7 | |||
c9708810e6
|
|||
79d0c2e222 | |||
8e87875c0e | |||
1ac6c02c06
|
|||
6e7907522e | |||
f747a5efdc
|
|||
6ffd55cec2 | |||
5e51b3de42
|
|||
bd059a2541 | |||
1a4d764f25 | |||
b38c617fae | |||
edcd5491f1 | |||
b4cb034b73 | |||
47c86aea5c |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -11,7 +11,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo check --release
|
||||
- run: cargo check
|
||||
# Check example with static pool configuration
|
||||
- run: cargo check -p satrs-example --no-default-features
|
||||
|
||||
test:
|
||||
name: Run Tests
|
||||
@@ -37,7 +39,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
|
||||
- run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
|
||||
- run: cargo check -p satrs --target=${{matrix.target}} --no-default-features
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
|
@@ -10,6 +10,5 @@ members = [
|
||||
|
||||
exclude = [
|
||||
"embedded-examples/stm32f3-disco-rtic",
|
||||
"embedded-examples/stm32h7-rtic",
|
||||
"embedded-examples/stm32h7-nucleo-rtic",
|
||||
]
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
|
||||
|
||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
|
||||
[](https://robamu.github.io/sat-rs/book/)
|
||||
[](https://crates.io/crates/satrs)
|
||||
[](https://docs.rs/satrs)
|
||||
[](https://matrix.to/#/#sat-rs:matrix.org)
|
||||
|
||||
sat-rs
|
||||
=========
|
||||
@@ -11,7 +12,7 @@ sat-rs
|
||||
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
|
||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||
for the special requirements for these systems. You can find an overview of the project and the
|
||||
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
||||
link to the [more high-level sat-rs book](https://robamu.github.io/sat-rs/book/)
|
||||
at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
|
||||
|
||||
This is early-stage software. Important features are missing. New releases
|
||||
@@ -38,7 +39,7 @@ This project currently contains following crates:
|
||||
Example of a simple example on-board software using various sat-rs components which can be run
|
||||
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
||||
* [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim):
|
||||
Mini-Simulator based on [asynchronix](https://github.com/asynchronics/asynchronix) which
|
||||
Mini-Simulator based on [nexosim](https://github.com/asynchronics/nexosim) which
|
||||
simulates some physical devices for the `satrs-example` application device handlers.
|
||||
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-mib):
|
||||
Components to build a mission information base from the on-board software directly.
|
||||
@@ -61,6 +62,8 @@ Each project has its own `CHANGELOG.md`.
|
||||
packet protocol implementations. This repository is re-exported in the
|
||||
[`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
|
||||
crate.
|
||||
* [`cfdp`](https://egit.irs.uni-stuttgart.de/rust/cfdp): CCSDS File Delivery Protocol
|
||||
(CFDP) high-level library components.
|
||||
|
||||
# Flight Heritage
|
||||
|
||||
|
3
docs.sh
Executable file
3
docs.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||
cargo +nightly doc --all-features --open
|
776
embedded-examples/stm32f3-disco-rtic/Cargo.lock
generated
776
embedded-examples/stm32f3-disco-rtic/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,50 +9,52 @@ default-run = "satrs-stm32f3-disco-rtic"
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = "0.7"
|
||||
defmt = "0.3"
|
||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
embedded-hal = "0.2.7"
|
||||
defmt = "1"
|
||||
defmt-rtt = { version = "1" }
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
embedded-hal = "1"
|
||||
cortex-m-semihosting = "0.5.0"
|
||||
embassy-stm32 = { version = "0.4", features = ["stm32f303vc", "unstable-pac"] }
|
||||
enumset = "1"
|
||||
heapless = "0.8"
|
||||
heapless = "0.9"
|
||||
spacepackets = { version = "0.15", default-features = false, features = ["defmt"] }
|
||||
static_cell = "2"
|
||||
|
||||
[dependencies.rtic]
|
||||
version = "2"
|
||||
features = ["thumbv7-backend"]
|
||||
|
||||
[dependencies.rtic-monotonics]
|
||||
version = "1"
|
||||
version = "2"
|
||||
features = ["cortex-m-systick"]
|
||||
|
||||
[dependencies.cobs]
|
||||
git = "https://github.com/robamu/cobs.rs.git"
|
||||
branch = "all_features"
|
||||
version = "0.4"
|
||||
default-features = false
|
||||
|
||||
[dependencies.stm32f3xx-hal]
|
||||
git = "https://github.com/robamu/stm32f3xx-hal"
|
||||
version = "0.11.0-alpha.0"
|
||||
features = ["stm32f303xc", "rt", "enumset"]
|
||||
branch = "complete-dma-update"
|
||||
# [dependencies.stm32f3xx-hal]
|
||||
# git = "https://github.com/robamu/stm32f3xx-hal"
|
||||
# version = "0.11.0-alpha.0"
|
||||
# features = ["stm32f303xc", "rt", "enumset"]
|
||||
# branch = "complete-dma-update"
|
||||
# Can be used in workspace to develop and update HAL
|
||||
# path = "../stm32f3xx-hal"
|
||||
|
||||
[dependencies.stm32f3-discovery]
|
||||
git = "https://github.com/robamu/stm32f3-discovery"
|
||||
version = "0.8.0-alpha.0"
|
||||
branch = "complete-dma-update-hal"
|
||||
# [dependencies.stm32f3-discovery]
|
||||
# git = "https://github.com/robamu/stm32f3-discovery"
|
||||
# version = "0.8.0-alpha.0"
|
||||
# branch = "complete-dma-update-hal"
|
||||
# Can be used in workspace to develop and update BSP
|
||||
# path = "../stm32f3-discovery"
|
||||
|
||||
[dependencies.satrs]
|
||||
# path = "satrs"
|
||||
version = "0.2"
|
||||
# version = "0.2"
|
||||
path = "../../satrs"
|
||||
default-features = false
|
||||
features = ["defmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
defmt-test = "0.3"
|
||||
defmt-test = "0.4"
|
||||
|
||||
# cargo test
|
||||
[profile.test]
|
||||
|
@@ -1,76 +1,61 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use satrs_stm32f3_disco_rtic as _;
|
||||
#![no_std]
|
||||
|
||||
use stm32f3_discovery::leds::Leds;
|
||||
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
|
||||
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
|
||||
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
|
||||
use panic_probe as _;
|
||||
use rtic::app;
|
||||
|
||||
#[cortex_m_rt::entry]
|
||||
fn main() -> ! {
|
||||
defmt::println!("STM32F3 Discovery Blinky");
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
let mut rcc = dp.RCC.constrain();
|
||||
let cp = cortex_m::Peripherals::take().unwrap();
|
||||
let mut flash = dp.FLASH.constrain();
|
||||
let clocks = rcc.cfgr.freeze(&mut flash.acr);
|
||||
let mut delay = Delay::new(cp.SYST, clocks);
|
||||
#[app(device = embassy_stm32)]
|
||||
mod app {
|
||||
use rtic_monotonics::fugit::ExtU32;
|
||||
use rtic_monotonics::Monotonic as _;
|
||||
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
|
||||
|
||||
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
|
||||
let mut leds = Leds::new(
|
||||
gpioe.pe8,
|
||||
gpioe.pe9,
|
||||
gpioe.pe10,
|
||||
gpioe.pe11,
|
||||
gpioe.pe12,
|
||||
gpioe.pe13,
|
||||
gpioe.pe14,
|
||||
gpioe.pe15,
|
||||
&mut gpioe.moder,
|
||||
&mut gpioe.otyper,
|
||||
);
|
||||
let delay_ms = 200u16;
|
||||
rtic_monotonics::systick_monotonic!(Mono, 1000);
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
leds: Leds,
|
||||
current_dir: Direction,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2");
|
||||
|
||||
let led_pin_set = LedPinSet {
|
||||
pin_n: p.PE8,
|
||||
pin_ne: p.PE9,
|
||||
pin_e: p.PE10,
|
||||
pin_se: p.PE11,
|
||||
pin_s: p.PE12,
|
||||
pin_sw: p.PE13,
|
||||
pin_w: p.PE14,
|
||||
pin_nw: p.PE15,
|
||||
};
|
||||
let leds = Leds::new(led_pin_set);
|
||||
|
||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||
Mono::start(cx.core.SYST, 8_000_000);
|
||||
blinky::spawn().expect("failed to spawn blinky task");
|
||||
(
|
||||
Shared {},
|
||||
Local {
|
||||
leds,
|
||||
current_dir: Direction::North,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[task(local = [leds, current_dir])]
|
||||
async fn blinky(cx: blinky::Context) {
|
||||
loop {
|
||||
leds.ld3_n.toggle().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld3_n.toggle().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
//explicit on/off
|
||||
leds.ld4_nw.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld4_nw.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld5_ne.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld5_ne.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld6_w.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld6_w.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld7_e.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld7_e.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld8_sw.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld8_sw.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld9_se.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld9_se.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
|
||||
leds.ld10_s.on().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
leds.ld10_s.off().ok();
|
||||
delay.delay_ms(delay_ms);
|
||||
cx.local.leds.blink_next(cx.local.current_dir);
|
||||
Mono::delay(200.millis()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,147 @@
|
||||
|
||||
use cortex_m_semihosting::debug;
|
||||
|
||||
use defmt_brtt as _; // global logger
|
||||
|
||||
use stm32f3xx_hal as _; // memory layout
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
use panic_probe as _;
|
||||
|
||||
use embassy_stm32::gpio::Output;
|
||||
|
||||
#[derive(defmt::Format, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Direction {
|
||||
North,
|
||||
NorthEast,
|
||||
East,
|
||||
SouthEast,
|
||||
South,
|
||||
SouthWest,
|
||||
West,
|
||||
NorthWest,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn switch_to_next(&mut self) -> (Self, Self) {
|
||||
let curr = *self;
|
||||
*self = match self {
|
||||
Direction::North => Direction::NorthEast,
|
||||
Direction::NorthEast => Direction::East,
|
||||
Direction::East => Direction::SouthEast,
|
||||
Direction::SouthEast => Direction::South,
|
||||
Direction::South => Direction::SouthWest,
|
||||
Direction::SouthWest => Direction::West,
|
||||
Direction::West => Direction::NorthWest,
|
||||
Direction::NorthWest => Direction::North,
|
||||
};
|
||||
(curr, *self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Leds {
|
||||
pub north: Output<'static>,
|
||||
pub north_east: Output<'static>,
|
||||
pub east: Output<'static>,
|
||||
pub south_east: Output<'static>,
|
||||
pub south: Output<'static>,
|
||||
pub south_west: Output<'static>,
|
||||
pub west: Output<'static>,
|
||||
pub north_west: Output<'static>,
|
||||
}
|
||||
|
||||
impl Leds {
|
||||
pub fn blink_next(&mut self, current_dir: &mut Direction) {
|
||||
let (prev, curr) = current_dir.switch_to_next();
|
||||
self.set_dir_low(prev);
|
||||
self.set_dir_high(curr);
|
||||
}
|
||||
|
||||
pub fn set_dir(&mut self, dir: Direction, level: embassy_stm32::gpio::Level) {
|
||||
match dir {
|
||||
Direction::North => self.north.set_level(level),
|
||||
Direction::NorthEast => self.north_east.set_level(level),
|
||||
Direction::East => self.east.set_level(level),
|
||||
Direction::SouthEast => self.south_east.set_level(level),
|
||||
Direction::South => self.south.set_level(level),
|
||||
Direction::SouthWest => self.south_west.set_level(level),
|
||||
Direction::West => self.west.set_level(level),
|
||||
Direction::NorthWest => self.north_west.set_level(level),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dir_low(&mut self, dir: Direction) {
|
||||
self.set_dir(dir, embassy_stm32::gpio::Level::Low);
|
||||
}
|
||||
|
||||
pub fn set_dir_high(&mut self, dir: Direction) {
|
||||
self.set_dir(dir, embassy_stm32::gpio::Level::High);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LedPinSet {
|
||||
pub pin_n: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE8>,
|
||||
pub pin_ne: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE9>,
|
||||
pub pin_e: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE10>,
|
||||
pub pin_se: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE11>,
|
||||
pub pin_s: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE12>,
|
||||
pub pin_sw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE13>,
|
||||
pub pin_w: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE14>,
|
||||
pub pin_nw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE15>,
|
||||
}
|
||||
|
||||
impl Leds {
|
||||
pub fn new(pin_set: LedPinSet) -> Self {
|
||||
let led_n = Output::new(
|
||||
pin_set.pin_n,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_ne = Output::new(
|
||||
pin_set.pin_ne,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_e = Output::new(
|
||||
pin_set.pin_e,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_se = Output::new(
|
||||
pin_set.pin_se,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_s = Output::new(
|
||||
pin_set.pin_s,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_sw = Output::new(
|
||||
pin_set.pin_sw,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_w = Output::new(
|
||||
pin_set.pin_w,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
let led_nw = Output::new(
|
||||
pin_set.pin_nw,
|
||||
embassy_stm32::gpio::Level::Low,
|
||||
embassy_stm32::gpio::Speed::Medium,
|
||||
);
|
||||
Self {
|
||||
north: led_n,
|
||||
north_east: led_ne,
|
||||
east: led_e,
|
||||
south_east: led_se,
|
||||
south: led_s,
|
||||
south_west: led_sw,
|
||||
west: led_w,
|
||||
north_west: led_nw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
|
||||
};
|
||||
use satrs::pus::verification::{FailParams, VerificationReportCreator};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
||||
use satrs::spacepackets::ecss::EcssEnumU16;
|
||||
@@ -13,16 +11,12 @@ use satrs_stm32f3_disco_rtic as _;
|
||||
|
||||
use rtic::app;
|
||||
|
||||
use heapless::{mpmc::Q8, Vec};
|
||||
use heapless::Vec;
|
||||
#[allow(unused_imports)]
|
||||
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
|
||||
use rtic_monotonics::systick::ExtU32;
|
||||
use satrs::seq_count::SequenceCountProviderCore;
|
||||
use rtic_monotonics::fugit::{MillisDurationU32, TimerInstantU32};
|
||||
use rtic_monotonics::systick::prelude::*;
|
||||
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
|
||||
use stm32f3xx_hal::dma::dma1;
|
||||
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
|
||||
use stm32f3xx_hal::pac::USART2;
|
||||
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
|
||||
use spacepackets::seq_count::SequenceCountProvider;
|
||||
|
||||
const UART_BAUD: u32 = 115200;
|
||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
||||
@@ -30,33 +24,24 @@ const TX_HANDLER_FREQ_MS: u32 = 20;
|
||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
|
||||
const MAX_TC_LEN: usize = 128;
|
||||
const MAX_TM_LEN: usize = 128;
|
||||
pub const PUS_APID: u16 = 0x02;
|
||||
|
||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
|
||||
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
|
||||
type InstantFugit = TimerInstantU32<1000>;
|
||||
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
|
||||
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
|
||||
pub const PUS_APID: u16 = 0x02;
|
||||
|
||||
// This is the predictable maximum overhead of the COBS encoding scheme.
|
||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
|
||||
const COBS_TC_OVERHEAD: usize = (MAX_TC_LEN + 254 - 1) / 254;
|
||||
const COBS_TM_OVERHEAD: usize = (MAX_TM_LEN + 254 - 1) / 254;
|
||||
const COBS_TC_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TC_LEN);
|
||||
const COBS_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN);
|
||||
|
||||
const TC_BUF_LEN: usize = MAX_TC_LEN + COBS_TC_OVERHEAD;
|
||||
const TM_BUF_LEN: usize = MAX_TC_LEN + COBS_TM_OVERHEAD;
|
||||
|
||||
// This is a static buffer which should ONLY (!) be used as the TX DMA
|
||||
// transfer buffer.
|
||||
static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
|
||||
// This is a static buffer which should ONLY (!) be used as the RX DMA
|
||||
// transfer buffer.
|
||||
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
|
||||
const TC_DMA_BUF_LEN: usize = 512;
|
||||
|
||||
type TmPacket = Vec<u8, MAX_TM_LEN>;
|
||||
type TcPacket = Vec<u8, MAX_TC_LEN>;
|
||||
type TmPacket = heapless::Vec<u8, MAX_TM_LEN>;
|
||||
type TcPacket = heapless::Vec<u8, MAX_TC_LEN>;
|
||||
|
||||
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
|
||||
static TM_REQUESTS: static_cell::ConstStaticCell<heapless::spsc::Queue<TmPacket, 8>> =
|
||||
static_cell::ConstStaticCell::new(heapless::spsc::Queue::new());
|
||||
|
||||
use core::sync::atomic::{AtomicU16, Ordering};
|
||||
|
||||
@@ -74,7 +59,10 @@ impl SeqCountProviderAtomicRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
|
||||
impl SequenceCountProvider for SeqCountProviderAtomicRef {
|
||||
type Raw = u16;
|
||||
const MAX_BIT_WIDTH: usize = 16;
|
||||
|
||||
fn get(&self) -> u16 {
|
||||
self.atomic.load(self.ordering)
|
||||
}
|
||||
@@ -92,8 +80,8 @@ static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
|
||||
SeqCountProviderAtomicRef::new(Ordering::Relaxed);
|
||||
|
||||
pub struct TxIdle {
|
||||
tx: TxType,
|
||||
dma_channel: dma1::C7,
|
||||
//tx: TxType,
|
||||
//dma_channel: dma1::C7,
|
||||
}
|
||||
|
||||
#[derive(Debug, defmt::Format)]
|
||||
@@ -108,49 +96,8 @@ impl From<ByteConversionError> for TmSendError {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
|
||||
if tm_creator.len_written() > MAX_TM_LEN {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
expected: tm_creator.len_written(),
|
||||
found: MAX_TM_LEN,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let mut tm_vec = TmPacket::new();
|
||||
tm_vec
|
||||
.resize(tm_creator.len_written(), 0)
|
||||
.expect("vec resize failed");
|
||||
tm_creator.write_to_bytes(tm_vec.as_mut_slice())?;
|
||||
defmt::info!(
|
||||
"Sending TM[{},{}] with size {}",
|
||||
tm_creator.service(),
|
||||
tm_creator.subservice(),
|
||||
tm_creator.len_written()
|
||||
);
|
||||
TM_REQUESTS
|
||||
.enqueue(tm_vec)
|
||||
.map_err(|_| TmSendError::Queue)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_tm_send_error(error: TmSendError) {
|
||||
defmt::warn!("sending tm failed with error {}", error);
|
||||
}
|
||||
|
||||
pub enum UartTxState {
|
||||
// Wrapped in an option because we need an owned type later.
|
||||
Idle(Option<TxIdle>),
|
||||
// Same as above
|
||||
Transmitting(Option<TxDmaTransferType>),
|
||||
}
|
||||
|
||||
pub struct UartTxShared {
|
||||
last_completed: Option<InstantFugit>,
|
||||
state: UartTxState,
|
||||
}
|
||||
|
||||
pub struct RequestWithToken {
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
request_id: satrs::pus::verification::RequestId,
|
||||
request: Request,
|
||||
}
|
||||
|
||||
@@ -168,148 +115,69 @@ pub enum RequestError {
|
||||
NotEnoughAppData = 4,
|
||||
}
|
||||
|
||||
pub fn convert_pus_tc_to_request(
|
||||
tc: &PusTcReader,
|
||||
verif_reporter: &mut VerificationReportCreator,
|
||||
src_data_buf: &mut [u8],
|
||||
timestamp: &[u8],
|
||||
) -> Result<RequestWithToken, RequestError> {
|
||||
defmt::info!(
|
||||
"Found PUS TC [{},{}] with length {}",
|
||||
tc.service(),
|
||||
tc.subservice(),
|
||||
tc.len_packed()
|
||||
);
|
||||
|
||||
let token = verif_reporter.add_tc(tc);
|
||||
if tc.apid() != PUS_APID {
|
||||
defmt::warn!("Received tc with unknown APID {}", tc.apid());
|
||||
let result = send_tm(
|
||||
verif_reporter
|
||||
.acceptance_failure(
|
||||
src_data_buf,
|
||||
token,
|
||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
||||
0,
|
||||
FailParams::new(timestamp, &EcssEnumU16::new(0), &[]),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
if let Err(e) = result {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
return Err(RequestError::InvalidApid);
|
||||
}
|
||||
let (tm_creator, accepted_token) = verif_reporter
|
||||
.acceptance_success(
|
||||
src_data_buf,
|
||||
token,
|
||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
||||
0,
|
||||
timestamp,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = send_tm(tm_creator) {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
|
||||
if tc.service() == 17 && tc.subservice() == 1 {
|
||||
if tc.subservice() == 1 {
|
||||
return Ok(RequestWithToken {
|
||||
request: Request::Ping,
|
||||
token: accepted_token,
|
||||
});
|
||||
} else {
|
||||
return Err(RequestError::InvalidSubservice);
|
||||
}
|
||||
} else if tc.service() == 8 {
|
||||
if tc.subservice() == 1 {
|
||||
if tc.user_data().len() < 4 {
|
||||
return Err(RequestError::NotEnoughAppData);
|
||||
}
|
||||
let new_freq_ms = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
|
||||
return Ok(RequestWithToken {
|
||||
request: Request::ChangeBlinkFrequency(new_freq_ms),
|
||||
token: accepted_token,
|
||||
});
|
||||
} else {
|
||||
return Err(RequestError::InvalidSubservice);
|
||||
}
|
||||
} else {
|
||||
return Err(RequestError::InvalidService);
|
||||
}
|
||||
}
|
||||
|
||||
#[app(device = stm32f3xx_hal::pac, peripherals = true)]
|
||||
#[app(device = embassy_stm32)]
|
||||
mod app {
|
||||
use super::*;
|
||||
use core::slice::Iter;
|
||||
use rtic_monotonics::systick::Systick;
|
||||
use rtic_monotonics::Monotonic;
|
||||
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
|
||||
use satrs::pus::verification::VerificationReportCreator;
|
||||
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
|
||||
#[allow(unused_imports)]
|
||||
use stm32f3_discovery::leds::Direction;
|
||||
use stm32f3_discovery::leds::Leds;
|
||||
use stm32f3xx_hal::prelude::*;
|
||||
use satrs_stm32f3_disco_rtic::LedPinSet;
|
||||
|
||||
use stm32f3_discovery::switch_hal::OutputSwitch;
|
||||
use stm32f3xx_hal::Switch;
|
||||
#[allow(dead_code)]
|
||||
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
|
||||
systick_monotonic!(Mono, 1000);
|
||||
|
||||
embassy_stm32::bind_interrupts!(struct Irqs {
|
||||
USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>;
|
||||
});
|
||||
|
||||
#[shared]
|
||||
struct Shared {
|
||||
blink_freq: MillisDurationU32,
|
||||
tx_shared: UartTxShared,
|
||||
rx_transfer: Option<RxDmaTransferType>,
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
verif_reporter: VerificationReportCreator,
|
||||
leds: Leds,
|
||||
last_dir: Direction,
|
||||
curr_dir: Iter<'static, Direction>,
|
||||
leds: satrs_stm32f3_disco_rtic::Leds,
|
||||
current_dir: satrs_stm32f3_disco_rtic::Direction,
|
||||
tm_prod: heapless::spsc::Producer<'static, TmPacket>,
|
||||
tm_cons: heapless::spsc::Consumer<'static, TmPacket>,
|
||||
tx: embassy_stm32::usart::UartTx<'static, embassy_stm32::mode::Async>,
|
||||
rx: embassy_stm32::usart::RingBufferedUartRx<'static>,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
let mut rcc = cx.device.RCC.constrain();
|
||||
static DMA_BUF: static_cell::ConstStaticCell<[u8; TC_DMA_BUF_LEN]> = static_cell::ConstStaticCell::new([0; TC_DMA_BUF_LEN]);
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
||||
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
|
||||
Mono::start(cx.core.SYST, 8_000_000);
|
||||
|
||||
let mut flash = cx.device.FLASH.constrain();
|
||||
let clocks = rcc
|
||||
.cfgr
|
||||
.use_hse(8.MHz())
|
||||
.sysclk(8.MHz())
|
||||
.pclk1(8.MHz())
|
||||
.freeze(&mut flash.acr);
|
||||
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery with RTICv2");
|
||||
let led_pin_set = LedPinSet {
|
||||
pin_n: p.PE8,
|
||||
pin_ne: p.PE9,
|
||||
pin_e: p.PE10,
|
||||
pin_se: p.PE11,
|
||||
pin_s: p.PE12,
|
||||
pin_sw: p.PE13,
|
||||
pin_w: p.PE14,
|
||||
pin_nw: p.PE15,
|
||||
};
|
||||
let leds = satrs_stm32f3_disco_rtic::Leds::new(led_pin_set);
|
||||
|
||||
// Set up monotonic timer.
|
||||
//let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
|
||||
let config = embassy_stm32::usart::Config::default();
|
||||
let uart = embassy_stm32::usart::Uart::new(
|
||||
p.USART2, p.PA3, p.PA2, Irqs, p.DMA1_CH7, p.DMA1_CH6, config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
|
||||
let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
|
||||
|
||||
let leds = Leds::new(
|
||||
gpioe.pe8,
|
||||
gpioe.pe9,
|
||||
gpioe.pe10,
|
||||
gpioe.pe11,
|
||||
gpioe.pe12,
|
||||
gpioe.pe13,
|
||||
gpioe.pe14,
|
||||
gpioe.pe15,
|
||||
&mut gpioe.moder,
|
||||
&mut gpioe.otyper,
|
||||
);
|
||||
let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
|
||||
let (tx, rx) = uart.split();
|
||||
let tm_queue = TM_REQUESTS.take();
|
||||
let (tm_prod, tm_cons) = tm_queue.split();
|
||||
//let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
|
||||
// USART2 pins
|
||||
/*
|
||||
let mut pins = (
|
||||
// TX pin: PA2
|
||||
gpioa
|
||||
@@ -320,7 +188,9 @@ mod app {
|
||||
.pa3
|
||||
.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
|
||||
);
|
||||
pins.1.internal_pull_up(&mut gpioa.pupdr, true);
|
||||
*/
|
||||
//pins.1.internal_pull_up(&mut gpioa.pupdr, true);
|
||||
/*
|
||||
let mut usart2 = Serial::new(
|
||||
cx.device.USART2,
|
||||
pins,
|
||||
@@ -331,18 +201,19 @@ mod app {
|
||||
usart2.configure_rx_interrupt(RxEvent::Idle, Switch::On);
|
||||
// This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
|
||||
usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Switch::On);
|
||||
*/
|
||||
|
||||
let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
|
||||
let (mut tx_serial, mut rx_serial) = usart2.split();
|
||||
//let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
|
||||
//let (mut tx_serial, mut rx_serial) = usart2.split();
|
||||
|
||||
// This interrupt is immediately triggered, clear it. It will only be reset
|
||||
// by the hardware when data is received on RX (RXNE event)
|
||||
rx_serial.clear_event(RxEvent::Idle);
|
||||
//rx_serial.clear_event(RxEvent::Idle);
|
||||
// For some reason, this is also immediately triggered..
|
||||
tx_serial.clear_event(TxEvent::TransmissionComplete);
|
||||
let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
|
||||
//tx_serial.clear_event(TxEvent::TransmissionComplete);
|
||||
//let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
|
||||
defmt::info!("Spawning tasks");
|
||||
blink::spawn().unwrap();
|
||||
blinky::spawn().unwrap();
|
||||
serial_tx_handler::spawn().unwrap();
|
||||
|
||||
let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
|
||||
@@ -350,126 +221,59 @@ mod app {
|
||||
(
|
||||
Shared {
|
||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
||||
tx_shared: UartTxShared {
|
||||
last_completed: None,
|
||||
state: UartTxState::Idle(Some(TxIdle {
|
||||
tx: tx_serial,
|
||||
dma_channel: dma1.ch7,
|
||||
})),
|
||||
},
|
||||
rx_transfer: Some(rx_transfer),
|
||||
},
|
||||
Local {
|
||||
verif_reporter,
|
||||
leds,
|
||||
last_dir: Direction::North,
|
||||
curr_dir: Direction::iter(),
|
||||
tm_prod,
|
||||
tm_cons,
|
||||
tx,
|
||||
rx: rx.into_ring_buffered(DMA_BUF.take()),
|
||||
current_dir: satrs_stm32f3_disco_rtic::Direction::North,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
|
||||
async fn blink(mut cx: blink::Context) {
|
||||
let blink::LocalResources {
|
||||
leds,
|
||||
curr_dir,
|
||||
last_dir,
|
||||
..
|
||||
} = cx.local;
|
||||
let mut toggle_leds = |dir: &Direction| {
|
||||
let last_led = leds.for_direction(*last_dir);
|
||||
last_led.off().ok();
|
||||
let led = leds.for_direction(*dir);
|
||||
led.on().ok();
|
||||
*last_dir = *dir;
|
||||
};
|
||||
#[task(local = [leds, current_dir], shared=[blink_freq])]
|
||||
async fn blinky(mut cx: blinky::Context) {
|
||||
loop {
|
||||
match curr_dir.next() {
|
||||
Some(dir) => {
|
||||
toggle_leds(dir);
|
||||
}
|
||||
None => {
|
||||
*curr_dir = Direction::iter();
|
||||
toggle_leds(curr_dir.next().unwrap());
|
||||
}
|
||||
}
|
||||
cx.local.leds.blink_next(cx.local.current_dir);
|
||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
||||
Systick::delay(current_blink_freq).await;
|
||||
Mono::delay(current_blink_freq).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[task(
|
||||
shared = [tx_shared],
|
||||
local = [
|
||||
tm_cons,
|
||||
tx,
|
||||
dma_buf: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN]
|
||||
],
|
||||
shared = [],
|
||||
)]
|
||||
async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
|
||||
async fn serial_tx_handler(cx: serial_tx_handler::Context) {
|
||||
loop {
|
||||
let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
|
||||
if let UartTxState::Idle(_) = tx_shared.state {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
});
|
||||
if is_idle {
|
||||
let last_completed = cx.shared.tx_shared.lock(|shared| shared.last_completed);
|
||||
if let Some(last_completed) = last_completed {
|
||||
let elapsed_ms = (Systick::now() - last_completed).to_millis();
|
||||
if elapsed_ms < MIN_DELAY_BETWEEN_TX_PACKETS_MS {
|
||||
Systick::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check for completion after 1 ms
|
||||
Systick::delay(1.millis()).await;
|
||||
continue;
|
||||
}
|
||||
if let Some(vec) = TM_REQUESTS.dequeue() {
|
||||
cx.shared
|
||||
.tx_shared
|
||||
.lock(|tx_shared| match &mut tx_shared.state {
|
||||
UartTxState::Idle(tx) => {
|
||||
let encoded_len;
|
||||
//debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
|
||||
// Safety: We only copy the data into the TX DMA buffer in this task.
|
||||
// If the DMA is active, another branch will be taken.
|
||||
unsafe {
|
||||
// 0 sentinel value as start marker
|
||||
DMA_TX_BUF[0] = 0;
|
||||
encoded_len =
|
||||
cobs::encode(&vec[0..vec.len()], &mut DMA_TX_BUF[1..]);
|
||||
while let Some(vec) = cx.local.tm_cons.dequeue() {
|
||||
cx.local.dma_buf[0] = 0;
|
||||
let encoded_len = cobs::encode(&vec[0..vec.len()], &mut cx.local.dma_buf[1..]);
|
||||
// Should never panic, we accounted for the overhead.
|
||||
// Write into transfer buffer directly, no need for intermediate
|
||||
// encoding buffer.
|
||||
// 0 end marker
|
||||
DMA_TX_BUF[encoded_len + 1] = 0;
|
||||
}
|
||||
//debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
|
||||
//debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
|
||||
let tx_idle = tx.take().unwrap();
|
||||
// Transfer completion and re-scheduling of new TX transfers will be done
|
||||
// by the IRQ handler.
|
||||
// SAFETY: The DMA is the exclusive writer to the DMA buffer now.
|
||||
let transfer = tx_idle.tx.write_all(
|
||||
unsafe { &DMA_TX_BUF[0..encoded_len + 2] },
|
||||
tx_idle.dma_channel,
|
||||
);
|
||||
tx_shared.state = UartTxState::Transmitting(Some(transfer));
|
||||
// The memory block is automatically returned to the pool when it is dropped.
|
||||
}
|
||||
UartTxState::Transmitting(_) => (),
|
||||
});
|
||||
// Check for completion after 1 ms
|
||||
Systick::delay(1.millis()).await;
|
||||
cx.local.dma_buf[encoded_len + 1] = 0;
|
||||
cx.local.tx.write(&vec[0..encoded_len + 2]).await.unwrap();
|
||||
continue;
|
||||
}
|
||||
// Nothing to do, and we are idle.
|
||||
Systick::delay(TX_HANDLER_FREQ_MS.millis()).await;
|
||||
Mono::delay(TX_HANDLER_FREQ_MS.millis()).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[task(
|
||||
local = [
|
||||
verif_reporter,
|
||||
tm_prod,
|
||||
rx,
|
||||
read_buf: [u8; 128] = [0; 128],
|
||||
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
|
||||
src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
|
||||
timestamp: [u8; 7] = [0; 7],
|
||||
@@ -480,6 +284,18 @@ mod app {
|
||||
mut cx: serial_rx_handler::Context,
|
||||
received_packet: Vec<u8, MAX_TC_LEN>,
|
||||
) {
|
||||
let decoder = cobs::CobsDecoder::new(cx.local.decode_buf);
|
||||
loop {
|
||||
let read_bytes = cx.local.rx.read(cx.local.read_buf).await;
|
||||
match decoder.push(&cx.local.read_buf[0..read_bytes]) {
|
||||
Ok(None) => (),
|
||||
Ok(Some(report)) => {
|
||||
|
||||
}
|
||||
Err(_) => {},
|
||||
}
|
||||
|
||||
}
|
||||
cx.local.timestamp[0] = P_FIELD_BASE;
|
||||
defmt::info!("Received packet with {} bytes", received_packet.len());
|
||||
let decode_buf = cx.local.decode_buf;
|
||||
@@ -496,21 +312,24 @@ mod app {
|
||||
return;
|
||||
}
|
||||
let start_idx = start_idx.unwrap();
|
||||
decoder.push(data)
|
||||
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
|
||||
Ok(len) => {
|
||||
defmt::info!("Decoded packet length: {}", len);
|
||||
Ok(decode_report) => {
|
||||
defmt::info!("Decoded packet length: {}", decode_report.frame_size());
|
||||
let pus_tc = PusTcReader::new(decode_buf);
|
||||
match pus_tc {
|
||||
Ok((tc, _tc_len)) => {
|
||||
Ok(tc) => {
|
||||
match convert_pus_tc_to_request(
|
||||
&tc,
|
||||
cx.local.verif_reporter,
|
||||
cx.local.tm_prod,
|
||||
cx.local.src_data_buf,
|
||||
cx.local.timestamp,
|
||||
) {
|
||||
Ok(request_with_token) => {
|
||||
let started_token = handle_start_verification(
|
||||
request_with_token.token,
|
||||
handle_start_verification(
|
||||
request_with_token.request_id,
|
||||
cx.local.tm_prod,
|
||||
cx.local.verif_reporter,
|
||||
cx.local.src_data_buf,
|
||||
cx.local.timestamp,
|
||||
@@ -518,7 +337,7 @@ mod app {
|
||||
|
||||
match request_with_token.request {
|
||||
Request::Ping => {
|
||||
handle_ping_request(cx.local.timestamp);
|
||||
handle_ping_request(cx.local.tm_prod, cx.local.timestamp);
|
||||
}
|
||||
Request::ChangeBlinkFrequency(new_freq_ms) => {
|
||||
defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
|
||||
@@ -529,7 +348,8 @@ mod app {
|
||||
}
|
||||
}
|
||||
handle_completion_verification(
|
||||
started_token,
|
||||
request_with_token.request_id,
|
||||
cx.local.tm_prod,
|
||||
cx.local.verif_reporter,
|
||||
cx.local.src_data_buf,
|
||||
cx.local.timestamp,
|
||||
@@ -552,7 +372,10 @@ mod app {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_ping_request(timestamp: &[u8]) {
|
||||
fn handle_ping_request(
|
||||
tm_prod: &mut heapless::spsc::Producer<'static, TmPacket>,
|
||||
timestamp: &[u8],
|
||||
) {
|
||||
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
|
||||
let sp_header =
|
||||
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
|
||||
@@ -563,36 +386,36 @@ mod app {
|
||||
.resize(ping_reply.len_written(), 0)
|
||||
.expect("vec resize failed");
|
||||
ping_reply.write_to_bytes(&mut tm_packet).unwrap();
|
||||
if TM_REQUESTS.enqueue(tm_packet).is_err() {
|
||||
if tm_prod.enqueue(tm_packet).is_err() {
|
||||
defmt::warn!("TC queue full");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_start_verification(
|
||||
accepted_token: VerificationToken<TcStateAccepted>,
|
||||
request_id: satrs::pus::verification::RequestId,
|
||||
tm_prod: &mut heapless::spsc::Producer<TmPacket>,
|
||||
verif_reporter: &mut VerificationReportCreator,
|
||||
src_data_buf: &mut [u8],
|
||||
timestamp: &[u8],
|
||||
) -> VerificationToken<TcStateStarted> {
|
||||
let (tm_creator, started_token) = verif_reporter
|
||||
) {
|
||||
let tm_creator = verif_reporter
|
||||
.start_success(
|
||||
src_data_buf,
|
||||
accepted_token,
|
||||
&request_id,
|
||||
SEQ_COUNT_PROVIDER.get(),
|
||||
0,
|
||||
×tamp,
|
||||
timestamp,
|
||||
)
|
||||
.unwrap();
|
||||
let result = send_tm(tm_creator);
|
||||
let result = send_tm(tm_creator, tm_prod);
|
||||
if let Err(e) = result {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
started_token
|
||||
}
|
||||
|
||||
fn handle_completion_verification(
|
||||
started_token: VerificationToken<TcStateStarted>,
|
||||
request_id: satrs::pus::verification::RequestId,
|
||||
tm_prod: &mut heapless::spsc::Producer<TmPacket>,
|
||||
verif_reporter: &mut VerificationReportCreator,
|
||||
src_data_buf: &mut [u8],
|
||||
timestamp: &[u8],
|
||||
@@ -601,20 +424,23 @@ mod app {
|
||||
verif_reporter
|
||||
.completion_success(
|
||||
src_data_buf,
|
||||
started_token,
|
||||
&request_id,
|
||||
SEQ_COUNT_PROVIDER.get(),
|
||||
0,
|
||||
timestamp,
|
||||
)
|
||||
.unwrap(),
|
||||
tm_prod,
|
||||
);
|
||||
if let Err(e) = result {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
|
||||
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
|
||||
/*
|
||||
#[task(binds = DMA1_CH6, shared = [])]
|
||||
fn rx_dma_isr(cx: rx_dma_isr::Context) {
|
||||
/*
|
||||
let mut tc_packet = TcPacket::new();
|
||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
||||
let rx_ref = rx_transfer.as_ref().unwrap();
|
||||
@@ -640,10 +466,14 @@ mod app {
|
||||
);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
||||
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
|
||||
/*
|
||||
#[task(binds = USART2_EXTI26, shared = [])]
|
||||
fn serial_isr(mut cx: serial_isr::Context) {
|
||||
/*
|
||||
cx.shared
|
||||
.tx_shared
|
||||
.lock(|tx_shared| match &mut tx_shared.state {
|
||||
@@ -657,7 +487,7 @@ mod app {
|
||||
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
|
||||
// We cache the last completed time to ensure that there is a minimum delay between consecutive
|
||||
// transferred packets.
|
||||
tx_shared.last_completed = Some(Systick::now());
|
||||
tx_shared.last_completed = Some(Mono::now());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -680,5 +510,112 @@ mod app {
|
||||
*rx_transfer = Some(rx.read_exact(buf, ch));
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn send_tm(
|
||||
tm_creator: PusTmCreator,
|
||||
tm_prod: &mut heapless::spsc::Producer<TmPacket>,
|
||||
) -> Result<(), TmSendError> {
|
||||
if tm_creator.len_written() > MAX_TM_LEN {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
expected: tm_creator.len_written(),
|
||||
found: MAX_TM_LEN,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let mut tm_vec = TmPacket::new();
|
||||
tm_vec
|
||||
.resize(tm_creator.len_written(), 0)
|
||||
.expect("vec resize failed");
|
||||
tm_creator.write_to_bytes(tm_vec.as_mut_slice())?;
|
||||
defmt::info!(
|
||||
"Sending TM[{},{}] with size {}",
|
||||
tm_creator.service(),
|
||||
tm_creator.subservice(),
|
||||
tm_creator.len_written()
|
||||
);
|
||||
tm_prod.enqueue(tm_vec).map_err(|_| TmSendError::Queue)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_tm_send_error(error: TmSendError) {
|
||||
defmt::warn!("sending tm failed with error {}", error);
|
||||
}
|
||||
|
||||
pub fn convert_pus_tc_to_request(
|
||||
tc: &PusTcReader,
|
||||
verif_reporter: &mut VerificationReportCreator,
|
||||
tm_prod: &mut heapless::spsc::Producer<'static, TmPacket>,
|
||||
src_data_buf: &mut [u8],
|
||||
timestamp: &[u8],
|
||||
) -> Result<RequestWithToken, RequestError> {
|
||||
defmt::info!(
|
||||
"Found PUS TC [{},{}] with length {}",
|
||||
tc.service(),
|
||||
tc.subservice(),
|
||||
tc.len_packed()
|
||||
);
|
||||
|
||||
let request_id = verif_reporter.read_request_id(tc);
|
||||
if tc.apid() != PUS_APID {
|
||||
defmt::warn!("Received tc with unknown APID {}", tc.apid());
|
||||
let result = send_tm(
|
||||
verif_reporter
|
||||
.acceptance_failure(
|
||||
src_data_buf,
|
||||
&request_id,
|
||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
||||
0,
|
||||
FailParams::new(timestamp, &EcssEnumU16::new(0), &[]),
|
||||
)
|
||||
.unwrap(),
|
||||
tm_prod,
|
||||
);
|
||||
if let Err(e) = result {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
return Err(RequestError::InvalidApid);
|
||||
}
|
||||
let tm_creator = verif_reporter
|
||||
.acceptance_success(
|
||||
src_data_buf,
|
||||
&request_id,
|
||||
SEQ_COUNT_PROVIDER.get_and_increment(),
|
||||
0,
|
||||
timestamp,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = send_tm(tm_creator, tm_prod) {
|
||||
handle_tm_send_error(e);
|
||||
}
|
||||
|
||||
if tc.service() == 17 && tc.subservice() == 1 {
|
||||
if tc.subservice() == 1 {
|
||||
Ok(RequestWithToken {
|
||||
request_id,
|
||||
request: Request::Ping,
|
||||
})
|
||||
} else {
|
||||
Err(RequestError::InvalidSubservice)
|
||||
}
|
||||
} else if tc.service() == 8 {
|
||||
if tc.subservice() == 1 {
|
||||
if tc.user_data().len() < 4 {
|
||||
return Err(RequestError::NotEnoughAppData);
|
||||
}
|
||||
let new_freq_ms = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
|
||||
Ok(RequestWithToken {
|
||||
request_id,
|
||||
request: Request::ChangeBlinkFrequency(new_freq_ms),
|
||||
})
|
||||
} else {
|
||||
Err(RequestError::InvalidSubservice)
|
||||
}
|
||||
} else {
|
||||
Err(RequestError::InvalidService)
|
||||
}
|
||||
}
|
||||
|
803
embedded-examples/stm32h7-nucleo-rtic/Cargo.lock
generated
803
embedded-examples/stm32h7-nucleo-rtic/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,16 +16,17 @@ harness = false
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = "0.7"
|
||||
defmt = "0.3"
|
||||
defmt = "1"
|
||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
cortex-m-semihosting = "0.5.0"
|
||||
# TODO: Replace with embassy-hal.
|
||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
||||
embedded-alloc = "0.5"
|
||||
embedded-alloc = "0.6"
|
||||
rtic-sync = { version = "1", features = ["defmt-03"] }
|
||||
|
||||
[dependencies.smoltcp]
|
||||
version = "0.11.0"
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
||||
|
||||
@@ -34,17 +35,17 @@ version = "2"
|
||||
features = ["thumbv7-backend"]
|
||||
|
||||
[dependencies.rtic-monotonics]
|
||||
version = "1"
|
||||
version = "2"
|
||||
features = ["cortex-m-systick"]
|
||||
|
||||
[dependencies.satrs]
|
||||
path = "../../satrs"
|
||||
version = "0.2"
|
||||
# version = "0.2"
|
||||
default-features = false
|
||||
features = ["defmt", "heapless"]
|
||||
|
||||
[dev-dependencies]
|
||||
defmt-test = "0.3"
|
||||
defmt-test = "0.4"
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
|
@@ -3,8 +3,7 @@
|
||||
extern crate alloc;
|
||||
|
||||
use rtic::app;
|
||||
use rtic_monotonics::systick::Systick;
|
||||
use rtic_monotonics::Monotonic;
|
||||
use rtic_monotonics::systick::prelude::*;
|
||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
||||
use satrs::static_subpool;
|
||||
// global logger + panicking-behavior + memory layout
|
||||
@@ -13,7 +12,7 @@ use smoltcp::socket::udp::UdpMetadata;
|
||||
use smoltcp::socket::{dhcpv4, udp};
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
use embedded_alloc::Heap;
|
||||
use embedded_alloc::LlffHeap as Heap;
|
||||
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
|
||||
use stm32h7xx_hal::ethernet;
|
||||
@@ -32,6 +31,8 @@ pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_
|
||||
#[global_allocator]
|
||||
static HEAP: Heap = Heap::empty();
|
||||
|
||||
systick_monotonic!(Mono, 1000);
|
||||
|
||||
// We place the memory pool buffers inside the larger AXISRAM.
|
||||
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
|
||||
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
|
||||
@@ -72,7 +73,7 @@ impl Net {
|
||||
let mut iface = Interface::new(
|
||||
config,
|
||||
&mut ethdev,
|
||||
smoltcp::time::Instant::from_millis((Systick::now() - Systick::ZERO).to_millis()),
|
||||
smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()),
|
||||
);
|
||||
// Create sockets
|
||||
let dhcp_socket = dhcpv4::Socket::new();
|
||||
@@ -91,7 +92,7 @@ impl Net {
|
||||
/// Polls on the ethernet interface. You should refer to the smoltcp
|
||||
/// documentation for poll() to understand how to call poll efficiently
|
||||
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
|
||||
let uptime = Systick::now() - Systick::ZERO;
|
||||
let uptime = Mono::now().duration_since_epoch();
|
||||
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
|
||||
|
||||
self.iface.poll(timestamp, &mut self.ethdev, sockets)
|
||||
@@ -203,8 +204,7 @@ mod app {
|
||||
use core::ptr::addr_of_mut;
|
||||
|
||||
use super::*;
|
||||
use rtic_monotonics::systick::fugit::MillisDurationU32;
|
||||
use rtic_monotonics::systick::Systick;
|
||||
use rtic_monotonics::fugit::MillisDurationU32;
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
||||
use stm32h7xx_hal::gpio::{Output, Pin};
|
||||
@@ -256,12 +256,7 @@ mod app {
|
||||
.freeze(pwrcfg, &cx.device.SYSCFG);
|
||||
|
||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
||||
Systick::start(
|
||||
cx.core.SYST,
|
||||
ccdr.clocks.sys_ck().to_Hz(),
|
||||
systick_mono_token,
|
||||
);
|
||||
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz());
|
||||
|
||||
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
|
||||
// good for.
|
||||
@@ -377,24 +372,24 @@ mod app {
|
||||
|
||||
shared_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_SMALL.assume_init_mut() },
|
||||
unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
|
||||
SUBPOOL_SMALL.get_mut().unwrap(),
|
||||
SUBPOOL_SMALL_SIZES.get_mut().unwrap(),
|
||||
SUBPOOL_SMALL_NUM_BLOCKS,
|
||||
true,
|
||||
)
|
||||
.expect("growing heapless memory pool failed");
|
||||
shared_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
|
||||
unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
|
||||
SUBPOOL_MEDIUM.get_mut().unwrap(),
|
||||
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(),
|
||||
SUBPOOL_MEDIUM_NUM_BLOCKS,
|
||||
true,
|
||||
)
|
||||
.expect("growing heapless memory pool failed");
|
||||
shared_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_LARGE.assume_init_mut() },
|
||||
unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
|
||||
SUBPOOL_LARGE.get_mut().unwrap(),
|
||||
SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
|
||||
SUBPOOL_LARGE_NUM_BLOCKS,
|
||||
true,
|
||||
)
|
||||
@@ -403,7 +398,7 @@ mod app {
|
||||
// Set up global allocator. Use AXISRAM for the heap.
|
||||
#[link_section = ".axisram"]
|
||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
||||
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
|
||||
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
||||
|
||||
eth_link_check::spawn().expect("eth link check failed");
|
||||
blinky::spawn().expect("spawning blink task failed");
|
||||
@@ -435,7 +430,7 @@ mod app {
|
||||
leds.led1.toggle();
|
||||
leds.led2.toggle();
|
||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
||||
Systick::delay(current_blink_freq).await;
|
||||
Mono::delay(current_blink_freq).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,7 +452,7 @@ mod app {
|
||||
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
|
||||
defmt::info!("Ethernet link down");
|
||||
}
|
||||
Systick::delay(100.millis()).await;
|
||||
Mono::delay(100.millis()).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +478,7 @@ mod app {
|
||||
cx.local.udp.poll(sockets, pool);
|
||||
})
|
||||
});
|
||||
Systick::delay(40.millis()).await;
|
||||
Mono::delay(40.millis()).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
8
experiments/satrs-gen/Cargo.toml
Normal file
8
experiments/satrs-gen/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "satrs-gen"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
toml = "0.8"
|
||||
heck = "0.5"
|
34
experiments/satrs-gen/components.toml
Normal file
34
experiments/satrs-gen/components.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[apid]
|
||||
Sched = 1
|
||||
GenericPus = 2
|
||||
Acs = 3
|
||||
Cfdp = 4
|
||||
Tmtc = 5
|
||||
Eps = 6
|
||||
|
||||
|
||||
[ids]
|
||||
[ids.Eps]
|
||||
Pcdu = 0
|
||||
Subsystem = 1
|
||||
|
||||
[ids.Tmtc]
|
||||
UdpServer = 0
|
||||
TcpServer = 1
|
||||
|
||||
[ids.GenericPus]
|
||||
PusEventManagement = 0
|
||||
PusRouting = 1
|
||||
PusTest = 2
|
||||
PusAction = 3
|
||||
PusMode = 4
|
||||
PusHk = 5
|
||||
|
||||
[ids.Sched]
|
||||
PusSched = 0
|
||||
|
||||
[ids.Acs]
|
||||
Subsystem = 1
|
||||
Assembly = 2
|
||||
Mgm0 = 3
|
||||
Mgm1 = 4
|
91
experiments/satrs-gen/src/main.rs
Normal file
91
experiments/satrs-gen/src/main.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use heck::{ToShoutySnakeCase, ToSnakeCase};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::{self, File},
|
||||
io::{self, Write},
|
||||
};
|
||||
|
||||
use toml::{Value, map::Map};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
// Read the configuration file
|
||||
let config_str = fs::read_to_string("components.toml").expect("Unable to read file");
|
||||
let config: Value = toml::from_str(&config_str).expect("Unable to parse TOML");
|
||||
|
||||
let mut output = File::create("../satrs-example/src/ids.rs")?;
|
||||
|
||||
generate_rust_code(&config, &mut output);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sort_enum_table(table_map: &Map<String, Value>) -> BTreeMap<u64, &str> {
|
||||
// Collect entries into a BTreeMap to sort them by key
|
||||
let mut sorted_entries: BTreeMap<u64, &str> = BTreeMap::new();
|
||||
|
||||
for (key, value) in table_map {
|
||||
if let Some(value) = value.as_integer() {
|
||||
if !(0..=0x7FF).contains(&value) {
|
||||
panic!("Invalid APID value: {}", value);
|
||||
}
|
||||
sorted_entries.insert(value as u64, key);
|
||||
}
|
||||
}
|
||||
sorted_entries
|
||||
}
|
||||
|
||||
fn generate_rust_code(config: &Value, writer: &mut impl Write) {
|
||||
writeln!(
|
||||
writer,
|
||||
"//! This is an auto-generated configuration module."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(writer, "use satrs::request::UniqueApidTargetId;").unwrap();
|
||||
writeln!(writer).unwrap();
|
||||
|
||||
// Generate the main module
|
||||
writeln!(
|
||||
writer,
|
||||
"#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::EnumIter)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(writer, "pub enum Apid {{").unwrap();
|
||||
|
||||
// Generate constants for the main module
|
||||
if let Some(apid_table) = config.get("apid").and_then(Value::as_table) {
|
||||
let sorted_entries = sort_enum_table(apid_table);
|
||||
// Write the sorted entries to the writer
|
||||
for (value, key) in sorted_entries {
|
||||
writeln!(writer, " {} = {},", key, value).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(writer, "}}").unwrap();
|
||||
|
||||
// Generate ID tables.
|
||||
if let Some(id_tables) = config.get("ids").and_then(Value::as_table) {
|
||||
for (mod_name, table) in id_tables {
|
||||
let mod_name_as_snake = mod_name.to_snake_case();
|
||||
writeln!(writer).unwrap();
|
||||
writeln!(writer, "pub mod {} {{", mod_name_as_snake).unwrap();
|
||||
let sorted_entries = sort_enum_table(table.as_table().unwrap());
|
||||
writeln!(writer, " #[derive(Debug, Copy, Clone, PartialEq, Eq)]").unwrap();
|
||||
writeln!(writer, " pub enum Id {{").unwrap();
|
||||
// Write the sorted entries to the writer
|
||||
for (value, key) in &sorted_entries {
|
||||
writeln!(writer, " {} = {},", key, value).unwrap();
|
||||
}
|
||||
writeln!(writer, " }}").unwrap();
|
||||
writeln!(writer).unwrap();
|
||||
|
||||
for (_value, key) in sorted_entries {
|
||||
let key_shouting = key.to_shouty_snake_case();
|
||||
writeln!(
|
||||
writer,
|
||||
" pub const {}: super::UniqueApidTargetId = super::UniqueApidTargetId::new(super::Apid::{} as u16, Id::{} as u32);",
|
||||
key_shouting, mod_name, key
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
writeln!(writer, "}}").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,20 +3,20 @@
|
||||
Software for space systems oftentimes has different requirements than the software for host
|
||||
systems or servers. Currently, most space systems are considered embedded systems.
|
||||
|
||||
For these systems, the computation power and the available heap are the most important resources
|
||||
which are constrained. This might make completeley heap based memory management schemes which
|
||||
For these systems, the computation power and the available heap are important resources
|
||||
which are also constrained. This might make completeley heap based memory management schemes which
|
||||
are oftentimes used on host and server based systems unfeasable. Still, completely forbidding
|
||||
heap allocations might make software development unnecessarilly difficult, especially in a
|
||||
time where the OBSW might be running on Linux based systems with hundreds of MBs of RAM.
|
||||
|
||||
A useful pattern used commonly in space systems is to limit heap allocations to program
|
||||
A useful pattern commonly used in space systems is to limit heap allocations to program
|
||||
initialization time and avoid frequent run-time allocations. This prevents issues like
|
||||
running out of memory (something even Rust can not protect from) or heap fragmentation on systems
|
||||
without a MMU.
|
||||
|
||||
# Using pre-allocated pool structures
|
||||
|
||||
A huge candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all
|
||||
A candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all
|
||||
candidates where the data size might vary greatly. The regular solution for host systems
|
||||
might be to send around this data as a `Vec<u8>` until it is dropped. `sat-rs` provides
|
||||
another solution to avoid run-time allocations by offering pre-allocated static
|
||||
|
@@ -8,18 +8,19 @@ homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
|
||||
[dependencies]
|
||||
fern = "0.6"
|
||||
fern = "0.7"
|
||||
chrono = "0.4"
|
||||
log = "0.4"
|
||||
crossbeam-channel = "0.5"
|
||||
delegate = "0.10"
|
||||
zerocopy = "0.6"
|
||||
delegate = "0.13"
|
||||
zerocopy = "0.8"
|
||||
csv = "1"
|
||||
num_enum = "0.7"
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
lazy_static = "1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
derive-new = "0.5"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
derive-new = "0.7"
|
||||
cfg-if = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
@@ -35,8 +36,8 @@ version = "0.1.1"
|
||||
path = "../satrs-mib"
|
||||
|
||||
[features]
|
||||
dyn_tmtc = []
|
||||
default = ["dyn_tmtc"]
|
||||
heap_tmtc = []
|
||||
default = ["heap_tmtc"]
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
|
@@ -14,7 +14,7 @@ You can run the application using `cargo run`.
|
||||
|
||||
# Features
|
||||
|
||||
The example has the `dyn_tmtc` feature which is enabled by default. With this feature enabled,
|
||||
The example has the `heap_tmtc` feature which is enabled by default. With this feature enabled,
|
||||
TMTC packets are exchanged using the heap as the backing memory instead of pre-allocated static
|
||||
stores.
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Example client for the sat-rs example application"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
@@ -12,7 +12,7 @@ authors = [
|
||||
{name = "Robin Mueller", email = "robin.mueller.m@gmail.com"},
|
||||
]
|
||||
dependencies = [
|
||||
"tmtccmd~=8.0",
|
||||
"tmtccmd~=8.1",
|
||||
"pydantic~=2.7"
|
||||
]
|
||||
|
||||
|
11
satrs-example/pytmtc/pytmtc/acs/__init__.py
Normal file
11
satrs-example/pytmtc/pytmtc/acs/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from tmtccmd.config import CmdTreeNode
|
||||
|
||||
|
||||
def create_acs_node(mode_node: CmdTreeNode, hk_node: CmdTreeNode) -> CmdTreeNode:
|
||||
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
|
||||
mgm_node = CmdTreeNode("mgms", "MGM devices node")
|
||||
mgm_node.add_child(mode_node)
|
||||
mgm_node.add_child(hk_node)
|
||||
|
||||
acs_node.add_child(mgm_node)
|
||||
return acs_node
|
@@ -39,7 +39,10 @@ class EventU32:
|
||||
|
||||
|
||||
class AcsId(enum.IntEnum):
|
||||
MGM_0 = 0
|
||||
SUBSYSTEM = 1
|
||||
MGM_ASSEMBLY = 2
|
||||
MGM_0 = 3
|
||||
MGM_1 = 4
|
||||
|
||||
|
||||
class AcsHkIds(enum.IntEnum):
|
||||
|
@@ -12,7 +12,7 @@ from pytmtc.pus_tc import create_cmd_definition_tree
|
||||
|
||||
class SatrsConfigHook(HookBase):
|
||||
def __init__(self, json_cfg_path: str):
|
||||
super().__init__(json_cfg_path=json_cfg_path)
|
||||
super().__init__(json_cfg_path)
|
||||
|
||||
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
||||
from tmtccmd.config.com import (
|
||||
|
0
satrs-example/pytmtc/pytmtc/eps/__init__.py
Normal file
0
satrs-example/pytmtc/pytmtc/eps/__init__.py
Normal file
0
satrs-example/pytmtc/pytmtc/eps/pcdu.py
Normal file
0
satrs-example/pytmtc/pytmtc/eps/pcdu.py
Normal file
@@ -4,7 +4,7 @@ from spacepackets.ecss.pus_3_hk import Subservice
|
||||
from spacepackets.ecss import PusTm
|
||||
|
||||
from pytmtc.common import AcsId, Apid
|
||||
from pytmtc.mgms import handle_mgm_hk_report
|
||||
from pytmtc.acs.mgms import handle_mgm_hk_report
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@@ -17,8 +17,9 @@ from tmtccmd.tmtc import (
|
||||
)
|
||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
|
||||
|
||||
from pytmtc.acs import create_acs_node
|
||||
from pytmtc.common import Apid
|
||||
from pytmtc.mgms import create_mgm_cmds
|
||||
from pytmtc.acs.mgms import create_mgm_cmds
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,7 +68,6 @@ class TcHandler(TcHandlerBase):
|
||||
|
||||
|
||||
def create_cmd_definition_tree() -> CmdTreeNode:
|
||||
|
||||
root_node = CmdTreeNode.root_node()
|
||||
|
||||
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
|
||||
@@ -101,15 +101,7 @@ def create_cmd_definition_tree() -> CmdTreeNode:
|
||||
)
|
||||
)
|
||||
root_node.add_child(scheduler_node)
|
||||
|
||||
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
|
||||
mgm_node = CmdTreeNode("mgms", "MGM devices node")
|
||||
mgm_node.add_child(mode_node)
|
||||
mgm_node.add_child(hk_node)
|
||||
|
||||
acs_node.add_child(mgm_node)
|
||||
root_node.add_child(acs_node)
|
||||
|
||||
root_node.add_child(create_acs_node(mode_node, hk_node))
|
||||
return root_node
|
||||
|
||||
|
||||
|
1
satrs-example/src/acs/assembly.rs
Normal file
1
satrs-example/src/acs/assembly.rs
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Write the assembly
|
1
satrs-example/src/acs/ctrl.rs
Normal file
1
satrs-example/src/acs/ctrl.rs
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Write dummy controller
|
@@ -1,7 +1,8 @@
|
||||
use derive_new::new;
|
||||
use satrs::hk::{HkRequest, HkRequestVariant};
|
||||
use satrs::mode_tree::{ModeChild, ModeNode};
|
||||
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
|
||||
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
|
||||
use satrs_example::ids::generic_pus::PUS_MODE;
|
||||
use satrs_example::{DeviceMode, TimestampHelper};
|
||||
use satrs_minisim::acs::lis3mdl::{
|
||||
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
|
||||
@@ -15,15 +16,18 @@ use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use satrs::mode::{
|
||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
|
||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||
ModeRequestHandlerMpscBounded,
|
||||
};
|
||||
use satrs::pus::{EcssTmSender, PusTmVariant};
|
||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||
use satrs_example::config::components::{NO_SENDER, PUS_MODE_SERVICE};
|
||||
use satrs_example::config::components::NO_SENDER;
|
||||
|
||||
use crate::hk::PusHkHelper;
|
||||
use crate::pus::hk::{HkReply, HkReplyVariant};
|
||||
use crate::requests::CompositeRequest;
|
||||
use crate::spi::SpiInterface;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -48,11 +52,6 @@ pub enum TransitionState {
|
||||
Done,
|
||||
}
|
||||
|
||||
pub trait SpiInterface {
|
||||
type Error: Debug;
|
||||
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpiDummyInterface {
|
||||
pub dummy_values: MgmLis3RawValues,
|
||||
@@ -84,7 +83,7 @@ impl SpiInterface for SpiSimInterface {
|
||||
.sim_request_tx
|
||||
.send(SimRequest::new_with_epoch_time(mgm_sensor_request))
|
||||
{
|
||||
log::error!("failed to send MGM LIS3 request: {}", e);
|
||||
log::error!("failed to send MGM LIS3 request: {e}");
|
||||
}
|
||||
match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) {
|
||||
Ok(sim_reply) => {
|
||||
@@ -98,7 +97,7 @@ impl SpiInterface for SpiSimInterface {
|
||||
.copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes());
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("MGM LIS3 SIM reply timeout: {}", e);
|
||||
log::warn!("MGM LIS3 SIM reply timeout: {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -129,12 +128,6 @@ pub struct MgmData {
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
pub struct MpscModeLeafInterface {
|
||||
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||
pub reply_to_pus_tx: mpsc::Sender<GenericMessage<ModeReply>>,
|
||||
pub reply_to_parent_tx: mpsc::SyncSender<GenericMessage<ModeReply>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BufWrapper {
|
||||
tx_buf: [u8; 32],
|
||||
@@ -165,16 +158,15 @@ impl Default for ModeHelpers {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub struct MgmHandlerLis3Mdl<
|
||||
ComInterface: SpiInterface,
|
||||
TmSender: EcssTmSender,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> {
|
||||
id: UniqueApidTargetId,
|
||||
dev_str: &'static str,
|
||||
mode_interface: MpscModeLeafInterface,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
|
||||
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||
switch_helper: SwitchHelper,
|
||||
tm_sender: TmSender,
|
||||
tm_sender: TmTcSender,
|
||||
pub com_interface: ComInterface,
|
||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||
#[new(value = "PusHkHelper::new(id)")]
|
||||
@@ -189,9 +181,8 @@ pub struct MgmHandlerLis3Mdl<
|
||||
|
||||
impl<
|
||||
ComInterface: SpiInterface,
|
||||
TmSender: EcssTmSender,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
|
||||
> MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||
{
|
||||
pub fn periodic_operation(&mut self) {
|
||||
self.stamp_helper.update_from_now();
|
||||
@@ -274,8 +265,9 @@ impl<
|
||||
pub fn handle_mode_requests(&mut self) {
|
||||
loop {
|
||||
// TODO: Only allow one set mode request per cycle?
|
||||
match self.mode_interface.request_rx.try_recv() {
|
||||
Ok(msg) => {
|
||||
match self.mode_node.try_recv_mode_request() {
|
||||
Ok(opt_msg) => {
|
||||
if let Some(msg) = opt_msg {
|
||||
let result = self.handle_mode_request(msg);
|
||||
// TODO: Trigger event?
|
||||
if result.is_err() {
|
||||
@@ -285,14 +277,16 @@ impl<
|
||||
result.err().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e != mpsc::TryRecvError::Empty {
|
||||
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
satrs::queue::GenericReceiveError::Empty => break,
|
||||
satrs::queue::GenericReceiveError::TxDisconnected(e) => {
|
||||
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,9 +358,8 @@ impl<
|
||||
|
||||
impl<
|
||||
ComInterface: SpiInterface,
|
||||
TmSender: EcssTmSender,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
|
||||
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||
{
|
||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||
self.mode_helpers.current
|
||||
@@ -375,9 +368,8 @@ impl<
|
||||
|
||||
impl<
|
||||
ComInterface: SpiInterface,
|
||||
TmSender: EcssTmSender,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
|
||||
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||
{
|
||||
type Error = ModeError;
|
||||
|
||||
@@ -385,6 +377,7 @@ impl<
|
||||
&mut self,
|
||||
requestor: MessageMetadata,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
_forced: bool,
|
||||
) -> Result<(), satrs::mode::ModeError> {
|
||||
log::info!(
|
||||
"{}: transitioning to mode {:?}",
|
||||
@@ -424,7 +417,7 @@ impl<
|
||||
if requestor.sender_id() == NO_SENDER {
|
||||
return Ok(());
|
||||
}
|
||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||
if requestor.sender_id() != PUS_MODE.id() {
|
||||
log::warn!(
|
||||
"can not send back mode reply to sender {:x}",
|
||||
requestor.sender_id()
|
||||
@@ -441,16 +434,15 @@ impl<
|
||||
requestor: MessageMetadata,
|
||||
reply: ModeReply,
|
||||
) -> Result<(), Self::Error> {
|
||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||
if requestor.sender_id() != PUS_MODE.id() {
|
||||
log::warn!(
|
||||
"can not send back mode reply to sender {}",
|
||||
requestor.sender_id()
|
||||
);
|
||||
}
|
||||
self.mode_interface
|
||||
.reply_to_pus_tx
|
||||
.send(GenericMessage::new(requestor, reply))
|
||||
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
|
||||
self.mode_node
|
||||
.send_mode_reply(requestor, reply)
|
||||
.map_err(ModeError::Send)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -463,17 +455,44 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
ComInterface: SpiInterface,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> ModeNode for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||
{
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
self.id.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
ComInterface: SpiInterface,
|
||||
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||
> ModeChild for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||
{
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
||||
|
||||
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
|
||||
self.mode_node.add_message_target(id, reply_sender);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{mpsc, Arc},
|
||||
};
|
||||
|
||||
use satrs::{
|
||||
mode::{ModeReply, ModeRequest},
|
||||
mode_tree::ModeParent,
|
||||
power::SwitchStateBinary,
|
||||
request::{GenericMessage, UniqueApidTargetId},
|
||||
tmtc::PacketAsVec,
|
||||
ComponentId,
|
||||
};
|
||||
use satrs_example::config::components::Apid;
|
||||
use satrs_example::ids::{acs::ASSEMBLY, Apid};
|
||||
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
|
||||
|
||||
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
|
||||
@@ -501,49 +520,88 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct MgmTestbench {
|
||||
pub mode_request_tx: mpsc::Sender<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_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
||||
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
pub handler:
|
||||
MgmHandlerLis3Mdl<TestSpiInterface, mpsc::Sender<PacketAsVec>, TestSwitchHelper>,
|
||||
pub handler: MgmHandlerLis3Mdl<TestSpiInterface, TestSwitchHelper>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MgmAssemblyMock(
|
||||
pub HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
);
|
||||
|
||||
impl ModeNode for MgmAssemblyMock {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
PUS_MODE.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeParent for MgmAssemblyMock {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||
|
||||
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
|
||||
self.0.insert(id, request_sender);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PusMock {
|
||||
pub request_sender_map: HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
}
|
||||
|
||||
impl ModeNode for PusMock {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
PUS_MODE.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeParent for PusMock {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||
|
||||
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
|
||||
self.request_sender_map.insert(id, request_sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl MgmTestbench {
|
||||
pub fn new() -> Self {
|
||||
let (request_tx, request_rx) = mpsc::channel();
|
||||
let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::channel();
|
||||
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_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
|
||||
let mode_interface = MpscModeLeafInterface {
|
||||
request_rx,
|
||||
reply_to_pus_tx: reply_tx_to_pus,
|
||||
reply_to_parent_tx: reply_tx_to_parent,
|
||||
};
|
||||
let id = UniqueApidTargetId::new(Apid::Acs as u16, 1);
|
||||
let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx);
|
||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
||||
let (hk_reply_tx, hk_reply_rx) = mpsc::channel();
|
||||
let (tm_tx, tm_rx) = mpsc::channel::<PacketAsVec>();
|
||||
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
|
||||
let (tm_tx, tm_rx) = mpsc::sync_channel(10);
|
||||
let tm_sender = TmTcSender::Heap(tm_tx);
|
||||
let shared_mgm_set = Arc::default();
|
||||
let mut handler = MgmHandlerLis3Mdl::new(
|
||||
id,
|
||||
"TEST_MGM",
|
||||
mode_node,
|
||||
composite_request_rx,
|
||||
hk_reply_tx,
|
||||
TestSwitchHelper::default(),
|
||||
tm_sender,
|
||||
TestSpiInterface::default(),
|
||||
shared_mgm_set,
|
||||
);
|
||||
handler.add_mode_parent(PUS_MODE.into(), reply_tx_to_pus);
|
||||
handler.add_mode_parent(ASSEMBLY.into(), reply_tx_to_parent);
|
||||
Self {
|
||||
mode_request_tx: request_tx,
|
||||
mode_reply_rx_to_pus: reply_rx_to_pus,
|
||||
mode_reply_rx_to_parent: reply_rx_to_parent,
|
||||
composite_request_tx,
|
||||
handler,
|
||||
tm_rx,
|
||||
hk_reply_rx,
|
||||
handler: MgmHandlerLis3Mdl::new(
|
||||
UniqueApidTargetId::new(Apid::Acs as u16, 1),
|
||||
"TEST_MGM",
|
||||
mode_interface,
|
||||
composite_request_rx,
|
||||
hk_reply_tx,
|
||||
TestSwitchHelper::default(),
|
||||
tm_tx,
|
||||
TestSpiInterface::default(),
|
||||
shared_mgm_set,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,8 +631,11 @@ mod tests {
|
||||
testbench
|
||||
.mode_request_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
|
||||
MessageMetadata::new(0, PUS_MODE.id()),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
.expect("failed to send mode request");
|
||||
testbench.handler.periodic_operation();
|
||||
@@ -631,8 +692,11 @@ mod tests {
|
||||
testbench
|
||||
.mode_request_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
|
||||
MessageMetadata::new(0, PUS_MODE.id()),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
.expect("failed to send mode request");
|
||||
testbench.handler.periodic_operation();
|
||||
|
@@ -1 +1,4 @@
|
||||
pub mod assembly;
|
||||
pub mod ctrl;
|
||||
pub mod mgm;
|
||||
pub mod subsystem;
|
||||
|
1
satrs-example/src/acs/subsystem.rs
Normal file
1
satrs-example/src/acs/subsystem.rs
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Write subsystem
|
@@ -1,10 +1,7 @@
|
||||
use satrs::pus::verification::RequestId;
|
||||
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
||||
use satrs::spacepackets::ecss::tm::PusTmReader;
|
||||
use satrs::{
|
||||
spacepackets::ecss::{PusPacket, WritablePusPacket},
|
||||
spacepackets::SpHeader,
|
||||
};
|
||||
use satrs::{spacepackets::ecss::PusPacket, spacepackets::SpHeader};
|
||||
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
||||
use std::net::{IpAddr, SocketAddr, UdpSocket};
|
||||
use std::time::Duration;
|
||||
@@ -29,7 +26,7 @@ fn main() {
|
||||
let res = client.recv(&mut buf);
|
||||
match res {
|
||||
Ok(_len) => {
|
||||
let (pus_tm, size) = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
|
||||
let pus_tm = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
|
||||
if pus_tm.service() == 17 && pus_tm.subservice() == 2 {
|
||||
println!("Received PUS Ping Reply TM[17,2]")
|
||||
} else if pus_tm.service() == 1 {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::thread;
|
||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
|
||||
|
||||
trait FieldDataProvider: Send {
|
||||
fn get_data(&self) -> &[u8];
|
||||
@@ -35,7 +35,7 @@ struct ExampleMgmSet {
|
||||
temperature: u16,
|
||||
}
|
||||
|
||||
#[derive(FromBytes, AsBytes, Unaligned)]
|
||||
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
|
||||
#[repr(C)]
|
||||
struct ExampleMgmSetZc {
|
||||
mgm_vec: [u8; 12],
|
||||
|
@@ -43,14 +43,14 @@ pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<Severi
|
||||
lazy_static! {
|
||||
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
||||
let mut set = HashSet::new();
|
||||
for id in components::Apid::iter() {
|
||||
for id in crate::ids::Apid::iter() {
|
||||
set.insert(PacketId::new(PacketType::Tc, true, id as u16));
|
||||
}
|
||||
set
|
||||
};
|
||||
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
||||
let mut set = HashSet::new();
|
||||
for id in components::Apid::iter() {
|
||||
for id in crate::ids::Apid::iter() {
|
||||
set.insert(id as u16);
|
||||
}
|
||||
set
|
||||
@@ -122,68 +122,8 @@ pub mod mode_err {
|
||||
}
|
||||
|
||||
pub mod components {
|
||||
use satrs::{request::UniqueApidTargetId, ComponentId};
|
||||
use strum::EnumIter;
|
||||
use satrs::ComponentId;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
|
||||
pub enum Apid {
|
||||
Sched = 1,
|
||||
GenericPus = 2,
|
||||
Acs = 3,
|
||||
Cfdp = 4,
|
||||
Tmtc = 5,
|
||||
Eps = 6,
|
||||
}
|
||||
|
||||
// Component IDs for components with the PUS APID.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PusId {
|
||||
PusEventManagement = 0,
|
||||
PusRouting = 1,
|
||||
PusTest = 2,
|
||||
PusAction = 3,
|
||||
PusMode = 4,
|
||||
PusHk = 5,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AcsId {
|
||||
Mgm0 = 0,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum EpsId {
|
||||
Pcdu = 0,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum TmtcId {
|
||||
UdpServer = 0,
|
||||
TcpServer = 1,
|
||||
}
|
||||
|
||||
pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
|
||||
pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
|
||||
pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
|
||||
pub const PUS_TEST_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
|
||||
pub const PUS_MODE_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
|
||||
pub const PUS_HK_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
|
||||
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Sched as u16, 0);
|
||||
pub const MGM_HANDLER_0: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
|
||||
pub const PCDU_HANDLER: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32);
|
||||
pub const UDP_SERVER: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
|
||||
pub const TCP_SERVER: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
|
||||
pub const NO_SENDER: ComponentId = ComponentId::MAX;
|
||||
}
|
||||
|
||||
|
@@ -88,6 +88,7 @@ impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(new)]
|
||||
pub struct SwitchRequestInfo {
|
||||
pub requestor_info: MessageMetadata,
|
||||
@@ -99,6 +100,7 @@ pub struct SwitchRequestInfo {
|
||||
pub struct TestSwitchHelper {
|
||||
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
|
||||
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
|
||||
#[allow(dead_code)]
|
||||
pub switch_delay_request_count: u32,
|
||||
pub next_switch_delay: Duration,
|
||||
pub switch_map: RefCell<SwitchMapWrapper>,
|
||||
|
@@ -8,15 +8,20 @@ use derive_new::new;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use satrs::{
|
||||
hk::{HkRequest, HkRequestVariant},
|
||||
mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler},
|
||||
mode::{
|
||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||
ModeRequestHandlerMpscBounded,
|
||||
},
|
||||
mode_tree::{ModeChild, ModeNode},
|
||||
power::SwitchRequest,
|
||||
pus::{EcssTmSender, PusTmVariant},
|
||||
queue::{GenericSendError, GenericTargetedMessagingError},
|
||||
queue::GenericSendError,
|
||||
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
|
||||
spacepackets::ByteConversionError,
|
||||
};
|
||||
use satrs_example::{
|
||||
config::components::{NO_SENDER, PUS_MODE_SERVICE},
|
||||
config::components::NO_SENDER,
|
||||
ids::{eps::PCDU, generic_pus::PUS_MODE},
|
||||
DeviceMode, TimestampHelper,
|
||||
};
|
||||
use satrs_minisim::{
|
||||
@@ -28,10 +33,10 @@ use satrs_minisim::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
acs::mgm::MpscModeLeafInterface,
|
||||
hk::PusHkHelper,
|
||||
pus::hk::{HkReply, HkReplyVariant},
|
||||
requests::CompositeRequest,
|
||||
tmtc::sender::TmTcSender,
|
||||
};
|
||||
|
||||
pub trait SerialInterface {
|
||||
@@ -200,14 +205,14 @@ pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
|
||||
/// Example PCDU device handler.
|
||||
#[derive(new)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
|
||||
pub struct PcduHandler<ComInterface: SerialInterface> {
|
||||
id: UniqueApidTargetId,
|
||||
dev_str: &'static str,
|
||||
mode_interface: MpscModeLeafInterface,
|
||||
mode_node: ModeRequestHandlerMpscBounded,
|
||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
|
||||
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||
tm_sender: TmSender,
|
||||
tm_sender: TmTcSender,
|
||||
pub com_interface: ComInterface,
|
||||
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||
#[new(value = "PusHkHelper::new(id)")]
|
||||
@@ -220,7 +225,7 @@ pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
|
||||
tm_buf: [u8; 256],
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInterface, TmSender> {
|
||||
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||
pub fn periodic_operation(&mut self, op_code: OpCode) {
|
||||
match op_code {
|
||||
OpCode::RegularOp => {
|
||||
@@ -324,8 +329,9 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInter
|
||||
pub fn handle_mode_requests(&mut self) {
|
||||
loop {
|
||||
// TODO: Only allow one set mode request per cycle?
|
||||
match self.mode_interface.request_rx.try_recv() {
|
||||
Ok(msg) => {
|
||||
match self.mode_node.try_recv_mode_request() {
|
||||
Ok(opt_msg) => {
|
||||
if let Some(msg) = opt_msg {
|
||||
let result = self.handle_mode_request(msg);
|
||||
// TODO: Trigger event?
|
||||
if result.is_err() {
|
||||
@@ -335,14 +341,18 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInter
|
||||
result.err().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e != mpsc::TryRecvError::Empty {
|
||||
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
satrs::queue::GenericReceiveError::Empty => {
|
||||
break;
|
||||
}
|
||||
satrs::queue::GenericReceiveError::TxDisconnected(_) => {
|
||||
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,27 +401,24 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInter
|
||||
}
|
||||
}
|
||||
}) {
|
||||
log::warn!("receiving PCDU replies failed: {:?}", e);
|
||||
log::warn!("receiving PCDU replies failed: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeProvider
|
||||
for PcduHandler<ComInterface, TmSender>
|
||||
{
|
||||
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> {
|
||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||
self.mode_and_submode
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
|
||||
for PcduHandler<ComInterface, TmSender>
|
||||
{
|
||||
impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterface> {
|
||||
type Error = ModeError;
|
||||
fn start_transition(
|
||||
&mut self,
|
||||
requestor: MessageMetadata,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
_forced: bool,
|
||||
) -> Result<(), satrs::mode::ModeError> {
|
||||
log::info!(
|
||||
"{}: transitioning to mode {:?}",
|
||||
@@ -443,7 +450,7 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
|
||||
if requestor.sender_id() == NO_SENDER {
|
||||
return Ok(());
|
||||
}
|
||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||
if requestor.sender_id() != PUS_MODE.id() {
|
||||
log::warn!(
|
||||
"can not send back mode reply to sender {}",
|
||||
requestor.sender_id()
|
||||
@@ -460,16 +467,15 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
|
||||
requestor: MessageMetadata,
|
||||
reply: ModeReply,
|
||||
) -> Result<(), Self::Error> {
|
||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||
if requestor.sender_id() != PUS_MODE.id() {
|
||||
log::warn!(
|
||||
"can not send back mode reply to sender {}",
|
||||
requestor.sender_id()
|
||||
);
|
||||
}
|
||||
self.mode_interface
|
||||
.reply_to_pus_tx
|
||||
.send(GenericMessage::new(requestor, reply))
|
||||
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
|
||||
self.mode_node
|
||||
.send_mode_reply(requestor, reply)
|
||||
.map_err(|_| GenericSendError::RxDisconnected)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -482,6 +488,20 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
|
||||
fn id(&self) -> satrs::ComponentId {
|
||||
PCDU.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
|
||||
|
||||
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
|
||||
self.mode_node.add_message_target(id, reply_sender);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::mpsc;
|
||||
@@ -489,7 +509,7 @@ mod tests {
|
||||
use satrs::{
|
||||
mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
|
||||
};
|
||||
use satrs_example::config::components::{Apid, MGM_HANDLER_0};
|
||||
use satrs_example::ids::{self, Apid};
|
||||
use satrs_minisim::eps::SwitchMapBinary;
|
||||
|
||||
use super::*;
|
||||
@@ -530,31 +550,40 @@ mod tests {
|
||||
}
|
||||
|
||||
pub struct PcduTestbench {
|
||||
pub mode_request_tx: mpsc::Sender<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_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
||||
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
|
||||
pub handler: PcduHandler<SerialInterfaceTest, mpsc::Sender<PacketAsVec>>,
|
||||
pub handler: PcduHandler<SerialInterfaceTest>,
|
||||
}
|
||||
|
||||
impl PcduTestbench {
|
||||
pub fn new() -> Self {
|
||||
let (mode_request_tx, mode_request_rx) = mpsc::channel();
|
||||
let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::channel();
|
||||
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_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
|
||||
let mode_interface = MpscModeLeafInterface {
|
||||
request_rx: mode_request_rx,
|
||||
reply_to_pus_tx: mode_reply_tx_to_pus,
|
||||
reply_to_parent_tx: mode_reply_tx_to_parent,
|
||||
};
|
||||
let mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx);
|
||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
||||
let (hk_reply_tx, hk_reply_rx) = mpsc::channel();
|
||||
let (tm_tx, tm_rx) = mpsc::channel::<PacketAsVec>();
|
||||
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
|
||||
let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
|
||||
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
|
||||
let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
|
||||
let mut handler = PcduHandler::new(
|
||||
UniqueApidTargetId::new(Apid::Eps as u16, 0),
|
||||
"TEST_PCDU",
|
||||
mode_node,
|
||||
composite_request_rx,
|
||||
hk_reply_tx,
|
||||
switch_reqest_rx,
|
||||
TmTcSender::Heap(tm_tx.clone()),
|
||||
SerialInterfaceTest::default(),
|
||||
shared_switch_map,
|
||||
);
|
||||
handler.add_mode_parent(ids::eps::SUBSYSTEM.into(), mode_reply_tx_to_parent);
|
||||
handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus);
|
||||
Self {
|
||||
mode_request_tx,
|
||||
mode_reply_rx_to_pus,
|
||||
@@ -563,17 +592,7 @@ mod tests {
|
||||
hk_reply_rx,
|
||||
tm_rx,
|
||||
switch_request_tx,
|
||||
handler: PcduHandler::new(
|
||||
UniqueApidTargetId::new(Apid::Eps as u16, 0),
|
||||
"TEST_PCDU",
|
||||
mode_interface,
|
||||
composite_request_rx,
|
||||
hk_reply_tx,
|
||||
switch_reqest_rx,
|
||||
tm_tx,
|
||||
SerialInterfaceTest::default(),
|
||||
shared_switch_map,
|
||||
),
|
||||
handler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,8 +678,11 @@ mod tests {
|
||||
testbench
|
||||
.mode_request_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
|
||||
MessageMetadata::new(0, PUS_MODE.id()),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
.expect("failed to send mode request");
|
||||
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
|
||||
@@ -691,14 +713,17 @@ mod tests {
|
||||
testbench
|
||||
.mode_request_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
|
||||
MessageMetadata::new(0, PUS_MODE.id()),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
.expect("failed to send mode request");
|
||||
testbench
|
||||
.switch_request_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, MGM_HANDLER_0.id()),
|
||||
MessageMetadata::new(0, ids::acs::MGM0.id()),
|
||||
SwitchRequest::new(0, SwitchStateBinary::On),
|
||||
))
|
||||
.expect("failed to send switch request");
|
||||
|
@@ -2,7 +2,7 @@ use std::sync::mpsc::{self};
|
||||
|
||||
use crate::pus::create_verification_reporter;
|
||||
use satrs::event_man::{EventMessageU32, EventRoutingError};
|
||||
use satrs::pus::event::EventTmHookProvider;
|
||||
use satrs::pus::event::EventTmHook;
|
||||
use satrs::pus::verification::VerificationReporter;
|
||||
use satrs::pus::EcssTmSender;
|
||||
use satrs::request::UniqueApidTargetId;
|
||||
@@ -16,7 +16,7 @@ use satrs::{
|
||||
},
|
||||
spacepackets::time::cds::CdsTime,
|
||||
};
|
||||
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
|
||||
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT;
|
||||
|
||||
use crate::update_time;
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct EventApidSetter {
|
||||
pub next_apid: u16,
|
||||
}
|
||||
|
||||
impl EventTmHookProvider for EventApidSetter {
|
||||
impl EventTmHook for EventApidSetter {
|
||||
fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
|
||||
tm.set_apid(self.next_apid);
|
||||
}
|
||||
@@ -219,7 +219,7 @@ impl<TmSender: EcssTmSender> EventHandler<TmSender> {
|
||||
mod tests {
|
||||
use satrs::{
|
||||
events::EventU32,
|
||||
pus::verification::VerificationReporterCfg,
|
||||
pus::verification::VerificationReporterConfig,
|
||||
spacepackets::{
|
||||
ecss::{tm::PusTmReader, PusPacket},
|
||||
CcsdsPacket,
|
||||
@@ -244,7 +244,7 @@ mod tests {
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(10);
|
||||
let (_event_req_tx, event_req_rx) = mpsc::sync_channel(10);
|
||||
let (tm_sender, tm_receiver) = mpsc::channel();
|
||||
let verif_reporter_cfg = VerificationReporterCfg::new(0x05, 2, 2, 128).unwrap();
|
||||
let verif_reporter_cfg = VerificationReporterConfig::new(0x05, 2, 2, 128).unwrap();
|
||||
let verif_reporter =
|
||||
VerificationReporter::new(PUS_EVENT_MANAGEMENT.id(), &verif_reporter_cfg);
|
||||
let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
|
||||
@@ -281,9 +281,7 @@ mod tests {
|
||||
.try_recv()
|
||||
.expect("failed to receive TM packet");
|
||||
assert_eq!(tm_packet.sender_id, PUS_EVENT_MANAGEMENT.id());
|
||||
let tm_reader = PusTmReader::new(&tm_packet.packet, 7)
|
||||
.expect("failed to create TM reader")
|
||||
.0;
|
||||
let tm_reader = PusTmReader::new(&tm_packet.packet, 7).expect("failed to create TM reader");
|
||||
assert_eq!(tm_reader.apid(), TEST_CREATOR_ID.apid);
|
||||
assert_eq!(tm_reader.user_data().len(), 4);
|
||||
let event_read_back = EventU32::from_be_bytes(tm_reader.user_data().try_into().unwrap());
|
||||
|
94
satrs-example/src/ids.rs
Normal file
94
satrs-example/src/ids.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! This is an auto-generated configuration module.
|
||||
use satrs::request::UniqueApidTargetId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::EnumIter)]
|
||||
pub enum Apid {
|
||||
Sched = 1,
|
||||
GenericPus = 2,
|
||||
Acs = 3,
|
||||
Cfdp = 4,
|
||||
Tmtc = 5,
|
||||
Eps = 6,
|
||||
}
|
||||
|
||||
pub mod acs {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Id {
|
||||
Subsystem = 1,
|
||||
Assembly = 2,
|
||||
Mgm0 = 3,
|
||||
Mgm1 = 4,
|
||||
}
|
||||
|
||||
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Acs as u16, Id::Subsystem as u32);
|
||||
pub const ASSEMBLY: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Acs as u16, Id::Assembly as u32);
|
||||
pub const MGM0: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Acs as u16, Id::Mgm0 as u32);
|
||||
pub const MGM1: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Acs as u16, Id::Mgm1 as u32);
|
||||
}
|
||||
|
||||
pub mod eps {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Id {
|
||||
Pcdu = 0,
|
||||
Subsystem = 1,
|
||||
}
|
||||
|
||||
pub const PCDU: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Eps as u16, Id::Pcdu as u32);
|
||||
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Eps as u16, Id::Subsystem as u32);
|
||||
}
|
||||
|
||||
pub mod generic_pus {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
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 as u16,
|
||||
Id::PusEventManagement as u32,
|
||||
);
|
||||
pub const PUS_ROUTING: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::GenericPus as u16, Id::PusRouting as u32);
|
||||
pub const PUS_TEST: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::GenericPus as u16, Id::PusTest as u32);
|
||||
pub const PUS_ACTION: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::GenericPus as u16, Id::PusAction as u32);
|
||||
pub const PUS_MODE: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::GenericPus as u16, Id::PusMode as u32);
|
||||
pub const PUS_HK: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::GenericPus as u16, Id::PusHk as u32);
|
||||
}
|
||||
|
||||
pub mod sched {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Id {
|
||||
PusSched = 0,
|
||||
}
|
||||
|
||||
pub const PUS_SCHED: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Sched as u16, Id::PusSched as u32);
|
||||
}
|
||||
|
||||
pub mod tmtc {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Id {
|
||||
UdpServer = 0,
|
||||
TcpServer = 1,
|
||||
}
|
||||
|
||||
pub const UDP_SERVER: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Tmtc as u16, Id::UdpServer as u32);
|
||||
pub const TCP_SERVER: super::UniqueApidTargetId =
|
||||
super::UniqueApidTargetId::new(super::Apid::Tmtc as u16, Id::TcpServer as u32);
|
||||
}
|
@@ -24,7 +24,7 @@ pub fn create_sim_client(sim_request_rx: mpsc::Receiver<SimRequest>) -> Option<S
|
||||
return Some(sim_client);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("sim client creation error: {}", e);
|
||||
log::warn!("sim client creation error: {e}");
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -116,7 +116,7 @@ impl SimClientUdp {
|
||||
.udp_client
|
||||
.send_to(request_json.as_bytes(), self.simulator_addr)
|
||||
{
|
||||
log::error!("error sending data to UDP SIM server: {}", e);
|
||||
log::error!("error sending data to UDP SIM server: {e}");
|
||||
break;
|
||||
} else {
|
||||
no_sim_requests_handled = false;
|
||||
@@ -151,7 +151,7 @@ impl SimClientUdp {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to deserialize SIM reply: {}", e);
|
||||
log::warn!("failed to deserialize SIM reply: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl SimClientUdp {
|
||||
{
|
||||
break;
|
||||
}
|
||||
log::error!("error receiving data from UDP SIM server: {}", e);
|
||||
log::error!("error receiving data from UDP SIM server: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,20 @@
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs::tmtc::StoreAndSendError;
|
||||
use satrs::{
|
||||
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
||||
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
|
||||
spacepackets::{CcsdsPacket, PacketId},
|
||||
tmtc::{PacketSenderRaw, PacketSource},
|
||||
tmtc::PacketSource,
|
||||
};
|
||||
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ConnectionFinishedHandler {}
|
||||
|
||||
@@ -30,7 +31,7 @@ impl SpacePacketValidator for SimplePacketValidator {
|
||||
if self.valid_ids.contains(&sp_header.packet_id()) {
|
||||
return SpValidity::Valid;
|
||||
}
|
||||
log::warn!("ignoring space packet with header {:?}", sp_header);
|
||||
log::warn!("ignoring space packet with header {sp_header:?}");
|
||||
// We could perform a CRC check.. but lets keep this simple and assume that TCP ensures
|
||||
// data integrity.
|
||||
SpValidity::Skip
|
||||
@@ -111,31 +112,23 @@ pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
|
||||
SendError,
|
||||
>;
|
||||
|
||||
pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
|
||||
pub TcpServer<TcSender, SendError>,
|
||||
PhantomData<SendError>,
|
||||
);
|
||||
pub struct TcpTask(pub TcpServer<TmTcSender, StoreAndSendError>);
|
||||
|
||||
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
||||
TcpTask<TcSender, SendError>
|
||||
{
|
||||
impl TcpTask {
|
||||
pub fn new(
|
||||
cfg: ServerConfig,
|
||||
tm_source: SyncTcpTmSource,
|
||||
tc_sender: TcSender,
|
||||
tc_sender: TmTcSender,
|
||||
valid_ids: HashSet<PacketId>,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
Ok(Self(
|
||||
TcpSpacepacketsServer::new(
|
||||
Ok(Self(TcpSpacepacketsServer::new(
|
||||
cfg,
|
||||
tm_source,
|
||||
tc_sender,
|
||||
SimplePacketValidator { valid_ids },
|
||||
ConnectionFinishedHandler::default(),
|
||||
None,
|
||||
)?,
|
||||
PhantomData,
|
||||
))
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) {
|
||||
|
@@ -1,19 +1,21 @@
|
||||
use core::fmt::Debug;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::sync::mpsc;
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs::pus::HandlingStatus;
|
||||
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
|
||||
use satrs::tmtc::{PacketAsVec, PacketInPool, StoreAndSendError};
|
||||
use satrs::{
|
||||
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
||||
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
|
||||
};
|
||||
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
pub trait UdpTmHandler {
|
||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct StaticUdpTmHandler {
|
||||
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
||||
pub tm_store: SharedStaticMemoryPool,
|
||||
@@ -65,21 +67,12 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UdpTmtcServer<
|
||||
TcSender: PacketSenderRaw<Error = SendError>,
|
||||
TmHandler: UdpTmHandler,
|
||||
SendError,
|
||||
> {
|
||||
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
|
||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> {
|
||||
pub udp_tc_server: UdpTcServer<TmTcSender, StoreAndSendError>,
|
||||
pub tm_handler: TmHandler,
|
||||
}
|
||||
|
||||
impl<
|
||||
TcSender: PacketSenderRaw<Error = SendError>,
|
||||
TmHandler: UdpTmHandler,
|
||||
SendError: Debug + 'static,
|
||||
> UdpTmtcServer<TcSender, TmHandler, SendError>
|
||||
{
|
||||
impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
loop {
|
||||
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||
@@ -115,7 +108,6 @@ impl<
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::VecDeque,
|
||||
net::IpAddr,
|
||||
sync::{Arc, Mutex},
|
||||
@@ -126,30 +118,17 @@ mod tests {
|
||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||
SpHeader,
|
||||
},
|
||||
tmtc::PacketSenderRaw,
|
||||
ComponentId,
|
||||
};
|
||||
use satrs_example::config::{components, OBSW_SERVER_ADDR};
|
||||
use satrs_example::config::OBSW_SERVER_ADDR;
|
||||
use satrs_example::ids;
|
||||
|
||||
use crate::tmtc::sender::{MockSender, TmTcSender};
|
||||
|
||||
use super::*;
|
||||
|
||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TestSender {
|
||||
tc_vec: RefCell<VecDeque<PacketAsVec>>,
|
||||
}
|
||||
|
||||
impl PacketSenderRaw for TestSender {
|
||||
type Error = ();
|
||||
|
||||
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
let mut mut_queue = self.tc_vec.borrow_mut();
|
||||
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct TestTmHandler {
|
||||
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
|
||||
@@ -164,8 +143,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||
let test_receiver = TestSender::default();
|
||||
// let tc_queue = test_receiver.tc_vec.clone();
|
||||
let test_receiver = TmTcSender::Mock(MockSender::default());
|
||||
let udp_tc_server =
|
||||
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||
let tm_handler = TestTmHandler::default();
|
||||
@@ -175,7 +153,13 @@ mod tests {
|
||||
tm_handler,
|
||||
};
|
||||
udp_dyn_server.periodic_operation();
|
||||
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
|
||||
let queue = udp_dyn_server
|
||||
.udp_tc_server
|
||||
.tc_sender
|
||||
.get_mock_sender()
|
||||
.unwrap()
|
||||
.0
|
||||
.borrow();
|
||||
assert!(queue.is_empty());
|
||||
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
||||
}
|
||||
@@ -183,8 +167,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_transactions() {
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
|
||||
let test_receiver = TestSender::default();
|
||||
// let tc_queue = test_receiver.tc_vec.clone();
|
||||
let test_receiver = TmTcSender::Mock(MockSender::default());
|
||||
let udp_tc_server =
|
||||
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
||||
@@ -194,7 +177,7 @@ mod tests {
|
||||
udp_tc_server,
|
||||
tm_handler,
|
||||
};
|
||||
let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
|
||||
let sph = SpHeader::new_for_unseg_tc(ids::Apid::GenericPus as u16, 0, 0);
|
||||
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
|
||||
.to_vec()
|
||||
.unwrap();
|
||||
@@ -204,7 +187,13 @@ mod tests {
|
||||
client.send_to(&ping_tc, server_addr).unwrap();
|
||||
udp_dyn_server.periodic_operation();
|
||||
{
|
||||
let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut();
|
||||
let mut queue = udp_dyn_server
|
||||
.udp_tc_server
|
||||
.tc_sender
|
||||
.get_mock_sender()
|
||||
.unwrap()
|
||||
.0
|
||||
.borrow_mut();
|
||||
assert!(!queue.is_empty());
|
||||
let packet_with_sender = queue.pop_front().unwrap();
|
||||
assert_eq!(packet_with_sender.packet, ping_tc);
|
||||
@@ -219,7 +208,13 @@ mod tests {
|
||||
assert_eq!(received_addr, client_addr);
|
||||
}
|
||||
udp_dyn_server.periodic_operation();
|
||||
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
|
||||
let queue = udp_dyn_server
|
||||
.udp_tc_server
|
||||
.tc_sender
|
||||
.get_mock_sender()
|
||||
.unwrap()
|
||||
.0
|
||||
.borrow();
|
||||
assert!(queue.is_empty());
|
||||
drop(queue);
|
||||
// Still tries to send to the same client.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
|
||||
|
||||
pub mod config;
|
||||
pub mod ids;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum DeviceMode {
|
||||
|
@@ -1,3 +1,77 @@
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper};
|
||||
use eps::{
|
||||
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
|
||||
PowerSwitchHelper,
|
||||
};
|
||||
use events::EventHandler;
|
||||
use interface::{
|
||||
sim_client_udp::create_sim_client,
|
||||
tcp::{SyncTcpTmSource, TcpTask},
|
||||
udp::UdpTmtcServer,
|
||||
};
|
||||
use log::info;
|
||||
use logger::setup_logger;
|
||||
use pus::{
|
||||
action::create_action_service,
|
||||
event::create_event_service,
|
||||
hk::create_hk_service,
|
||||
mode::create_mode_service,
|
||||
scheduler::{create_scheduler_service, TcReleaser},
|
||||
stack::PusStack,
|
||||
test::create_test_service,
|
||||
PusTcDistributor, PusTcMpscRouter,
|
||||
};
|
||||
use requests::GenericRequestRouter;
|
||||
use satrs::{
|
||||
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
||||
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
|
||||
mode_tree::connect_mode_nodes,
|
||||
pus::{event_man::EventRequestWithToken, EcssTcCacher, HandlingStatus},
|
||||
request::{GenericMessage, MessageMetadata},
|
||||
spacepackets::time::{cds::CdsTime, TimeWriter},
|
||||
};
|
||||
use satrs_example::{
|
||||
config::{
|
||||
components::NO_SENDER,
|
||||
pool::create_sched_tc_pool,
|
||||
tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS},
|
||||
OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
|
||||
},
|
||||
ids::{
|
||||
acs::*,
|
||||
eps::*,
|
||||
tmtc::{TCP_SERVER, UDP_SERVER},
|
||||
},
|
||||
DeviceMode,
|
||||
};
|
||||
use tmtc::sender::TmTcSender;
|
||||
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "heap_tmtc")] {
|
||||
use interface::udp::DynamicUdpTmHandler;
|
||||
use satrs::pus::EcssTcVecCacher;
|
||||
use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic};
|
||||
} else {
|
||||
use std::sync::RwLock;
|
||||
use interface::udp::StaticUdpTmHandler;
|
||||
use satrs::pus::EcssTcInSharedPoolCacher;
|
||||
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
||||
use satrs_example::config::pool::create_static_pools;
|
||||
use tmtc::{
|
||||
tc_source::TcSourceTaskStatic,
|
||||
tm_sink::TmSinkStatic,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
mod acs;
|
||||
mod eps;
|
||||
mod events;
|
||||
@@ -6,106 +80,74 @@ mod interface;
|
||||
mod logger;
|
||||
mod pus;
|
||||
mod requests;
|
||||
mod spi;
|
||||
mod tmtc;
|
||||
|
||||
use crate::eps::pcdu::{
|
||||
PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper,
|
||||
};
|
||||
use crate::eps::PowerSwitchHelper;
|
||||
use crate::events::EventHandler;
|
||||
use crate::interface::udp::DynamicUdpTmHandler;
|
||||
use crate::pus::stack::PusStack;
|
||||
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
|
||||
use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic};
|
||||
use log::info;
|
||||
use pus::test::create_test_service_dynamic;
|
||||
use satrs::hal::std::tcp_server::ServerConfig;
|
||||
use satrs::hal::std::udp_server::UdpTcServer;
|
||||
use satrs::pus::HandlingStatus;
|
||||
use satrs::request::{GenericMessage, MessageMetadata};
|
||||
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
||||
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
|
||||
use satrs_example::config::tasks::{
|
||||
FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS,
|
||||
};
|
||||
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
|
||||
use satrs_example::DeviceMode;
|
||||
fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
println!("Running OBSW example");
|
||||
|
||||
use crate::acs::mgm::{
|
||||
MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface, SpiSimInterface,
|
||||
SpiSimInterfaceWrapper,
|
||||
};
|
||||
use crate::interface::sim_client_udp::create_sim_client;
|
||||
use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
|
||||
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
|
||||
use crate::logger::setup_logger;
|
||||
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
|
||||
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
|
||||
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
|
||||
use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static};
|
||||
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
|
||||
use crate::pus::test::create_test_service_static;
|
||||
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
|
||||
use crate::requests::{CompositeRequest, GenericRequestRouter};
|
||||
use satrs::mode::{Mode, ModeAndSubmode, ModeRequest};
|
||||
use satrs::pus::event_man::EventRequestWithToken;
|
||||
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
|
||||
use satrs_example::config::components::{
|
||||
MGM_HANDLER_0, NO_SENDER, PCDU_HANDLER, TCP_SERVER, UDP_SERVER,
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::{mpsc, Mutex};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn static_tmtc_pool_main() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let (tm_pool, tc_pool) = create_static_pools();
|
||||
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
|
||||
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
|
||||
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
|
||||
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
|
||||
}
|
||||
}
|
||||
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
|
||||
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
||||
|
||||
let tm_sink_tx_sender =
|
||||
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let tm_sender = TmTcSender::Static(
|
||||
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone())
|
||||
);
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let tm_sender = TmTcSender::Heap(tm_sink_tx.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let (sim_request_tx, sim_request_rx) = mpsc::channel();
|
||||
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
|
||||
let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel();
|
||||
let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
|
||||
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
|
||||
let mut opt_sim_client = create_sim_client(sim_request_rx);
|
||||
|
||||
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
|
||||
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(30);
|
||||
|
||||
let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
|
||||
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
|
||||
let (mgm_0_handler_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10);
|
||||
let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10);
|
||||
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30);
|
||||
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);
|
||||
|
||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||
let mut request_map = GenericRequestRouter::default();
|
||||
request_map
|
||||
.composite_router_map
|
||||
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
|
||||
request_map
|
||||
.mode_router_map
|
||||
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
|
||||
.insert(MGM0.id(), mgm_0_handler_composite_tx);
|
||||
request_map
|
||||
.composite_router_map
|
||||
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
|
||||
.insert(MGM1.id(), mgm_1_handler_composite_tx);
|
||||
request_map
|
||||
.mode_router_map
|
||||
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone());
|
||||
.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.
|
||||
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
|
||||
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
|
||||
@@ -117,17 +159,24 @@ fn static_tmtc_pool_main() {
|
||||
// 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::channel();
|
||||
let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
||||
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
||||
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
||||
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::channel();
|
||||
let (pus_mode_reply_tx, pus_mode_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,
|
||||
@@ -136,41 +185,42 @@ fn static_tmtc_pool_main() {
|
||||
action_tc_sender: pus_action_tx,
|
||||
mode_tc_sender: pus_mode_tx,
|
||||
};
|
||||
let pus_test_service = create_test_service_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
shared_tc_pool.clone(),
|
||||
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_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
tc_source.clone(),
|
||||
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_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
shared_tc_pool.clone(),
|
||||
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_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
shared_tc_pool.clone(),
|
||||
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_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
shared_tc_pool.clone(),
|
||||
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_static(
|
||||
tm_sink_tx_sender.clone(),
|
||||
shared_tc_pool.clone(),
|
||||
let pus_mode_service = create_mode_service(
|
||||
tm_sender.clone(),
|
||||
tc_in_mem_converter.clone(),
|
||||
pus_mode_rx,
|
||||
request_map,
|
||||
pus_mode_reply_rx,
|
||||
@@ -184,21 +234,36 @@ fn static_tmtc_pool_main() {
|
||||
pus_mode_service,
|
||||
);
|
||||
|
||||
let mut tmtc_task = TcSourceTaskStatic::new(
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new(
|
||||
shared_tc_pool_wrapper.clone(),
|
||||
tc_source_rx,
|
||||
PusTcDistributor::new(tm_sink_tx_sender, pus_router),
|
||||
);
|
||||
PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||
));
|
||||
let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool);
|
||||
let udp_tm_handler = StaticUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
tm_store: shared_tm_pool.clone(),
|
||||
};
|
||||
} else if #[cfg(feature = "heap_tmtc")] {
|
||||
let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new(
|
||||
tc_source_rx,
|
||||
PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||
));
|
||||
let tc_sender = TmTcSender::Heap(tc_source_tx.clone());
|
||||
let udp_tm_handler = DynamicUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone())
|
||||
.expect("creating UDP TMTC server failed");
|
||||
let mut udp_tmtc_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler: StaticUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
tm_store: shared_tm_pool.clone(),
|
||||
},
|
||||
tm_handler: udp_tm_handler,
|
||||
};
|
||||
|
||||
let tcp_server_cfg = ServerConfig::new(
|
||||
@@ -212,60 +277,94 @@ fn static_tmtc_pool_main() {
|
||||
let mut tcp_server = TcpTask::new(
|
||||
tcp_server_cfg,
|
||||
sync_tm_tcp_source.clone(),
|
||||
tc_source.clone(),
|
||||
tc_sender,
|
||||
PACKET_ID_VALIDATOR.clone(),
|
||||
)
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
let mut tm_sink = TmSinkStatic::new(
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "heap_tmtc"))] {
|
||||
let mut tm_sink = TmSink::Static(TmSinkStatic::new(
|
||||
shared_tm_pool_wrapper,
|
||||
sync_tm_tcp_source,
|
||||
tm_sink_rx,
|
||||
tm_server_tx,
|
||||
);
|
||||
|
||||
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
|
||||
mpsc::sync_channel(5);
|
||||
));
|
||||
} 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 (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
||||
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
||||
|
||||
let shared_mgm_set = Arc::default();
|
||||
let mgm_mode_leaf_interface = MpscModeLeafInterface {
|
||||
request_rx: mgm_handler_mode_rx,
|
||||
reply_to_pus_tx: pus_mode_reply_tx.clone(),
|
||||
reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
|
||||
};
|
||||
|
||||
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
|
||||
let shared_mgm_0_set = Arc::default();
|
||||
let shared_mgm_1_set = Arc::default();
|
||||
let mgm_0_mode_node = ModeRequestHandlerMpscBounded::new(MGM0.into(), mgm_0_handler_mode_rx);
|
||||
let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), mgm_1_handler_mode_rx);
|
||||
let (mgm_0_spi_interface, mgm_1_spi_interface) =
|
||||
if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||
sim_client
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
|
||||
sim_client
|
||||
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
|
||||
(
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_sim_reply_rx,
|
||||
})
|
||||
sim_reply_rx: mgm_0_sim_reply_rx,
|
||||
}),
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_1_sim_reply_rx,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
|
||||
(
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||
)
|
||||
};
|
||||
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM_HANDLER_0,
|
||||
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM0,
|
||||
"MGM_0",
|
||||
mgm_mode_leaf_interface,
|
||||
mgm_handler_composite_rx,
|
||||
mgm_0_mode_node,
|
||||
mgm_0_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sink_tx.clone(),
|
||||
mgm_spi_interface,
|
||||
shared_mgm_set,
|
||||
tm_sender.clone(),
|
||||
mgm_0_spi_interface,
|
||||
shared_mgm_0_set,
|
||||
);
|
||||
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM1,
|
||||
"MGM_1",
|
||||
mgm_1_mode_node,
|
||||
mgm_1_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sender.clone(),
|
||||
mgm_1_spi_interface,
|
||||
shared_mgm_1_set,
|
||||
);
|
||||
// Connect PUS service to device handlers.
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_0_handler_mode_tx,
|
||||
&mut mgm_0_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
mgm_1_handler_mode_tx,
|
||||
&mut mgm_1_handler,
|
||||
pus_mode_reply_tx.clone(),
|
||||
);
|
||||
|
||||
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
|
||||
mpsc::sync_channel(10);
|
||||
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
|
||||
request_rx: pcdu_handler_mode_rx,
|
||||
reply_to_pus_tx: pus_mode_reply_tx,
|
||||
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
|
||||
};
|
||||
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);
|
||||
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
|
||||
@@ -275,23 +374,33 @@ fn static_tmtc_pool_main() {
|
||||
} else {
|
||||
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
|
||||
};
|
||||
|
||||
let pcdu_mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), pcdu_handler_mode_rx);
|
||||
let mut pcdu_handler = PcduHandler::new(
|
||||
PCDU_HANDLER,
|
||||
PCDU,
|
||||
"PCDU",
|
||||
pcdu_mode_leaf_interface,
|
||||
pcdu_mode_node,
|
||||
pcdu_handler_composite_rx,
|
||||
pus_hk_reply_tx,
|
||||
switch_request_rx,
|
||||
tm_sink_tx,
|
||||
tm_sender.clone(),
|
||||
pcdu_serial_interface,
|
||||
shared_switch_set,
|
||||
);
|
||||
connect_mode_nodes(
|
||||
&mut pus_stack.mode_srv,
|
||||
pcdu_handler_mode_tx.clone(),
|
||||
&mut pcdu_handler,
|
||||
pus_mode_reply_tx,
|
||||
);
|
||||
|
||||
// The PCDU is a critical component which should be in normal mode immediately.
|
||||
pcdu_handler_mode_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, NO_SENDER),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as Mode, 0),
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
.expect("sending initial mode request failed");
|
||||
|
||||
@@ -346,7 +455,8 @@ fn static_tmtc_pool_main() {
|
||||
let jh_aocs = thread::Builder::new()
|
||||
.name("sat-rs aocs".to_string())
|
||||
.spawn(move || loop {
|
||||
mgm_handler.periodic_operation();
|
||||
mgm_0_handler.periodic_operation();
|
||||
mgm_1_handler.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||
})
|
||||
.unwrap();
|
||||
@@ -357,11 +467,13 @@ fn static_tmtc_pool_main() {
|
||||
.spawn(move || loop {
|
||||
// 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.
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
|
||||
//
|
||||
// TODO: The fixed timeslot handler exists.. use it.
|
||||
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
})
|
||||
.unwrap();
|
||||
@@ -397,322 +509,6 @@ fn static_tmtc_pool_main() {
|
||||
.expect("Joining PUS handler thread failed");
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn dyn_tmtc_pool_main() {
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||
let (tm_sink_tx, tm_sink_rx) = mpsc::channel();
|
||||
let (tm_server_tx, tm_server_rx) = mpsc::channel();
|
||||
|
||||
let (sim_request_tx, sim_request_rx) = mpsc::channel();
|
||||
let (mgm_sim_reply_tx, mgm_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);
|
||||
|
||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(5);
|
||||
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
|
||||
let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
|
||||
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
|
||||
mpsc::sync_channel::<GenericMessage<ModeRequest>>(10);
|
||||
|
||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||
let mut request_map = GenericRequestRouter::default();
|
||||
request_map
|
||||
.composite_router_map
|
||||
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
|
||||
request_map
|
||||
.mode_router_map
|
||||
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
|
||||
request_map
|
||||
.composite_router_map
|
||||
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
|
||||
request_map
|
||||
.mode_router_map
|
||||
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone());
|
||||
|
||||
// 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::channel();
|
||||
let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
||||
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
||||
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
||||
|
||||
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
|
||||
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
|
||||
|
||||
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_dynamic(tm_sink_tx.clone(), event_tx.clone(), pus_test_rx);
|
||||
let pus_scheduler_service = create_scheduler_service_dynamic(
|
||||
tm_sink_tx.clone(),
|
||||
tc_source_tx.clone(),
|
||||
pus_sched_rx,
|
||||
create_sched_tc_pool(),
|
||||
);
|
||||
|
||||
let pus_event_service =
|
||||
create_event_service_dynamic(tm_sink_tx.clone(), pus_event_rx, event_request_tx);
|
||||
let pus_action_service = create_action_service_dynamic(
|
||||
tm_sink_tx.clone(),
|
||||
pus_action_rx,
|
||||
request_map.clone(),
|
||||
pus_action_reply_rx,
|
||||
);
|
||||
let pus_hk_service = create_hk_service_dynamic(
|
||||
tm_sink_tx.clone(),
|
||||
pus_hk_rx,
|
||||
request_map.clone(),
|
||||
pus_hk_reply_rx,
|
||||
);
|
||||
let pus_mode_service = create_mode_service_dynamic(
|
||||
tm_sink_tx.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,
|
||||
);
|
||||
|
||||
let mut tmtc_task = TcSourceTaskDynamic::new(
|
||||
tc_source_rx,
|
||||
PusTcDistributor::new(tm_sink_tx.clone(), pus_router),
|
||||
);
|
||||
|
||||
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_source_tx.clone())
|
||||
.expect("creating UDP TMTC server failed");
|
||||
let mut udp_tmtc_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler: DynamicUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
},
|
||||
};
|
||||
|
||||
let tcp_server_cfg = ServerConfig::new(
|
||||
TCP_SERVER.id(),
|
||||
sock_addr,
|
||||
Duration::from_millis(400),
|
||||
4096,
|
||||
8192,
|
||||
);
|
||||
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
||||
let mut tcp_server = TcpTask::new(
|
||||
tcp_server_cfg,
|
||||
sync_tm_tcp_source.clone(),
|
||||
tc_source_tx.clone(),
|
||||
PACKET_ID_VALIDATOR.clone(),
|
||||
)
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
|
||||
|
||||
let shared_switch_set = Arc::new(Mutex::default());
|
||||
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
||||
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
||||
|
||||
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
|
||||
mpsc::sync_channel(5);
|
||||
let shared_mgm_set = Arc::default();
|
||||
let mode_leaf_interface = MpscModeLeafInterface {
|
||||
request_rx: mgm_handler_mode_rx,
|
||||
reply_to_pus_tx: pus_mode_reply_tx.clone(),
|
||||
reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
|
||||
};
|
||||
|
||||
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
|
||||
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||
sim_request_tx: sim_request_tx.clone(),
|
||||
sim_reply_rx: mgm_sim_reply_rx,
|
||||
})
|
||||
} else {
|
||||
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
|
||||
};
|
||||
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
||||
MGM_HANDLER_0,
|
||||
"MGM_0",
|
||||
mode_leaf_interface,
|
||||
mgm_handler_composite_rx,
|
||||
pus_hk_reply_tx.clone(),
|
||||
switch_helper.clone(),
|
||||
tm_sink_tx.clone(),
|
||||
mgm_spi_interface,
|
||||
shared_mgm_set,
|
||||
);
|
||||
|
||||
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
|
||||
mpsc::sync_channel(10);
|
||||
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
|
||||
request_rx: pcdu_handler_mode_rx,
|
||||
reply_to_pus_tx: pus_mode_reply_tx,
|
||||
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
|
||||
};
|
||||
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);
|
||||
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
|
||||
sim_request_tx.clone(),
|
||||
pcdu_sim_reply_rx,
|
||||
))
|
||||
} else {
|
||||
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
|
||||
};
|
||||
let mut pcdu_handler = PcduHandler::new(
|
||||
PCDU_HANDLER,
|
||||
"PCDU",
|
||||
pcdu_mode_leaf_interface,
|
||||
pcdu_handler_composite_rx,
|
||||
pus_hk_reply_tx,
|
||||
switch_request_rx,
|
||||
tm_sink_tx,
|
||||
pcdu_serial_interface,
|
||||
shared_switch_set,
|
||||
);
|
||||
// The PCDU is a critical component which should be in normal mode immediately.
|
||||
pcdu_handler_mode_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(0, NO_SENDER),
|
||||
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)),
|
||||
))
|
||||
.expect("sending initial mode request failed");
|
||||
|
||||
info!("Starting TMTC and UDP task");
|
||||
let jh_udp_tmtc = thread::Builder::new()
|
||||
.name("sat-rs tmtc-udp".to_string())
|
||||
.spawn(move || {
|
||||
info!("Running UDP server on port {SERVER_PORT}");
|
||||
loop {
|
||||
udp_tmtc_server.periodic_operation();
|
||||
tmtc_task.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting TCP task");
|
||||
let jh_tcp = thread::Builder::new()
|
||||
.name("sat-rs tcp".to_string())
|
||||
.spawn(move || {
|
||||
info!("Running TCP server on port {SERVER_PORT}");
|
||||
loop {
|
||||
tcp_server.periodic_operation();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting TM funnel task");
|
||||
let jh_tm_funnel = thread::Builder::new()
|
||||
.name("sat-rs tm-sink".to_string())
|
||||
.spawn(move || loop {
|
||||
tm_funnel.operation();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut opt_jh_sim_client = None;
|
||||
if let Some(mut sim_client) = opt_sim_client {
|
||||
info!("Starting UDP sim client task");
|
||||
opt_jh_sim_client = Some(
|
||||
thread::Builder::new()
|
||||
.name("sat-rs sim adapter".to_string())
|
||||
.spawn(move || loop {
|
||||
if sim_client.operation() == HandlingStatus::Empty {
|
||||
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
|
||||
}
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
info!("Starting AOCS thread");
|
||||
let jh_aocs = thread::Builder::new()
|
||||
.name("sat-rs aocs".to_string())
|
||||
.spawn(move || loop {
|
||||
mgm_handler.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting EPS thread");
|
||||
let jh_eps = thread::Builder::new()
|
||||
.name("sat-rs eps".to_string())
|
||||
.spawn(move || loop {
|
||||
// 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.
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting PUS handler thread");
|
||||
let jh_pus_handler = thread::Builder::new()
|
||||
.name("sat-rs pus".to_string())
|
||||
.spawn(move || loop {
|
||||
pus_stack.periodic_operation();
|
||||
event_handler.periodic_operation();
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
jh_udp_tmtc
|
||||
.join()
|
||||
.expect("Joining UDP TMTC server thread failed");
|
||||
jh_tcp
|
||||
.join()
|
||||
.expect("Joining TCP TMTC server thread failed");
|
||||
jh_tm_funnel
|
||||
.join()
|
||||
.expect("Joining TM Funnel thread failed");
|
||||
if let Some(jh_sim_client) = opt_jh_sim_client {
|
||||
jh_sim_client
|
||||
.join()
|
||||
.expect("Joining SIM client thread failed");
|
||||
}
|
||||
jh_aocs.join().expect("Joining AOCS thread failed");
|
||||
jh_eps.join().expect("Joining EPS thread failed");
|
||||
jh_pus_handler
|
||||
.join()
|
||||
.expect("Joining PUS handler thread failed");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
println!("Running OBSW example");
|
||||
#[cfg(not(feature = "dyn_tmtc"))]
|
||||
static_tmtc_pool_main();
|
||||
#[cfg(feature = "dyn_tmtc")]
|
||||
dyn_tmtc_pool_main();
|
||||
}
|
||||
|
||||
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
|
||||
time_provider
|
||||
.update_from_now()
|
||||
|
@@ -1,6 +1,5 @@
|
||||
use log::warn;
|
||||
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||
use satrs::pool::SharedStaticMemoryPool;
|
||||
use satrs::pus::action::{
|
||||
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
|
||||
};
|
||||
@@ -10,21 +9,21 @@ use satrs::pus::verification::{
|
||||
VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
||||
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
|
||||
MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper,
|
||||
PusTcToRequestConverter,
|
||||
ActiveRequest, EcssTcAndToken, EcssTcCacher, EcssTmSender, EcssTmtcError,
|
||||
GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler,
|
||||
PusServiceHelper, PusTcToRequestConverter,
|
||||
};
|
||||
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId};
|
||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||
use satrs_example::config::components::PUS_ACTION_SERVICE;
|
||||
use satrs_example::config::tmtc_err;
|
||||
use satrs_example::ids;
|
||||
use satrs_example::ids::generic_pus::PUS_ACTION;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
use super::{
|
||||
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
||||
@@ -207,20 +206,20 @@ impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for Actio
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pub fn create_action_service(
|
||||
tm_sender: TmTcSender,
|
||||
tc_in_mem_converter: EcssTcCacher,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
action_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||
) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> ActionServiceWrapper {
|
||||
let action_request_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_ACTION_SERVICE.id(),
|
||||
ids::generic_pus::PUS_ACTION.id(),
|
||||
pus_action_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
||||
create_verification_reporter(PUS_ACTION.id(), PUS_ACTION.apid),
|
||||
tc_in_mem_converter,
|
||||
),
|
||||
ActionRequestConverter::default(),
|
||||
// TODO: Implementation which does not use run-time allocation? Maybe something like
|
||||
@@ -235,36 +234,9 @@ pub fn create_action_service_static(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
action_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||
) -> ActionServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
let action_request_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_ACTION_SERVICE.id(),
|
||||
pus_action_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ActionRequestConverter::default(),
|
||||
DefaultActiveActionRequestMap::default(),
|
||||
ActionReplyHandler::default(),
|
||||
action_router,
|
||||
reply_receiver,
|
||||
);
|
||||
ActionServiceWrapper {
|
||||
service: action_request_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub struct ActionServiceWrapper {
|
||||
pub(crate) service: PusTargetedRequestService<
|
||||
MpscTcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
ActionRequestConverter,
|
||||
ActionReplyHandler,
|
||||
@@ -275,9 +247,7 @@ pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTc
|
||||
>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
||||
for ActionServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl TargetedPusService for ActionServiceWrapper {
|
||||
const SERVICE_ID: u8 = PusServiceId::Action as u8;
|
||||
const SERVICE_STR: &'static str = "action";
|
||||
|
||||
@@ -303,9 +273,10 @@ mod tests {
|
||||
use satrs::pus::test_util::{
|
||||
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
||||
};
|
||||
use satrs::pus::verification;
|
||||
use satrs::pus::verification::test_util::TestVerificationReporter;
|
||||
use satrs::pus::{verification, EcssTcVecCacher};
|
||||
use satrs::request::MessageMetadata;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use satrs::ComponentId;
|
||||
use satrs::{
|
||||
res_code::ResultU16,
|
||||
@@ -338,7 +309,7 @@ mod tests {
|
||||
{
|
||||
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
||||
let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(5);
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||
let (action_req_tx, action_req_rx) = mpsc::sync_channel(10);
|
||||
@@ -352,9 +323,9 @@ mod tests {
|
||||
PusServiceHelper::new(
|
||||
owner_id,
|
||||
pus_action_rx,
|
||||
tm_funnel_tx.clone(),
|
||||
TmTcSender::Heap(tm_funnel_tx.clone()),
|
||||
verif_reporter,
|
||||
EcssTcInVecConverter::default(),
|
||||
EcssTcCacher::Heap(EcssTcVecCacher::default()),
|
||||
),
|
||||
ActionRequestConverter::default(),
|
||||
DefaultActiveActionRequestMap::default(),
|
||||
@@ -397,7 +368,7 @@ mod tests {
|
||||
if let Err(mpsc::TryRecvError::Empty) = packet {
|
||||
} else {
|
||||
let tm = packet.unwrap();
|
||||
let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap().0;
|
||||
let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap();
|
||||
panic!("unexpected TM packet {unexpected_tm:?}");
|
||||
}
|
||||
}
|
||||
@@ -440,7 +411,11 @@ mod tests {
|
||||
|
||||
pub fn add_tc(&mut self, tc: &PusTcCreator) {
|
||||
self.request_id = Some(verification::RequestId::new(tc).into());
|
||||
let token = self.service.service_helper.verif_reporter_mut().add_tc(tc);
|
||||
let token = self
|
||||
.service
|
||||
.service_helper
|
||||
.verif_reporter_mut()
|
||||
.start_verification(tc);
|
||||
let accepted_token = self
|
||||
.service
|
||||
.service_helper
|
||||
|
@@ -1,34 +1,32 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use crate::pus::create_verification_reporter;
|
||||
use satrs::pool::SharedStaticMemoryPool;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use satrs::pus::event_man::EventRequestWithToken;
|
||||
use satrs::pus::event_srv::PusEventServiceHandler;
|
||||
use satrs::pus::verification::VerificationReporter;
|
||||
use satrs::pus::{
|
||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
|
||||
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
|
||||
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
|
||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher, MpscTcReceiver,
|
||||
PartialPusHandlingError, PusServiceHelper,
|
||||
};
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
|
||||
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT;
|
||||
|
||||
use super::{DirectPusService, HandlingStatus};
|
||||
|
||||
pub fn create_event_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pub fn create_event_service(
|
||||
tm_sender: TmTcSender,
|
||||
tm_in_pool_converter: EcssTcCacher,
|
||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||
) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> EventServiceWrapper {
|
||||
let pus_5_handler = PusEventServiceHandler::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_EVENT_MANAGEMENT.id(),
|
||||
pus_event_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
||||
tm_in_pool_converter,
|
||||
),
|
||||
event_request_tx,
|
||||
);
|
||||
@@ -37,34 +35,12 @@ pub fn create_event_service_static(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_event_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||
) -> EventServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
let pus_5_handler = PusEventServiceHandler::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_EVENT_MANAGEMENT.id(),
|
||||
pus_event_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
event_request_tx,
|
||||
);
|
||||
EventServiceWrapper {
|
||||
handler: pus_5_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub struct EventServiceWrapper {
|
||||
pub handler:
|
||||
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
PusEventServiceHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
||||
for EventServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl DirectPusService for EventServiceWrapper {
|
||||
const SERVICE_ID: u8 = PusServiceId::Event as u8;
|
||||
|
||||
const SERVICE_STR: &'static str = "events";
|
||||
|
@@ -1,28 +1,26 @@
|
||||
use derive_new::new;
|
||||
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
|
||||
use satrs::pool::SharedStaticMemoryPool;
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||
VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
|
||||
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
|
||||
EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
|
||||
PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
|
||||
ActivePusRequestStd, ActiveRequest, DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher,
|
||||
EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, PusPacketHandlingError,
|
||||
PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
|
||||
};
|
||||
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||
use satrs::res_code::ResultU16;
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId};
|
||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||
use satrs_example::config::components::PUS_HK_SERVICE;
|
||||
use satrs_example::config::{hk_err, tmtc_err};
|
||||
use satrs_example::ids::generic_pus::PUS_HK;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
|
||||
use crate::requests::GenericRequestRouter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
|
||||
use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService};
|
||||
|
||||
@@ -242,20 +240,20 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_hk_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pub fn create_hk_service(
|
||||
tm_sender: TmTcSender,
|
||||
tc_in_mem_converter: EcssTcCacher,
|
||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> HkServiceWrapper {
|
||||
let pus_3_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_HK_SERVICE.id(),
|
||||
PUS_HK.id(),
|
||||
pus_hk_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||
create_verification_reporter(PUS_HK.id(), PUS_HK.apid),
|
||||
tc_in_mem_converter,
|
||||
),
|
||||
HkRequestConverter::default(),
|
||||
DefaultActiveRequestMap::default(),
|
||||
@@ -268,36 +266,9 @@ pub fn create_hk_service_static(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_hk_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||
) -> HkServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
let pus_3_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_HK_SERVICE.id(),
|
||||
pus_hk_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
HkRequestConverter::default(),
|
||||
DefaultActiveRequestMap::default(),
|
||||
HkReplyHandler::default(),
|
||||
request_router,
|
||||
reply_receiver,
|
||||
);
|
||||
HkServiceWrapper {
|
||||
service: pus_3_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub struct HkServiceWrapper {
|
||||
pub(crate) service: PusTargetedRequestService<
|
||||
MpscTcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
HkRequestConverter,
|
||||
HkReplyHandler,
|
||||
@@ -308,9 +279,7 @@ pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMe
|
||||
>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
||||
for HkServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl TargetedPusService for HkServiceWrapper {
|
||||
const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8;
|
||||
const SERVICE_STR: &'static str = "housekeeping";
|
||||
|
||||
|
@@ -1,12 +1,13 @@
|
||||
use crate::requests::GenericRequestRouter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use log::warn;
|
||||
use satrs::pool::PoolAddr;
|
||||
use satrs::pus::verification::{
|
||||
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||
VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
|
||||
VerificationReporterConfig, VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
|
||||
ActiveRequest, ActiveRequestStore, CacheAndReadRawEcssTc, EcssTcAndToken, EcssTcCacher,
|
||||
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
|
||||
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
|
||||
PusTcToRequestConverter, TcInMemory,
|
||||
@@ -17,11 +18,11 @@ use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
||||
use satrs::tmtc::{PacketAsVec, PacketInPool};
|
||||
use satrs::ComponentId;
|
||||
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
||||
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
||||
use satrs_example::ids::generic_pus::PUS_ROUTING;
|
||||
use satrs_example::TimestampHelper;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub mod action;
|
||||
pub mod event;
|
||||
@@ -32,7 +33,7 @@ pub mod stack;
|
||||
pub mod test;
|
||||
|
||||
pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> VerificationReporter {
|
||||
let verif_cfg = VerificationReporterCfg::new(apid, 1, 2, 8).unwrap();
|
||||
let verif_cfg = VerificationReporterConfig::new(apid, 1, 2, 8).unwrap();
|
||||
// Every software component which needs to generate verification telemetry, gets a cloned
|
||||
// verification reporter.
|
||||
VerificationReporter::new(owner_id, &verif_cfg)
|
||||
@@ -40,31 +41,30 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi
|
||||
|
||||
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
||||
pub struct PusTcMpscRouter {
|
||||
pub test_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub event_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub sched_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub hk_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub action_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub mode_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
pub event_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
pub sched_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
pub hk_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
#[allow(dead_code)]
|
||||
pub action_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
pub mode_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||
}
|
||||
|
||||
pub struct PusTcDistributor<TmSender: EcssTmSender> {
|
||||
pub struct PusTcDistributor {
|
||||
#[allow(dead_code)]
|
||||
pub id: ComponentId,
|
||||
pub tm_sender: TmSender,
|
||||
pub tm_sender: TmTcSender,
|
||||
pub verif_reporter: VerificationReporter,
|
||||
pub pus_router: PusTcMpscRouter,
|
||||
stamp_helper: TimestampHelper,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
||||
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
||||
impl PusTcDistributor {
|
||||
pub fn new(tm_sender: TmTcSender, pus_router: PusTcMpscRouter) -> Self {
|
||||
Self {
|
||||
id: PUS_ROUTING_SERVICE.raw(),
|
||||
id: PUS_ROUTING.raw(),
|
||||
tm_sender,
|
||||
verif_reporter: create_verification_reporter(
|
||||
PUS_ROUTING_SERVICE.id(),
|
||||
PUS_ROUTING_SERVICE.apid,
|
||||
),
|
||||
verif_reporter: create_verification_reporter(PUS_ROUTING.id(), PUS_ROUTING.apid),
|
||||
pus_router,
|
||||
stamp_helper: TimestampHelper::default(),
|
||||
}
|
||||
@@ -102,12 +102,12 @@ impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
||||
sender_id,
|
||||
pus_tc_result.unwrap_err()
|
||||
);
|
||||
log::warn!("raw data: {:x?}", raw_tc);
|
||||
log::warn!("raw data: {raw_tc:x?}");
|
||||
// TODO: Shouldn't this be an error?
|
||||
return Ok(HandlingStatus::HandledOne);
|
||||
}
|
||||
let pus_tc = pus_tc_result.unwrap().0;
|
||||
let init_token = self.verif_reporter.add_tc(&pus_tc);
|
||||
let pus_tc = pus_tc_result.unwrap();
|
||||
let init_token = self.verif_reporter.start_verification(&pus_tc);
|
||||
self.stamp_helper.update_from_now();
|
||||
let accepted_token = self
|
||||
.verif_reporter
|
||||
@@ -266,21 +266,19 @@ pub trait DirectPusService {
|
||||
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
|
||||
pub struct PusTargetedRequestService<
|
||||
TcReceiver: EcssTcReceiver,
|
||||
TmSender: EcssTmSender,
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
RequestType,
|
||||
ReplyType,
|
||||
> {
|
||||
pub service_helper:
|
||||
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
PusServiceHelper<TcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||
pub request_router: GenericRequestRouter,
|
||||
pub request_converter: RequestConverter,
|
||||
pub active_request_map: ActiveRequestMap,
|
||||
pub active_request_map: ActiveRequestMapInstance,
|
||||
pub reply_handler: ReplyHandler,
|
||||
pub reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
|
||||
@@ -288,24 +286,20 @@ pub struct PusTargetedRequestService<
|
||||
|
||||
impl<
|
||||
TcReceiver: EcssTcReceiver,
|
||||
TmSender: EcssTmSender,
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
RequestType,
|
||||
ReplyType,
|
||||
>
|
||||
PusTargetedRequestService<
|
||||
TcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
RequestConverter,
|
||||
ReplyHandler,
|
||||
ActiveRequestMap,
|
||||
ActiveRequestMapInstance,
|
||||
ActiveRequestInfo,
|
||||
RequestType,
|
||||
ReplyType,
|
||||
@@ -316,12 +310,12 @@ where
|
||||
pub fn new(
|
||||
service_helper: PusServiceHelper<
|
||||
TcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
TmTcSender,
|
||||
EcssTcCacher,
|
||||
VerificationReporter,
|
||||
>,
|
||||
request_converter: RequestConverter,
|
||||
active_request_map: ActiveRequestMap,
|
||||
active_request_map: ActiveRequestMapInstance,
|
||||
reply_hook: ReplyHandler,
|
||||
request_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||
@@ -515,7 +509,7 @@ where
|
||||
/// and also log the error.
|
||||
pub fn generic_pus_request_timeout_handler(
|
||||
sender: &(impl EcssTmSender + ?Sized),
|
||||
active_request: &(impl ActiveRequestProvider + Debug),
|
||||
active_request: &(impl ActiveRequest + Debug),
|
||||
verification_handler: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
service_str: &'static str,
|
||||
@@ -543,7 +537,7 @@ pub(crate) mod tests {
|
||||
use satrs::{
|
||||
pus::{
|
||||
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
|
||||
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
|
||||
ActiveRequestStore, MpscTcReceiver,
|
||||
},
|
||||
request::UniqueApidTargetId,
|
||||
spacepackets::{
|
||||
@@ -562,7 +556,7 @@ pub(crate) mod tests {
|
||||
// Testbench dedicated to the testing of [PusReplyHandler]s
|
||||
pub struct ReplyHandlerTestbench<
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
Reply,
|
||||
> {
|
||||
pub id: ComponentId,
|
||||
@@ -576,7 +570,7 @@ pub(crate) mod tests {
|
||||
|
||||
impl<
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
Reply,
|
||||
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
|
||||
{
|
||||
@@ -602,7 +596,7 @@ pub(crate) mod tests {
|
||||
) -> (verification::RequestId, ActivePusRequestStd) {
|
||||
let sp_header = SpHeader::new_from_apid(apid);
|
||||
let sec_header_dummy = PusTcSecondaryHeader::new_simple(0, 0);
|
||||
let init = self.verif_reporter.add_tc(&PusTcCreator::new(
|
||||
let init = self.verif_reporter.start_verification(&PusTcCreator::new(
|
||||
sp_header,
|
||||
sec_header_dummy,
|
||||
&[],
|
||||
@@ -677,7 +671,7 @@ pub(crate) mod tests {
|
||||
// Testbench dedicated to the testing of [PusTcToRequestConverter]s
|
||||
pub struct PusConverterTestbench<
|
||||
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
Request,
|
||||
> {
|
||||
pub id: ComponentId,
|
||||
@@ -691,7 +685,7 @@ pub(crate) mod tests {
|
||||
|
||||
impl<
|
||||
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
Request,
|
||||
> PusConverterTestbench<Converter, ActiveRequestInfo, Request>
|
||||
{
|
||||
@@ -709,7 +703,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
pub fn add_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
|
||||
let token = self.verif_reporter.add_tc(tc);
|
||||
let token = self.verif_reporter.start_verification(tc);
|
||||
self.current_request_id = Some(verification::RequestId::new(tc));
|
||||
self.current_packet = Some(tc.to_vec().unwrap());
|
||||
self.verif_reporter
|
||||
@@ -737,7 +731,7 @@ pub(crate) mod tests {
|
||||
let tc_reader = PusTcReader::new(¤t_packet).unwrap();
|
||||
let (active_info, request) = self.converter.convert(
|
||||
token,
|
||||
&tc_reader.0,
|
||||
&tc_reader,
|
||||
&self.dummy_sender,
|
||||
&self.verif_reporter,
|
||||
time_stamp,
|
||||
@@ -757,19 +751,17 @@ pub(crate) mod tests {
|
||||
pub struct TargetedPusRequestTestbench<
|
||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequestProvider,
|
||||
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||
ActiveRequestInfo: ActiveRequest,
|
||||
RequestType,
|
||||
ReplyType,
|
||||
> {
|
||||
pub service: PusTargetedRequestService<
|
||||
MpscTcReceiver,
|
||||
MpscTmAsVecSender,
|
||||
EcssTcInVecConverter,
|
||||
TestVerificationReporter,
|
||||
RequestConverter,
|
||||
ReplyHandler,
|
||||
ActiveRequestMap,
|
||||
ActiveRequestMapInstance,
|
||||
ActiveRequestInfo,
|
||||
RequestType,
|
||||
ReplyType,
|
||||
|
@@ -1,14 +1,14 @@
|
||||
use derive_new::new;
|
||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||
use satrs::mode_tree::{ModeNode, ModeParent};
|
||||
use satrs_example::ids;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
use satrs::pool::SharedStaticMemoryPool;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use satrs::pus::verification::VerificationReporter;
|
||||
use satrs::pus::{
|
||||
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
||||
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError,
|
||||
DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher, MpscTcReceiver, PusPacketHandlingError,
|
||||
PusServiceHelper,
|
||||
};
|
||||
use satrs::request::GenericMessage;
|
||||
@@ -20,8 +20,8 @@ use satrs::{
|
||||
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
|
||||
VerificationToken,
|
||||
},
|
||||
ActivePusRequestStd, ActiveRequestProvider, EcssTmSender, EcssTmtcError,
|
||||
GenericConversionError, PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
|
||||
ActivePusRequestStd, ActiveRequest, EcssTmSender, EcssTmtcError, GenericConversionError,
|
||||
PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
|
||||
},
|
||||
request::UniqueApidTargetId,
|
||||
spacepackets::{
|
||||
@@ -34,7 +34,6 @@ use satrs::{
|
||||
},
|
||||
ComponentId,
|
||||
};
|
||||
use satrs_example::config::components::PUS_MODE_SERVICE;
|
||||
use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId};
|
||||
|
||||
use super::{
|
||||
@@ -110,6 +109,7 @@ impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
|
||||
),
|
||||
)?;
|
||||
}
|
||||
ModeReply::ModeInfo(_mode_and_submode) => (),
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
@@ -190,7 +190,13 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
|
||||
}
|
||||
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
|
||||
.expect("mode and submode extraction failed");
|
||||
Ok((active_request, ModeRequest::SetMode(mode_and_submode)))
|
||||
Ok((
|
||||
active_request,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode,
|
||||
forced: false,
|
||||
},
|
||||
))
|
||||
}
|
||||
Subservice::TcReadMode => Ok((active_request, ModeRequest::ReadMode)),
|
||||
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
|
||||
@@ -202,24 +208,27 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_mode_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pub fn create_mode_service(
|
||||
tm_sender: TmTcSender,
|
||||
tc_in_mem_converter: EcssTcCacher,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
mode_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> ModeServiceWrapper {
|
||||
let mode_request_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_MODE_SERVICE.id(),
|
||||
ids::generic_pus::PUS_MODE.id(),
|
||||
pus_action_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||
create_verification_reporter(
|
||||
ids::generic_pus::PUS_MODE.id(),
|
||||
ids::generic_pus::PUS_MODE.apid,
|
||||
),
|
||||
tc_in_mem_converter,
|
||||
),
|
||||
ModeRequestConverter::default(),
|
||||
DefaultActiveRequestMap::default(),
|
||||
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
|
||||
ModeReplyHandler::new(ids::generic_pus::PUS_MODE.id()),
|
||||
mode_router,
|
||||
reply_receiver,
|
||||
);
|
||||
@@ -228,36 +237,9 @@ pub fn create_mode_service_static(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_mode_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
mode_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
) -> ModeServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
let mode_request_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_MODE_SERVICE.id(),
|
||||
pus_action_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ModeRequestConverter::default(),
|
||||
DefaultActiveRequestMap::default(),
|
||||
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
|
||||
mode_router,
|
||||
reply_receiver,
|
||||
);
|
||||
ModeServiceWrapper {
|
||||
service: mode_request_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub struct ModeServiceWrapper {
|
||||
pub(crate) service: PusTargetedRequestService<
|
||||
MpscTcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
ModeRequestConverter,
|
||||
ModeReplyHandler,
|
||||
@@ -268,9 +250,24 @@ pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcIn
|
||||
>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
||||
for ModeServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl ModeNode for ModeServiceWrapper {
|
||||
fn id(&self) -> ComponentId {
|
||||
self.service.service_helper.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeParent for ModeServiceWrapper {
|
||||
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
|
||||
|
||||
fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender) {
|
||||
self.service
|
||||
.request_router
|
||||
.mode_router_map
|
||||
.insert(id, request_sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetedPusService for ModeServiceWrapper {
|
||||
const SERVICE_ID: u8 = CustomPusServiceId::Mode as u8;
|
||||
const SERVICE_STR: &'static str = "mode";
|
||||
|
||||
@@ -346,7 +343,13 @@ mod tests {
|
||||
let (_active_req, req) = testbench
|
||||
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||
.expect("conversion has failed");
|
||||
assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
|
||||
assert_eq!(
|
||||
req,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode,
|
||||
forced: false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -2,28 +2,28 @@ use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::pus::create_verification_reporter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use log::info;
|
||||
use satrs::pool::{PoolProvider, StaticMemoryPool};
|
||||
use satrs::pus::scheduler::{PusScheduler, TcInfo};
|
||||
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
|
||||
use satrs::pus::verification::VerificationReporter;
|
||||
use satrs::pus::{
|
||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
|
||||
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
|
||||
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
|
||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher, MpscTcReceiver,
|
||||
PartialPusHandlingError, PusServiceHelper,
|
||||
};
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
|
||||
use satrs::ComponentId;
|
||||
use satrs_example::config::components::PUS_SCHED_SERVICE;
|
||||
use satrs_example::ids::sched::PUS_SCHED;
|
||||
|
||||
use super::{DirectPusService, HandlingStatus};
|
||||
|
||||
pub trait TcReleaser {
|
||||
pub trait TcReleaseProvider {
|
||||
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
|
||||
}
|
||||
|
||||
impl TcReleaser for PacketSenderWithSharedPool {
|
||||
impl TcReleaseProvider for PacketSenderWithSharedPool {
|
||||
fn release(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
@@ -48,7 +48,7 @@ impl TcReleaser for PacketSenderWithSharedPool {
|
||||
}
|
||||
}
|
||||
|
||||
impl TcReleaser for mpsc::Sender<PacketAsVec> {
|
||||
impl TcReleaseProvider for mpsc::SyncSender<PacketAsVec> {
|
||||
fn release(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
@@ -65,23 +65,35 @@ impl TcReleaser for mpsc::Sender<PacketAsVec> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SchedulingServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
pub enum TcReleaser {
|
||||
Static(PacketSenderWithSharedPool),
|
||||
Heap(mpsc::SyncSender<PacketAsVec>),
|
||||
}
|
||||
|
||||
impl TcReleaseProvider for TcReleaser {
|
||||
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool {
|
||||
match self {
|
||||
TcReleaser::Static(sender) => sender.release(sender_id, enabled, info, tc),
|
||||
TcReleaser::Heap(sender) => sender.release(sender_id, enabled, info, tc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SchedulingServiceWrapper {
|
||||
pub pus_11_handler: PusSchedServiceHandler<
|
||||
MpscTcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
TmTcSender,
|
||||
EcssTcCacher,
|
||||
VerificationReporter,
|
||||
PusScheduler,
|
||||
>,
|
||||
pub sched_tc_pool: StaticMemoryPool,
|
||||
pub releaser_buf: [u8; 4096],
|
||||
pub tc_releaser: Box<dyn TcReleaser + Send>,
|
||||
pub tc_releaser: TcReleaser,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
||||
for SchedulingServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl DirectPusService for SchedulingServiceWrapper {
|
||||
const SERVICE_ID: u8 = PusServiceId::Verification as u8;
|
||||
|
||||
const SERVICE_STR: &'static str = "verification";
|
||||
@@ -134,9 +146,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
||||
}
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||
SchedulingServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl SchedulingServiceWrapper {
|
||||
pub fn release_tcs(&mut self) {
|
||||
let id = self.pus_11_handler.service_helper.id();
|
||||
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
||||
@@ -162,21 +172,22 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_scheduler_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_releaser: PacketSenderWithSharedPool,
|
||||
pub fn create_scheduler_service(
|
||||
tm_sender: TmTcSender,
|
||||
tc_in_mem_converter: EcssTcCacher,
|
||||
tc_releaser: TcReleaser,
|
||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
sched_tc_pool: StaticMemoryPool,
|
||||
) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> SchedulingServiceWrapper {
|
||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||
.expect("Creating PUS Scheduler failed");
|
||||
let pus_11_handler = PusSchedServiceHandler::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_SCHED_SERVICE.id(),
|
||||
PUS_SCHED.id(),
|
||||
pus_sched_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
|
||||
create_verification_reporter(PUS_SCHED.id(), PUS_SCHED.apid),
|
||||
tc_in_mem_converter,
|
||||
),
|
||||
scheduler,
|
||||
);
|
||||
@@ -184,34 +195,6 @@ pub fn create_scheduler_service_static(
|
||||
pus_11_handler,
|
||||
sched_tc_pool,
|
||||
releaser_buf: [0; 4096],
|
||||
tc_releaser: Box::new(tc_releaser),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_scheduler_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
tc_source_sender: mpsc::Sender<PacketAsVec>,
|
||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
sched_tc_pool: StaticMemoryPool,
|
||||
) -> SchedulingServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
//let sched_srv_receiver =
|
||||
//MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx);
|
||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||
.expect("Creating PUS Scheduler failed");
|
||||
let pus_11_handler = PusSchedServiceHandler::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_SCHED_SERVICE.id(),
|
||||
pus_sched_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
scheduler,
|
||||
);
|
||||
SchedulingServiceWrapper {
|
||||
pus_11_handler,
|
||||
sched_tc_pool,
|
||||
releaser_buf: [0; 4096],
|
||||
tc_releaser: Box::new(tc_source_sender),
|
||||
tc_releaser,
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
use crate::pus::mode::ModeServiceWrapper;
|
||||
use derive_new::new;
|
||||
use satrs::{
|
||||
pus::{EcssTcInMemConverter, EcssTmSender},
|
||||
spacepackets::time::{cds, TimeWriter},
|
||||
};
|
||||
use satrs::spacepackets::time::{cds, TimeWriter};
|
||||
|
||||
use super::{
|
||||
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
|
||||
@@ -11,21 +8,17 @@ use super::{
|
||||
HandlingStatus, TargetedPusService,
|
||||
};
|
||||
|
||||
// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one
|
||||
// for targeted services..
|
||||
#[derive(new)]
|
||||
pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
|
||||
hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
|
||||
event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
|
||||
action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
|
||||
schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
|
||||
mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
|
||||
pub struct PusStack {
|
||||
pub test_srv: TestCustomServiceWrapper,
|
||||
pub hk_srv_wrapper: HkServiceWrapper,
|
||||
pub event_srv: EventServiceWrapper,
|
||||
pub action_srv_wrapper: ActionServiceWrapper,
|
||||
pub schedule_srv: SchedulingServiceWrapper,
|
||||
pub mode_srv: ModeServiceWrapper,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||
PusStack<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl PusStack {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
// Release all telecommands which reached their release time before calling the service
|
||||
// handlers.
|
||||
|
@@ -1,35 +1,34 @@
|
||||
use crate::pus::create_verification_reporter;
|
||||
use crate::tmtc::sender::TmTcSender;
|
||||
use log::info;
|
||||
use satrs::event_man::{EventMessage, EventMessageU32};
|
||||
use satrs::pool::SharedStaticMemoryPool;
|
||||
use satrs::pus::test::PusService17TestHandler;
|
||||
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
|
||||
use satrs::pus::PartialPusHandlingError;
|
||||
use satrs::pus::{
|
||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter,
|
||||
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper,
|
||||
CacheAndReadRawEcssTc, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher,
|
||||
MpscTcReceiver, PusServiceHelper,
|
||||
};
|
||||
use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||
use satrs_example::config::components::PUS_TEST_SERVICE;
|
||||
use satrs_example::config::{tmtc_err, TEST_EVENT};
|
||||
use satrs_example::ids::generic_pus::PUS_TEST;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use super::{DirectPusService, HandlingStatus};
|
||||
|
||||
pub fn create_test_service_static(
|
||||
tm_sender: PacketSenderWithSharedPool,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pub fn create_test_service(
|
||||
tm_sender: TmTcSender,
|
||||
tc_in_mem_converter: EcssTcCacher,
|
||||
event_sender: mpsc::SyncSender<EventMessageU32>,
|
||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||
) -> TestCustomServiceWrapper {
|
||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||
PUS_TEST_SERVICE.id(),
|
||||
PUS_TEST.id(),
|
||||
pus_test_rx,
|
||||
tm_sender,
|
||||
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||
create_verification_reporter(PUS_TEST.id(), PUS_TEST.apid),
|
||||
tc_in_mem_converter,
|
||||
));
|
||||
TestCustomServiceWrapper {
|
||||
handler: pus17_handler,
|
||||
@@ -37,34 +36,13 @@ pub fn create_test_service_static(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_test_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
event_sender: mpsc::SyncSender<EventMessageU32>,
|
||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||
PUS_TEST_SERVICE.id(),
|
||||
pus_test_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
));
|
||||
TestCustomServiceWrapper {
|
||||
handler: pus17_handler,
|
||||
event_tx: event_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestCustomServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||
{
|
||||
pub struct TestCustomServiceWrapper {
|
||||
pub handler:
|
||||
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
PusService17TestHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||
pub event_tx: mpsc::SyncSender<EventMessageU32>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
||||
for TestCustomServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl DirectPusService for TestCustomServiceWrapper {
|
||||
const SERVICE_ID: u8 = PusServiceId::Test as u8;
|
||||
|
||||
const SERVICE_STR: &'static str = "test";
|
||||
@@ -108,7 +86,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
||||
);
|
||||
}
|
||||
DirectPusPacketHandlerResult::CustomSubservice(subservice, token) => {
|
||||
let (tc, _) = PusTcReader::new(
|
||||
let tc = PusTcReader::new(
|
||||
self.handler
|
||||
.service_helper
|
||||
.tc_in_mem_converter
|
||||
@@ -118,7 +96,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
||||
if subservice == 128 {
|
||||
info!("generating test event");
|
||||
self.event_tx
|
||||
.send(EventMessage::new(PUS_TEST_SERVICE.id(), TEST_EVENT.into()))
|
||||
.send(EventMessage::new(PUS_TEST.id(), TEST_EVENT.into()))
|
||||
.expect("Sending test event failed");
|
||||
match self.handler.service_helper.verif_reporter().start_success(
|
||||
self.handler.service_helper.tm_sender(),
|
||||
|
@@ -8,14 +8,14 @@ use satrs::mode::ModeRequest;
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{ActiveRequestProvider, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
||||
use satrs::pus::{ActiveRequest, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::ComponentId;
|
||||
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
||||
use satrs_example::config::tmtc_err;
|
||||
use satrs_example::ids;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
@@ -26,6 +26,7 @@ pub enum CompositeRequest {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GenericRequestRouter {
|
||||
#[allow(dead_code)]
|
||||
pub id: ComponentId,
|
||||
// All messages which do not have a dedicated queue.
|
||||
pub composite_router_map:
|
||||
@@ -36,7 +37,7 @@ pub struct GenericRequestRouter {
|
||||
impl Default for GenericRequestRouter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: PUS_ROUTING_SERVICE.raw(),
|
||||
id: ids::generic_pus::PUS_ROUTING.raw(),
|
||||
composite_router_map: Default::default(),
|
||||
mode_router_map: Default::default(),
|
||||
}
|
||||
@@ -45,7 +46,7 @@ impl Default for GenericRequestRouter {
|
||||
impl GenericRequestRouter {
|
||||
pub(crate) fn handle_error_generic(
|
||||
&self,
|
||||
active_request: &impl ActiveRequestProvider,
|
||||
active_request: &impl ActiveRequest,
|
||||
tc: &PusTcReader,
|
||||
error: GenericRoutingError,
|
||||
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||
|
6
satrs-example/src/spi.rs
Normal file
6
satrs-example/src/spi.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
pub trait SpiInterface {
|
||||
type Error: Debug;
|
||||
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
@@ -1,2 +1,3 @@
|
||||
pub mod sender;
|
||||
pub mod tc_source;
|
||||
pub mod tm_sink;
|
||||
|
75
satrs-example/src/tmtc/sender.rs
Normal file
75
satrs-example/src/tmtc/sender.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
|
||||
|
||||
use satrs::{
|
||||
pus::EcssTmSender,
|
||||
queue::GenericSendError,
|
||||
spacepackets::ecss::WritablePusPacket,
|
||||
tmtc::{PacketAsVec, PacketSenderRaw, PacketSenderWithSharedPool, StoreAndSendError},
|
||||
ComponentId,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MockSender(pub RefCell<VecDeque<PacketAsVec>>);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TmTcSender {
|
||||
Static(PacketSenderWithSharedPool),
|
||||
Heap(mpsc::SyncSender<PacketAsVec>),
|
||||
Mock(MockSender),
|
||||
}
|
||||
|
||||
impl TmTcSender {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_mock_sender(&mut self) -> Option<&mut MockSender> {
|
||||
match self {
|
||||
TmTcSender::Mock(sender) => Some(sender),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 PacketSenderRaw for TmTcSender {
|
||||
type Error = StoreAndSendError;
|
||||
|
||||
fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
||||
match self {
|
||||
TmTcSender::Static(packet_sender_with_shared_pool) => {
|
||||
packet_sender_with_shared_pool.send_packet(sender_id, packet)
|
||||
}
|
||||
TmTcSender::Heap(sync_sender) => sync_sender
|
||||
.send_packet(sender_id, packet)
|
||||
.map_err(StoreAndSendError::Send),
|
||||
TmTcSender::Mock(sender) => sender.send_packet(sender_id, packet),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketSenderRaw for MockSender {
|
||||
type Error = StoreAndSendError;
|
||||
|
||||
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
let mut mut_queue = self.0.borrow_mut();
|
||||
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -1,32 +1,33 @@
|
||||
use satrs::{
|
||||
pool::PoolProvider,
|
||||
pus::HandlingStatus,
|
||||
tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
|
||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
||||
};
|
||||
use std::sync::mpsc::{self, TryRecvError};
|
||||
|
||||
use satrs::pus::MpscTmAsVecSender;
|
||||
|
||||
use crate::pus::PusTcDistributor;
|
||||
|
||||
// TC source components where static pools are the backing memory of the received telecommands.
|
||||
pub struct TcSourceTaskStatic {
|
||||
shared_tc_pool: SharedPacketPool,
|
||||
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||
tc_buf: [u8; 4096],
|
||||
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
|
||||
/// 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<PacketSenderWithSharedPool>,
|
||||
pus_receiver: PusTcDistributor,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared_tc_pool,
|
||||
tc_receiver,
|
||||
tc_buf: [0; 4096],
|
||||
tc_buf: Box::new([0; 4096]),
|
||||
pus_distributor: pus_receiver,
|
||||
}
|
||||
}
|
||||
@@ -45,11 +46,11 @@ impl TcSourceTaskStatic {
|
||||
.0
|
||||
.read()
|
||||
.expect("locking tc pool failed");
|
||||
pool.read(&packet_in_pool.store_addr, &mut self.tc_buf)
|
||||
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)
|
||||
.handle_tc_packet_in_store(packet_in_pool, self.tc_buf.as_slice())
|
||||
.ok();
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
@@ -67,14 +68,12 @@ impl TcSourceTaskStatic {
|
||||
// TC source components where the heap is the backing memory of the received telecommands.
|
||||
pub struct TcSourceTaskDynamic {
|
||||
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
|
||||
pus_distributor: PusTcDistributor,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TcSourceTaskDynamic {
|
||||
pub fn new(
|
||||
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
||||
) -> Self {
|
||||
pub fn new(tc_receiver: mpsc::Receiver<PacketAsVec>, pus_receiver: PusTcDistributor) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
pus_distributor: pus_receiver,
|
||||
@@ -105,3 +104,18 @@ impl TcSourceTaskDynamic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum TcSourceTask {
|
||||
Static(TcSourceTaskStatic),
|
||||
Heap(TcSourceTaskDynamic),
|
||||
}
|
||||
|
||||
impl TcSourceTask {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
match self {
|
||||
TcSourceTask::Static(task) => task.periodic_operation(),
|
||||
TcSourceTask::Heap(task) => task.periodic_operation(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,16 +4,19 @@ use std::{
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
|
||||
use satrs::{
|
||||
pool::PoolProvider,
|
||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||
spacepackets::{
|
||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||
seq_count::CcsdsSimpleSeqCountProvider,
|
||||
time::cds::MIN_CDS_FIELD_LEN,
|
||||
CcsdsPacket,
|
||||
},
|
||||
};
|
||||
use satrs::{
|
||||
spacepackets::seq_count::SequenceCountProvider,
|
||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
||||
};
|
||||
|
||||
use crate::interface::tcp::SyncTcpTmSource;
|
||||
|
||||
@@ -86,6 +89,7 @@ pub struct TmSinkStatic {
|
||||
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TmSinkStatic {
|
||||
pub fn new(
|
||||
shared_tm_store: SharedPacketPool,
|
||||
@@ -129,14 +133,15 @@ impl TmSinkStatic {
|
||||
pub struct TmSinkDynamic {
|
||||
common: TmFunnelCommon,
|
||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
tm_server_tx: mpsc::Sender<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::Sender<PacketAsVec>,
|
||||
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
@@ -159,3 +164,18 @@ impl TmSinkDynamic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.1.3] 2024-08-26
|
||||
|
||||
Bump `satrs-shared`.
|
||||
|
||||
# [v0.1.2] 2024-04-17
|
||||
|
||||
Allow `satrs-shared` from `v0.1.3` to `<v0.2`.
|
||||
@@ -19,3 +23,6 @@ Allow `satrs-shared` from `v0.1.3` to `<v0.2`.
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release containing the `resultcode` macro.
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-mib-v0.1.3...HEAD
|
||||
[v0.1.3]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-mib-v0.1.2...satrs-mib-v0.1.3
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "satrs-mib"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
@@ -23,7 +23,8 @@ version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.satrs-shared]
|
||||
version = ">=0.1.3, <0.2"
|
||||
version = "0.2"
|
||||
path = "../satrs-shared"
|
||||
features = ["serde"]
|
||||
|
||||
[dependencies.satrs-mib-codegen]
|
||||
|
@@ -28,7 +28,8 @@ features = ["full"]
|
||||
trybuild = { version = "1", features = ["diff"] }
|
||||
|
||||
[dev-dependencies.satrs-shared]
|
||||
version = ">=0.1.3, <0.2"
|
||||
version = "0.2"
|
||||
path = "../../satrs-shared"
|
||||
|
||||
[dev-dependencies.satrs-mib]
|
||||
path = ".."
|
||||
|
@@ -1,6 +1,4 @@
|
||||
#![no_std]
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
extern crate std;
|
||||
|
||||
|
@@ -9,20 +9,18 @@ edition = "2021"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
thiserror = "1"
|
||||
fern = "0.5"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
thiserror = "2"
|
||||
fern = "0.7"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
num_enum = "0.7"
|
||||
humantime = "2"
|
||||
tai-time = { version = "0.3", features = ["serde"] }
|
||||
|
||||
[dependencies.asynchronix]
|
||||
version = "0.2.2"
|
||||
# git = "https://github.com/asynchronics/asynchronix.git"
|
||||
# branch = "main"
|
||||
features = ["serde"]
|
||||
[dependencies.nexosim]
|
||||
version = "0.3.1"
|
||||
|
||||
[dependencies.satrs]
|
||||
path = "../satrs"
|
||||
|
||||
[dev-dependencies]
|
||||
delegate = "0.12"
|
||||
delegate = "0.13"
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::{f32::consts::PI, sync::mpsc, time::Duration};
|
||||
|
||||
use asynchronix::{
|
||||
model::{Model, Output},
|
||||
time::Scheduler,
|
||||
use nexosim::{
|
||||
model::{Context, Model},
|
||||
ports::Output,
|
||||
};
|
||||
use satrs::power::SwitchStateBinary;
|
||||
use satrs_minisim::{
|
||||
@@ -31,6 +31,7 @@ const PHASE_Z: f32 = 0.2;
|
||||
/// might still be possible and is probably sufficient for many OBSW needs.
|
||||
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
|
||||
pub switch_state: SwitchStateBinary,
|
||||
#[allow(dead_code)]
|
||||
pub periodicity: Duration,
|
||||
pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
|
||||
pub reply_sender: mpsc::Sender<SimReply>,
|
||||
@@ -54,7 +55,7 @@ impl<ReplyProvider: MgmReplyProvider> MagnetometerModel<ReplyProvider> {
|
||||
self.switch_state = switch_state;
|
||||
}
|
||||
|
||||
pub async fn send_sensor_values(&mut self, _: (), scheduler: &Scheduler<Self>) {
|
||||
pub async fn send_sensor_values(&mut self, _: (), scheduler: &mut Context<Self>) {
|
||||
self.reply_sender
|
||||
.send(ReplyProvider::create_mgm_reply(MgmReplyCommon {
|
||||
switch_state: self.switch_state,
|
||||
@@ -113,11 +114,11 @@ impl MagnetorquerModel {
|
||||
pub async fn apply_torque(
|
||||
&mut self,
|
||||
duration_and_dipole: (Duration, MgtDipole),
|
||||
scheduler: &Scheduler<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.torque_dipole = duration_and_dipole.1;
|
||||
self.torquing = true;
|
||||
if scheduler
|
||||
if cx
|
||||
.schedule_event(duration_and_dipole.0, Self::clear_torque, ())
|
||||
.is_err()
|
||||
{
|
||||
@@ -137,12 +138,11 @@ impl MagnetorquerModel {
|
||||
self.generate_magnetic_field(()).await;
|
||||
}
|
||||
|
||||
pub async fn request_housekeeping_data(&mut self, _: (), scheduler: &Scheduler<Self>) {
|
||||
pub async fn request_housekeeping_data(&mut self, _: (), cx: &mut Context<Self>) {
|
||||
if self.switch_state != SwitchStateBinary::On {
|
||||
return;
|
||||
}
|
||||
scheduler
|
||||
.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
|
||||
cx.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
|
||||
.expect("requesting housekeeping data failed")
|
||||
}
|
||||
|
||||
@@ -199,11 +199,11 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply.is_some());
|
||||
let sim_reply = sim_reply.unwrap();
|
||||
assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
|
||||
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
|
||||
let reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||
.expect("failed to deserialize MGM sensor values");
|
||||
assert_eq!(reply.common.switch_state, SwitchStateBinary::Off);
|
||||
@@ -222,21 +222,21 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let mut sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply_res.is_some());
|
||||
let mut sim_reply = sim_reply_res.unwrap();
|
||||
assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
|
||||
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
|
||||
let first_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||
.expect("failed to deserialize MGM sensor values");
|
||||
sim_testbench.step_by(Duration::from_millis(50));
|
||||
sim_testbench.step_until(Duration::from_millis(50)).unwrap();
|
||||
|
||||
request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
||||
sim_testbench
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply_res.is_some());
|
||||
sim_reply = sim_reply_res.unwrap();
|
||||
@@ -271,7 +271,7 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply_res.is_none());
|
||||
}
|
||||
@@ -286,7 +286,7 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply_res.is_some());
|
||||
let sim_reply = sim_reply_res.unwrap();
|
||||
@@ -307,7 +307,7 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply_res.is_some());
|
||||
let sim_reply = sim_reply_res.unwrap();
|
||||
@@ -338,7 +338,7 @@ pub mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step_by(Duration::from_millis(5));
|
||||
sim_testbench.step_until(Duration::from_millis(5)).unwrap();
|
||||
|
||||
check_mgt_hk(
|
||||
&mut sim_testbench,
|
||||
@@ -347,7 +347,9 @@ pub mod tests {
|
||||
torquing: true,
|
||||
},
|
||||
);
|
||||
sim_testbench.step_by(Duration::from_millis(100));
|
||||
sim_testbench
|
||||
.step_until(Duration::from_millis(100))
|
||||
.unwrap();
|
||||
check_mgt_hk(
|
||||
&mut sim_testbench,
|
||||
MgtHkSet {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
|
||||
use asynchronix::{
|
||||
simulation::{Address, Simulation},
|
||||
use nexosim::{
|
||||
simulation::{Address, Scheduler, Simulation},
|
||||
time::{Clock, MonotonicTime, SystemClock},
|
||||
};
|
||||
use satrs_minisim::{
|
||||
@@ -23,35 +23,55 @@ const MGM_REQ_WIRETAPPING: bool = false;
|
||||
const PCDU_REQ_WIRETAPPING: bool = false;
|
||||
const MGT_REQ_WIRETAPPING: bool = false;
|
||||
|
||||
pub struct ModelAddrWrapper {
|
||||
mgm_0_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
mgm_1_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
pcdu_addr: Address<PcduModel>,
|
||||
mgt_addr: Address<MagnetorquerModel>,
|
||||
}
|
||||
|
||||
// The simulation controller processes requests and drives the simulation.
|
||||
#[allow(dead_code)]
|
||||
pub struct SimController {
|
||||
pub sys_clock: SystemClock,
|
||||
pub request_receiver: mpsc::Receiver<SimRequest>,
|
||||
pub reply_sender: mpsc::Sender<SimReply>,
|
||||
pub simulation: Simulation,
|
||||
pub mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
pub pcdu_addr: Address<PcduModel>,
|
||||
pub mgt_addr: Address<MagnetorquerModel>,
|
||||
pub scheduler: Scheduler,
|
||||
pub addr_wrapper: ModelAddrWrapper,
|
||||
}
|
||||
|
||||
impl ModelAddrWrapper {
|
||||
pub fn new(
|
||||
mgm_0_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
mgm_1_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
pcdu_addr: Address<PcduModel>,
|
||||
mgt_addr: Address<MagnetorquerModel>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mgm_0_addr,
|
||||
mgm_1_addr,
|
||||
pcdu_addr,
|
||||
mgt_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SimController {
|
||||
pub fn new(
|
||||
sys_clock: SystemClock,
|
||||
request_receiver: mpsc::Receiver<SimRequest>,
|
||||
reply_sender: mpsc::Sender<SimReply>,
|
||||
simulation: Simulation,
|
||||
mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
|
||||
pcdu_addr: Address<PcduModel>,
|
||||
mgt_addr: Address<MagnetorquerModel>,
|
||||
scheduler: Scheduler,
|
||||
addr_wrapper: ModelAddrWrapper,
|
||||
) -> Self {
|
||||
Self {
|
||||
sys_clock,
|
||||
request_receiver,
|
||||
reply_sender,
|
||||
simulation,
|
||||
mgm_addr,
|
||||
pcdu_addr,
|
||||
mgt_addr,
|
||||
scheduler,
|
||||
addr_wrapper,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +82,7 @@ impl SimController {
|
||||
// Check for UDP requests every millisecond. Shift the simulator ahead here to prevent
|
||||
// replies lying in the past.
|
||||
t += Duration::from_millis(udp_polling_interval_ms);
|
||||
self.sys_clock.synchronize(t);
|
||||
let _synch_status = self.sys_clock.synchronize(t);
|
||||
self.handle_sim_requests(t_old);
|
||||
self.simulation
|
||||
.step_until(t)
|
||||
@@ -79,7 +99,8 @@ impl SimController {
|
||||
}
|
||||
if let Err(e) = match request.component() {
|
||||
SimComponent::SimCtrl => self.handle_ctrl_request(&request),
|
||||
SimComponent::MgmLis3Mdl => self.handle_mgm_request(&request),
|
||||
SimComponent::Mgm0Lis3Mdl => self.handle_mgm_request(0, &request),
|
||||
SimComponent::Mgm1Lis3Mdl => self.handle_mgm_request(1, &request),
|
||||
SimComponent::Mgt => self.handle_mgt_request(&request),
|
||||
SimComponent::Pcdu => self.handle_pcdu_request(&request),
|
||||
} {
|
||||
@@ -99,7 +120,7 @@ impl SimController {
|
||||
fn handle_ctrl_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||
let sim_ctrl_request = SimCtrlRequest::from_sim_message(request)?;
|
||||
if SIM_CTRL_REQ_WIRETAPPING {
|
||||
log::info!("received sim ctrl request: {:?}", sim_ctrl_request);
|
||||
log::info!("received sim ctrl request: {sim_ctrl_request:?}");
|
||||
}
|
||||
match sim_ctrl_request {
|
||||
SimCtrlRequest::Ping => {
|
||||
@@ -111,18 +132,26 @@ impl SimController {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||
fn handle_mgm_request(
|
||||
&mut self,
|
||||
mgm_idx: usize,
|
||||
request: &SimRequest,
|
||||
) -> Result<(), SimRequestError> {
|
||||
let mgm_request = MgmRequestLis3Mdl::from_sim_message(request)?;
|
||||
if MGM_REQ_WIRETAPPING {
|
||||
log::info!("received MGM request: {:?}", mgm_request);
|
||||
log::info!("received MGM request: {mgm_request:?}");
|
||||
}
|
||||
match mgm_request {
|
||||
MgmRequestLis3Mdl::RequestSensorData => {
|
||||
self.simulation.send_event(
|
||||
MagnetometerModel::send_sensor_values,
|
||||
(),
|
||||
&self.mgm_addr,
|
||||
);
|
||||
let addr = match mgm_idx {
|
||||
0 => &self.addr_wrapper.mgm_0_addr,
|
||||
1 => &self.addr_wrapper.mgm_1_addr,
|
||||
|
||||
_ => panic!("invalid mgm index"),
|
||||
};
|
||||
self.simulation
|
||||
.process_event(MagnetometerModel::send_sensor_values, (), addr)
|
||||
.expect("event execution error for mgm");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -131,19 +160,26 @@ impl SimController {
|
||||
fn handle_pcdu_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||
let pcdu_request = PcduRequest::from_sim_message(request)?;
|
||||
if PCDU_REQ_WIRETAPPING {
|
||||
log::info!("received PCDU request: {:?}", pcdu_request);
|
||||
log::info!("received PCDU request: {pcdu_request:?}");
|
||||
}
|
||||
match pcdu_request {
|
||||
PcduRequest::RequestSwitchInfo => {
|
||||
self.simulation
|
||||
.send_event(PcduModel::request_switch_info, (), &self.pcdu_addr);
|
||||
.process_event(
|
||||
PcduModel::request_switch_info,
|
||||
(),
|
||||
&self.addr_wrapper.pcdu_addr,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
PcduRequest::SwitchDevice { switch, state } => {
|
||||
self.simulation.send_event(
|
||||
self.simulation
|
||||
.process_event(
|
||||
PcduModel::switch_device,
|
||||
(switch, state),
|
||||
&self.pcdu_addr,
|
||||
);
|
||||
&self.addr_wrapper.pcdu_addr,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -152,20 +188,26 @@ impl SimController {
|
||||
fn handle_mgt_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||
let mgt_request = MgtRequest::from_sim_message(request)?;
|
||||
if MGT_REQ_WIRETAPPING {
|
||||
log::info!("received MGT request: {:?}", mgt_request);
|
||||
log::info!("received MGT request: {mgt_request:?}");
|
||||
}
|
||||
match mgt_request {
|
||||
MgtRequest::ApplyTorque { duration, dipole } => self.simulation.send_event(
|
||||
MgtRequest::ApplyTorque { duration, dipole } => self
|
||||
.simulation
|
||||
.process_event(
|
||||
MagnetorquerModel::apply_torque,
|
||||
(duration, dipole),
|
||||
&self.mgt_addr,
|
||||
),
|
||||
MgtRequest::RequestHk => self.simulation.send_event(
|
||||
&self.addr_wrapper.mgt_addr,
|
||||
)
|
||||
.unwrap(),
|
||||
MgtRequest::RequestHk => self
|
||||
.simulation
|
||||
.process_event(
|
||||
MagnetorquerModel::request_housekeeping_data,
|
||||
(),
|
||||
&self.mgt_addr,
|
||||
),
|
||||
}
|
||||
&self.addr_wrapper.mgt_addr,
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -199,7 +241,7 @@ mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending sim ctrl request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply.is_some());
|
||||
let sim_reply = sim_reply.unwrap();
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
|
||||
use asynchronix::{
|
||||
model::{Model, Output},
|
||||
time::Scheduler,
|
||||
use nexosim::{
|
||||
model::{Context, Model},
|
||||
ports::Output,
|
||||
};
|
||||
use satrs::power::SwitchStateBinary;
|
||||
use satrs_minisim::{
|
||||
@@ -14,7 +14,8 @@ pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
||||
|
||||
pub struct PcduModel {
|
||||
pub switcher_map: SwitchMapBinaryWrapper,
|
||||
pub mgm_switch: Output<SwitchStateBinary>,
|
||||
pub mgm_0_switch: Output<SwitchStateBinary>,
|
||||
pub mgm_1_switch: Output<SwitchStateBinary>,
|
||||
pub mgt_switch: Output<SwitchStateBinary>,
|
||||
pub reply_sender: mpsc::Sender<SimReply>,
|
||||
}
|
||||
@@ -23,15 +24,15 @@ impl PcduModel {
|
||||
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||
Self {
|
||||
switcher_map: Default::default(),
|
||||
mgm_switch: Output::new(),
|
||||
mgm_0_switch: Output::new(),
|
||||
mgm_1_switch: Output::new(),
|
||||
mgt_switch: Output::new(),
|
||||
reply_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_switch_info(&mut self, _: (), scheduler: &Scheduler<Self>) {
|
||||
scheduler
|
||||
.schedule_event(
|
||||
pub async fn request_switch_info(&mut self, _: (), cx: &mut Context<Self>) {
|
||||
cx.schedule_event(
|
||||
Duration::from_millis(SWITCH_INFO_DELAY_MS),
|
||||
Self::send_switch_info,
|
||||
(),
|
||||
@@ -56,7 +57,7 @@ impl PcduModel {
|
||||
*val = switch_and_target_state.1;
|
||||
match switch_and_target_state.0 {
|
||||
PcduSwitch::Mgm => {
|
||||
self.mgm_switch.send(switch_and_target_state.1).await;
|
||||
self.mgm_0_switch.send(switch_and_target_state.1).await;
|
||||
}
|
||||
PcduSwitch::Mgt => {
|
||||
self.mgt_switch.send(switch_and_target_state.1).await;
|
||||
@@ -92,7 +93,7 @@ pub(crate) mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM switch request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -113,7 +114,7 @@ pub(crate) mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step();
|
||||
sim_testbench.step().unwrap();
|
||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply.is_some());
|
||||
let sim_reply = sim_reply.unwrap();
|
||||
@@ -143,12 +144,12 @@ pub(crate) mod tests {
|
||||
.send_request(request)
|
||||
.expect("sending MGM request failed");
|
||||
sim_testbench.handle_sim_requests_time_agnostic();
|
||||
sim_testbench.step_by(Duration::from_millis(1));
|
||||
sim_testbench.step_until(Duration::from_millis(1)).unwrap();
|
||||
|
||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply.is_none());
|
||||
// Reply takes 20ms
|
||||
sim_testbench.step_by(Duration::from_millis(25));
|
||||
sim_testbench.step_until(Duration::from_millis(25)).unwrap();
|
||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||
assert!(sim_reply.is_some());
|
||||
let sim_reply = sim_reply.unwrap();
|
||||
|
@@ -1,11 +1,12 @@
|
||||
use asynchronix::time::MonotonicTime;
|
||||
use nexosim::time::MonotonicTime;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub enum SimComponent {
|
||||
SimCtrl,
|
||||
MgmLis3Mdl,
|
||||
Mgm0Lis3Mdl,
|
||||
Mgm1Lis3Mdl,
|
||||
Mgt,
|
||||
Pcdu,
|
||||
}
|
||||
@@ -277,7 +278,7 @@ pub mod acs {
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl {
|
||||
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
|
||||
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||
}
|
||||
|
||||
// Normally, small magnetometers generate their output as a signed 16 bit raw format or something
|
||||
@@ -368,7 +369,7 @@ pub mod acs {
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply {
|
||||
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
|
||||
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||
}
|
||||
|
||||
impl MgmReplyProvider for MgmLis3MdlReply {
|
||||
@@ -418,7 +419,7 @@ pub mod acs {
|
||||
}
|
||||
|
||||
impl SerializableSimMsgPayload<SimReply> for MgtReply {
|
||||
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
|
||||
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use acs::{MagnetometerModel, MagnetorquerModel};
|
||||
use asynchronix::simulation::{Mailbox, SimInit};
|
||||
use asynchronix::time::{MonotonicTime, SystemClock};
|
||||
use controller::SimController;
|
||||
use controller::{ModelAddrWrapper, SimController};
|
||||
use eps::PcduModel;
|
||||
use nexosim::simulation::{Mailbox, SimInit};
|
||||
use nexosim::time::{MonotonicTime, SystemClock};
|
||||
use satrs_minisim::udp::SIM_CTRL_PORT;
|
||||
use satrs_minisim::{SimReply, SimRequest};
|
||||
use std::sync::mpsc;
|
||||
@@ -31,11 +31,15 @@ fn create_sim_controller(
|
||||
request_receiver: mpsc::Receiver<SimRequest>,
|
||||
) -> SimController {
|
||||
// Instantiate models and their mailboxes.
|
||||
let mgm_model =
|
||||
let mgm_0_model =
|
||||
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
|
||||
let mgm_1_model =
|
||||
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
|
||||
|
||||
let mgm_mailbox = Mailbox::new();
|
||||
let mgm_addr = mgm_mailbox.address();
|
||||
let mgm_0_mailbox = Mailbox::new();
|
||||
let mgm_0_addr = mgm_0_mailbox.address();
|
||||
let mgm_1_mailbox = Mailbox::new();
|
||||
let mgm_1_addr = mgm_1_mailbox.address();
|
||||
let pcdu_mailbox = Mailbox::new();
|
||||
let pcdu_addr = pcdu_mailbox.address();
|
||||
let mgt_mailbox = Mailbox::new();
|
||||
@@ -43,8 +47,11 @@ fn create_sim_controller(
|
||||
|
||||
let mut pcdu_model = PcduModel::new(reply_sender.clone());
|
||||
pcdu_model
|
||||
.mgm_switch
|
||||
.connect(MagnetometerModel::switch_device, &mgm_addr);
|
||||
.mgm_0_switch
|
||||
.connect(MagnetometerModel::switch_device, &mgm_0_addr);
|
||||
pcdu_model
|
||||
.mgm_1_switch
|
||||
.connect(MagnetometerModel::switch_device, &mgm_1_addr);
|
||||
|
||||
let mut mgt_model = MagnetorquerModel::new(reply_sender.clone());
|
||||
// Input connections.
|
||||
@@ -52,9 +59,14 @@ fn create_sim_controller(
|
||||
.mgt_switch
|
||||
.connect(MagnetorquerModel::switch_device, &mgt_addr);
|
||||
// Output connections.
|
||||
mgt_model
|
||||
.gen_magnetic_field
|
||||
.connect(MagnetometerModel::apply_external_magnetic_field, &mgm_addr);
|
||||
mgt_model.gen_magnetic_field.connect(
|
||||
MagnetometerModel::apply_external_magnetic_field,
|
||||
&mgm_0_addr,
|
||||
);
|
||||
mgt_model.gen_magnetic_field.connect(
|
||||
MagnetometerModel::apply_external_magnetic_field,
|
||||
&mgm_1_addr,
|
||||
);
|
||||
|
||||
// Instantiate the simulator
|
||||
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
||||
@@ -63,19 +75,21 @@ fn create_sim_controller(
|
||||
} else {
|
||||
SimInit::new()
|
||||
};
|
||||
let simulation = sim_init
|
||||
.add_model(mgm_model, mgm_mailbox)
|
||||
.add_model(pcdu_model, pcdu_mailbox)
|
||||
.add_model(mgt_model, mgt_mailbox)
|
||||
.init(start_time);
|
||||
let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr);
|
||||
let (simulation, scheduler) = sim_init
|
||||
.add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model")
|
||||
.add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model")
|
||||
.add_model(pcdu_model, pcdu_mailbox, "PCDU model")
|
||||
.add_model(mgt_model, mgt_mailbox, "MGT model")
|
||||
.init(start_time)
|
||||
.unwrap();
|
||||
SimController::new(
|
||||
sys_clock,
|
||||
request_receiver,
|
||||
reply_sender,
|
||||
simulation,
|
||||
mgm_addr,
|
||||
pcdu_addr,
|
||||
mgt_addr,
|
||||
scheduler,
|
||||
addrs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,7 +130,7 @@ fn main() {
|
||||
let mut udp_server =
|
||||
SimUdpServer::new(SIM_CTRL_PORT, request_sender, reply_receiver, 200, None)
|
||||
.expect("could not create UDP request server");
|
||||
log::info!("starting UDP server on port {}", SIM_CTRL_PORT);
|
||||
log::info!("starting UDP server on port {SIM_CTRL_PORT}");
|
||||
// This thread manages the simulator UDP server.
|
||||
let udp_tc_thread = thread::spawn(move || {
|
||||
udp_server.run();
|
||||
|
@@ -1,7 +1,10 @@
|
||||
use delegate::delegate;
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
use std::sync::mpsc;
|
||||
|
||||
use asynchronix::time::MonotonicTime;
|
||||
use nexosim::{
|
||||
simulation::ExecutionError,
|
||||
time::{Deadline, MonotonicTime},
|
||||
};
|
||||
use satrs_minisim::{SimReply, SimRequest};
|
||||
|
||||
use crate::{controller::SimController, create_sim_controller, ThreadingModel};
|
||||
@@ -35,8 +38,8 @@ impl SimTestbench {
|
||||
pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime);
|
||||
}
|
||||
to self.sim_controller.simulation {
|
||||
pub fn step(&mut self);
|
||||
pub fn step_by(&mut self, duration: Duration);
|
||||
pub fn step(&mut self) -> Result<(), ExecutionError>;
|
||||
pub fn step_until(&mut self, duration: impl Deadline) -> Result<(), ExecutionError>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use asynchronix::time::MonotonicTime;
|
||||
use nexosim::time::MonotonicTime;
|
||||
|
||||
pub fn current_millis(time: MonotonicTime) -> u64 {
|
||||
(time.as_secs() as u64 * 1000) + (time.subsec_nanos() as u64 / 1_000_000)
|
||||
|
@@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.2.3] 2025-07-22
|
||||
|
||||
`spacepackets` range v0.14 to v0.15
|
||||
|
||||
# [v0.2.2] 2025-05-10
|
||||
|
||||
- Bump to `spacepackests` v0.14
|
||||
|
||||
# [v0.2.1] 2024-11-15
|
||||
|
||||
Increased allowed spacepackets to v0.13
|
||||
|
||||
# [v0.2.0] 2024-11-04
|
||||
|
||||
Semver bump, due to added features in v0.1.4
|
||||
@@ -37,3 +49,7 @@ Allow `spacepackets` range starting with v0.10 and v0.11.
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release.
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.3...HEAD
|
||||
[v0.2.3]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.1...satrs-shared-v0.2.3
|
||||
[v0.2.2]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.1...satrs-shared-v0.2.2
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "satrs-shared"
|
||||
description = "Components shared by multiple sat-rs crates"
|
||||
version = "0.2.0"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||
@@ -11,6 +11,7 @@ license = "Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
spacepackets = { version = ">=0.14, <=0.15", default-features = false }
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
@@ -18,12 +19,9 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3"
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.spacepackets]
|
||||
version = ">0.9, <=0.12"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "spacepackets/serde"]
|
||||
|
@@ -8,14 +8,53 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
- Bump `sat-rs` edition to 2024.
|
||||
|
||||
## Changed
|
||||
|
||||
Some trait renaming to be more in-line with Rust naming conventions.
|
||||
|
||||
- `EventTmHookProvider` -> `EventTmHook`
|
||||
- `ActiveRequestProvider` -> `ActiveRequest`
|
||||
- `EcssTcInMemConversionProvider` -> `CacheAndReadRawEcssTc`
|
||||
- `ActiveRequestMapProvider` -> `ActiveRequestStore`
|
||||
- `CountdownProvider` -> `Countdown`
|
||||
|
||||
# [v0.3.0-alpha.2] 2025-07-22
|
||||
|
||||
`satrs-shared` update
|
||||
|
||||
# [v0.3.0-alpha.1] 2025-07-22
|
||||
|
||||
`spacepackets` range v0.14 to v0.15
|
||||
|
||||
# [v0.3.0-alpha.0] 2025-02-18
|
||||
|
||||
`spacepackets` v0.13
|
||||
|
||||
## Changed
|
||||
|
||||
- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new
|
||||
`new` implementation expects a type struct instead of tuples.
|
||||
- Moved `cfdp` module to [dedicated crate](https://egit.irs.uni-stuttgart.de/rust/cfdp)
|
||||
- Moved `seq_count` module to [spacepackets](https://egit.irs.uni-stuttgart.de/rust/spacepackets)
|
||||
crate
|
||||
|
||||
## Added
|
||||
|
||||
- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers.
|
||||
- Scheduling table for systems with a standard runtime
|
||||
- Mode Tree Feature which allows building a network of mode components which can send mode
|
||||
messages to each other.
|
||||
- Added first helper features like the `SubsystemExecutionHelper` and the
|
||||
`SubsystemCommandingHelper` which allows to build subsystem components. Subsystem components
|
||||
are able to execute mode sequences and perform target keeping based on a declarative table
|
||||
format.
|
||||
- Added `DevManagerCommandingHelper` which performs some of the boilerplate logik required
|
||||
by Assembly and Device Management components. This includes forwarding mode requests and
|
||||
handling mode replies.
|
||||
- First basic health module with `HealthState`s and the `HealthTableProvider` trait. These
|
||||
components are important for any FDIR components which get added in the future.
|
||||
|
||||
# [v0.2.1] 2024-05-19
|
||||
|
||||
@@ -179,3 +218,8 @@ docs-rs hotfix
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release.
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.2...HEAD
|
||||
[v0.3.0-alpha.2]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.1...satrs-v0.3.0-alpha.2
|
||||
[v0.3.0-alpha.1]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.0...satrs-v0.3.0-alpha.1
|
||||
[v0.3.0-alpha.0]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.2.1...satrs-v0.3.0-alpha.0
|
||||
|
119
satrs/Cargo.toml
119
satrs/Cargo.toml
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "satrs"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.71.1"
|
||||
version = "0.3.0-alpha.2"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = "A framework to build software for remote systems"
|
||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||
description = "A library collection to build software for remote systems"
|
||||
homepage = "https://github.com/us-irs/sat-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "space", "aerospace"]
|
||||
@@ -13,88 +13,34 @@ keywords = ["no-std", "space", "aerospace"]
|
||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||
|
||||
[dependencies]
|
||||
delegate = ">0.7, <=0.10"
|
||||
satrs-shared = { version = "0.2", path = "../satrs-shared" }
|
||||
spacepackets = { version = ">=0.14, <=0.15", default-features = false }
|
||||
|
||||
delegate = ">0.7, <=0.13"
|
||||
paste = "1"
|
||||
derive-new = "0.6"
|
||||
smallvec = "1"
|
||||
crc = "3"
|
||||
derive-new = ">=0.6, <=0.7"
|
||||
num_enum = { version = ">0.5, <=0.7", default-features = false }
|
||||
cobs = { version = "0.4", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
[dependencies.satrs-shared]
|
||||
version = ">=0.1.3, <=0.2"
|
||||
|
||||
[dependencies.num_enum]
|
||||
version = ">0.5, <=0.7"
|
||||
default-features = false
|
||||
|
||||
[dependencies.spacepackets]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
|
||||
[dependencies.cobs]
|
||||
git = "https://github.com/robamu/cobs.rs.git"
|
||||
version = "0.2.3"
|
||||
branch = "all_features"
|
||||
default-features = false
|
||||
|
||||
[dependencies.num-traits]
|
||||
version = "0.2"
|
||||
default-features = false
|
||||
|
||||
[dependencies.dyn-clone]
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.hashbrown]
|
||||
version = "0.14"
|
||||
optional = true
|
||||
|
||||
[dependencies.heapless]
|
||||
version = "0.7"
|
||||
optional = true
|
||||
|
||||
[dependencies.downcast-rs]
|
||||
version = "1.2"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.bus]
|
||||
version = "2.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.crossbeam-channel]
|
||||
version= "0.5"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.socket2]
|
||||
version = "0.5.4"
|
||||
features = ["all"]
|
||||
optional = true
|
||||
|
||||
[dependencies.mio]
|
||||
version = "0.8"
|
||||
features = ["os-poll", "net"]
|
||||
optional = true
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
hashbrown = { version = ">=0.14, <=0.15", optional = true }
|
||||
static_cell = { version = "2", optional = true }
|
||||
dyn-clone = { version = "1", optional = true }
|
||||
heapless = { version = "0.9", optional = true }
|
||||
downcast-rs = { version = "2", default-features = false, optional = true }
|
||||
bus = { version = "2.2", optional = true }
|
||||
crossbeam-channel = { version = "0.5", default-features = false, optional = true }
|
||||
serde = { version = "1", default-features = false, optional = true }
|
||||
socket2 = { version = "0.6", features = ["all"], optional = true }
|
||||
mio = { version = "1", features = ["os-poll", "net"], optional = true }
|
||||
defmt = { version = "1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1"
|
||||
zerocopy = "0.7"
|
||||
zerocopy = "0.8"
|
||||
once_cell = "1"
|
||||
serde_json = "1"
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
tempfile = "3"
|
||||
|
||||
[dev-dependencies.postcard]
|
||||
@@ -106,12 +52,11 @@ std = [
|
||||
"downcast-rs/std",
|
||||
"alloc",
|
||||
"bus",
|
||||
"postcard/use-std",
|
||||
"crossbeam-channel/std",
|
||||
"serde/std",
|
||||
"spacepackets/std",
|
||||
"num_enum/std",
|
||||
"thiserror",
|
||||
"thiserror/std",
|
||||
"socket2",
|
||||
"mio"
|
||||
]
|
||||
@@ -124,11 +69,15 @@ alloc = [
|
||||
]
|
||||
serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"]
|
||||
crossbeam = ["crossbeam-channel"]
|
||||
heapless = ["dep:heapless"]
|
||||
heapless = ["dep:heapless", "static_cell"]
|
||||
defmt = ["dep:defmt", "spacepackets/defmt"]
|
||||
test_util = []
|
||||
doc-images = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[[test]]
|
||||
name = "event_test"
|
||||
path = "tests/pus_events.rs"
|
||||
required-features = ["test_util"]
|
||||
|
@@ -4,5 +4,5 @@
|
||||
sat-rs
|
||||
======
|
||||
|
||||
This crate contains the primary components of the sat-rs framework.
|
||||
This crate contains the primary components of the sat-rs library collection.
|
||||
You can find more information on the [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
||||
|
3
satrs/docs.sh
Executable file
3
satrs/docs.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||
cargo +nightly doc --all-features --open
|
@@ -3,8 +3,7 @@ Checklist for new releases
|
||||
|
||||
# Pre-Release
|
||||
|
||||
1. Make sure any new modules are documented sufficiently enough and check docs with
|
||||
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`.
|
||||
1. Make sure any new modules are documented sufficiently enough and check docs by running `docs.sh`.
|
||||
2. Bump version specifier in `Cargo.toml`.
|
||||
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
|
||||
`unreleased` section.
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,769 +0,0 @@
|
||||
use alloc::string::{String, ToString};
|
||||
use core::fmt::Display;
|
||||
use crc::{Crc, CRC_32_CKSUM};
|
||||
use spacepackets::cfdp::ChecksumType;
|
||||
use spacepackets::ByteConversionError;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std_mod::*;
|
||||
|
||||
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FilestoreError {
|
||||
FileDoesNotExist,
|
||||
FileAlreadyExists,
|
||||
DirDoesNotExist,
|
||||
Permission,
|
||||
IsNotFile,
|
||||
IsNotDirectory,
|
||||
ByteConversion(ByteConversionError),
|
||||
Io {
|
||||
raw_errno: Option<i32>,
|
||||
string: String,
|
||||
},
|
||||
ChecksumTypeNotImplemented(ChecksumType),
|
||||
}
|
||||
|
||||
impl From<ByteConversionError> for FilestoreError {
|
||||
fn from(value: ByteConversionError) -> Self {
|
||||
Self::ByteConversion(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FilestoreError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FilestoreError::FileDoesNotExist => {
|
||||
write!(f, "file does not exist")
|
||||
}
|
||||
FilestoreError::FileAlreadyExists => {
|
||||
write!(f, "file already exists")
|
||||
}
|
||||
FilestoreError::DirDoesNotExist => {
|
||||
write!(f, "directory does not exist")
|
||||
}
|
||||
FilestoreError::Permission => {
|
||||
write!(f, "permission error")
|
||||
}
|
||||
FilestoreError::IsNotFile => {
|
||||
write!(f, "is not a file")
|
||||
}
|
||||
FilestoreError::IsNotDirectory => {
|
||||
write!(f, "is not a directory")
|
||||
}
|
||||
FilestoreError::ByteConversion(e) => {
|
||||
write!(f, "filestore error: {e}")
|
||||
}
|
||||
FilestoreError::Io { raw_errno, string } => {
|
||||
write!(
|
||||
f,
|
||||
"filestore generic IO error with raw errno {:?}: {}",
|
||||
raw_errno, string
|
||||
)
|
||||
}
|
||||
FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
|
||||
write!(f, "checksum {:?} not implemented", checksum_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FilestoreError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
FilestoreError::ByteConversion(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl From<std::io::Error> for FilestoreError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::Io {
|
||||
raw_errno: value.raw_os_error(),
|
||||
string: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VirtualFilestore {
|
||||
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||
|
||||
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||
|
||||
/// Truncating a file means deleting all its data so the resulting file is empty.
|
||||
/// This can be more efficient than removing and re-creating a file.
|
||||
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||
|
||||
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
|
||||
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
|
||||
|
||||
fn read_data(
|
||||
&self,
|
||||
file_path: &str,
|
||||
offset: u64,
|
||||
read_len: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(), FilestoreError>;
|
||||
|
||||
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
|
||||
|
||||
fn filename_from_full_path(path: &str) -> Option<&str>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Convert the path string to a Path
|
||||
let path = Path::new(path);
|
||||
|
||||
// Extract the file name using the file_name() method
|
||||
path.file_name().and_then(|name| name.to_str())
|
||||
}
|
||||
|
||||
fn is_file(&self, path: &str) -> bool;
|
||||
|
||||
fn is_dir(&self, path: &str) -> bool {
|
||||
!self.is_file(path)
|
||||
}
|
||||
|
||||
fn exists(&self, path: &str) -> bool;
|
||||
|
||||
/// This special function is the CFDP specific abstraction to verify the checksum of a file.
|
||||
/// This allows to keep OS specific details like reading the whole file in the most efficient
|
||||
/// manner inside the file system abstraction.
|
||||
///
|
||||
/// The passed verification buffer argument will be used by the specific implementation as
|
||||
/// a buffer to read the file into. It is recommended to use common buffer sizes like
|
||||
/// 4096 or 8192 bytes.
|
||||
fn checksum_verify(
|
||||
&self,
|
||||
file_path: &str,
|
||||
checksum_type: ChecksumType,
|
||||
expected_checksum: u32,
|
||||
verification_buf: &mut [u8],
|
||||
) -> Result<bool, FilestoreError>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod std_mod {
|
||||
use super::*;
|
||||
use std::{
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NativeFilestore {}
|
||||
|
||||
impl VirtualFilestore for NativeFilestore {
|
||||
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||
if self.exists(file_path) {
|
||||
return Err(FilestoreError::FileAlreadyExists);
|
||||
}
|
||||
File::create(file_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||
if !self.exists(file_path) {
|
||||
return Err(FilestoreError::FileDoesNotExist);
|
||||
}
|
||||
if !self.is_file(file_path) {
|
||||
return Err(FilestoreError::IsNotFile);
|
||||
}
|
||||
fs::remove_file(file_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||
if !self.exists(file_path) {
|
||||
return Err(FilestoreError::FileDoesNotExist);
|
||||
}
|
||||
if !self.is_file(file_path) {
|
||||
return Err(FilestoreError::IsNotFile);
|
||||
}
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(file_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
|
||||
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
|
||||
raw_errno: e.raw_os_error(),
|
||||
string: e.to_string(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
|
||||
if !self.exists(dir_path) {
|
||||
return Err(FilestoreError::DirDoesNotExist);
|
||||
}
|
||||
if !self.is_dir(dir_path) {
|
||||
return Err(FilestoreError::IsNotDirectory);
|
||||
}
|
||||
if !all {
|
||||
fs::remove_dir(dir_path)?;
|
||||
return Ok(());
|
||||
}
|
||||
fs::remove_dir_all(dir_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_data(
|
||||
&self,
|
||||
file_name: &str,
|
||||
offset: u64,
|
||||
read_len: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(), FilestoreError> {
|
||||
if buf.len() < read_len as usize {
|
||||
return Err(ByteConversionError::ToSliceTooSmall {
|
||||
found: buf.len(),
|
||||
expected: read_len as usize,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if !self.exists(file_name) {
|
||||
return Err(FilestoreError::FileDoesNotExist);
|
||||
}
|
||||
if !self.is_file(file_name) {
|
||||
return Err(FilestoreError::IsNotFile);
|
||||
}
|
||||
let mut file = File::open(file_name)?;
|
||||
file.seek(SeekFrom::Start(offset))?;
|
||||
file.read_exact(&mut buf[0..read_len as usize])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
|
||||
if !self.exists(file) {
|
||||
return Err(FilestoreError::FileDoesNotExist);
|
||||
}
|
||||
if !self.is_file(file) {
|
||||
return Err(FilestoreError::IsNotFile);
|
||||
}
|
||||
let mut file = OpenOptions::new().write(true).open(file)?;
|
||||
file.seek(SeekFrom::Start(offset))?;
|
||||
file.write_all(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_file(&self, path: &str) -> bool {
|
||||
let path = Path::new(path);
|
||||
path.is_file()
|
||||
}
|
||||
|
||||
fn exists(&self, path: &str) -> bool {
|
||||
let path = Path::new(path);
|
||||
if !path.exists() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn checksum_verify(
|
||||
&self,
|
||||
file_path: &str,
|
||||
checksum_type: ChecksumType,
|
||||
expected_checksum: u32,
|
||||
verification_buf: &mut [u8],
|
||||
) -> Result<bool, FilestoreError> {
|
||||
match checksum_type {
|
||||
ChecksumType::Modular => {
|
||||
if self.calc_modular_checksum(file_path)? == expected_checksum {
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
ChecksumType::Crc32 => {
|
||||
let mut digest = CRC_32.digest();
|
||||
let file_to_check = File::open(file_path)?;
|
||||
let mut buf_reader = BufReader::new(file_to_check);
|
||||
loop {
|
||||
let bytes_read = buf_reader.read(verification_buf)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
digest.update(&verification_buf[0..bytes_read]);
|
||||
}
|
||||
if digest.finalize() == expected_checksum {
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
ChecksumType::NullChecksum => Ok(true),
|
||||
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeFilestore {
|
||||
pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
|
||||
let mut checksum: u32 = 0;
|
||||
let file = File::open(file_path)?;
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
let mut buffer = [0; 4];
|
||||
|
||||
loop {
|
||||
let bytes_read = buf_reader.read(&mut buffer)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
// Perform padding directly in the buffer
|
||||
(bytes_read..4).for_each(|i| {
|
||||
buffer[i] = 0;
|
||||
});
|
||||
|
||||
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||
}
|
||||
Ok(checksum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, path::Path, println};
|
||||
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
use tempfile::tempdir;
|
||||
|
||||
const EXAMPLE_DATA_CFDP: [u8; 15] = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
];
|
||||
|
||||
const NATIVE_FS: NativeFilestore = NativeFilestore {};
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_filestore_create() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result =
|
||||
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||
assert!(result.is_ok());
|
||||
let path = Path::new(&file_path);
|
||||
assert!(path.exists());
|
||||
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_fs_file_exists() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_fs_dir_exists() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let dir_path = tmpdir.path().join("testdir");
|
||||
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
assert!(NATIVE_FS.is_dir(dir_path.as_path().to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_fs_remove_file() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.expect("creating file failed");
|
||||
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.remove_file(file_path.to_str().unwrap())
|
||||
.expect("removing file failed");
|
||||
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_fs_write() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||
println!("{}", file_path.to_str().unwrap());
|
||||
let write_data = "hello world\n";
|
||||
NATIVE_FS
|
||||
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
|
||||
.expect("writing to file failed");
|
||||
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
|
||||
assert_eq!(read_back, write_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_native_fs_read() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||
println!("{}", file_path.to_str().unwrap());
|
||||
let write_data = "hello world\n";
|
||||
NATIVE_FS
|
||||
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
|
||||
.expect("writing to file failed");
|
||||
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
|
||||
assert_eq!(read_back, write_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate_file() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.expect("creating file failed");
|
||||
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
|
||||
NATIVE_FS
|
||||
.truncate_file(file_path.to_str().unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(fs::read(file_path.clone()).unwrap(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dir() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let dir_path = tmpdir.path().join("testdir");
|
||||
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
NATIVE_FS
|
||||
.remove_dir(dir_path.to_str().unwrap(), false)
|
||||
.unwrap();
|
||||
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_file() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.expect("creating file failed");
|
||||
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
|
||||
let read_buf: &mut [u8] = &mut [0; 4];
|
||||
NATIVE_FS
|
||||
.read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
|
||||
.unwrap();
|
||||
assert_eq!([1, 2, 3, 4], read_buf);
|
||||
NATIVE_FS
|
||||
.write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
|
||||
.expect("writing to file failed");
|
||||
NATIVE_FS
|
||||
.read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
|
||||
.unwrap();
|
||||
assert_eq!([3, 4, 5, 6], read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_which_does_not_exist() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::FileDoesNotExist = error {
|
||||
assert_eq!(error.to_string(), "file does not exist");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_already_exists() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result =
|
||||
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||
assert!(result.is_ok());
|
||||
let result =
|
||||
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::FileAlreadyExists = error {
|
||||
assert_eq!(error.to_string(), "file already exists");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_file_with_dir_api() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::IsNotDirectory = error {
|
||||
assert_eq!(error.to_string(), "is not a directory");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dir_remove_all() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let dir_path = tmpdir.path().join("test");
|
||||
NATIVE_FS
|
||||
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let file_path = dir_path.as_path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
|
||||
assert!(result.is_ok());
|
||||
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dir_with_file_api() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test");
|
||||
NATIVE_FS
|
||||
.create_dir(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::IsNotFile = error {
|
||||
assert_eq!(error.to_string(), "is not a file");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dir_which_does_not_exist() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test");
|
||||
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::DirDoesNotExist = error {
|
||||
assert_eq!(error.to_string(), "directory does not exist");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_file_which_does_not_exist() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::FileDoesNotExist = error {
|
||||
assert_eq!(error.to_string(), "file does not exist");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate_file_which_does_not_exist() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::FileDoesNotExist = error {
|
||||
assert_eq!(error.to_string(), "file does not exist");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate_file_on_directory() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test");
|
||||
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
|
||||
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::IsNotFile = error {
|
||||
assert_eq!(error.to_string(), "is not a file");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_byte_conversion_error_when_reading() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::ByteConversion(byte_conv_error) = error {
|
||||
if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
|
||||
assert_eq!(found, 0);
|
||||
assert_eq!(expected, 2);
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!("filestore error: {}", byte_conv_error)
|
||||
);
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_file_on_dir() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let dir_path = tmpdir.path().join("test");
|
||||
NATIVE_FS
|
||||
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::IsNotFile = error {
|
||||
assert_eq!(error.to_string(), "is not a file");
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_non_existing() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::FileDoesNotExist = error {
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_on_dir() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test");
|
||||
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
|
||||
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::IsNotFile = error {
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename_extraction() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("test.txt");
|
||||
NATIVE_FS
|
||||
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||
.unwrap();
|
||||
NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modular_checksum() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||
fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
|
||||
// Kind of re-writing the modular checksum impl here which we are trying to test, but the
|
||||
// numbers/correctness were verified manually using calculators, so this is okay.
|
||||
let mut checksum: u32 = 0;
|
||||
let mut buffer: [u8; 4] = [0; 4];
|
||||
for i in 0..3 {
|
||||
buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
|
||||
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||
}
|
||||
buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
|
||||
buffer[3] = 0;
|
||||
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||
let mut verif_buf: [u8; 32] = [0; 32];
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::Modular,
|
||||
checksum,
|
||||
&mut verif_buf,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_checksum_impl() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||
// The file to check does not even need to exist, and the verification buffer can be
|
||||
// empty: the null checksum is always yields the same result.
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::NullChecksum,
|
||||
0,
|
||||
&mut [],
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checksum_not_implemented() {
|
||||
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||
// The file to check does not even need to exist, and the verification buffer can be
|
||||
// empty: the null checksum is always yields the same result.
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::Crc32Proximity1,
|
||||
0,
|
||||
&mut [],
|
||||
);
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!("checksum {:?} not implemented", cksum_type)
|
||||
);
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,668 +0,0 @@
|
||||
//! This module contains the implementation of the CFDP high level classes as specified in the
|
||||
//! CCSDS 727.0-B-5.
|
||||
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
||||
|
||||
use crc::{Crc, CRC_32_CKSUM};
|
||||
use hashbrown::HashMap;
|
||||
use spacepackets::{
|
||||
cfdp::{
|
||||
pdu::{FileDirectiveType, PduError, PduHeader},
|
||||
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
|
||||
},
|
||||
util::{UnsignedByteField, UnsignedEnum},
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::time::CountdownProvider;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod dest;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod filestore;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod source;
|
||||
pub mod user;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EntityType {
|
||||
Sending,
|
||||
Receiving,
|
||||
}
|
||||
|
||||
pub enum TimerContext {
|
||||
CheckLimit {
|
||||
local_id: UnsignedByteField,
|
||||
remote_id: UnsignedByteField,
|
||||
entity_type: EntityType,
|
||||
},
|
||||
NakActivity {
|
||||
expiry_time_seconds: f32,
|
||||
},
|
||||
PositiveAck {
|
||||
expiry_time_seconds: f32,
|
||||
},
|
||||
}
|
||||
|
||||
/// A generic trait which allows CFDP entities to create check timers which are required to
|
||||
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
|
||||
/// and 4.6.3.3.
|
||||
///
|
||||
/// This trait also allows the creation of different check timers depending on context and purpose
|
||||
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
|
||||
/// other factors.
|
||||
///
|
||||
/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
|
||||
///
|
||||
/// ## 1. Check limit handling
|
||||
///
|
||||
/// The first mechanism is the check limit handling for unacknowledged transfers as specified
|
||||
/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
|
||||
/// For this mechanism, the timer has different functionality depending on whether
|
||||
/// the using entity is the sending entity or the receiving entity for the unacknowledged
|
||||
/// transmission mode.
|
||||
///
|
||||
/// For the sending entity, this timer determines the expiry period for declaring a check limit
|
||||
/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
|
||||
/// Also see 4.6.3.2 of the CFDP standard.
|
||||
///
|
||||
/// For the receiving entity, this timer determines the expiry period for incrementing a check
|
||||
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
|
||||
///
|
||||
/// ## 2. NAK activity limit
|
||||
///
|
||||
/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
|
||||
/// standard. The expiration period will be provided by the NAK timer expiration limit of the
|
||||
/// remote entity configuration.
|
||||
///
|
||||
/// ## 3. Positive ACK procedures
|
||||
///
|
||||
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
|
||||
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
|
||||
/// interval of the remote entity configuration.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait CheckTimerCreator {
|
||||
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CountdownProvider>;
|
||||
}
|
||||
|
||||
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
|
||||
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Debug)]
|
||||
pub struct StdCheckTimer {
|
||||
expiry_time_seconds: u64,
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl StdCheckTimer {
|
||||
pub fn new(expiry_time_seconds: u64) -> Self {
|
||||
Self {
|
||||
expiry_time_seconds,
|
||||
start_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl CountdownProvider for StdCheckTimer {
|
||||
fn has_expired(&self) -> bool {
|
||||
let elapsed_time = self.start_time.elapsed();
|
||||
if elapsed_time.as_secs() > self.expiry_time_seconds {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.start_time = std::time::Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure models the remote entity configuration information as specified in chapter 8.3
|
||||
/// of the CFDP standard.
|
||||
|
||||
/// Some of the fields which were not considered necessary for the Rust implementation
|
||||
/// were omitted. Some other fields which are not contained inside the standard but are considered
|
||||
/// necessary for the Rust implementation are included.
|
||||
///
|
||||
/// ## Notes on Positive Acknowledgment Procedures
|
||||
///
|
||||
/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
|
||||
/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
|
||||
/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
|
||||
/// Once the expected ACK response has not been received for that interval, as counter will be
|
||||
/// incremented and the timer will be reset. Once the counter exceeds the
|
||||
/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
|
||||
///
|
||||
/// ## Notes on Deferred Lost Segment Procedures
|
||||
///
|
||||
/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
|
||||
/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
|
||||
/// reset when missing segments or missing metadata is received. The timer will be deactivated if
|
||||
/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
|
||||
/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
|
||||
///
|
||||
/// ## Fields
|
||||
///
|
||||
/// * `entity_id` - The ID of the remote entity.
|
||||
/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
|
||||
/// to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
|
||||
/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
|
||||
/// of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
|
||||
/// to None, the maximum file segment length will be derived from the maximum packet length.
|
||||
/// If this has some value which is smaller than the segment value derived from
|
||||
/// `max_packet_len`, this value will be picked.
|
||||
/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
|
||||
/// the Put Request, it will be determined from this field in the remote configuration.
|
||||
/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
|
||||
/// Request, it will be determined from this field in the remote configuration.
|
||||
/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
|
||||
/// Put Request, it will be determined from this field in the remote configuration.
|
||||
/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
|
||||
/// transaction cancellation. Defaults to False.
|
||||
/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
|
||||
/// this remote entity.
|
||||
/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
|
||||
/// after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
|
||||
/// 2, so the check limit timer may expire twice.
|
||||
/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
|
||||
/// Procedures inside the class documentation. Expected as floating point seconds. Defaults to
|
||||
/// 10 seconds.
|
||||
/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
|
||||
/// Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
|
||||
/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
|
||||
/// file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
|
||||
/// * `nak_timer_interval_seconds` - See the notes on the Deferred Lost Segment Procedure inside
|
||||
/// the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
|
||||
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
|
||||
/// the class documentation. Defaults to 2, so the timer may expire two times.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RemoteEntityConfig {
|
||||
pub entity_id: UnsignedByteField,
|
||||
pub max_packet_len: usize,
|
||||
pub max_file_segment_len: usize,
|
||||
pub closure_requested_by_default: bool,
|
||||
pub crc_on_transmission_by_default: bool,
|
||||
pub default_transmission_mode: TransmissionMode,
|
||||
pub default_crc_type: ChecksumType,
|
||||
pub positive_ack_timer_interval_seconds: f32,
|
||||
pub positive_ack_timer_expiration_limit: u32,
|
||||
pub check_limit: u32,
|
||||
pub disposition_on_cancellation: bool,
|
||||
pub immediate_nak_mode: bool,
|
||||
pub nak_timer_interval_seconds: f32,
|
||||
pub nak_timer_expiration_limit: u32,
|
||||
}
|
||||
|
||||
impl RemoteEntityConfig {
|
||||
pub fn new_with_default_values(
|
||||
entity_id: UnsignedByteField,
|
||||
max_file_segment_len: usize,
|
||||
max_packet_len: usize,
|
||||
closure_requested_by_default: bool,
|
||||
crc_on_transmission_by_default: bool,
|
||||
default_transmission_mode: TransmissionMode,
|
||||
default_crc_type: ChecksumType,
|
||||
) -> Self {
|
||||
Self {
|
||||
entity_id,
|
||||
max_file_segment_len,
|
||||
max_packet_len,
|
||||
closure_requested_by_default,
|
||||
crc_on_transmission_by_default,
|
||||
default_transmission_mode,
|
||||
default_crc_type,
|
||||
check_limit: 2,
|
||||
positive_ack_timer_interval_seconds: 10.0,
|
||||
positive_ack_timer_expiration_limit: 2,
|
||||
disposition_on_cancellation: false,
|
||||
immediate_nak_mode: true,
|
||||
nak_timer_interval_seconds: 10.0,
|
||||
nak_timer_expiration_limit: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemoteEntityConfigProvider {
|
||||
/// Retrieve the remote entity configuration for the given remote ID.
|
||||
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
|
||||
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
|
||||
/// Add a new remote configuration. Return [true] if the configuration was
|
||||
/// inserted successfully, and [false] if a configuration already exists.
|
||||
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
|
||||
/// Remote a configuration. Returns [true] if the configuration was removed successfully,
|
||||
/// and [false] if no configuration exists for the given remote ID.
|
||||
fn remove_config(&mut self, remote_id: u64) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Default)]
|
||||
pub struct StdRemoteEntityConfigProvider {
|
||||
remote_cfg_table: HashMap<u64, RemoteEntityConfig>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
|
||||
self.remote_cfg_table.get(&remote_id)
|
||||
}
|
||||
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
|
||||
self.remote_cfg_table.get_mut(&remote_id)
|
||||
}
|
||||
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
|
||||
self.remote_cfg_table
|
||||
.insert(cfg.entity_id.value(), *cfg)
|
||||
.is_some()
|
||||
}
|
||||
fn remove_config(&mut self, remote_id: u64) -> bool {
|
||||
self.remote_cfg_table.remove(&remote_id).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait introduces some callbacks which will be called when a particular CFDP fault
|
||||
/// handler is called.
|
||||
///
|
||||
/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity
|
||||
/// configuration and provides a way to specify custom user error handlers. This allows to
|
||||
/// implement some CFDP features like fault handler logging, which would not be possible
|
||||
/// generically otherwise.
|
||||
///
|
||||
/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback
|
||||
/// will be called depending on the [FaultHandlerCode].
|
||||
pub trait UserFaultHandler {
|
||||
fn notice_of_suspension_cb(
|
||||
&mut self,
|
||||
transaction_id: TransactionId,
|
||||
cond: ConditionCode,
|
||||
progress: u64,
|
||||
);
|
||||
|
||||
fn notice_of_cancellation_cb(
|
||||
&mut self,
|
||||
transaction_id: TransactionId,
|
||||
cond: ConditionCode,
|
||||
progress: u64,
|
||||
);
|
||||
|
||||
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||
|
||||
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||
}
|
||||
|
||||
/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
|
||||
/// standard.
|
||||
///
|
||||
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
|
||||
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
|
||||
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
|
||||
/// user-provided callback function provided by the [UserFaultHandler].
|
||||
///
|
||||
/// Some note on the provided default settings:
|
||||
///
|
||||
/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
|
||||
/// cancelling the transfer immediately would interfere with the check limit mechanism specified
|
||||
/// in chapter 4.6.3.3.
|
||||
/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
|
||||
/// not supported the file transfer might still have worked properly.
|
||||
///
|
||||
/// For all other faults, the default fault handling operation will be to cancel the transaction.
|
||||
/// These defaults can be overriden by using the [Self::set_fault_handler] method.
|
||||
/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
|
||||
/// entity.
|
||||
pub struct DefaultFaultHandler {
|
||||
handler_array: [FaultHandlerCode; 10],
|
||||
// Could also change the user fault handler trait to have non mutable methods, but that limits
|
||||
// flexbility on the user side..
|
||||
user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
|
||||
}
|
||||
|
||||
impl DefaultFaultHandler {
|
||||
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
|
||||
Some(match conditon_code {
|
||||
ConditionCode::PositiveAckLimitReached => 0,
|
||||
ConditionCode::KeepAliveLimitReached => 1,
|
||||
ConditionCode::InvalidTransmissionMode => 2,
|
||||
ConditionCode::FilestoreRejection => 3,
|
||||
ConditionCode::FileChecksumFailure => 4,
|
||||
ConditionCode::FileSizeError => 5,
|
||||
ConditionCode::NakLimitReached => 6,
|
||||
ConditionCode::InactivityDetected => 7,
|
||||
ConditionCode::CheckLimitReached => 8,
|
||||
ConditionCode::UnsupportedChecksumType => 9,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_fault_handler(
|
||||
&mut self,
|
||||
condition_code: ConditionCode,
|
||||
fault_handler: FaultHandlerCode,
|
||||
) {
|
||||
let array_idx = Self::condition_code_to_array_index(condition_code);
|
||||
if array_idx.is_none() {
|
||||
return;
|
||||
}
|
||||
self.handler_array[array_idx.unwrap()] = fault_handler;
|
||||
}
|
||||
|
||||
pub fn new(user_fault_handler: Box<dyn UserFaultHandler + Send>) -> Self {
|
||||
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
|
||||
init_array
|
||||
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
|
||||
FaultHandlerCode::IgnoreError;
|
||||
init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
|
||||
.unwrap()] = FaultHandlerCode::IgnoreError;
|
||||
Self {
|
||||
handler_array: init_array,
|
||||
user_fault_handler: RefCell::new(user_fault_handler),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
|
||||
let array_idx = Self::condition_code_to_array_index(condition_code);
|
||||
if array_idx.is_none() {
|
||||
return FaultHandlerCode::IgnoreError;
|
||||
}
|
||||
self.handler_array[array_idx.unwrap()]
|
||||
}
|
||||
|
||||
pub fn report_fault(
|
||||
&self,
|
||||
transaction_id: TransactionId,
|
||||
condition: ConditionCode,
|
||||
progress: u64,
|
||||
) -> FaultHandlerCode {
|
||||
let array_idx = Self::condition_code_to_array_index(condition);
|
||||
if array_idx.is_none() {
|
||||
return FaultHandlerCode::IgnoreError;
|
||||
}
|
||||
let fh_code = self.handler_array[array_idx.unwrap()];
|
||||
let mut handler_mut = self.user_fault_handler.borrow_mut();
|
||||
match fh_code {
|
||||
FaultHandlerCode::NoticeOfCancellation => {
|
||||
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
|
||||
}
|
||||
FaultHandlerCode::NoticeOfSuspension => {
|
||||
handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
|
||||
}
|
||||
FaultHandlerCode::IgnoreError => {
|
||||
handler_mut.ignore_cb(transaction_id, condition, progress);
|
||||
}
|
||||
FaultHandlerCode::AbandonTransaction => {
|
||||
handler_mut.abandoned_cb(transaction_id, condition, progress);
|
||||
}
|
||||
}
|
||||
fh_code
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndicationConfig {
|
||||
pub eof_sent: bool,
|
||||
pub eof_recv: bool,
|
||||
pub file_segment_recv: bool,
|
||||
pub transaction_finished: bool,
|
||||
pub suspended: bool,
|
||||
pub resumed: bool,
|
||||
}
|
||||
|
||||
impl Default for IndicationConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
eof_sent: true,
|
||||
eof_recv: true,
|
||||
file_segment_recv: true,
|
||||
transaction_finished: true,
|
||||
suspended: true,
|
||||
resumed: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalEntityConfig {
|
||||
pub id: UnsignedByteField,
|
||||
pub indication_cfg: IndicationConfig,
|
||||
pub default_fault_handler: DefaultFaultHandler,
|
||||
}
|
||||
|
||||
/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
|
||||
/// number of that transfer which is also determined by the CFDP source entity.
|
||||
#[derive(Debug, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct TransactionId {
|
||||
source_id: UnsignedByteField,
|
||||
seq_num: UnsignedByteField,
|
||||
}
|
||||
|
||||
impl TransactionId {
|
||||
pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
|
||||
Self { source_id, seq_num }
|
||||
}
|
||||
|
||||
pub fn source_id(&self) -> &UnsignedByteField {
|
||||
&self.source_id
|
||||
}
|
||||
|
||||
pub fn seq_num(&self) -> &UnsignedByteField {
|
||||
&self.seq_num
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for TransactionId {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.source_id.value().hash(state);
|
||||
self.seq_num.value().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TransactionId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.source_id.value() == other.source_id.value()
|
||||
&& self.seq_num.value() == other.seq_num.value()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum TransactionStep {
|
||||
Idle = 0,
|
||||
TransactionStart = 1,
|
||||
ReceivingFileDataPdus = 2,
|
||||
ReceivingFileDataPdusWithCheckLimitHandling = 3,
|
||||
SendingAckPdu = 4,
|
||||
TransferCompletion = 5,
|
||||
SendingFinishedPdu = 6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum State {
|
||||
Idle = 0,
|
||||
Busy = 1,
|
||||
Suspended = 2,
|
||||
}
|
||||
|
||||
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum PacketTarget {
|
||||
SourceEntity,
|
||||
DestEntity,
|
||||
}
|
||||
|
||||
/// This is a helper struct which contains base information about a particular PDU packet.
|
||||
/// This is also necessary information for CFDP packet routing. For example, some packet types
|
||||
/// like file data PDUs can only be used by CFDP source entities.
|
||||
pub struct PacketInfo<'raw_packet> {
|
||||
pdu_type: PduType,
|
||||
pdu_directive: Option<FileDirectiveType>,
|
||||
target: PacketTarget,
|
||||
raw_packet: &'raw_packet [u8],
|
||||
}
|
||||
|
||||
impl<'raw> PacketInfo<'raw> {
|
||||
pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
|
||||
let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
|
||||
if pdu_header.pdu_type() == PduType::FileData {
|
||||
return Ok(Self {
|
||||
pdu_type: pdu_header.pdu_type(),
|
||||
pdu_directive: None,
|
||||
target: PacketTarget::DestEntity,
|
||||
raw_packet,
|
||||
});
|
||||
}
|
||||
if pdu_header.pdu_datafield_len() < 1 {
|
||||
return Err(PduError::FormatError);
|
||||
}
|
||||
// Route depending on PDU type and directive type if applicable. Retrieve directive type
|
||||
// from the raw stream for better performance (with sanity and directive code check).
|
||||
// The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
|
||||
// procedure.
|
||||
let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
|
||||
PduError::InvalidDirectiveType {
|
||||
found: raw_packet[header_len],
|
||||
expected: None,
|
||||
}
|
||||
})?;
|
||||
let packet_target = match directive {
|
||||
// Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
|
||||
// the source handler
|
||||
FileDirectiveType::NakPdu
|
||||
| FileDirectiveType::FinishedPdu
|
||||
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
|
||||
// Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
|
||||
// the destination handler
|
||||
FileDirectiveType::MetadataPdu
|
||||
| FileDirectiveType::EofPdu
|
||||
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
|
||||
// Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
|
||||
// extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
|
||||
// the source handler, for a Finished PDU, it is passed to the destination handler.
|
||||
FileDirectiveType::AckPdu => {
|
||||
let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1])
|
||||
.map_err(|_| PduError::InvalidDirectiveType {
|
||||
found: raw_packet[header_len],
|
||||
expected: None,
|
||||
})?;
|
||||
if acked_directive == FileDirectiveType::EofPdu {
|
||||
PacketTarget::SourceEntity
|
||||
} else if acked_directive == FileDirectiveType::FinishedPdu {
|
||||
PacketTarget::DestEntity
|
||||
} else {
|
||||
// TODO: Maybe a better error? This might be confusing..
|
||||
return Err(PduError::InvalidDirectiveType {
|
||||
found: raw_packet[header_len + 1],
|
||||
expected: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
pdu_type: pdu_header.pdu_type(),
|
||||
pdu_directive: Some(directive),
|
||||
target: packet_target,
|
||||
raw_packet,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pdu_type(&self) -> PduType {
|
||||
self.pdu_type
|
||||
}
|
||||
|
||||
pub fn pdu_directive(&self) -> Option<FileDirectiveType> {
|
||||
self.pdu_directive
|
||||
}
|
||||
|
||||
pub fn target(&self) -> PacketTarget {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn raw_packet(&self) -> &[u8] {
|
||||
self.raw_packet
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use spacepackets::cfdp::{
|
||||
lv::Lv,
|
||||
pdu::{
|
||||
eof::EofPdu,
|
||||
file_data::FileDataPdu,
|
||||
metadata::{MetadataGenericParams, MetadataPduCreator},
|
||||
CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
|
||||
},
|
||||
PduType,
|
||||
};
|
||||
|
||||
use crate::cfdp::PacketTarget;
|
||||
|
||||
use super::PacketInfo;
|
||||
|
||||
fn generic_pdu_header() -> PduHeader {
|
||||
let pdu_conf = CommonPduConfig::default();
|
||||
PduHeader::new_no_file_data(pdu_conf, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metadata_pdu_info() {
|
||||
let mut buf: [u8; 128] = [0; 128];
|
||||
let pdu_header = generic_pdu_header();
|
||||
let metadata_params = MetadataGenericParams::default();
|
||||
let src_file_name = "hello.txt";
|
||||
let dest_file_name = "hello-dest.txt";
|
||||
let src_lv = Lv::new_from_str(src_file_name).unwrap();
|
||||
let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
|
||||
let metadata_pdu =
|
||||
MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
|
||||
metadata_pdu
|
||||
.write_to_bytes(&mut buf)
|
||||
.expect("writing metadata PDU failed");
|
||||
|
||||
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||
assert!(packet_info.pdu_directive().is_some());
|
||||
assert_eq!(
|
||||
packet_info.pdu_directive().unwrap(),
|
||||
FileDirectiveType::MetadataPdu
|
||||
);
|
||||
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filedata_pdu_info() {
|
||||
let mut buf: [u8; 128] = [0; 128];
|
||||
let pdu_header = generic_pdu_header();
|
||||
let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
|
||||
file_data_pdu
|
||||
.write_to_bytes(&mut buf)
|
||||
.expect("writing file data PDU failed");
|
||||
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||
assert_eq!(packet_info.pdu_type(), PduType::FileData);
|
||||
assert!(packet_info.pdu_directive().is_none());
|
||||
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eof_pdu_info() {
|
||||
let mut buf: [u8; 128] = [0; 128];
|
||||
let pdu_header = generic_pdu_header();
|
||||
let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
|
||||
eof_pdu
|
||||
.write_to_bytes(&mut buf)
|
||||
.expect("writing file data PDU failed");
|
||||
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||
assert!(packet_info.pdu_directive().is_some());
|
||||
assert_eq!(
|
||||
packet_info.pdu_directive().unwrap(),
|
||||
FileDirectiveType::EofPdu
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use spacepackets::util::UnsignedByteField;
|
||||
|
||||
pub struct SourceHandler {
|
||||
id: UnsignedByteField,
|
||||
}
|
||||
|
||||
impl SourceHandler {
|
||||
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
|
||||
Self { id: id.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
@@ -1,96 +0,0 @@
|
||||
use spacepackets::{
|
||||
cfdp::{
|
||||
pdu::{
|
||||
file_data::SegmentMetadata,
|
||||
finished::{DeliveryCode, FileStatus},
|
||||
},
|
||||
tlv::{msg_to_user::MsgToUserTlv, WritableTlv},
|
||||
ConditionCode,
|
||||
},
|
||||
util::UnsignedByteField,
|
||||
};
|
||||
|
||||
use super::TransactionId;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TransactionFinishedParams {
|
||||
pub id: TransactionId,
|
||||
pub condition_code: ConditionCode,
|
||||
pub delivery_code: DeliveryCode,
|
||||
pub file_status: FileStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
|
||||
pub id: TransactionId,
|
||||
pub source_id: UnsignedByteField,
|
||||
pub file_size: u64,
|
||||
pub src_file_name: &'src_file str,
|
||||
pub dest_file_name: &'dest_file str,
|
||||
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Debug)]
|
||||
pub struct OwnedMetadataRecvdParams {
|
||||
pub id: TransactionId,
|
||||
pub source_id: UnsignedByteField,
|
||||
pub file_size: u64,
|
||||
pub src_file_name: alloc::string::String,
|
||||
pub dest_file_name: alloc::string::String,
|
||||
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl From<MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
|
||||
fn from(value: MetadataReceivedParams) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
|
||||
fn from(value: &MetadataReceivedParams) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
source_id: value.source_id,
|
||||
file_size: value.file_size,
|
||||
src_file_name: value.src_file_name.into(),
|
||||
dest_file_name: value.dest_file_name.into(),
|
||||
msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileSegmentRecvdParams<'seg_meta> {
|
||||
pub id: TransactionId,
|
||||
pub offset: u64,
|
||||
pub length: usize,
|
||||
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
|
||||
}
|
||||
|
||||
pub trait CfdpUser {
|
||||
fn transaction_indication(&mut self, id: &TransactionId);
|
||||
fn eof_sent_indication(&mut self, id: &TransactionId);
|
||||
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
|
||||
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
|
||||
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
|
||||
// TODO: The standard does not strictly specify how the report information looks..
|
||||
fn report_indication(&mut self, id: &TransactionId);
|
||||
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
|
||||
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
|
||||
fn fault_indication(
|
||||
&mut self,
|
||||
id: &TransactionId,
|
||||
condition_code: ConditionCode,
|
||||
progress: u64,
|
||||
);
|
||||
fn abandoned_indication(
|
||||
&mut self,
|
||||
id: &TransactionId,
|
||||
condition_code: ConditionCode,
|
||||
progress: u64,
|
||||
);
|
||||
fn eof_recvd_indication(&mut self, id: &TransactionId);
|
||||
}
|
448
satrs/src/dev_mgmt.rs
Normal file
448
satrs/src/dev_mgmt.rs
Normal file
@@ -0,0 +1,448 @@
|
||||
use crate::{
|
||||
ComponentId,
|
||||
mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender},
|
||||
mode_tree::{ModeStoreProvider, ModeStoreVec},
|
||||
queue::{GenericSendError, GenericTargetedMessagingError},
|
||||
request::{GenericMessage, RequestId},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ActiveModeCommandContext {
|
||||
pub target_mode: ModeAndSubmode,
|
||||
pub active_request_id: RequestId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum DevManagerHelperResult {
|
||||
#[default]
|
||||
Idle,
|
||||
Busy,
|
||||
ModeCommandingDone(ActiveModeCommandContext),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DevManagerHelperError {
|
||||
ChildNotInStore,
|
||||
}
|
||||
|
||||
pub trait DevManagerUserHook: Debug {
|
||||
fn send_mode_cmd_to_child(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
mode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
children_mode_store: &mut ModeStoreVec,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError>;
|
||||
|
||||
fn send_mode_cmds_to_children(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
commanded_parent_mode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
children_mode_store: &mut ModeStoreVec,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TransparentDevManagerHook {}
|
||||
|
||||
impl DevManagerUserHook for TransparentDevManagerHook {
|
||||
fn send_mode_cmds_to_children(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
commanded_parent_mode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
children_mode_store: &mut ModeStoreVec,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError> {
|
||||
for child in children_mode_store {
|
||||
mode_req_sender.send_mode_request(
|
||||
request_id,
|
||||
child.id(),
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: commanded_parent_mode,
|
||||
forced,
|
||||
},
|
||||
)?;
|
||||
child.awaiting_reply = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_mode_cmd_to_child(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
mode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
children_mode_store: &mut ModeStoreVec,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError> {
|
||||
let mut_val = children_mode_store
|
||||
.get_mut(target_id)
|
||||
.ok_or(GenericSendError::TargetDoesNotExist(target_id))?;
|
||||
mut_val.awaiting_reply = true;
|
||||
mode_req_sender.send_mode_request(
|
||||
request_id,
|
||||
target_id,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: mode,
|
||||
forced,
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DevManagerCommandingState {
|
||||
#[default]
|
||||
Idle,
|
||||
AwaitingReplies(ActiveModeCommandContext),
|
||||
}
|
||||
|
||||
impl DevManagerCommandingState {
|
||||
fn new_active_cmd(mode_and_submode: ModeAndSubmode, active_request_id: RequestId) -> Self {
|
||||
DevManagerCommandingState::AwaitingReplies(ActiveModeCommandContext {
|
||||
target_mode: mode_and_submode,
|
||||
active_request_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic helper for manager components which manage child components in a mode tree.
|
||||
///
|
||||
/// Mode commands are usually forwarded to all children components transparently.
|
||||
/// For example, this could be used in an Assembly component which manages multiple redundant
|
||||
/// child components. It can also be used inside a manager component which only manages one device.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DevManagerCommandingHelper<UserHook: DevManagerUserHook> {
|
||||
/// The IDs, modes and reply awaition status of all children are tracked in this data
|
||||
/// structure.
|
||||
pub children_mode_store: ModeStoreVec,
|
||||
pub user_hook: UserHook,
|
||||
pub state: DevManagerCommandingState,
|
||||
}
|
||||
|
||||
impl<UserHook: DevManagerUserHook> DevManagerCommandingHelper<UserHook> {
|
||||
pub fn new(user_hook: UserHook) -> Self {
|
||||
Self {
|
||||
children_mode_store: Default::default(),
|
||||
user_hook,
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mode_cmd_to_one_child(
|
||||
&mut self,
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
|
||||
self.user_hook.send_mode_cmd_to_child(
|
||||
request_id,
|
||||
target_id,
|
||||
mode_and_submode,
|
||||
forced,
|
||||
&mut self.children_mode_store,
|
||||
mode_req_sender,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_mode_cmd_to_all_children(
|
||||
&mut self,
|
||||
request_id: RequestId,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
|
||||
self.user_hook.send_mode_cmds_to_children(
|
||||
request_id,
|
||||
mode_and_submode,
|
||||
forced,
|
||||
&mut self.children_mode_store,
|
||||
mode_req_sender,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn target_mode(&self) -> Option<ModeAndSubmode> {
|
||||
match self.state {
|
||||
DevManagerCommandingState::Idle => None,
|
||||
DevManagerCommandingState::AwaitingReplies(context) => Some(context.target_mode),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> DevManagerCommandingState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn send_announce_mode_cmd_to_children(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
mode_req_sender: &impl ModeRequestSender,
|
||||
recursive: bool,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
let mut request = ModeRequest::AnnounceMode;
|
||||
if recursive {
|
||||
request = ModeRequest::AnnounceModeRecursive;
|
||||
}
|
||||
for child in self.children_mode_store.0.iter() {
|
||||
mode_req_sender.send_mode_request(request_id, child.id(), request)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_mode_child(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
|
||||
self.children_mode_store.add_component(target_id, mode);
|
||||
}
|
||||
|
||||
/// Helper method which counts the number of children which have a certain mode.
|
||||
pub fn count_number_of_children_with_mode(&self, mode_and_submode: ModeAndSubmode) -> usize {
|
||||
let mut children_in_target_mode = 0;
|
||||
for child in &self.children_mode_store {
|
||||
if child.mode_and_submode() == mode_and_submode {
|
||||
children_in_target_mode += 1;
|
||||
}
|
||||
}
|
||||
children_in_target_mode
|
||||
}
|
||||
|
||||
pub fn handle_mode_reply(
|
||||
&mut self,
|
||||
mode_reply: &GenericMessage<ModeReply>,
|
||||
) -> Result<DevManagerHelperResult, DevManagerHelperError> {
|
||||
let context = match self.state {
|
||||
DevManagerCommandingState::Idle => return Ok(DevManagerHelperResult::Idle),
|
||||
DevManagerCommandingState::AwaitingReplies(active_mode_command_context) => {
|
||||
Some(active_mode_command_context)
|
||||
}
|
||||
};
|
||||
if !self
|
||||
.children_mode_store
|
||||
.has_component(mode_reply.sender_id())
|
||||
{
|
||||
return Err(DevManagerHelperError::ChildNotInStore);
|
||||
}
|
||||
let mut generic_mode_reply_handler = |mode_and_submode: Option<ModeAndSubmode>| {
|
||||
// Tying the reply awaition to the request ID ensures that something like replies
|
||||
// belonging to older requests do not interfere with the completion handling of
|
||||
// the mode commanding. This is important for forced mode commands.
|
||||
let mut handle_awaition = false;
|
||||
if let DevManagerCommandingState::AwaitingReplies { .. } = self.state {
|
||||
handle_awaition = true;
|
||||
}
|
||||
let still_awating_replies = self.children_mode_store.mode_reply_handler(
|
||||
mode_reply.sender_id(),
|
||||
mode_and_submode,
|
||||
handle_awaition,
|
||||
);
|
||||
// It is okay to unwrap: If awaition should be handled, the returned value should
|
||||
// always be some valid value.
|
||||
if handle_awaition && !still_awating_replies.unwrap() {
|
||||
self.state = DevManagerCommandingState::Idle;
|
||||
return Ok(DevManagerHelperResult::ModeCommandingDone(context.unwrap()));
|
||||
}
|
||||
Ok(DevManagerHelperResult::Busy)
|
||||
};
|
||||
match mode_reply.message {
|
||||
ModeReply::ModeInfo(mode_and_submode) | ModeReply::ModeReply(mode_and_submode) => {
|
||||
generic_mode_reply_handler(Some(mode_and_submode))
|
||||
}
|
||||
ModeReply::CantReachMode(_result_u16) => generic_mode_reply_handler(None),
|
||||
ModeReply::WrongMode {
|
||||
expected: _,
|
||||
reached,
|
||||
} => generic_mode_reply_handler(Some(reached)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
mode::{UNKNOWN_MODE, tests::ModeReqSenderMock},
|
||||
request::MessageMetadata,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub enum ExampleId {
|
||||
Id1 = 1,
|
||||
Id2 = 2,
|
||||
}
|
||||
|
||||
pub enum ExampleMode {
|
||||
Mode1 = 1,
|
||||
Mode2 = 2,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
|
||||
assert_eq!(assy_helper.state(), DevManagerCommandingState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_announce() {
|
||||
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
|
||||
let mode_req_sender = ModeReqSenderMock::default();
|
||||
assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
|
||||
assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
|
||||
assy_helper
|
||||
.send_announce_mode_cmd_to_children(1, &mode_req_sender, false)
|
||||
.unwrap();
|
||||
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
|
||||
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id1 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(req.request, ModeRequest::AnnounceMode);
|
||||
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id2 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(req.request, ModeRequest::AnnounceMode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_announce_recursive() {
|
||||
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
|
||||
let mode_req_sender = ModeReqSenderMock::default();
|
||||
assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
|
||||
assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
|
||||
assy_helper
|
||||
.send_announce_mode_cmd_to_children(1, &mode_req_sender, true)
|
||||
.unwrap();
|
||||
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
|
||||
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id1 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
|
||||
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id2 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_commanding_one_child() {
|
||||
let mut dev_mgmt_helper =
|
||||
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
|
||||
let mode_req_sender = ModeReqSenderMock::default();
|
||||
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
|
||||
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0);
|
||||
dev_mgmt_helper
|
||||
.send_mode_cmd_to_one_child(
|
||||
1,
|
||||
ExampleId::Id1 as u64,
|
||||
expected_mode,
|
||||
false,
|
||||
&mode_req_sender,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(mode_req_sender.requests.borrow().len(), 1);
|
||||
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id1 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(
|
||||
req.request,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: expected_mode,
|
||||
forced: false
|
||||
}
|
||||
);
|
||||
matches!(
|
||||
dev_mgmt_helper.state(),
|
||||
DevManagerCommandingState::AwaitingReplies { .. }
|
||||
);
|
||||
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
|
||||
assert_eq!(ctx.target_mode, expected_mode);
|
||||
assert_eq!(ctx.active_request_id, 1);
|
||||
}
|
||||
let reply = GenericMessage::new(
|
||||
MessageMetadata::new(1, ExampleId::Id1 as u64),
|
||||
ModeReply::ModeReply(expected_mode),
|
||||
);
|
||||
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
|
||||
target_mode,
|
||||
active_request_id,
|
||||
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
|
||||
{
|
||||
assert_eq!(target_mode, expected_mode);
|
||||
assert_eq!(active_request_id, 1);
|
||||
}
|
||||
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mode_commanding_multi_child() {
|
||||
let mut dev_mgmt_helper =
|
||||
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
|
||||
let mode_req_sender = ModeReqSenderMock::default();
|
||||
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
|
||||
dev_mgmt_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
|
||||
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0);
|
||||
dev_mgmt_helper
|
||||
.send_mode_cmd_to_all_children(1, expected_mode, false, &mode_req_sender)
|
||||
.unwrap();
|
||||
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
|
||||
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id1 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(
|
||||
req.request,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: expected_mode,
|
||||
forced: false
|
||||
}
|
||||
);
|
||||
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
|
||||
assert_eq!(req.target_id, ExampleId::Id2 as u64);
|
||||
assert_eq!(req.request_id, 1);
|
||||
assert_eq!(
|
||||
req.request,
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode: expected_mode,
|
||||
forced: false
|
||||
}
|
||||
);
|
||||
matches!(
|
||||
dev_mgmt_helper.state(),
|
||||
DevManagerCommandingState::AwaitingReplies { .. }
|
||||
);
|
||||
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
|
||||
assert_eq!(ctx.target_mode, expected_mode);
|
||||
assert_eq!(ctx.active_request_id, 1);
|
||||
}
|
||||
|
||||
let reply = GenericMessage::new(
|
||||
MessageMetadata::new(1, ExampleId::Id1 as u64),
|
||||
ModeReply::ModeReply(expected_mode),
|
||||
);
|
||||
assert_eq!(
|
||||
dev_mgmt_helper.handle_mode_reply(&reply).unwrap(),
|
||||
DevManagerHelperResult::Busy
|
||||
);
|
||||
let reply = GenericMessage::new(
|
||||
MessageMetadata::new(1, ExampleId::Id2 as u64),
|
||||
ModeReply::ModeReply(expected_mode),
|
||||
);
|
||||
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
|
||||
target_mode,
|
||||
active_request_id,
|
||||
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
|
||||
{
|
||||
assert_eq!(target_mode, expected_mode);
|
||||
assert_eq!(active_request_id, 1);
|
||||
}
|
||||
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use spacepackets::{CcsdsPacket, SpHeader};
|
||||
|
||||
use crate::{tmtc::PacketSenderRaw, ComponentId};
|
||||
use crate::{ComponentId, tmtc::PacketSenderRaw};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum SpValidity {
|
||||
@@ -90,13 +90,13 @@ pub fn parse_buffer_for_ccsds_space_packets<SendError>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use spacepackets::{
|
||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||
CcsdsPacket, PacketId, PacketSequenceCtrl, PacketType, SequenceFlags, SpHeader,
|
||||
ecss::tc::PusTcCreator,
|
||||
};
|
||||
|
||||
use crate::{encoding::tests::TcCacher, ComponentId};
|
||||
use crate::{ComponentId, encoding::tests::TcCacher};
|
||||
|
||||
use super::{parse_buffer_for_ccsds_space_packets, SpValidity, SpacePacketValidator};
|
||||
use super::{SpValidity, SpacePacketValidator, parse_buffer_for_ccsds_space_packets};
|
||||
|
||||
const PARSER_ID: ComponentId = 0x05;
|
||||
const TEST_APID_0: u16 = 0x02;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::{tmtc::PacketSenderRaw, ComponentId};
|
||||
use crate::{ComponentId, tmtc::PacketSenderRaw};
|
||||
use cobs::{decode_in_place, encode, max_encoding_length};
|
||||
|
||||
/// This function encodes the given packet with COBS and also wraps the encoded packet with
|
||||
@@ -24,8 +24,8 @@ use cobs::{decode_in_place, encode, max_encoding_length};
|
||||
/// assert!(encode_packet_with_cobs(&INVERTED_PACKET, &mut encoding_buf, &mut current_idx));
|
||||
/// assert_eq!(encoding_buf[0], 0);
|
||||
/// let dec_report = decode_in_place_report(&mut encoding_buf[1..]).expect("decoding failed");
|
||||
/// assert_eq!(encoding_buf[1 + dec_report.src_used], 0);
|
||||
/// assert_eq!(dec_report.dst_used, 5);
|
||||
/// assert_eq!(encoding_buf[1 + dec_report.parsed_size()], 0);
|
||||
/// assert_eq!(dec_report.frame_size(), 5);
|
||||
/// assert_eq!(current_idx, 16);
|
||||
/// ```
|
||||
pub fn encode_packet_with_cobs(
|
||||
@@ -104,8 +104,8 @@ pub(crate) mod tests {
|
||||
use cobs::encode;
|
||||
|
||||
use crate::{
|
||||
encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET},
|
||||
ComponentId,
|
||||
encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET, TcCacher, encode_simple_packet},
|
||||
};
|
||||
|
||||
use super::parse_buffer_for_cobs_encoded_packets;
|
||||
|
@@ -11,8 +11,8 @@ pub(crate) mod tests {
|
||||
use alloc::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
tmtc::{PacketAsVec, PacketSenderRaw},
|
||||
ComponentId,
|
||||
tmtc::{PacketAsVec, PacketSenderRaw},
|
||||
};
|
||||
|
||||
use super::cobs::encode_packet_with_cobs;
|
||||
|
@@ -71,18 +71,14 @@ pub enum ListenerKey {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventMessage<Event: GenericEvent, ParamProvider: Debug = Params> {
|
||||
pub struct EventMessage<Event: GenericEvent, Parameters: Debug = Params> {
|
||||
sender_id: ComponentId,
|
||||
event: Event,
|
||||
params: Option<ParamProvider>,
|
||||
params: Option<Parameters>,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent, ParamProvider: Debug + Clone> EventMessage<Event, ParamProvider> {
|
||||
pub fn new_generic(
|
||||
sender_id: ComponentId,
|
||||
event: Event,
|
||||
params: Option<&ParamProvider>,
|
||||
) -> Self {
|
||||
impl<Event: GenericEvent, Parameters: Debug + Clone> EventMessage<Event, Parameters> {
|
||||
pub fn new_generic(sender_id: ComponentId, event: Event, params: Option<&Parameters>) -> Self {
|
||||
Self {
|
||||
sender_id,
|
||||
event,
|
||||
@@ -98,7 +94,7 @@ impl<Event: GenericEvent, ParamProvider: Debug + Clone> EventMessage<Event, Para
|
||||
self.event
|
||||
}
|
||||
|
||||
pub fn params(&self) -> Option<&ParamProvider> {
|
||||
pub fn params(&self) -> Option<&Parameters> {
|
||||
self.params.as_ref()
|
||||
}
|
||||
|
||||
@@ -106,7 +102,7 @@ impl<Event: GenericEvent, ParamProvider: Debug + Clone> EventMessage<Event, Para
|
||||
Self::new_generic(sender_id, event, None)
|
||||
}
|
||||
|
||||
pub fn new_with_params(sender_id: ComponentId, event: Event, params: &ParamProvider) -> Self {
|
||||
pub fn new_with_params(sender_id: ComponentId, event: Event, params: &Parameters) -> Self {
|
||||
Self::new_generic(sender_id, event, Some(params))
|
||||
}
|
||||
}
|
||||
@@ -136,7 +132,7 @@ pub trait ListenerMapProvider {
|
||||
#[cfg(feature = "alloc")]
|
||||
fn get_listeners(&self) -> alloc::vec::Vec<ListenerKey>;
|
||||
fn contains_listener(&self, key: &ListenerKey) -> bool;
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ComponentId>>;
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<'_, ComponentId>>;
|
||||
fn add_listener(&mut self, key: ListenerKey, listener_id: ComponentId) -> bool;
|
||||
fn remove_duplicates(&mut self, key: &ListenerKey);
|
||||
}
|
||||
@@ -204,7 +200,7 @@ impl<
|
||||
EventSender: EventSendProvider<Event, ParamProvider>,
|
||||
Event: GenericEvent + Copy,
|
||||
ParamProvider: Debug,
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSender, Event, ParamProvider>
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSender, Event, ParamProvider>
|
||||
{
|
||||
pub fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||
self.listener_map.remove_duplicates(key)
|
||||
@@ -235,7 +231,7 @@ impl<
|
||||
EventSenderMap: EventSendProvider<Event, ParamProvider>,
|
||||
Event: GenericEvent + Copy,
|
||||
ParamProvider: Debug,
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSenderMap, Event, ParamProvider>
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSenderMap, Event, ParamProvider>
|
||||
{
|
||||
pub fn new_with_custom_maps(
|
||||
event_receiver: EventReceiver,
|
||||
@@ -273,7 +269,7 @@ impl<
|
||||
EventSenderMap: EventSendProvider<Event, ParamProvider, Error = GenericSendError>,
|
||||
Event: GenericEvent + Copy,
|
||||
ParamProvider: Clone + Debug,
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSenderMap, Event, ParamProvider>
|
||||
> EventManager<EventReceiver, SenderMap, ListenerMap, EventSenderMap, Event, ParamProvider>
|
||||
{
|
||||
/// This function will use the cached event receiver and try to receive one event.
|
||||
/// If an event was received, it will try to route that event to all subscribed event listeners.
|
||||
@@ -402,7 +398,7 @@ pub mod alloc_mod {
|
||||
self.listeners.contains_key(key)
|
||||
}
|
||||
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ComponentId>> {
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<'_, ComponentId>> {
|
||||
self.listeners.get(key).map(|vec| vec.iter())
|
||||
}
|
||||
|
||||
|
@@ -31,9 +31,9 @@ use core::fmt::Debug;
|
||||
use core::hash::Hash;
|
||||
use core::marker::PhantomData;
|
||||
use delegate::delegate;
|
||||
use spacepackets::ByteConversionError;
|
||||
use spacepackets::ecss::EcssEnumeration;
|
||||
use spacepackets::util::{ToBeBytes, UnsignedEnum};
|
||||
use spacepackets::ByteConversionError;
|
||||
|
||||
/// Using a type definition allows to change this to u64 in the future more easily
|
||||
pub type LargestEventRaw = u32;
|
||||
|
@@ -15,20 +15,24 @@ pub enum OpResult {
|
||||
TerminationRequested,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutionType {
|
||||
Infinite,
|
||||
Cycles(u32),
|
||||
OneShot,
|
||||
}
|
||||
|
||||
pub trait Executable: Send {
|
||||
pub trait Executable {
|
||||
type Error;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType;
|
||||
fn task_name(&self) -> &'static str;
|
||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error>;
|
||||
}
|
||||
|
||||
pub trait ExecutableWithType: Executable {
|
||||
fn exec_type(&self) -> ExecutionType;
|
||||
}
|
||||
|
||||
/// This function allows executing one task which implements the [Executable] trait
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -39,7 +43,10 @@ pub trait Executable: Send {
|
||||
/// * `op_code`: Operation code which is passed to the executable task
|
||||
/// [operation call][Executable::periodic_op]
|
||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||
pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
||||
pub fn exec_sched_single<
|
||||
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
|
||||
E: Send + 'static,
|
||||
>(
|
||||
mut executable: Box<T>,
|
||||
task_freq: Option<Duration>,
|
||||
op_code: i32,
|
||||
@@ -48,7 +55,8 @@ pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E:
|
||||
let mut cycle_count = 0;
|
||||
thread::Builder::new()
|
||||
.name(String::from(executable.task_name()))
|
||||
.spawn(move || loop {
|
||||
.spawn(move || {
|
||||
loop {
|
||||
if let Some(ref mut terminator) = termination {
|
||||
match terminator.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||
@@ -76,6 +84,7 @@ pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E:
|
||||
if let Some(freq) = task_freq {
|
||||
thread::sleep(freq);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,7 +97,10 @@ pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E:
|
||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||
pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
||||
pub fn exec_sched_multi<
|
||||
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
|
||||
E: Send + 'static,
|
||||
>(
|
||||
task_name: &'static str,
|
||||
mut executable_vec: Vec<Box<T>>,
|
||||
task_freq: Option<Duration>,
|
||||
@@ -100,7 +112,8 @@ pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: S
|
||||
|
||||
thread::Builder::new()
|
||||
.name(String::from(task_name))
|
||||
.spawn(move || loop {
|
||||
.spawn(move || {
|
||||
loop {
|
||||
if let Some(ref mut terminator) = termination {
|
||||
match terminator.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||
@@ -137,12 +150,16 @@ pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: S
|
||||
}
|
||||
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
||||
thread::sleep(freq);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{exec_sched_multi, exec_sched_single, Executable, ExecutionType, OpResult};
|
||||
use super::{
|
||||
Executable, ExecutableWithType, ExecutionType, OpResult, exec_sched_multi,
|
||||
exec_sched_single,
|
||||
};
|
||||
use bus::Bus;
|
||||
use std::boxed::Box;
|
||||
use std::error::Error;
|
||||
@@ -208,10 +225,6 @@ mod tests {
|
||||
impl Executable for OneShotTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::OneShot
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
ONE_SHOT_TASK_NAME
|
||||
}
|
||||
@@ -229,15 +242,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableWithType for OneShotTask {
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::OneShot
|
||||
}
|
||||
}
|
||||
|
||||
const CYCLE_TASK_NAME: &str = "Fixed Cycles Task";
|
||||
|
||||
impl Executable for FixedCyclesTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Cycles(self.cycles)
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
CYCLE_TASK_NAME
|
||||
}
|
||||
@@ -255,15 +270,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableWithType for FixedCyclesTask {
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Cycles(self.cycles)
|
||||
}
|
||||
}
|
||||
|
||||
const PERIODIC_TASK_NAME: &str = "Periodic Task";
|
||||
|
||||
impl Executable for PeriodicTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Infinite
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
PERIODIC_TASK_NAME
|
||||
}
|
||||
@@ -281,6 +298,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableWithType for PeriodicTask {
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Infinite
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_one_shot() {
|
||||
let expected_op_code = 42;
|
||||
@@ -423,7 +446,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(cycled_task_0.task_name(), CYCLE_TASK_NAME);
|
||||
assert_eq!(one_shot_task.task_name(), ONE_SHOT_TASK_NAME);
|
||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
|
||||
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
||||
let jh = exec_sched_multi(
|
||||
"multi-task-name",
|
||||
@@ -493,7 +516,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(periodic_task_0.task_name(), PERIODIC_TASK_NAME);
|
||||
assert_eq!(periodic_task_1.task_name(), PERIODIC_TASK_NAME);
|
||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
|
||||
vec![cycled_task, periodic_task_0, periodic_task_1];
|
||||
let jh = exec_sched_multi(
|
||||
"multi-task-name",
|
||||
|
@@ -13,10 +13,10 @@ use crate::encoding::parse_buffer_for_cobs_encoded_packets;
|
||||
use crate::tmtc::PacketSenderRaw;
|
||||
use crate::tmtc::PacketSource;
|
||||
|
||||
use crate::ComponentId;
|
||||
use crate::hal::std::tcp_server::{
|
||||
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
|
||||
};
|
||||
use crate::ComponentId;
|
||||
|
||||
use super::tcp_server::HandledConnectionHandler;
|
||||
use super::tcp_server::HandledConnectionInfo;
|
||||
@@ -141,7 +141,7 @@ impl<
|
||||
HandledConnection: HandledConnectionHandler,
|
||||
TmError: 'static,
|
||||
TcError: 'static,
|
||||
> TcpTmtcInCobsServer<TmSource, TcReceiver, HandledConnection, TmError, TcError>
|
||||
> TcpTmtcInCobsServer<TmSource, TcReceiver, HandledConnection, TmError, TcError>
|
||||
{
|
||||
/// Create a new TCP TMTC server which exchanges TMTC packets encoded with
|
||||
/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
||||
@@ -206,14 +206,14 @@ mod tests {
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ComponentId,
|
||||
encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET},
|
||||
hal::std::tcp_server::{
|
||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
||||
ConnectionResult, ServerConfig,
|
||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
||||
},
|
||||
queue::GenericSendError,
|
||||
tmtc::PacketAsVec,
|
||||
ComponentId,
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
use cobs::encode;
|
||||
@@ -377,13 +377,13 @@ mod tests {
|
||||
current_idx += 1;
|
||||
let mut dec_report = cobs::decode_in_place_report(&mut read_buf[current_idx..])
|
||||
.expect("COBS decoding failed");
|
||||
assert_eq!(dec_report.dst_used, 5);
|
||||
assert_eq!(dec_report.frame_size(), 5);
|
||||
// Skip first sentinel byte.
|
||||
assert_eq!(
|
||||
&read_buf[current_idx..current_idx + INVERTED_PACKET.len()],
|
||||
&INVERTED_PACKET
|
||||
);
|
||||
current_idx += dec_report.src_used;
|
||||
current_idx += dec_report.parsed_size();
|
||||
// End sentinel.
|
||||
assert_eq!(read_buf[current_idx], 0, "invalid sentinel end byte");
|
||||
current_idx += 1;
|
||||
@@ -393,13 +393,13 @@ mod tests {
|
||||
current_idx += 1;
|
||||
dec_report = cobs::decode_in_place_report(&mut read_buf[current_idx..])
|
||||
.expect("COBS decoding failed");
|
||||
assert_eq!(dec_report.dst_used, 5);
|
||||
assert_eq!(dec_report.frame_size(), 5);
|
||||
// Skip first sentinel byte.
|
||||
assert_eq!(
|
||||
&read_buf[current_idx..current_idx + SIMPLE_PACKET.len()],
|
||||
&SIMPLE_PACKET
|
||||
);
|
||||
current_idx += dec_report.src_used;
|
||||
current_idx += dec_report.parsed_size();
|
||||
// End sentinel.
|
||||
assert_eq!(read_buf[current_idx], 0);
|
||||
break;
|
||||
@@ -435,7 +435,8 @@ mod tests {
|
||||
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source, None);
|
||||
let start = Instant::now();
|
||||
// Call the connection handler in separate thread, does block.
|
||||
let thread_jh = thread::spawn(move || loop {
|
||||
let thread_jh = thread::spawn(move || {
|
||||
loop {
|
||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
|
||||
if result.is_err() {
|
||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||
@@ -447,6 +448,7 @@ mod tests {
|
||||
if Instant::now() - start > Duration::from_millis(100) {
|
||||
panic!("regular stop signal handling failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
thread_jh.join().expect("thread join failed");
|
||||
}
|
||||
@@ -469,7 +471,8 @@ mod tests {
|
||||
let stop_signal_copy = stop_signal.clone();
|
||||
let start = Instant::now();
|
||||
// Call the connection handler in separate thread, does block.
|
||||
let thread_jh = thread::spawn(move || loop {
|
||||
let thread_jh = thread::spawn(move || {
|
||||
loop {
|
||||
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
|
||||
if result.is_err() {
|
||||
panic!("handling connection failed: {:?}", result.unwrap_err());
|
||||
@@ -484,6 +487,7 @@ mod tests {
|
||||
if Instant::now() - start > Duration::from_millis(100) {
|
||||
panic!("regular stop signal handling failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
// We connect but do not do anything.
|
||||
let _stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
|
||||
|
@@ -11,8 +11,8 @@ use std::io::{self, Read};
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
|
||||
use crate::tmtc::{PacketSenderRaw, PacketSource};
|
||||
use crate::ComponentId;
|
||||
use crate::tmtc::{PacketSenderRaw, PacketSource};
|
||||
use thiserror::Error;
|
||||
|
||||
// Re-export the TMTC in COBS server.
|
||||
@@ -194,7 +194,7 @@ impl<
|
||||
HandledConnection: HandledConnectionHandler,
|
||||
TmError: 'static,
|
||||
TcSendError: 'static,
|
||||
>
|
||||
>
|
||||
TcpTmtcGenericServer<
|
||||
TmSource,
|
||||
TcSender,
|
||||
|
@@ -5,9 +5,9 @@ use mio::net::{TcpListener, TcpStream};
|
||||
use std::{io::Write, net::SocketAddr};
|
||||
|
||||
use crate::{
|
||||
ComponentId,
|
||||
encoding::{ccsds::SpacePacketValidator, parse_buffer_for_ccsds_space_packets},
|
||||
tmtc::{PacketSenderRaw, PacketSource},
|
||||
ComponentId,
|
||||
};
|
||||
|
||||
use super::tcp_server::{
|
||||
@@ -113,7 +113,7 @@ impl<
|
||||
HandledConnection: HandledConnectionHandler,
|
||||
TmError: 'static,
|
||||
TcError: 'static,
|
||||
> TcpSpacepacketsServer<TmSource, TcSender, Validator, HandledConnection, TmError, TcError>
|
||||
> TcpSpacepacketsServer<TmSource, TcSender, Validator, HandledConnection, TmError, TcError>
|
||||
{
|
||||
///
|
||||
/// ## Parameter
|
||||
@@ -185,19 +185,19 @@ mod tests {
|
||||
use alloc::sync::Arc;
|
||||
use hashbrown::HashSet;
|
||||
use spacepackets::{
|
||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||
CcsdsPacket, PacketId, SpHeader,
|
||||
ecss::{WritablePusPacket, tc::PusTcCreator},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ComponentId,
|
||||
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
||||
hal::std::tcp_server::{
|
||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
||||
ConnectionResult, ServerConfig,
|
||||
tests::{ConnectionFinishedHandler, SyncTmSource},
|
||||
},
|
||||
queue::GenericSendError,
|
||||
tmtc::PacketAsVec,
|
||||
ComponentId,
|
||||
};
|
||||
|
||||
use super::TcpSpacepacketsServer;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
//! Generic UDP TC server.
|
||||
use crate::tmtc::PacketSenderRaw;
|
||||
use crate::ComponentId;
|
||||
use crate::tmtc::PacketSenderRaw;
|
||||
use core::fmt::Debug;
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
|
||||
@@ -105,7 +105,7 @@ impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
||||
Err(ReceiveResult::NothingReceived)
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
let (num_bytes, from) = res;
|
||||
@@ -123,14 +123,13 @@ impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ComponentId;
|
||||
use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||
use crate::queue::GenericSendError;
|
||||
use crate::tmtc::PacketSenderRaw;
|
||||
use crate::ComponentId;
|
||||
use core::cell::RefCell;
|
||||
use spacepackets::ecss::tc::PusTcCreator;
|
||||
use spacepackets::ecss::WritablePusPacket;
|
||||
use spacepackets::SpHeader;
|
||||
use spacepackets::ecss::tc::PusTcCreator;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::vec::Vec;
|
||||
|
39
satrs/src/health.rs
Normal file
39
satrs/src/health.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::ComponentId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum HealthState {
|
||||
Healthy = 1,
|
||||
Faulty = 2,
|
||||
PermanentFaulty = 3,
|
||||
ExternalControl = 4,
|
||||
NeedsRecovery = 5,
|
||||
}
|
||||
|
||||
pub trait HealthTableProvider {
|
||||
fn health(&self, id: ComponentId) -> Option<HealthState>;
|
||||
fn set_health(&mut self, id: ComponentId, health: HealthState);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HealthTableMapSync(
|
||||
std::sync::Arc<std::sync::Mutex<hashbrown::HashMap<ComponentId, HealthState>>>,
|
||||
);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl HealthTableMapSync {
|
||||
pub fn new(health_table: hashbrown::HashMap<ComponentId, HealthState>) -> Self {
|
||||
Self(std::sync::Arc::new(std::sync::Mutex::new(health_table)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl HealthTableProvider for HealthTableMapSync {
|
||||
fn health(&self, id: ComponentId) -> Option<HealthState> {
|
||||
self.0.lock().unwrap().get(&id).copied()
|
||||
}
|
||||
|
||||
fn set_health(&mut self, id: ComponentId, health: HealthState) {
|
||||
self.0.lock().unwrap().insert(id, health);
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@
|
||||
//! - 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/).
|
||||
#![no_std]
|
||||
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -22,31 +22,34 @@ extern crate downcast_rs;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
extern crate std;
|
||||
|
||||
pub mod action;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod cfdp;
|
||||
pub mod dev_mgmt;
|
||||
pub mod encoding;
|
||||
pub mod event_man;
|
||||
pub mod events;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod executable;
|
||||
pub mod hal;
|
||||
pub mod health;
|
||||
pub mod hk;
|
||||
pub mod mode;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod mode_tree;
|
||||
pub mod params;
|
||||
pub mod pool;
|
||||
pub mod power;
|
||||
pub mod pus;
|
||||
pub mod queue;
|
||||
pub mod request;
|
||||
pub mod res_code;
|
||||
pub mod seq_count;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod scheduling;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod subsystem;
|
||||
pub mod time;
|
||||
pub mod tmtc;
|
||||
|
||||
pub mod action;
|
||||
pub mod hk;
|
||||
pub mod mode;
|
||||
pub mod params;
|
||||
|
||||
pub use spacepackets;
|
||||
|
||||
use spacepackets::PacketId;
|
||||
|
@@ -11,9 +11,11 @@ pub use alloc_mod::*;
|
||||
pub use std_mod::*;
|
||||
|
||||
use crate::{
|
||||
queue::GenericTargetedMessagingError,
|
||||
request::{GenericMessage, MessageMetadata, MessageReceiver, MessageReceiverWithId, RequestId},
|
||||
ComponentId,
|
||||
queue::{GenericReceiveError, GenericSendError},
|
||||
request::{
|
||||
GenericMessage, MessageMetadata, MessageReceiverProvider, MessageReceiverWithId, RequestId,
|
||||
},
|
||||
};
|
||||
|
||||
pub type Mode = u32;
|
||||
@@ -26,6 +28,11 @@ pub struct ModeAndSubmode {
|
||||
submode: Submode,
|
||||
}
|
||||
|
||||
pub const INVALID_MODE_VAL: Mode = Mode::MAX;
|
||||
pub const UNKNOWN_MODE_VAL: Mode = Mode::MAX - 1;
|
||||
pub const INVALID_MODE: ModeAndSubmode = ModeAndSubmode::new(INVALID_MODE_VAL, 0);
|
||||
pub const UNKNOWN_MODE: ModeAndSubmode = ModeAndSubmode::new(UNKNOWN_MODE_VAL, 0);
|
||||
|
||||
impl ModeAndSubmode {
|
||||
pub const RAW_LEN: usize = size_of::<Mode>() + size_of::<Submode>();
|
||||
|
||||
@@ -111,7 +118,10 @@ impl TargetedModeCommand {
|
||||
pub enum ModeRequest {
|
||||
/// Mode information. Can be used to notify other components of changed modes.
|
||||
ModeInfo(ModeAndSubmode),
|
||||
SetMode(ModeAndSubmode),
|
||||
SetMode {
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
},
|
||||
ReadMode,
|
||||
AnnounceMode,
|
||||
AnnounceModeRecursive,
|
||||
@@ -127,6 +137,8 @@ pub struct TargetedModeRequest {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ModeReply {
|
||||
/// Mode information. Can be used to notify other components of changed modes.
|
||||
ModeInfo(ModeAndSubmode),
|
||||
/// Reply to a mode request to confirm the commanded mode was reached.
|
||||
ModeReply(ModeAndSubmode),
|
||||
// Can not reach the commanded mode. Contains a reason as a [ResultU16].
|
||||
@@ -147,34 +159,33 @@ pub trait ModeRequestSender {
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError>;
|
||||
) -> Result<(), GenericSendError>;
|
||||
}
|
||||
|
||||
pub trait ModeRequestReceiver {
|
||||
fn try_recv_mode_request(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError>;
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError>;
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver<ModeRequest>> ModeRequestReceiver
|
||||
impl<R: MessageReceiverProvider<ModeRequest>> ModeRequestReceiver
|
||||
for MessageReceiverWithId<ModeRequest, R>
|
||||
{
|
||||
fn try_recv_mode_request(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
|
||||
self.try_recv_message()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum ModeError {
|
||||
Messaging(GenericTargetedMessagingError),
|
||||
}
|
||||
|
||||
impl From<GenericTargetedMessagingError> for ModeError {
|
||||
fn from(value: GenericTargetedMessagingError) -> Self {
|
||||
Self::Messaging(value)
|
||||
}
|
||||
#[error("Messaging send error: {0}")]
|
||||
Send(#[from] GenericSendError),
|
||||
#[error("Messaging receive error: {0}")]
|
||||
Receive(#[from] GenericReceiveError),
|
||||
#[error("busy with other mode request")]
|
||||
Busy,
|
||||
}
|
||||
|
||||
pub trait ModeProvider {
|
||||
@@ -196,6 +207,7 @@ pub trait ModeRequestHandler: ModeProvider {
|
||||
&mut self,
|
||||
requestor: MessageMetadata,
|
||||
mode_and_submode: ModeAndSubmode,
|
||||
forced: bool,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
fn announce_mode(&self, requestor_info: Option<MessageMetadata>, recursive: bool);
|
||||
@@ -222,9 +234,10 @@ pub trait ModeRequestHandler: ModeProvider {
|
||||
request: GenericMessage<ModeRequest>,
|
||||
) -> Result<(), Self::Error> {
|
||||
match request.message {
|
||||
ModeRequest::SetMode(mode_and_submode) => {
|
||||
self.start_transition(request.requestor_info, mode_and_submode)
|
||||
}
|
||||
ModeRequest::SetMode {
|
||||
mode_and_submode,
|
||||
forced,
|
||||
} => self.start_transition(request.requestor_info, mode_and_submode, forced),
|
||||
ModeRequest::ReadMode => self.send_mode_reply(
|
||||
request.requestor_info,
|
||||
ModeReply::ModeReply(self.mode_and_submode()),
|
||||
@@ -243,15 +256,16 @@ pub trait ModeRequestHandler: ModeProvider {
|
||||
}
|
||||
|
||||
pub trait ModeReplyReceiver {
|
||||
fn try_recv_mode_reply(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError>;
|
||||
fn try_recv_mode_reply(&self)
|
||||
-> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError>;
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver<ModeReply>> ModeReplyReceiver for MessageReceiverWithId<ModeReply, R> {
|
||||
impl<R: MessageReceiverProvider<ModeReply>> ModeReplyReceiver
|
||||
for MessageReceiverWithId<ModeReply, R>
|
||||
{
|
||||
fn try_recv_mode_reply(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
|
||||
self.try_recv_message()
|
||||
}
|
||||
}
|
||||
@@ -264,24 +278,28 @@ pub trait ModeReplySender {
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
reply: ModeReply,
|
||||
) -> Result<(), GenericTargetedMessagingError>;
|
||||
) -> Result<(), GenericSendError>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use crate::request::{
|
||||
MessageSender, MessageSenderAndReceiver, MessageSenderMap, RequestAndReplySenderAndReceiver,
|
||||
use crate::{
|
||||
queue::{GenericReceiveError, GenericSendError},
|
||||
request::{
|
||||
MessageSenderAndReceiver, MessageSenderMap, MessageSenderProvider,
|
||||
MessageSenderStoreProvider, RequestAndReplySenderAndReceiver,
|
||||
},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<S: MessageSender<ModeReply>> MessageSenderMap<ModeReply, S> {
|
||||
impl<S: MessageSenderProvider<ModeReply>> MessageSenderMap<ModeReply, S> {
|
||||
pub fn send_mode_reply(
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
target_id: ComponentId,
|
||||
request: ModeReply,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.send_message(requestor_info, target_id, request)
|
||||
}
|
||||
|
||||
@@ -290,8 +308,12 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<FROM, S: MessageSender<ModeReply>, R: MessageReceiver<FROM>> ModeReplySender
|
||||
for MessageSenderAndReceiver<ModeReply, FROM, S, R>
|
||||
impl<
|
||||
From,
|
||||
Sender: MessageSenderProvider<ModeReply>,
|
||||
Receiver: MessageReceiverProvider<From>,
|
||||
SenderStore: MessageSenderStoreProvider<ModeReply, Sender>,
|
||||
> ModeReplySender for MessageSenderAndReceiver<ModeReply, From, Sender, Receiver, SenderStore>
|
||||
{
|
||||
fn local_channel_id(&self) -> ComponentId {
|
||||
self.local_channel_id_generic()
|
||||
@@ -301,8 +323,8 @@ pub mod alloc_mod {
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
request: ModeReply,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
self.message_sender_map.send_mode_reply(
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.message_sender_store.send_message(
|
||||
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
|
||||
requestor_info.sender_id(),
|
||||
request,
|
||||
@@ -310,37 +332,66 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeReply>> ModeReplyReceiver
|
||||
for MessageSenderAndReceiver<TO, ModeReply, S, R>
|
||||
impl<
|
||||
To,
|
||||
Sender: MessageSenderProvider<To>,
|
||||
Receiver: MessageReceiverProvider<ModeReply>,
|
||||
SenderStore: MessageSenderStoreProvider<To, Sender>,
|
||||
> ModeReplyReceiver for MessageSenderAndReceiver<To, ModeReply, Sender, Receiver, SenderStore>
|
||||
{
|
||||
fn try_recv_mode_reply(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
|
||||
self.message_receiver.try_recv_message()
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
REQUEST,
|
||||
S0: MessageSender<REQUEST>,
|
||||
R0: MessageReceiver<ModeReply>,
|
||||
S1: MessageSender<ModeReply>,
|
||||
R1: MessageReceiver<REQUEST>,
|
||||
> RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
|
||||
Request,
|
||||
ReqSender: MessageSenderProvider<Request>,
|
||||
ReqReceiver: MessageReceiverProvider<Request>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
|
||||
Reply,
|
||||
ReplySender: MessageSenderProvider<Reply>,
|
||||
ReplyReceiver: MessageReceiverProvider<Reply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
|
||||
>
|
||||
RequestAndReplySenderAndReceiver<
|
||||
Request,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
Reply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: S1) {
|
||||
self.reply_sender_map
|
||||
pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: ReplySender) {
|
||||
self.reply_sender_store
|
||||
.add_message_target(target_id, reply_sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
REQUEST,
|
||||
S0: MessageSender<REQUEST>,
|
||||
R0: MessageReceiver<ModeReply>,
|
||||
S1: MessageSender<ModeReply>,
|
||||
R1: MessageReceiver<REQUEST>,
|
||||
> ModeReplySender for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
|
||||
Request,
|
||||
ReqSender: MessageSenderProvider<Request>,
|
||||
ReqReceiver: MessageReceiverProvider<Request>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
|
||||
ReplySender: MessageSenderProvider<ModeReply>,
|
||||
ReplyReceiver: MessageReceiverProvider<ModeReply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<ModeReply, ReplySender>,
|
||||
> ModeReplySender
|
||||
for RequestAndReplySenderAndReceiver<
|
||||
Request,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
ModeReply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
fn local_channel_id(&self) -> ComponentId {
|
||||
self.local_channel_id_generic()
|
||||
@@ -349,42 +400,56 @@ pub mod alloc_mod {
|
||||
fn send_mode_reply(
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
request: ModeReply,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
self.reply_sender_map.send_mode_reply(
|
||||
reply: ModeReply,
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.reply_sender_store.send_message(
|
||||
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
|
||||
requestor_info.sender_id(),
|
||||
request,
|
||||
reply,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
REQUEST,
|
||||
S0: MessageSender<REQUEST>,
|
||||
R0: MessageReceiver<ModeReply>,
|
||||
S1: MessageSender<ModeReply>,
|
||||
R1: MessageReceiver<REQUEST>,
|
||||
Request,
|
||||
ReqSender: MessageSenderProvider<Request>,
|
||||
ReqReceiver: MessageReceiverProvider<Request>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
|
||||
ReplySender: MessageSenderProvider<ModeReply>,
|
||||
ReplyReceiver: MessageReceiverProvider<ModeReply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<ModeReply, ReplySender>,
|
||||
> ModeReplyReceiver
|
||||
for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
|
||||
for RequestAndReplySenderAndReceiver<
|
||||
Request,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
ModeReply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
fn try_recv_mode_reply(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
|
||||
self.reply_receiver.try_recv_message()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type definition for a mode handler which can handle mode requests.
|
||||
pub type ModeRequestHandlerInterface<S, R> =
|
||||
MessageSenderAndReceiver<ModeReply, ModeRequest, S, R>;
|
||||
pub type ModeRequestHandlerInterface<Sender, Receiver, ReplySenderStore> =
|
||||
MessageSenderAndReceiver<ModeReply, ModeRequest, Sender, Receiver, ReplySenderStore>;
|
||||
|
||||
impl<S: MessageSender<ModeReply>, R: MessageReceiver<ModeRequest>>
|
||||
ModeRequestHandlerInterface<S, R>
|
||||
impl<
|
||||
Sender: MessageSenderProvider<ModeReply>,
|
||||
Receiver: MessageReceiverProvider<ModeRequest>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<ModeReply, Sender>,
|
||||
> ModeRequestHandlerInterface<Sender, Receiver, ReplySenderStore>
|
||||
{
|
||||
pub fn try_recv_mode_request(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
|
||||
self.try_recv_message()
|
||||
}
|
||||
|
||||
@@ -392,7 +457,7 @@ pub mod alloc_mod {
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
reply: ModeReply,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.send_message(
|
||||
requestor_info.request_id(),
|
||||
requestor_info.sender_id(),
|
||||
@@ -403,12 +468,18 @@ pub mod alloc_mod {
|
||||
|
||||
/// Helper type defintion for a mode handler object which can send mode requests and receive
|
||||
/// mode replies.
|
||||
pub type ModeRequestorInterface<S, R> = MessageSenderAndReceiver<ModeRequest, ModeReply, S, R>;
|
||||
pub type ModeRequestorInterface<Sender, Receiver, RequestSenderStore> =
|
||||
MessageSenderAndReceiver<ModeRequest, ModeReply, Sender, Receiver, RequestSenderStore>;
|
||||
|
||||
impl<S: MessageSender<ModeRequest>, R: MessageReceiver<ModeReply>> ModeRequestorInterface<S, R> {
|
||||
impl<
|
||||
Sender: MessageSenderProvider<ModeRequest>,
|
||||
Receiver: MessageReceiverProvider<ModeReply>,
|
||||
RequestSenderStore: MessageSenderStoreProvider<ModeRequest, Sender>,
|
||||
> ModeRequestorInterface<Sender, Receiver, RequestSenderStore>
|
||||
{
|
||||
pub fn try_recv_mode_reply(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
|
||||
self.try_recv_message()
|
||||
}
|
||||
|
||||
@@ -417,23 +488,38 @@ pub mod alloc_mod {
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
reply: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.send_message(request_id, target_id, reply)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type defintion for a mode handler object which can both send mode requests and
|
||||
/// process mode requests.
|
||||
pub type ModeInterface<S0, R0, S1, R1> =
|
||||
RequestAndReplySenderAndReceiver<ModeRequest, ModeReply, S0, R0, S1, R1>;
|
||||
pub type ModeInterface<
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
> = RequestAndReplySenderAndReceiver<
|
||||
ModeRequest,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
ModeReply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>;
|
||||
|
||||
impl<S: MessageSender<ModeRequest>> MessageSenderMap<ModeRequest, S> {
|
||||
impl<S: MessageSenderProvider<ModeRequest>> MessageSenderMap<ModeRequest, S> {
|
||||
pub fn send_mode_request(
|
||||
&self,
|
||||
requestor_info: MessageMetadata,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.send_message(requestor_info, target_id, request)
|
||||
}
|
||||
|
||||
@@ -442,35 +528,28 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl<S: MessageSender<ModeRequest>> ModeRequestSender for MessageSenderMapWithId<ModeRequest, S> {
|
||||
fn local_channel_id(&self) -> ComponentId {
|
||||
self.local_channel_id
|
||||
}
|
||||
|
||||
fn send_mode_request(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
self.send_message(request_id, target_id, request)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeRequest>> ModeRequestReceiver
|
||||
for MessageSenderAndReceiver<TO, ModeRequest, S, R>
|
||||
impl<
|
||||
To,
|
||||
Sender: MessageSenderProvider<To>,
|
||||
Receiver: MessageReceiverProvider<ModeRequest>,
|
||||
SenderStore: MessageSenderStoreProvider<To, Sender>,
|
||||
> ModeRequestReceiver
|
||||
for MessageSenderAndReceiver<To, ModeRequest, Sender, Receiver, SenderStore>
|
||||
{
|
||||
fn try_recv_mode_request(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
|
||||
self.message_receiver.try_recv_message()
|
||||
}
|
||||
}
|
||||
|
||||
impl<FROM, S: MessageSender<ModeRequest>, R: MessageReceiver<FROM>> ModeRequestSender
|
||||
for MessageSenderAndReceiver<ModeRequest, FROM, S, R>
|
||||
impl<
|
||||
From,
|
||||
Sender: MessageSenderProvider<ModeRequest>,
|
||||
Receiver: MessageReceiverProvider<From>,
|
||||
SenderStore: MessageSenderStoreProvider<ModeRequest, Sender>,
|
||||
> ModeRequestSender
|
||||
for MessageSenderAndReceiver<ModeRequest, From, Sender, Receiver, SenderStore>
|
||||
{
|
||||
fn local_channel_id(&self) -> ComponentId {
|
||||
self.local_channel_id_generic()
|
||||
@@ -481,8 +560,8 @@ pub mod alloc_mod {
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
self.message_sender_map.send_mode_request(
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.message_sender_store.send_message(
|
||||
MessageMetadata::new(request_id, self.local_channel_id()),
|
||||
target_id,
|
||||
request,
|
||||
@@ -491,27 +570,50 @@ pub mod alloc_mod {
|
||||
}
|
||||
|
||||
impl<
|
||||
REPLY,
|
||||
S0: MessageSender<ModeRequest>,
|
||||
R0: MessageReceiver<REPLY>,
|
||||
S1: MessageSender<REPLY>,
|
||||
R1: MessageReceiver<ModeRequest>,
|
||||
> RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
|
||||
ReqSender: MessageSenderProvider<ModeRequest>,
|
||||
ReqReceiver: MessageReceiverProvider<ModeRequest>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
|
||||
Reply,
|
||||
ReplySender: MessageSenderProvider<Reply>,
|
||||
ReplyReceiver: MessageReceiverProvider<Reply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
|
||||
>
|
||||
RequestAndReplySenderAndReceiver<
|
||||
ModeRequest,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
Reply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: S0) {
|
||||
self.request_sender_map
|
||||
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: ReqSender) {
|
||||
self.request_sender_store
|
||||
.add_message_target(target_id, request_sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
REPLY,
|
||||
S0: MessageSender<ModeRequest>,
|
||||
R0: MessageReceiver<REPLY>,
|
||||
S1: MessageSender<REPLY>,
|
||||
R1: MessageReceiver<ModeRequest>,
|
||||
ReqSender: MessageSenderProvider<ModeRequest>,
|
||||
ReqReceiver: MessageReceiverProvider<ModeRequest>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
|
||||
Reply,
|
||||
ReplySender: MessageSenderProvider<Reply>,
|
||||
ReplyReceiver: MessageReceiverProvider<Reply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
|
||||
> ModeRequestSender
|
||||
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
|
||||
for RequestAndReplySenderAndReceiver<
|
||||
ModeRequest,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
Reply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
fn local_channel_id(&self) -> ComponentId {
|
||||
self.local_channel_id_generic()
|
||||
@@ -522,8 +624,8 @@ pub mod alloc_mod {
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericTargetedMessagingError> {
|
||||
self.request_sender_map.send_mode_request(
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.request_sender_store.send_message(
|
||||
MessageMetadata::new(request_id, self.local_channel_id()),
|
||||
target_id,
|
||||
request,
|
||||
@@ -532,17 +634,28 @@ pub mod alloc_mod {
|
||||
}
|
||||
|
||||
impl<
|
||||
REPLY,
|
||||
S0: MessageSender<ModeRequest>,
|
||||
R0: MessageReceiver<REPLY>,
|
||||
S1: MessageSender<REPLY>,
|
||||
R1: MessageReceiver<ModeRequest>,
|
||||
ReqSender: MessageSenderProvider<ModeRequest>,
|
||||
ReqReceiver: MessageReceiverProvider<ModeRequest>,
|
||||
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
|
||||
Reply,
|
||||
ReplySender: MessageSenderProvider<Reply>,
|
||||
ReplyReceiver: MessageReceiverProvider<Reply>,
|
||||
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
|
||||
> ModeRequestReceiver
|
||||
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
|
||||
for RequestAndReplySenderAndReceiver<
|
||||
ModeRequest,
|
||||
ReqSender,
|
||||
ReqReceiver,
|
||||
ReqSenderStore,
|
||||
Reply,
|
||||
ReplySender,
|
||||
ReplyReceiver,
|
||||
ReplySenderStore,
|
||||
>
|
||||
{
|
||||
fn try_recv_mode_request(
|
||||
&self,
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
|
||||
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
|
||||
self.request_receiver.try_recv_message()
|
||||
}
|
||||
}
|
||||
@@ -552,39 +665,97 @@ pub mod alloc_mod {
|
||||
pub mod std_mod {
|
||||
use std::sync::mpsc;
|
||||
|
||||
use crate::request::{MessageSenderList, OneMessageSender};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub type ModeRequestHandlerMpsc = ModeRequestHandlerInterface<
|
||||
mpsc::Sender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||
MessageSenderList<ModeReply, mpsc::Sender<GenericMessage<ModeReply>>>,
|
||||
>;
|
||||
pub type ModeRequestHandlerMpscBounded = ModeRequestHandlerInterface<
|
||||
mpsc::SyncSender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||
MessageSenderList<ModeReply, mpsc::SyncSender<GenericMessage<ModeReply>>>,
|
||||
>;
|
||||
|
||||
pub type ModeRequestorMpsc = ModeRequestorInterface<
|
||||
pub type ModeRequestorOneChildMpsc = ModeRequestorInterface<
|
||||
mpsc::Sender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
OneMessageSender<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
|
||||
>;
|
||||
pub type ModeRequestorBoundedMpsc = ModeRequestorInterface<
|
||||
pub type ModeRequestorOneChildBoundedMpsc = ModeRequestorInterface<
|
||||
mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
OneMessageSender<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
>;
|
||||
pub type ModeRequestorChildListMpsc = ModeRequestorInterface<
|
||||
mpsc::Sender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
MessageSenderList<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
|
||||
>;
|
||||
pub type ModeRequestorChildListBoundedMpsc = ModeRequestorInterface<
|
||||
mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
MessageSenderList<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
>;
|
||||
|
||||
pub type ModeRequestorAndHandlerMpsc = ModeInterface<
|
||||
mpsc::Sender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
mpsc::Sender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||
MessageSenderList<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
|
||||
mpsc::Sender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
MessageSenderList<ModeReply, mpsc::Sender<GenericMessage<ModeReply>>>,
|
||||
>;
|
||||
pub type ModeRequestorAndHandlerMpscBounded = ModeInterface<
|
||||
mpsc::SyncSender<GenericMessage<ModeRequest>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
mpsc::SyncSender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||
MessageSenderList<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||
mpsc::SyncSender<GenericMessage<ModeReply>>,
|
||||
mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||
MessageSenderList<ModeReply, mpsc::SyncSender<GenericMessage<ModeReply>>>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
||||
pub(crate) mod tests {
|
||||
use core::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{ComponentId, request::RequestId};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ModeReqWrapper {
|
||||
pub request_id: RequestId,
|
||||
pub target_id: ComponentId,
|
||||
pub request: ModeRequest,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModeReqSenderMock {
|
||||
pub requests: RefCell<VecDeque<ModeReqWrapper>>,
|
||||
}
|
||||
|
||||
impl ModeRequestSender for ModeReqSenderMock {
|
||||
fn local_channel_id(&self) -> crate::ComponentId {
|
||||
0
|
||||
}
|
||||
|
||||
fn send_mode_request(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
target_id: ComponentId,
|
||||
request: ModeRequest,
|
||||
) -> Result<(), GenericSendError> {
|
||||
self.requests.borrow_mut().push_back(ModeReqWrapper {
|
||||
request_id,
|
||||
target_id,
|
||||
request,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,57 @@ use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::{
|
||||
mode::{Mode, ModeAndSubmode, Submode},
|
||||
ComponentId,
|
||||
mode::{Mode, ModeAndSubmode, ModeReply, ModeRequest, Submode},
|
||||
request::MessageSenderProvider,
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
|
||||
/// Common trait for node modes which can have mode parents or mode children.
|
||||
pub trait ModeNode {
|
||||
fn id(&self) -> ComponentId;
|
||||
}
|
||||
/// Trait which denotes that an object is a parent in a mode tree.
|
||||
///
|
||||
/// A mode parent is capable of sending mode requests to child objects and has a unique component
|
||||
/// ID.
|
||||
pub trait ModeParent: ModeNode {
|
||||
type Sender: MessageSenderProvider<ModeRequest>;
|
||||
|
||||
fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender);
|
||||
}
|
||||
|
||||
/// Trait which denotes that an object is a child in a mode tree.
|
||||
///
|
||||
/// A child is capable of sending mode replies to parent objects and has a unique component ID.
|
||||
pub trait ModeChild: ModeNode {
|
||||
type Sender: MessageSenderProvider<ModeReply>;
|
||||
|
||||
fn add_mode_parent(&mut self, id: ComponentId, reply_sender: Self::Sender);
|
||||
}
|
||||
|
||||
/// Utility method which connects a mode tree parent object to a child object by calling
|
||||
/// [ModeParent::add_mode_child] on the [parent][ModeParent] and calling
|
||||
/// [ModeChild::add_mode_parent] on the [child][ModeChild].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `parent` - The parent object which implements [ModeParent].
|
||||
/// * `request_sender` - Sender object to send mode requests to the child.
|
||||
/// * `child` - The child object which implements [ModeChild].
|
||||
/// * `reply_sender` - Sender object to send mode replies to the parent.
|
||||
pub fn connect_mode_nodes<ReqSender, ReplySender>(
|
||||
parent: &mut impl ModeParent<Sender = ReqSender>,
|
||||
request_sender: ReqSender,
|
||||
child: &mut impl ModeChild<Sender = ReplySender>,
|
||||
reply_sender: ReplySender,
|
||||
) {
|
||||
parent.add_mode_child(child.id(), request_sender);
|
||||
child.add_mode_parent(parent.id(), reply_sender);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum TableEntryType {
|
||||
/// Target table containing information of the expected children modes for given mode.
|
||||
@@ -15,23 +62,553 @@ pub enum TableEntryType {
|
||||
Sequence,
|
||||
}
|
||||
|
||||
pub struct ModeTableEntry {
|
||||
/// Common fields required for both target and sequence table entries.
|
||||
///
|
||||
/// The most important parameters here are the target ID which this entry belongs to, and the mode
|
||||
/// and submode the entry either will be commanded to for sequence table entries or which will be
|
||||
/// monitored for target table entries.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ModeTableEntryCommon {
|
||||
/// Name of respective table entry.
|
||||
pub name: &'static str,
|
||||
/// Target channel ID.
|
||||
pub channel_id: ComponentId,
|
||||
/// Target component ID.
|
||||
pub target_id: ComponentId,
|
||||
/// Has a different meaning depending on whether this is a sequence table or a target table.
|
||||
///
|
||||
/// - For sequence tables, this denotes the mode which will be commanded
|
||||
/// - For target tables, this is the mode which the target children should have and which
|
||||
/// might be monitored depending on configuration.
|
||||
pub mode_submode: ModeAndSubmode,
|
||||
/// This mask allows to specify multiple allowed submodes for a given mode.
|
||||
pub allowed_submode_mask: Option<Submode>,
|
||||
}
|
||||
|
||||
impl ModeTableEntryCommon {
|
||||
pub fn set_allowed_submode_mask(&mut self, mask: Submode) {
|
||||
self.allowed_submode_mask = Some(mask);
|
||||
}
|
||||
|
||||
pub fn allowed_submode_mask(&self) -> Option<Submode> {
|
||||
self.allowed_submode_mask
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry for the target tables.
|
||||
#[derive(Debug)]
|
||||
pub struct TargetTableEntry {
|
||||
pub common: ModeTableEntryCommon,
|
||||
pub monitor_state: bool,
|
||||
}
|
||||
|
||||
impl TargetTableEntry {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
target_id: ComponentId,
|
||||
mode_submode: ModeAndSubmode,
|
||||
allowed_submode_mask: Option<Submode>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: ModeTableEntryCommon {
|
||||
name,
|
||||
target_id,
|
||||
mode_submode,
|
||||
allowed_submode_mask,
|
||||
},
|
||||
monitor_state: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_precise_submode(
|
||||
name: &'static str,
|
||||
target_id: ComponentId,
|
||||
mode_submode: ModeAndSubmode,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: ModeTableEntryCommon {
|
||||
name,
|
||||
target_id,
|
||||
mode_submode,
|
||||
allowed_submode_mask: None,
|
||||
},
|
||||
monitor_state: true,
|
||||
}
|
||||
}
|
||||
|
||||
delegate::delegate! {
|
||||
to self.common {
|
||||
pub fn set_allowed_submode_mask(&mut self, mask: Submode);
|
||||
pub fn allowed_submode_mask(&self) -> Option<Submode>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry for the sequence tables.
|
||||
///
|
||||
/// The [Self::check_success] field specifies that a mode sequence executor should check that the
|
||||
/// target mode was actually reached before executing the next sequence.
|
||||
#[derive(Debug)]
|
||||
pub struct SequenceTableEntry {
|
||||
pub common: ModeTableEntryCommon,
|
||||
pub check_success: bool,
|
||||
}
|
||||
|
||||
pub struct ModeTableMapValue {
|
||||
/// Name for a given mode table entry.
|
||||
pub name: &'static str,
|
||||
pub entries: Vec<ModeTableEntry>,
|
||||
impl SequenceTableEntry {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
target_id: ComponentId,
|
||||
mode_submode: ModeAndSubmode,
|
||||
check_success: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: ModeTableEntryCommon {
|
||||
name,
|
||||
target_id,
|
||||
mode_submode,
|
||||
allowed_submode_mask: None,
|
||||
},
|
||||
check_success,
|
||||
}
|
||||
}
|
||||
|
||||
delegate::delegate! {
|
||||
to self.common {
|
||||
pub fn set_allowed_submode_mask(&mut self, mask: Submode);
|
||||
pub fn allowed_submode_mask(&self) -> Option<Submode>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ModeTable = HashMap<Mode, ModeTableMapValue>;
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("target {0} not in mode store")]
|
||||
pub struct TargetNotInModeStoreError(pub ComponentId);
|
||||
|
||||
/// Mode store value type.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ModeStoreValue {
|
||||
/// ID of the mode component.
|
||||
id: ComponentId,
|
||||
/// Current mode and submode of the component.
|
||||
pub mode_and_submode: ModeAndSubmode,
|
||||
/// State information to track whether a reply should be awaited for the mode component.
|
||||
pub awaiting_reply: bool,
|
||||
}
|
||||
|
||||
impl ModeStoreValue {
|
||||
pub fn new(id: ComponentId, mode_and_submode: ModeAndSubmode) -> Self {
|
||||
Self {
|
||||
id,
|
||||
mode_and_submode,
|
||||
awaiting_reply: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||
self.mode_and_submode
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ModeStoreProvider {
|
||||
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode);
|
||||
|
||||
fn has_component(&self, target_id: ComponentId) -> bool;
|
||||
|
||||
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue>;
|
||||
|
||||
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue>;
|
||||
|
||||
/// Generic handler for mode replies received from child components.
|
||||
///
|
||||
/// Implementation should clear the awaition flag if the `handle_reply_awaition` argument is
|
||||
/// true and returns whether any children are still awaiting replies. If the flag is not set
|
||||
fn mode_reply_handler_with_reply_awaition(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
reported_mode_and_submode: Option<ModeAndSubmode>,
|
||||
) -> bool {
|
||||
self.mode_reply_handler(sender_id, reported_mode_and_submode, true)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn mode_reply_handler_without_reply_awaition(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
reported_mode_and_submode: Option<ModeAndSubmode>,
|
||||
) {
|
||||
self.mode_reply_handler(sender_id, reported_mode_and_submode, false);
|
||||
}
|
||||
|
||||
fn mode_reply_handler(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
reported_mode_and_submode: Option<ModeAndSubmode>,
|
||||
with_reply_awaition: bool,
|
||||
) -> Option<bool>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TargetTablesMapValue {
|
||||
/// Name for a given mode table entry.
|
||||
pub name: &'static str,
|
||||
/// Optional fallback mode if the target mode can not be kept.
|
||||
pub fallback_mode: Option<Mode>,
|
||||
/// These are the rows of the a target table.
|
||||
pub entries: Vec<TargetTableEntry>,
|
||||
}
|
||||
|
||||
impl TargetTablesMapValue {
|
||||
pub fn new(name: &'static str, fallback_mode: Option<Mode>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
fallback_mode,
|
||||
entries: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_entry(&mut self, entry: TargetTableEntry) {
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// One sequence of a [SequenceTablesMapValue] in a [SequenceModeTables].
|
||||
///
|
||||
/// It contains all mode requests which need to be executed for a sequence step and it also
|
||||
/// associates a [Self::name] with the sequence.
|
||||
#[derive(Debug)]
|
||||
pub struct SequenceTableMapTable {
|
||||
/// Name for a given mode sequence.
|
||||
pub name: &'static str,
|
||||
/// These are the rows of the a sequence table.
|
||||
pub entries: Vec<SequenceTableEntry>,
|
||||
}
|
||||
|
||||
impl SequenceTableMapTable {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
entries: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_entry(&mut self, entry: SequenceTableEntry) {
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence table entry.
|
||||
///
|
||||
/// This is simply a list of [SequenceTableMapTable]s which also associates a [Self::name]
|
||||
/// with the sequence. The order of sub-tables in the list also specifies the execution order
|
||||
/// in the mode sequence.
|
||||
#[derive(Debug)]
|
||||
pub struct SequenceTablesMapValue {
|
||||
/// Name for a given mode sequence.
|
||||
pub name: &'static str,
|
||||
/// Each sequence can consists of multiple sequences that are executed consecutively.
|
||||
pub entries: Vec<SequenceTableMapTable>,
|
||||
}
|
||||
|
||||
impl SequenceTablesMapValue {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
entries: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sequence_table(&mut self, entry: SequenceTableMapTable) {
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TargetModeTables(pub HashMap<Mode, TargetTablesMapValue>);
|
||||
|
||||
impl TargetModeTables {
|
||||
pub fn name(&self, mode: Mode) -> Option<&'static str> {
|
||||
self.0.get(&mode).map(|value| value.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl SequenceModeTables {
|
||||
pub fn name(&self, mode: Mode) -> Option<&'static str> {
|
||||
self.0.get(&mode).map(|value| value.name)
|
||||
}
|
||||
|
||||
pub fn name_of_sequence(&self, mode: Mode, seq_idx: usize) -> Option<&'static str> {
|
||||
self.0
|
||||
.get(&mode)
|
||||
.map(|value| value.entries.get(seq_idx).map(|v| v.name))?
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the core data structure used to store mode sequence tables.
|
||||
///
|
||||
/// A mode sequence table specifies which commands have to be sent in which order
|
||||
/// to reach a certain [Mode]. Therefore, it simply maps a [Mode] to a [SequenceTablesMapValue].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SequenceModeTables(pub HashMap<Mode, SequenceTablesMapValue>);
|
||||
|
||||
/// Mode store which tracks the [mode information][ModeStoreValue] inside a [Vec]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModeStoreVec(pub alloc::vec::Vec<ModeStoreValue>);
|
||||
|
||||
impl<'a> IntoIterator for &'a ModeStoreVec {
|
||||
type Item = &'a ModeStoreValue;
|
||||
type IntoIter = std::slice::Iter<'a, ModeStoreValue>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut ModeStoreVec {
|
||||
type Item = &'a mut ModeStoreValue;
|
||||
type IntoIter = std::slice::IterMut<'a, ModeStoreValue>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode store which tracks the mode information inside a [hashbrown::HashMap]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModeStoreMap(pub hashbrown::HashMap<ComponentId, ModeStoreValue>);
|
||||
|
||||
impl<'a> IntoIterator for &'a ModeStoreMap {
|
||||
type Item = (&'a ComponentId, &'a ModeStoreValue);
|
||||
type IntoIter = hashbrown::hash_map::Iter<'a, ComponentId, ModeStoreValue>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeStoreProvider for ModeStoreVec {
|
||||
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
|
||||
self.0.push(ModeStoreValue::new(target_id, mode));
|
||||
}
|
||||
|
||||
fn has_component(&self, target_id: ComponentId) -> bool {
|
||||
self.0.iter().any(|val| val.id == target_id)
|
||||
}
|
||||
|
||||
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> {
|
||||
self.0.iter().find(|val| val.id == target_id)
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> {
|
||||
self.0.iter_mut().find(|val| val.id == target_id)
|
||||
}
|
||||
|
||||
fn mode_reply_handler(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
reported_mode_and_submode: Option<ModeAndSubmode>,
|
||||
handle_reply_awaition: bool,
|
||||
) -> Option<bool> {
|
||||
let mut still_awating_replies = None;
|
||||
if handle_reply_awaition {
|
||||
still_awating_replies = Some(false);
|
||||
}
|
||||
self.0.iter_mut().for_each(|val| {
|
||||
if val.id() == sender_id {
|
||||
if let Some(mode_and_submode) = reported_mode_and_submode {
|
||||
val.mode_and_submode = mode_and_submode;
|
||||
}
|
||||
if handle_reply_awaition {
|
||||
val.awaiting_reply = false;
|
||||
}
|
||||
}
|
||||
if handle_reply_awaition && val.awaiting_reply {
|
||||
still_awating_replies = Some(true);
|
||||
}
|
||||
});
|
||||
still_awating_replies
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeStoreProvider for ModeStoreMap {
|
||||
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
|
||||
self.0
|
||||
.insert(target_id, ModeStoreValue::new(target_id, mode));
|
||||
}
|
||||
|
||||
fn has_component(&self, target_id: ComponentId) -> bool {
|
||||
self.0.contains_key(&target_id)
|
||||
}
|
||||
|
||||
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> {
|
||||
self.0.get(&target_id)
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> {
|
||||
self.0.get_mut(&target_id)
|
||||
}
|
||||
|
||||
fn mode_reply_handler(
|
||||
&mut self,
|
||||
sender_id: ComponentId,
|
||||
reported_mode_and_submode: Option<ModeAndSubmode>,
|
||||
handle_reply_awaition: bool,
|
||||
) -> Option<bool> {
|
||||
let mut still_awating_replies = None;
|
||||
if handle_reply_awaition {
|
||||
still_awating_replies = Some(false);
|
||||
}
|
||||
for val in self.0.values_mut() {
|
||||
if val.id() == sender_id {
|
||||
if let Some(mode_and_submode) = reported_mode_and_submode {
|
||||
val.mode_and_submode = mode_and_submode;
|
||||
}
|
||||
if handle_reply_awaition {
|
||||
val.awaiting_reply = false;
|
||||
}
|
||||
}
|
||||
if handle_reply_awaition && val.awaiting_reply {
|
||||
still_awating_replies = Some(true);
|
||||
}
|
||||
}
|
||||
still_awating_replies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn generic_test(mode_store: &mut impl ModeStoreProvider) {
|
||||
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
|
||||
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
|
||||
assert!(mode_store.has_component(1));
|
||||
assert!(mode_store.has_component(2));
|
||||
assert_eq!(
|
||||
mode_store.get(1).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(0, 0)
|
||||
);
|
||||
assert!(!mode_store.get(1).unwrap().awaiting_reply);
|
||||
assert!(!mode_store.get(2).unwrap().awaiting_reply);
|
||||
assert_eq!(mode_store.get(1).unwrap().id, 1);
|
||||
assert_eq!(mode_store.get(2).unwrap().id, 2);
|
||||
assert!(mode_store.get(3).is_none());
|
||||
assert!(mode_store.get_mut(3).is_none());
|
||||
}
|
||||
|
||||
fn generic_reply_handling_with_reply_awaition(mode_store: &mut impl ModeStoreProvider) {
|
||||
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
|
||||
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
|
||||
mode_store.get_mut(1).unwrap().awaiting_reply = true;
|
||||
mode_store.get_mut(2).unwrap().awaiting_reply = true;
|
||||
let mut reply_awation_pending =
|
||||
mode_store.mode_reply_handler_with_reply_awaition(1, Some(ModeAndSubmode::new(2, 0)));
|
||||
assert!(reply_awation_pending);
|
||||
reply_awation_pending = mode_store.mode_reply_handler_with_reply_awaition(2, None);
|
||||
assert!(!reply_awation_pending);
|
||||
assert!(!mode_store.get(1).unwrap().awaiting_reply);
|
||||
assert!(!mode_store.get(2).unwrap().awaiting_reply);
|
||||
assert_eq!(
|
||||
mode_store.get(1).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(2, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
mode_store.get(2).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_reply_handling_test_no_reply_awaition(mode_store: &mut impl ModeStoreProvider) {
|
||||
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
|
||||
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
|
||||
mode_store.get_mut(1).unwrap().awaiting_reply = true;
|
||||
mode_store.get_mut(2).unwrap().awaiting_reply = true;
|
||||
mode_store.mode_reply_handler_without_reply_awaition(1, Some(ModeAndSubmode::new(2, 0)));
|
||||
mode_store.mode_reply_handler_without_reply_awaition(2, None);
|
||||
assert!(mode_store.get(1).unwrap().awaiting_reply);
|
||||
assert!(mode_store.get(2).unwrap().awaiting_reply);
|
||||
assert_eq!(
|
||||
mode_store.get(1).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(2, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
mode_store.get(2).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_reply_handling_with_reply_awaition_2(mode_store: &mut impl ModeStoreProvider) {
|
||||
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
|
||||
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
|
||||
mode_store.get_mut(1).unwrap().awaiting_reply = true;
|
||||
mode_store.get_mut(2).unwrap().awaiting_reply = true;
|
||||
let mut reply_awation_pending =
|
||||
mode_store.mode_reply_handler(1, Some(ModeAndSubmode::new(2, 0)), true);
|
||||
assert!(reply_awation_pending.unwrap());
|
||||
reply_awation_pending = mode_store.mode_reply_handler(2, None, true);
|
||||
assert!(!reply_awation_pending.unwrap());
|
||||
assert!(!mode_store.get(1).unwrap().awaiting_reply);
|
||||
assert!(!mode_store.get(2).unwrap().awaiting_reply);
|
||||
assert_eq!(
|
||||
mode_store.get(1).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(2, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
mode_store.get(2).unwrap().mode_and_submode(),
|
||||
ModeAndSubmode::new(1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_mode_store() {
|
||||
let mut mode_store = ModeStoreVec::default();
|
||||
generic_test(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_mode_store() {
|
||||
let mut mode_store = ModeStoreMap::default();
|
||||
generic_test(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_reply_handler_vec_with_reply_awaition() {
|
||||
let mut mode_store = ModeStoreVec::default();
|
||||
generic_reply_handling_with_reply_awaition(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_reply_handler_vec_with_reply_awaition_2() {
|
||||
let mut mode_store = ModeStoreVec::default();
|
||||
generic_reply_handling_with_reply_awaition_2(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_reply_handler_map_with_reply_awaition() {
|
||||
let mut mode_store = ModeStoreMap::default();
|
||||
generic_reply_handling_with_reply_awaition(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_reply_handler_map_with_reply_awaition_2() {
|
||||
let mut mode_store = ModeStoreMap::default();
|
||||
generic_reply_handling_with_reply_awaition_2(&mut mode_store);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_reply_handler_vec_no_reply_awaition() {
|
||||
let mut mode_store = ModeStoreVec::default();
|
||||
generic_reply_handling_test_no_reply_awaition(&mut mode_store);
|
||||
}
|
||||
#[test]
|
||||
fn test_generic_reply_handler_map_no_reply_awaition() {
|
||||
let mut mode_store = ModeStoreMap::default();
|
||||
generic_reply_handling_test_no_reply_awaition(&mut mode_store);
|
||||
}
|
||||
}
|
||||
|
@@ -47,10 +47,10 @@ use crate::pool::PoolAddr;
|
||||
use core::fmt::Debug;
|
||||
use core::mem::size_of;
|
||||
use paste::paste;
|
||||
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8};
|
||||
use spacepackets::ByteConversionError;
|
||||
use spacepackets::ecss::{EcssEnumU8, EcssEnumU16, EcssEnumU32, EcssEnumU64};
|
||||
pub use spacepackets::util::ToBeBytes;
|
||||
use spacepackets::util::UnsignedEnum;
|
||||
use spacepackets::ByteConversionError;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
|
@@ -222,7 +222,7 @@ impl Error for PoolError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic trait for pool providers which provide memory pools for variable sized data.
|
||||
/// Generic trait for pool providers which provide memory pools for variable sized packet data.
|
||||
///
|
||||
/// It specifies a basic API to [Self::add], [Self::modify], [Self::read] and [Self::delete] data
|
||||
/// in the store at its core. The API was designed so internal optimizations can be performed
|
||||
@@ -281,7 +281,7 @@ pub trait PoolProviderWithGuards: PoolProvider {
|
||||
/// This can prevent memory leaks. Users can read the data and release the guard
|
||||
/// if the data in the store is valid for further processing. If the data is faulty, no
|
||||
/// manual deletion is necessary when returning from a processing function prematurely.
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<Self>;
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<'_, Self>;
|
||||
|
||||
/// This function behaves like [PoolProvider::modify], but consumes the provided
|
||||
/// address and returns a RAII conformant guard object.
|
||||
@@ -291,7 +291,7 @@ pub trait PoolProviderWithGuards: PoolProvider {
|
||||
/// This can prevent memory leaks. Users can read (and modify) the data and release the guard
|
||||
/// if the data in the store is valid for further processing. If the data is faulty, no
|
||||
/// manual deletion is necessary when returning from a processing function prematurely.
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<Self>;
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<'_, Self>;
|
||||
}
|
||||
|
||||
pub struct PoolGuard<'a, MemProvider: PoolProvider + ?Sized> {
|
||||
@@ -387,18 +387,18 @@ pub mod heapless_mod {
|
||||
#[macro_export]
|
||||
macro_rules! static_subpool {
|
||||
($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr) => {
|
||||
static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> =
|
||||
core::mem::MaybeUninit::new([0; $num_blocks * $block_size]);
|
||||
static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> =
|
||||
core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]);
|
||||
static $pool_name: static_cell::ConstStaticCell<[u8; $num_blocks * $block_size]> =
|
||||
static_cell::ConstStaticCell::new([0; $num_blocks * $block_size]);
|
||||
static $sizes_list_name: static_cell::ConstStaticCell<[usize; $num_blocks]> =
|
||||
static_cell::ConstStaticCell::new([$crate::pool::STORE_FREE; $num_blocks]);
|
||||
};
|
||||
($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr, $meta_data: meta) => {
|
||||
#[$meta_data]
|
||||
static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> =
|
||||
core::mem::MaybeUninit::new([0; $num_blocks * $block_size]);
|
||||
static $pool_name: static_cell::ConstStaticCell<[u8; $num_blocks * $block_size]> =
|
||||
static_cell::ConstStaticCell::new([0; $num_blocks * $block_size]);
|
||||
#[$meta_data]
|
||||
static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> =
|
||||
core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]);
|
||||
static $sizes_list_name: static_cell::ConstStaticCell<[usize; $num_blocks]> =
|
||||
static_cell::ConstStaticCell::new([$crate::pool::STORE_FREE; $num_blocks]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,17 +435,17 @@ pub mod heapless_mod {
|
||||
///
|
||||
/// let mut mem_pool: StaticHeaplessMemoryPool<2> = StaticHeaplessMemoryPool::new(true);
|
||||
/// mem_pool.grow(
|
||||
/// unsafe { SUBPOOL_SMALL.assume_init_mut() },
|
||||
/// unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
|
||||
/// SUBPOOL_SMALL.take(),
|
||||
/// SUBPOOL_SMALL_SIZES.take(),
|
||||
/// SUBPOOL_SMALL_NUM_BLOCKS,
|
||||
/// false
|
||||
/// );
|
||||
/// ).unwrap();
|
||||
/// mem_pool.grow(
|
||||
/// unsafe { SUBPOOL_LARGE.assume_init_mut() },
|
||||
/// unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
|
||||
/// SUBPOOL_LARGE.take(),
|
||||
/// SUBPOOL_LARGE_SIZES.take(),
|
||||
/// SUBPOOL_LARGE_NUM_BLOCKS,
|
||||
/// false
|
||||
/// );
|
||||
/// ).unwrap();
|
||||
///
|
||||
/// let mut read_buf: [u8; 16] = [0; 16];
|
||||
/// let mut addr;
|
||||
@@ -522,12 +522,14 @@ pub mod heapless_mod {
|
||||
num_blocks: NumBlocks,
|
||||
set_sizes_list_to_all_free: bool,
|
||||
) -> Result<(), PoolIsFull> {
|
||||
assert!(
|
||||
(subpool_memory.len() % num_blocks as usize) == 0,
|
||||
assert_eq!(
|
||||
(subpool_memory.len() % num_blocks as usize),
|
||||
0,
|
||||
"pool slice length must be multiple of number of blocks"
|
||||
);
|
||||
assert!(
|
||||
num_blocks as usize == sizes_list.len(),
|
||||
assert_eq!(
|
||||
num_blocks as usize,
|
||||
sizes_list.len(),
|
||||
"used block size list slice must be of same length as number of blocks"
|
||||
);
|
||||
let subpool_config = SubpoolConfig {
|
||||
@@ -600,7 +602,7 @@ pub mod heapless_mod {
|
||||
if i < start_at_subpool as usize {
|
||||
continue;
|
||||
}
|
||||
if pool_cfg.block_size as usize >= req_size {
|
||||
if pool_cfg.block_size >= req_size {
|
||||
return Ok(i as u16);
|
||||
}
|
||||
}
|
||||
@@ -636,7 +638,7 @@ pub mod heapless_mod {
|
||||
|
||||
fn raw_pos(&self, addr: &StaticPoolAddr) -> Option<usize> {
|
||||
let (pool_cfg, _) = self.pool.get(addr.pool_idx as usize)?;
|
||||
Some(addr.packet_idx as usize * pool_cfg.block_size as usize)
|
||||
Some(addr.packet_idx as usize * pool_cfg.block_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,7 +707,7 @@ pub mod heapless_mod {
|
||||
let subpool_cfg = self.pool.get(addr.pool_idx as usize).unwrap().0;
|
||||
let raw_pos = self.raw_pos(&addr).unwrap();
|
||||
let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap().1
|
||||
[raw_pos..raw_pos + subpool_cfg.block_size as usize];
|
||||
[raw_pos..raw_pos + subpool_cfg.block_size];
|
||||
let size_list = self.sizes_lists.get_mut(addr.pool_idx as usize).unwrap();
|
||||
size_list[addr.packet_idx as usize] = STORE_FREE;
|
||||
block.fill(0);
|
||||
@@ -740,11 +742,11 @@ pub mod heapless_mod {
|
||||
impl<const MAX_NUM_SUBPOOLS: usize> PoolProviderWithGuards
|
||||
for StaticHeaplessMemoryPool<MAX_NUM_SUBPOOLS>
|
||||
{
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<Self> {
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<'_, Self> {
|
||||
PoolRwGuard::new(self, addr)
|
||||
}
|
||||
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<Self> {
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<'_, Self> {
|
||||
PoolGuard::new(self, addr)
|
||||
}
|
||||
}
|
||||
@@ -773,7 +775,7 @@ mod alloc_mod {
|
||||
/// if the next fitting subpool is full. This is useful to ensure the pool remains useful
|
||||
/// for all data sizes as long as possible. However, an undesirable side-effect might be
|
||||
/// the chocking of larger subpools by underdimensioned smaller subpools.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StaticPoolConfig {
|
||||
cfg: Vec<SubpoolConfig>,
|
||||
spill_to_higher_subpools: bool,
|
||||
@@ -832,6 +834,7 @@ mod alloc_mod {
|
||||
/// [address][PoolAddr] type. Adding any data to the pool will yield a store address.
|
||||
/// Modification and read operations are done using a reference to a store address. Deletion
|
||||
/// will consume the store address.
|
||||
#[derive(Debug)]
|
||||
pub struct StaticMemoryPool {
|
||||
pool_cfg: StaticPoolConfig,
|
||||
pool: Vec<Vec<u8>>,
|
||||
@@ -1055,11 +1058,11 @@ mod alloc_mod {
|
||||
}
|
||||
|
||||
impl PoolProviderWithGuards for StaticMemoryPool {
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<Self> {
|
||||
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<'_, Self> {
|
||||
PoolRwGuard::new(self, addr)
|
||||
}
|
||||
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<Self> {
|
||||
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<'_, Self> {
|
||||
PoolGuard::new(self, addr)
|
||||
}
|
||||
}
|
||||
@@ -1304,9 +1307,11 @@ mod tests {
|
||||
let addr = pool_provider.add(&test_buf).expect("Adding data failed");
|
||||
let read_guard = PoolGuard::new(pool_provider, addr);
|
||||
drop(read_guard);
|
||||
assert!(!pool_provider
|
||||
assert!(
|
||||
!pool_provider
|
||||
.has_element_at(&addr)
|
||||
.expect("Invalid address"));
|
||||
.expect("Invalid address")
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_test_pool_guard_deletion(pool_provider: &mut impl PoolProviderWithGuards) {
|
||||
@@ -1314,9 +1319,11 @@ mod tests {
|
||||
let addr = pool_provider.add(&test_buf).expect("Adding data failed");
|
||||
let read_guard = pool_provider.read_with_guard(addr);
|
||||
drop(read_guard);
|
||||
assert!(!pool_provider
|
||||
assert!(
|
||||
!pool_provider
|
||||
.has_element_at(&addr)
|
||||
.expect("Invalid address"));
|
||||
.expect("Invalid address")
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_test_pool_guard_with_release(pool_provider: &mut impl PoolProviderWithGuards) {
|
||||
@@ -1325,9 +1332,11 @@ mod tests {
|
||||
let mut read_guard = PoolGuard::new(pool_provider, addr);
|
||||
read_guard.release();
|
||||
drop(read_guard);
|
||||
assert!(pool_provider
|
||||
assert!(
|
||||
pool_provider
|
||||
.has_element_at(&addr)
|
||||
.expect("Invalid address"));
|
||||
.expect("Invalid address")
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_test_pool_modify_guard_man_creation(
|
||||
@@ -1338,9 +1347,11 @@ mod tests {
|
||||
let mut rw_guard = PoolRwGuard::new(pool_provider, addr);
|
||||
rw_guard.update(&mut |_| {}).expect("modify failed");
|
||||
drop(rw_guard);
|
||||
assert!(!pool_provider
|
||||
assert!(
|
||||
!pool_provider
|
||||
.has_element_at(&addr)
|
||||
.expect("Invalid address"));
|
||||
.expect("Invalid address")
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_test_pool_modify_guard(pool_provider: &mut impl PoolProviderWithGuards) {
|
||||
@@ -1349,9 +1360,11 @@ mod tests {
|
||||
let mut rw_guard = pool_provider.modify_with_guard(addr);
|
||||
rw_guard.update(&mut |_| {}).expect("modify failed");
|
||||
drop(rw_guard);
|
||||
assert!(!pool_provider
|
||||
assert!(
|
||||
!pool_provider
|
||||
.has_element_at(&addr)
|
||||
.expect("Invalid address"));
|
||||
.expect("Invalid address")
|
||||
);
|
||||
}
|
||||
|
||||
fn generic_modify_pool_index_above_0(pool_provider: &mut impl PoolProvider) {
|
||||
@@ -1584,23 +1597,33 @@ mod tests {
|
||||
mod heapless_tests {
|
||||
use super::*;
|
||||
use crate::static_subpool;
|
||||
use core::mem::MaybeUninit;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const SUBPOOL_1_BLOCK_SIZE: usize = 4;
|
||||
const SUBPOOL_1_NUM_ELEMENTS: u16 = 4;
|
||||
static mut SUBPOOL_1: MaybeUninit<
|
||||
|
||||
static SUBPOOL_1: static_cell::ConstStaticCell<
|
||||
[u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE],
|
||||
> = MaybeUninit::new([0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]);
|
||||
static mut SUBPOOL_1_SIZES: MaybeUninit<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]> =
|
||||
MaybeUninit::new([STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]);
|
||||
> = static_cell::ConstStaticCell::new(
|
||||
[0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE],
|
||||
);
|
||||
|
||||
static SUBPOOL_1_SIZES: Mutex<UnsafeCell<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]>> =
|
||||
Mutex::new(UnsafeCell::new(
|
||||
[STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize],
|
||||
));
|
||||
|
||||
const SUBPOOL_2_NUM_ELEMENTS: u16 = 2;
|
||||
const SUBPOOL_2_BLOCK_SIZE: usize = 8;
|
||||
static mut SUBPOOL_2: MaybeUninit<
|
||||
static SUBPOOL_2: static_cell::ConstStaticCell<
|
||||
[u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE],
|
||||
> = MaybeUninit::new([0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]);
|
||||
static mut SUBPOOL_2_SIZES: MaybeUninit<[usize; SUBPOOL_2_NUM_ELEMENTS as usize]> =
|
||||
MaybeUninit::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]);
|
||||
> = static_cell::ConstStaticCell::new(
|
||||
[0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE],
|
||||
);
|
||||
static SUBPOOL_2_SIZES: static_cell::ConstStaticCell<
|
||||
[usize; SUBPOOL_2_NUM_ELEMENTS as usize],
|
||||
> = static_cell::ConstStaticCell::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]);
|
||||
|
||||
const SUBPOOL_3_NUM_ELEMENTS: u16 = 1;
|
||||
const SUBPOOL_3_BLOCK_SIZE: usize = 16;
|
||||
@@ -1641,30 +1664,36 @@ mod tests {
|
||||
fn small_heapless_pool() -> StaticHeaplessMemoryPool<3> {
|
||||
let mut heapless_pool: StaticHeaplessMemoryPool<3> =
|
||||
StaticHeaplessMemoryPool::new(false);
|
||||
assert!(heapless_pool
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_1.assume_init_mut() },
|
||||
unsafe { SUBPOOL_1_SIZES.assume_init_mut() },
|
||||
SUBPOOL_1.take(),
|
||||
unsafe { &mut *SUBPOOL_1_SIZES.lock().unwrap().get() },
|
||||
SUBPOOL_1_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_2.assume_init_mut() },
|
||||
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
|
||||
SUBPOOL_2.take(),
|
||||
SUBPOOL_2_SIZES.take(),
|
||||
SUBPOOL_2_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||
SUBPOOL_3.take(),
|
||||
SUBPOOL_3_SIZES.take(),
|
||||
SUBPOOL_3_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
.is_ok()
|
||||
);
|
||||
heapless_pool
|
||||
}
|
||||
|
||||
@@ -1780,22 +1809,26 @@ mod tests {
|
||||
fn test_spills_to_higher_subpools() {
|
||||
let mut heapless_pool: StaticHeaplessMemoryPool<2> =
|
||||
StaticHeaplessMemoryPool::new(true);
|
||||
assert!(heapless_pool
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_2.assume_init_mut() },
|
||||
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
|
||||
SUBPOOL_2.take(),
|
||||
SUBPOOL_2_SIZES.take(),
|
||||
SUBPOOL_2_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_4.assume_init_mut() },
|
||||
unsafe { SUBPOOL_4_SIZES.assume_init_mut() },
|
||||
SUBPOOL_4.take(),
|
||||
SUBPOOL_4_SIZES.take(),
|
||||
SUBPOOL_4_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
.is_ok()
|
||||
);
|
||||
generic_test_spills_to_higher_subpools(&mut heapless_pool);
|
||||
}
|
||||
|
||||
@@ -1803,22 +1836,26 @@ mod tests {
|
||||
fn test_spillage_fails_as_well() {
|
||||
let mut heapless_pool: StaticHeaplessMemoryPool<2> =
|
||||
StaticHeaplessMemoryPool::new(true);
|
||||
assert!(heapless_pool
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||
SUBPOOL_5.take(),
|
||||
SUBPOOL_5_SIZES.take(),
|
||||
SUBPOOL_5_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||
SUBPOOL_3.take(),
|
||||
SUBPOOL_3_SIZES.take(),
|
||||
SUBPOOL_3_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
.is_ok()
|
||||
);
|
||||
generic_test_spillage_fails_as_well(&mut heapless_pool);
|
||||
}
|
||||
|
||||
@@ -1826,30 +1863,36 @@ mod tests {
|
||||
fn test_spillage_works_across_multiple_subpools() {
|
||||
let mut heapless_pool: StaticHeaplessMemoryPool<3> =
|
||||
StaticHeaplessMemoryPool::new(true);
|
||||
assert!(heapless_pool
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||
SUBPOOL_5.take(),
|
||||
SUBPOOL_5_SIZES.take(),
|
||||
SUBPOOL_5_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_6.assume_init_mut() },
|
||||
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
||||
SUBPOOL_6.take(),
|
||||
SUBPOOL_6_SIZES.take(),
|
||||
SUBPOOL_6_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||
SUBPOOL_3.take(),
|
||||
SUBPOOL_3_SIZES.take(),
|
||||
SUBPOOL_3_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
.is_ok()
|
||||
);
|
||||
generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool);
|
||||
}
|
||||
|
||||
@@ -1857,30 +1900,36 @@ mod tests {
|
||||
fn test_spillage_fails_across_multiple_subpools() {
|
||||
let mut heapless_pool: StaticHeaplessMemoryPool<3> =
|
||||
StaticHeaplessMemoryPool::new(true);
|
||||
assert!(heapless_pool
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||
SUBPOOL_5.take(),
|
||||
SUBPOOL_5_SIZES.take(),
|
||||
SUBPOOL_5_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_6.assume_init_mut() },
|
||||
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
||||
SUBPOOL_6.take(),
|
||||
SUBPOOL_6_SIZES.take(),
|
||||
SUBPOOL_6_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
assert!(heapless_pool
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
heapless_pool
|
||||
.grow(
|
||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||
SUBPOOL_3.take(),
|
||||
SUBPOOL_3_SIZES.take(),
|
||||
SUBPOOL_3_NUM_ELEMENTS,
|
||||
false
|
||||
true
|
||||
)
|
||||
.is_ok());
|
||||
.is_ok()
|
||||
);
|
||||
generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user