Compare commits
137 Commits
serializat
...
move-to-cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f4c0e7d51 | ||
|
|
b3a7f3f9d2 | ||
|
|
e1bf81e804 | ||
| d7e6732888 | |||
|
|
c27569a526 | ||
| fef8d6a792 | |||
| b2bc87641c | |||
|
|
c8245772bb | ||
| 56d9cafa59 | |||
|
|
7de057aaf2
|
||
| 2f4a11a0bc | |||
|
|
0cd929a19c | ||
| d54167837e | |||
|
|
ecce07a471 | ||
| 526254851a | |||
|
|
440e757141
|
||
| 02aa825783 | |||
|
|
bc9e0e4a94 | ||
| 06e713a557 | |||
|
|
778512d50e | ||
| 5d40638964 | |||
|
|
1b07b845f2 | ||
| 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 | |||
| c8bba48e76 | |||
|
176da4838a
|
|||
| 8114195bc6 | |||
| 1726e34fa7 | |||
| 1f2d6c9474 | |||
|
9960930339
|
|||
| cb270964a1 | |||
|
a45e634214
|
|||
| 9e4132706c | |||
|
36d889a504
|
|||
| 97a6510af7 | |||
|
2e5d6a5c41
|
|||
|
29167736db
|
|||
| a4c433a7be | |||
| 472a8ce0f9 | |||
| e753319dac | |||
| 99dddf36f3 |
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -11,7 +11,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- run: cargo check --release
|
- name: Install libudev-dev on Ubuntu
|
||||||
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
|
run: sudo apt update && sudo apt install -y libudev-dev
|
||||||
|
- run: cargo check
|
||||||
|
# Check example with static pool configuration
|
||||||
|
- run: cargo check -p satrs-example --no-default-features
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Run Tests
|
name: Run Tests
|
||||||
@@ -21,6 +26,7 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@nextest
|
uses: taiki-e/install-action@nextest
|
||||||
|
- run: sudo apt update && sudo apt install -y libudev-dev
|
||||||
- run: cargo nextest run --all-features
|
- run: cargo nextest run --all-features
|
||||||
- run: cargo test --doc --all-features
|
- run: cargo test --doc --all-features
|
||||||
|
|
||||||
@@ -37,7 +43,7 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
|
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:
|
fmt:
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
@@ -45,6 +51,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
@@ -53,7 +61,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
|
- run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features --no-deps
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
@@ -61,4 +69,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy
|
||||||
|
- run: sudo apt update && sudo apt install -y libudev-dev
|
||||||
- run: cargo clippy -- -D warnings
|
- run: cargo clippy -- -D warnings
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ members = [
|
|||||||
"satrs-example",
|
"satrs-example",
|
||||||
"satrs-minisim",
|
"satrs-minisim",
|
||||||
"satrs-shared",
|
"satrs-shared",
|
||||||
|
"embedded-examples/embedded-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"embedded-examples/stm32f3-disco-rtic",
|
"embedded-examples/stm32f3-disco-rtic",
|
||||||
"embedded-examples/stm32h7-rtic",
|
"embedded-examples/stm32h7-nucleo-rtic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,9 +1,10 @@
|
|||||||
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
|
<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/)
|
||||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
|
[](https://robamu.github.io/sat-rs/book/)
|
||||||
[](https://crates.io/crates/satrs)
|
[](https://crates.io/crates/satrs)
|
||||||
[](https://docs.rs/satrs)
|
[](https://docs.rs/satrs)
|
||||||
|
[](https://matrix.to/#/#sat-rs:matrix.org)
|
||||||
|
|
||||||
sat-rs
|
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
|
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
|
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
|
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/).
|
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
|
This is early-stage software. Important features are missing. New releases
|
||||||
@@ -37,13 +38,16 @@ This project currently contains following crates:
|
|||||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
|
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
|
||||||
Example of a simple example on-board software using various sat-rs components which can be run
|
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.
|
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 [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):
|
* [`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.
|
Components to build a mission information base from the on-board software directly.
|
||||||
* [`satrs-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/satrs-stm32f3-disco-rtic):
|
* [`satrs-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32f3-disco-rtic):
|
||||||
Example of a simple example using low-level sat-rs components on a bare-metal system
|
Example of a simple example using low-level sat-rs components on a bare-metal system
|
||||||
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
||||||
framework on the STM32F3-Discovery device.
|
framework on the STM32F3-Discovery device.
|
||||||
* [`satrs-stm32h-nucleo-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/satrs-stm32h7-nucleo-rtic):
|
* [`satrs-stm32h-nucleo-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32h7-nucleo-rtic):
|
||||||
Example of a simple example using sat-rs components on a bare-metal system
|
Example of a simple example using sat-rs components on a bare-metal system
|
||||||
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
|
||||||
framework on the STM32H743ZIT device.
|
framework on the STM32H743ZIT device.
|
||||||
@@ -58,6 +62,8 @@ Each project has its own `CHANGELOG.md`.
|
|||||||
packet protocol implementations. This repository is re-exported in the
|
packet protocol implementations. This repository is re-exported in the
|
||||||
[`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
|
[`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
|
||||||
crate.
|
crate.
|
||||||
|
* [`cfdp`](https://egit.irs.uni-stuttgart.de/rust/cfdp): CCSDS File Delivery Protocol
|
||||||
|
(CFDP) high-level library components.
|
||||||
|
|
||||||
# Flight Heritage
|
# Flight Heritage
|
||||||
|
|
||||||
@@ -69,6 +75,10 @@ Currently this library has the following flight heritage:
|
|||||||
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
||||||
The application is strongly based on the sat-rs example application. You can find the repository
|
The application is strongly based on the sat-rs example application. You can find the repository
|
||||||
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
||||||
|
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
|
||||||
|
alongside a Flight System Simulator in the context of a
|
||||||
|
[Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
|
||||||
|
at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
|
||||||
|
|
||||||
# Coverage
|
# Coverage
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
18
embedded-examples/embedded-client/Cargo.toml
Normal file
18
embedded-examples/embedded-client/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "embedded-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
serialport = "4"
|
||||||
|
toml = "0.9"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
satrs-stm32f3-disco-rtic = { path = "../stm32f3-disco-rtic" }
|
||||||
|
spacepackets = { version = "0.17" }
|
||||||
|
tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
|
||||||
|
postcard = { version = "1", features = ["alloc"] }
|
||||||
|
cobs = "0.5"
|
||||||
|
fern = "0.7"
|
||||||
|
humantime = "2"
|
||||||
|
log = "0.4"
|
||||||
2
embedded-examples/embedded-client/config.toml
Normal file
2
embedded-examples/embedded-client/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[interface]
|
||||||
|
serial_port = "/dev/ttyUSB0"
|
||||||
107
embedded-examples/embedded-client/src/main.rs
Normal file
107
embedded-examples/embedded-client/src/main.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Read,
|
||||||
|
path::Path,
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use cobs::CobsDecoderOwned;
|
||||||
|
use satrs_stm32f3_disco_rtic::Request;
|
||||||
|
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
|
||||||
|
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(short, long)]
|
||||||
|
ping: bool,
|
||||||
|
|
||||||
|
/// Set frequency in milliseconds.
|
||||||
|
#[arg(short, long)]
|
||||||
|
set_led_frequency: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
interface: Interface,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Interface {
|
||||||
|
serial_port: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_logger() -> Result<(), fern::InitError> {
|
||||||
|
fern::Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{} {} {}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
record.level(),
|
||||||
|
record.target(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.level(log::LevelFilter::Debug)
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file("output.log")?)
|
||||||
|
.apply()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
setup_logger().expect("failed to initialize logger");
|
||||||
|
println!("sat-rs embedded examples TMTC client");
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let mut config_file =
|
||||||
|
File::open(Path::new("config.toml")).expect("opening config.toml file failed");
|
||||||
|
let mut toml_str = String::new();
|
||||||
|
config_file
|
||||||
|
.read_to_string(&mut toml_str)
|
||||||
|
.expect("reading config.toml file failed");
|
||||||
|
let config: Config = toml::from_str(&toml_str).expect("parsing config.toml file failed");
|
||||||
|
println!("Connecting to serial port {}", config.interface.serial_port);
|
||||||
|
|
||||||
|
let serial = serialport::new(config.interface.serial_port, 115200)
|
||||||
|
.open()
|
||||||
|
.expect("opening serial port failed");
|
||||||
|
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(1024));
|
||||||
|
|
||||||
|
if cli.ping {
|
||||||
|
let request = Request::Ping;
|
||||||
|
let tc = create_stm32f3_tc(&request);
|
||||||
|
log::info!(
|
||||||
|
"Sending ping request with TC ID: {:#010x}",
|
||||||
|
tc.ccsds_packet_id_and_psc().raw()
|
||||||
|
);
|
||||||
|
transport.send(&tc.to_vec()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(freq_ms) = cli.set_led_frequency {
|
||||||
|
let request = Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
|
||||||
|
let tc = create_stm32f3_tc(&request);
|
||||||
|
log::info!(
|
||||||
|
"Sending change blink frequency request {:?} with TC ID: {:#010x}",
|
||||||
|
request,
|
||||||
|
tc.ccsds_packet_id_and_psc().raw()
|
||||||
|
);
|
||||||
|
transport.send(&tc.to_vec()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Waiting for response...");
|
||||||
|
loop {
|
||||||
|
transport
|
||||||
|
.receive(|packet: &[u8]| {
|
||||||
|
let reader = CcsdsPacketReader::new_with_checksum(packet);
|
||||||
|
log::info!("Received packet: {:?}", reader);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_stm32f3_tc(request: &Request) -> CcsdsPacketCreatorOwned {
|
||||||
|
let req_raw = postcard::to_allocvec(&request).unwrap();
|
||||||
|
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
|
||||||
|
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
||||||
|
}
|
||||||
@@ -34,4 +34,4 @@ rustflags = [
|
|||||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "info"
|
DEFMT_LOG = "info"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/itm.txt
|
/itm.txt
|
||||||
/.cargo/config*
|
/.cargo/config.toml
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|||||||
1280
embedded-examples/stm32f3-disco-rtic/Cargo.lock
generated
1280
embedded-examples/stm32f3-disco-rtic/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,50 +9,28 @@ default-run = "satrs-stm32f3-disco-rtic"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
defmt = "0.3"
|
defmt = "1"
|
||||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
defmt-rtt = { version = "1" }
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||||
embedded-hal = "0.2.7"
|
embedded-hal = "1"
|
||||||
cortex-m-semihosting = "0.5.0"
|
cortex-m-semihosting = "0.5.0"
|
||||||
|
embassy-stm32 = { version = "0.4", features = ["defmt", "stm32f303vc", "unstable-pac"] }
|
||||||
enumset = "1"
|
enumset = "1"
|
||||||
heapless = "0.8"
|
heapless = "0.9"
|
||||||
|
spacepackets = { version = "0.17", default-features = false, features = ["defmt", "serde"] }
|
||||||
|
static_cell = "2"
|
||||||
|
cobs = { version = "0.5", default-features = false, features = ["defmt"] }
|
||||||
|
postcard = { version = "1" }
|
||||||
|
arbitrary-int = "2"
|
||||||
|
thiserror = { version = "2", default-features = false }
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
[dependencies.rtic]
|
rtic = { version = "2", features = ["thumbv7-backend"] }
|
||||||
version = "2"
|
rtic-sync = { version = "1" }
|
||||||
features = ["thumbv7-backend"]
|
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "1"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[dependencies.cobs]
|
|
||||||
git = "https://github.com/robamu/cobs.rs.git"
|
|
||||||
branch = "all_features"
|
|
||||||
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"
|
|
||||||
# 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"
|
|
||||||
# Can be used in workspace to develop and update BSP
|
|
||||||
# path = "../stm32f3-discovery"
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
# path = "satrs"
|
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
|
||||||
features = ["defmt"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
defmt-test = "0.3"
|
defmt-test = "0.4"
|
||||||
|
|
||||||
# cargo test
|
# cargo test
|
||||||
[profile.test]
|
[profile.test]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
|||||||
target extended-remote localhost:2331
|
|
||||||
|
|
||||||
monitor reset
|
|
||||||
|
|
||||||
# *try* to stop at the user entry point (it might be gone due to inlining)
|
|
||||||
break main
|
|
||||||
|
|
||||||
load
|
|
||||||
|
|
||||||
continue
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Sample OpenOCD configuration for the STM32F3DISCOVERY development board
|
|
||||||
|
|
||||||
# Depending on the hardware revision you got you'll have to pick ONE of these
|
|
||||||
# interfaces. At any time only one interface should be commented out.
|
|
||||||
|
|
||||||
# Revision C (newer revision)
|
|
||||||
source [find interface/stlink.cfg]
|
|
||||||
|
|
||||||
# Revision A and B (older revisions)
|
|
||||||
# source [find interface/stlink-v2.cfg]
|
|
||||||
|
|
||||||
source [find target/stm32f3x.cfg]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
target extended-remote :3333
|
|
||||||
|
|
||||||
# print demangled symbols
|
|
||||||
set print asm-demangle on
|
|
||||||
|
|
||||||
# set backtrace limit to not have infinite backtrace loops
|
|
||||||
set backtrace limit 32
|
|
||||||
|
|
||||||
# detect unhandled exceptions, hard faults and panics
|
|
||||||
break DefaultHandler
|
|
||||||
break HardFault
|
|
||||||
break rust_begin_unwind
|
|
||||||
# # run the next few lines so the panic message is printed immediately
|
|
||||||
# # the number needs to be adjusted for your panic handler
|
|
||||||
# commands $bpnum
|
|
||||||
# next 4
|
|
||||||
# end
|
|
||||||
|
|
||||||
# *try* to stop at the user entry point (it might be gone due to inlining)
|
|
||||||
break main
|
|
||||||
|
|
||||||
# monitor arm semihosting enable
|
|
||||||
|
|
||||||
# # send captured ITM to the file itm.fifo
|
|
||||||
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
|
|
||||||
# # 8000000 must match the core clock frequency
|
|
||||||
# # 2000000 is the frequency of the SWO pin. This was added for newer
|
|
||||||
# openocd versions like v0.12.0.
|
|
||||||
# monitor tpiu config internal itm.txt uart off 8000000 2000000
|
|
||||||
|
|
||||||
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
|
|
||||||
# # 8000000 must match the core clock frequency
|
|
||||||
# # 2000000 is the frequency of the SWO pin
|
|
||||||
# monitor tpiu config external uart off 8000000 2000000
|
|
||||||
|
|
||||||
# # enable ITM port 0
|
|
||||||
# monitor itm port 0 on
|
|
||||||
|
|
||||||
load
|
|
||||||
|
|
||||||
# start the process but immediately halt the processor
|
|
||||||
stepi
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/venv
|
|
||||||
/.tmtc-history.txt
|
|
||||||
/log
|
|
||||||
/.idea/*
|
|
||||||
!/.idea/runConfigurations
|
|
||||||
|
|
||||||
/seqcnt.txt
|
|
||||||
/tmtc_conf.json
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"com_if": "serial_cobs",
|
|
||||||
"serial_baudrate": 115200
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Example client for the sat-rs example application"""
|
|
||||||
import struct
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import Any, Optional, cast
|
|
||||||
from prompt_toolkit.history import FileHistory, History
|
|
||||||
from spacepackets.ecss.tm import CdsShortTimestamp
|
|
||||||
|
|
||||||
import tmtccmd
|
|
||||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
|
|
||||||
from spacepackets.ecss.pus_17_test import Service17Tm
|
|
||||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
|
|
||||||
|
|
||||||
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
|
||||||
from tmtccmd.core.base import BackendRequest
|
|
||||||
from tmtccmd.core.ccsds_backend import QueueWrapper
|
|
||||||
from tmtccmd.logging import add_colorlog_console_logger
|
|
||||||
from tmtccmd.pus import VerificationWrapper
|
|
||||||
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
|
|
||||||
from tmtccmd.com import ComInterface
|
|
||||||
from tmtccmd.config import (
|
|
||||||
CmdTreeNode,
|
|
||||||
default_json_path,
|
|
||||||
SetupParams,
|
|
||||||
HookBase,
|
|
||||||
params_to_procedure_conversion,
|
|
||||||
)
|
|
||||||
from tmtccmd.config.com import SerialCfgWrapper
|
|
||||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
|
||||||
from tmtccmd.logging.pus import (
|
|
||||||
RegularTmtcLogWrapper,
|
|
||||||
RawTmtcTimedLogWrapper,
|
|
||||||
TimedLogWhen,
|
|
||||||
)
|
|
||||||
from tmtccmd.tmtc import (
|
|
||||||
TcQueueEntryType,
|
|
||||||
ProcedureWrapper,
|
|
||||||
TcProcedureType,
|
|
||||||
FeedWrapper,
|
|
||||||
SendCbParams,
|
|
||||||
DefaultPusQueueHelper,
|
|
||||||
)
|
|
||||||
from tmtccmd.pus.s5_fsfw_event import Service5Tm
|
|
||||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
|
|
||||||
from tmtccmd.util.obj_id import ObjectIdDictT
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
|
||||||
|
|
||||||
EXAMPLE_PUS_APID = 0x02
|
|
||||||
|
|
||||||
|
|
||||||
class SatRsConfigHook(HookBase):
|
|
||||||
def __init__(self, json_cfg_path: str):
|
|
||||||
super().__init__(json_cfg_path)
|
|
||||||
|
|
||||||
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
|
||||||
from tmtccmd.config.com import (
|
|
||||||
create_com_interface_default,
|
|
||||||
create_com_interface_cfg_default,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert self.cfg_path is not None
|
|
||||||
cfg = create_com_interface_cfg_default(
|
|
||||||
com_if_key=com_if_key,
|
|
||||||
json_cfg_path=self.cfg_path,
|
|
||||||
space_packet_ids=None,
|
|
||||||
)
|
|
||||||
if cfg is None:
|
|
||||||
raise ValueError(
|
|
||||||
f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
|
|
||||||
)
|
|
||||||
if cfg.com_if_key == "serial_cobs":
|
|
||||||
cfg = cast(SerialCfgWrapper, cfg)
|
|
||||||
cfg.serial_cfg.serial_timeout = 0.5
|
|
||||||
return create_com_interface_default(cfg)
|
|
||||||
|
|
||||||
def get_command_definitions(self) -> CmdTreeNode:
|
|
||||||
"""This function should return the root node of the command definition tree."""
|
|
||||||
return create_cmd_definition_tree()
|
|
||||||
|
|
||||||
def get_cmd_history(self) -> Optional[History]:
|
|
||||||
"""Optionlly return a history class for the past command paths which will be used
|
|
||||||
when prompting a command path from the user in CLI mode."""
|
|
||||||
return FileHistory(".tmtc-history.txt")
|
|
||||||
|
|
||||||
def get_object_ids(self) -> ObjectIdDictT:
|
|
||||||
from tmtccmd.config.objects import get_core_object_ids
|
|
||||||
|
|
||||||
return get_core_object_ids()
|
|
||||||
|
|
||||||
|
|
||||||
def create_cmd_definition_tree() -> CmdTreeNode:
|
|
||||||
root_node = CmdTreeNode.root_node()
|
|
||||||
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
|
|
||||||
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
|
|
||||||
return root_node
|
|
||||||
|
|
||||||
|
|
||||||
class PusHandler(SpecificApidHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
file_logger: logging.Logger,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
raw_logger: RawTmtcTimedLogWrapper,
|
|
||||||
):
|
|
||||||
super().__init__(EXAMPLE_PUS_APID, None)
|
|
||||||
self.file_logger = file_logger
|
|
||||||
self.raw_logger = raw_logger
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
|
|
||||||
def handle_tm(self, packet: bytes, _user_args: Any):
|
|
||||||
try:
|
|
||||||
pus_tm = PusTm.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
_LOGGER.warning("Could not generate PUS TM object from raw data")
|
|
||||||
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
|
||||||
raise e
|
|
||||||
service = pus_tm.service
|
|
||||||
tm_packet = None
|
|
||||||
if service == 1:
|
|
||||||
tm_packet = Service1Tm.unpack(
|
|
||||||
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
|
|
||||||
)
|
|
||||||
res = self.verif_wrapper.add_tm(tm_packet)
|
|
||||||
if res is None:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
|
||||||
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
|
||||||
)
|
|
||||||
_LOGGER.warning(
|
|
||||||
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.verif_wrapper.log_to_console(tm_packet, res)
|
|
||||||
self.verif_wrapper.log_to_file(tm_packet, res)
|
|
||||||
if service == 3:
|
|
||||||
_LOGGER.info("No handling for HK packets implemented")
|
|
||||||
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
|
|
||||||
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if pus_tm.subservice == 25:
|
|
||||||
if len(pus_tm.source_data) < 8:
|
|
||||||
raise ValueError("No addressable ID in HK packet")
|
|
||||||
json_str = pus_tm.source_data[8:]
|
|
||||||
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
|
|
||||||
if service == 5:
|
|
||||||
tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if service == 17:
|
|
||||||
tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if tm_packet.subservice == 2:
|
|
||||||
_LOGGER.info("Received Ping Reply TM[17,2]")
|
|
||||||
else:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
|
||||||
)
|
|
||||||
if tm_packet is None:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"The service {service} is not implemented in Telemetry Factory"
|
|
||||||
)
|
|
||||||
tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
self.raw_logger.log_tm(pus_tm)
|
|
||||||
|
|
||||||
|
|
||||||
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
|
|
||||||
byte_string = bytearray(struct.pack("!I", target_id))
|
|
||||||
byte_string.extend(struct.pack("!I", unique_id))
|
|
||||||
return byte_string
|
|
||||||
|
|
||||||
|
|
||||||
class TcHandler(TcHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
seq_count_provider: FileSeqCountProvider,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
):
|
|
||||||
super(TcHandler, self).__init__()
|
|
||||||
self.seq_count_provider = seq_count_provider
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
self.queue_helper = DefaultPusQueueHelper(
|
|
||||||
queue_wrapper=QueueWrapper.empty(),
|
|
||||||
tc_sched_timestamp_len=7,
|
|
||||||
seq_cnt_provider=seq_count_provider,
|
|
||||||
pus_verificator=verif_wrapper.pus_verificator,
|
|
||||||
default_pus_apid=EXAMPLE_PUS_APID,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_cb(self, send_params: SendCbParams):
|
|
||||||
entry_helper = send_params.entry
|
|
||||||
if entry_helper.is_tc:
|
|
||||||
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
|
|
||||||
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
|
|
||||||
pus_tc_wrapper.pus_tc.seq_count = (
|
|
||||||
self.seq_count_provider.get_and_increment()
|
|
||||||
)
|
|
||||||
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
|
|
||||||
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
|
||||||
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
|
||||||
send_params.com_if.send(raw_tc)
|
|
||||||
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
|
||||||
log_entry = entry_helper.to_log_entry()
|
|
||||||
_LOGGER.info(log_entry.log_str)
|
|
||||||
|
|
||||||
def queue_finished_cb(self, info: ProcedureWrapper):
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
|
|
||||||
|
|
||||||
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
|
|
||||||
q = self.queue_helper
|
|
||||||
q.queue_wrapper = wrapper.queue_wrapper
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
cmd_path = def_proc.cmd_path
|
|
||||||
if cmd_path == "/ping":
|
|
||||||
q.add_log_cmd("Sending PUS ping telecommand")
|
|
||||||
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
|
|
||||||
if cmd_path == "/change_blink_freq":
|
|
||||||
self.create_change_blink_freq_command(q)
|
|
||||||
|
|
||||||
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
|
|
||||||
q.add_log_cmd("Changing blink frequency")
|
|
||||||
while True:
|
|
||||||
blink_freq = int(
|
|
||||||
input(
|
|
||||||
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if blink_freq < 2 or blink_freq > 10000:
|
|
||||||
print(
|
|
||||||
"Invalid blink frequency. Please specify a value between 2 and 10000."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
app_data = struct.pack("!I", blink_freq)
|
|
||||||
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
add_colorlog_console_logger(_LOGGER)
|
|
||||||
tmtccmd.init_printout(False)
|
|
||||||
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
|
|
||||||
parser_wrapper = PreArgsParsingWrapper()
|
|
||||||
parser_wrapper.create_default_parent_parser()
|
|
||||||
parser_wrapper.create_default_parser()
|
|
||||||
parser_wrapper.add_def_proc_args()
|
|
||||||
params = SetupParams()
|
|
||||||
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
|
|
||||||
proc_wrapper = ProcedureParamsWrapper()
|
|
||||||
if post_args_wrapper.use_gui:
|
|
||||||
post_args_wrapper.set_params_without_prompts(proc_wrapper)
|
|
||||||
else:
|
|
||||||
post_args_wrapper.set_params_with_prompts(proc_wrapper)
|
|
||||||
params.apid = EXAMPLE_PUS_APID
|
|
||||||
setup_args = SetupWrapper(
|
|
||||||
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
|
||||||
)
|
|
||||||
# Create console logger helper and file loggers
|
|
||||||
tmtc_logger = RegularTmtcLogWrapper()
|
|
||||||
file_logger = tmtc_logger.logger
|
|
||||||
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
|
|
||||||
verificator = PusVerificator()
|
|
||||||
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
|
|
||||||
# Create primary TM handler and add it to the CCSDS Packet Handler
|
|
||||||
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
|
|
||||||
ccsds_handler = CcsdsTmHandler(generic_handler=None)
|
|
||||||
ccsds_handler.add_apid_handler(tm_handler)
|
|
||||||
|
|
||||||
# Create TC handler
|
|
||||||
seq_count_provider = PusFileSeqCountProvider()
|
|
||||||
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
|
|
||||||
tmtccmd.setup(setup_args=setup_args)
|
|
||||||
init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
|
|
||||||
tmtc_backend = tmtccmd.create_default_tmtc_backend(
|
|
||||||
setup_wrapper=setup_args,
|
|
||||||
tm_handler=ccsds_handler,
|
|
||||||
tc_handler=tc_handler,
|
|
||||||
init_procedure=init_proc,
|
|
||||||
)
|
|
||||||
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
state = tmtc_backend.periodic_op(None)
|
|
||||||
if state.request == BackendRequest.TERMINATION_NO_ERROR:
|
|
||||||
sys.exit(0)
|
|
||||||
elif state.request == BackendRequest.DELAY_IDLE:
|
|
||||||
_LOGGER.info("TMTC Client in IDLE mode")
|
|
||||||
time.sleep(3.0)
|
|
||||||
elif state.request == BackendRequest.DELAY_LISTENER:
|
|
||||||
time.sleep(0.8)
|
|
||||||
elif state.request == BackendRequest.DELAY_CUSTOM:
|
|
||||||
if state.next_delay.total_seconds() <= 0.4:
|
|
||||||
time.sleep(state.next_delay.total_seconds())
|
|
||||||
else:
|
|
||||||
time.sleep(0.4)
|
|
||||||
elif state.request == BackendRequest.CALL_NEXT:
|
|
||||||
pass
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
tmtccmd == 8.0.1
|
|
||||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
|
|
||||||
@@ -1,76 +1,61 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use satrs_stm32f3_disco_rtic as _;
|
#![no_std]
|
||||||
|
|
||||||
use stm32f3_discovery::leds::Leds;
|
use panic_probe as _;
|
||||||
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
|
use rtic::app;
|
||||||
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
|
|
||||||
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
|
|
||||||
|
|
||||||
#[cortex_m_rt::entry]
|
#[app(device = embassy_stm32)]
|
||||||
fn main() -> ! {
|
mod app {
|
||||||
defmt::println!("STM32F3 Discovery Blinky");
|
use rtic_monotonics::fugit::ExtU32;
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
use rtic_monotonics::Monotonic as _;
|
||||||
let mut rcc = dp.RCC.constrain();
|
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds};
|
||||||
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);
|
|
||||||
|
|
||||||
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
|
rtic_monotonics::systick_monotonic!(Mono, 1000);
|
||||||
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;
|
|
||||||
loop {
|
|
||||||
leds.ld3_n.toggle().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld3_n.toggle().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
//explicit on/off
|
#[shared]
|
||||||
leds.ld4_nw.on().ok();
|
struct Shared {}
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld4_nw.off().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld5_ne.on().ok();
|
#[local]
|
||||||
delay.delay_ms(delay_ms);
|
struct Local {
|
||||||
leds.ld5_ne.off().ok();
|
leds: Leds,
|
||||||
delay.delay_ms(delay_ms);
|
current_dir: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
leds.ld6_w.on().ok();
|
#[init]
|
||||||
delay.delay_ms(delay_ms);
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
leds.ld6_w.off().ok();
|
let p = embassy_stm32::init(Default::default());
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld7_e.on().ok();
|
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2");
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld7_e.off().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld8_sw.on().ok();
|
let led_pin_set = LedPinSet {
|
||||||
delay.delay_ms(delay_ms);
|
pin_n: p.PE8,
|
||||||
leds.ld8_sw.off().ok();
|
pin_ne: p.PE9,
|
||||||
delay.delay_ms(delay_ms);
|
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);
|
||||||
|
|
||||||
leds.ld9_se.on().ok();
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
delay.delay_ms(delay_ms);
|
Mono::start(cx.core.SYST, 8_000_000);
|
||||||
leds.ld9_se.off().ok();
|
blinky::spawn().expect("failed to spawn blinky task");
|
||||||
delay.delay_ms(delay_ms);
|
(
|
||||||
|
Shared {},
|
||||||
|
Local {
|
||||||
|
leds,
|
||||||
|
current_dir: Direction::North,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
leds.ld10_s.on().ok();
|
#[task(local = [leds, current_dir])]
|
||||||
delay.delay_ms(delay_ms);
|
async fn blinky(cx: blinky::Context) {
|
||||||
leds.ld10_s.off().ok();
|
loop {
|
||||||
delay.delay_ms(delay_ms);
|
cx.local.leds.blink_next(cx.local.current_dir);
|
||||||
|
Mono::delay(200.millis()).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,190 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_semihosting::debug;
|
use arbitrary_int::u11;
|
||||||
|
use core::time::Duration;
|
||||||
|
use embassy_stm32::gpio::Output;
|
||||||
|
use spacepackets::{
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError,
|
||||||
|
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
|
||||||
|
};
|
||||||
|
|
||||||
use defmt_brtt as _; // global logger
|
pub const APID: u11 = u11::new(0x02);
|
||||||
|
|
||||||
use stm32f3xx_hal as _; // memory layout
|
#[derive(defmt::Format, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum Direction {
|
||||||
use panic_probe as _;
|
North,
|
||||||
|
NorthEast,
|
||||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
East,
|
||||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
SouthEast,
|
||||||
#[defmt::panic_handler]
|
South,
|
||||||
fn panic() -> ! {
|
SouthWest,
|
||||||
cortex_m::asm::udf()
|
West,
|
||||||
|
NorthWest,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
impl Direction {
|
||||||
/// with status code 0.
|
pub fn switch_to_next(&mut self) -> (Self, Self) {
|
||||||
pub fn exit() -> ! {
|
let curr = *self;
|
||||||
loop {
|
*self = match self {
|
||||||
debug::exit(debug::EXIT_SUCCESS);
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hardfault handler.
|
#[derive(Copy, Clone, Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
||||||
///
|
pub enum Request {
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
Ping,
|
||||||
/// with an error. This seems better than the default, which is to spin in a
|
ChangeBlinkFrequency(Duration),
|
||||||
/// loop.
|
}
|
||||||
#[cortex_m_rt::exception]
|
|
||||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
||||||
loop {
|
pub struct TmHeader {
|
||||||
debug::exit(debug::EXIT_FAILURE);
|
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
pub uptime_millis: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum Response {
|
||||||
|
CommandDone,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tm_size(tm_header: &TmHeader, response: &Response) -> usize {
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum(
|
||||||
|
postcard::experimental::serialized_size(tm_header).unwrap()
|
||||||
|
+ postcard::experimental::serialized_size(response).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_tm_packet(
|
||||||
|
buf: &mut [u8],
|
||||||
|
sp_header: SpacePacketHeader,
|
||||||
|
tm_header: TmHeader,
|
||||||
|
response: Response,
|
||||||
|
) -> Result<usize, CcsdsPacketCreationError> {
|
||||||
|
let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
|
||||||
|
+ postcard::experimental::serialized_size(&response).unwrap();
|
||||||
|
let mut creator =
|
||||||
|
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
|
||||||
|
|
||||||
|
let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
|
||||||
|
Ok(creator.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
pub struct LedPinSet {
|
||||||
// once within a crate. the module can be in any file but there can only be at most
|
pub pin_n: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE8>,
|
||||||
// one `#[tests]` module in this library crate
|
pub pin_ne: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE9>,
|
||||||
#[cfg(test)]
|
pub pin_e: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE10>,
|
||||||
#[defmt_test::tests]
|
pub pin_se: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE11>,
|
||||||
mod unit_tests {
|
pub pin_s: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE12>,
|
||||||
use defmt::assert;
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
impl Leds {
|
||||||
fn it_works() {
|
pub fn new(pin_set: LedPinSet) -> Self {
|
||||||
assert!(true)
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,684 +1,349 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use satrs::pus::verification::{
|
use arbitrary_int::{u11, u14};
|
||||||
FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
|
use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS};
|
||||||
};
|
use satrs_stm32f3_disco_rtic::{create_tm_packet, tm_size, CcsdsPacketId, Request, Response};
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use spacepackets::{CcsdsPacketCreationError, SpHeader};
|
||||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
|
||||||
use satrs::spacepackets::ecss::EcssEnumU16;
|
use defmt_rtt as _; // global logger
|
||||||
use satrs::spacepackets::CcsdsPacket;
|
|
||||||
use satrs::spacepackets::{ByteConversionError, SpHeader};
|
use panic_probe as _;
|
||||||
// global logger + panicking-behavior + memory layout
|
|
||||||
use satrs_stm32f3_disco_rtic as _;
|
|
||||||
|
|
||||||
use rtic::app;
|
use rtic::app;
|
||||||
|
|
||||||
use heapless::{mpmc::Q8, Vec};
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
|
use rtic_monotonics::fugit::{MillisDurationU32, TimerInstantU32};
|
||||||
use rtic_monotonics::systick::ExtU32;
|
use rtic_monotonics::systick::prelude::*;
|
||||||
use satrs::seq_count::SequenceCountProviderCore;
|
|
||||||
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
|
use crate::app::Mono;
|
||||||
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};
|
|
||||||
|
|
||||||
const UART_BAUD: u32 = 115200;
|
const UART_BAUD: u32 = 115200;
|
||||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
||||||
const TX_HANDLER_FREQ_MS: u32 = 20;
|
const TX_HANDLER_FREQ_MS: u32 = 20;
|
||||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
|
|
||||||
const MAX_TC_LEN: usize = 128;
|
const MAX_TC_LEN: usize = 128;
|
||||||
const MAX_TM_LEN: usize = 128;
|
const MAX_TM_LEN: usize = 128;
|
||||||
pub const PUS_APID: u16 = 0x02;
|
|
||||||
|
|
||||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
|
pub const PUS_APID: u11 = u11::new(0x02);
|
||||||
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>;
|
|
||||||
|
|
||||||
// This is the predictable maximum overhead of the COBS encoding scheme.
|
// This is the predictable maximum overhead of the COBS encoding scheme.
|
||||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
|
// 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 = cobs::max_encoding_overhead(MAX_TM_LEN);
|
||||||
const COBS_TM_OVERHEAD: usize = (MAX_TM_LEN + 254 - 1) / 254;
|
|
||||||
|
|
||||||
const TC_BUF_LEN: usize = MAX_TC_LEN + COBS_TC_OVERHEAD;
|
|
||||||
const TM_BUF_LEN: usize = MAX_TC_LEN + COBS_TM_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
|
const TC_DMA_BUF_LEN: usize = 512;
|
||||||
// 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];
|
|
||||||
|
|
||||||
type TmPacket = Vec<u8, MAX_TM_LEN>;
|
type TmPacket = heapless::Vec<u8, MAX_TM_LEN>;
|
||||||
type TcPacket = Vec<u8, MAX_TC_LEN>;
|
|
||||||
|
|
||||||
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
|
static TM_QUEUE: heapless::mpmc::Queue<TmPacket, 16> = heapless::mpmc::Queue::new();
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU16, Ordering};
|
#[derive(Debug, defmt::Format, thiserror::Error)]
|
||||||
|
|
||||||
pub struct SeqCountProviderAtomicRef {
|
|
||||||
atomic: AtomicU16,
|
|
||||||
ordering: Ordering,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeqCountProviderAtomicRef {
|
|
||||||
pub const fn new(ordering: Ordering) -> Self {
|
|
||||||
Self {
|
|
||||||
atomic: AtomicU16::new(0),
|
|
||||||
ordering,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
|
|
||||||
fn get(&self) -> u16 {
|
|
||||||
self.atomic.load(self.ordering)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn increment(&self) {
|
|
||||||
self.atomic.fetch_add(1, self.ordering);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_and_increment(&self) -> u16 {
|
|
||||||
self.atomic.fetch_add(1, self.ordering)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
|
|
||||||
SeqCountProviderAtomicRef::new(Ordering::Relaxed);
|
|
||||||
|
|
||||||
pub struct TxIdle {
|
|
||||||
tx: TxType,
|
|
||||||
dma_channel: dma1::C7,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
|
||||||
pub enum TmSendError {
|
pub enum TmSendError {
|
||||||
ByteConversion(ByteConversionError),
|
#[error("packet creation error: {0}")]
|
||||||
|
PacketCreation(#[from] CcsdsPacketCreationError),
|
||||||
|
#[error("queue error")]
|
||||||
Queue,
|
Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ByteConversionError> for TmSendError {
|
|
||||||
fn from(value: ByteConversionError) -> Self {
|
|
||||||
Self::ByteConversion(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Request,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
#[derive(Debug, defmt::Format)]
|
||||||
pub enum Request {
|
pub struct RequestWithTcId {
|
||||||
Ping,
|
pub request: Request,
|
||||||
ChangeBlinkFrequency(u32),
|
pub tc_id: CcsdsPacketId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
#[app(device = embassy_stm32)]
|
||||||
pub enum RequestError {
|
|
||||||
InvalidApid = 1,
|
|
||||||
InvalidService = 2,
|
|
||||||
InvalidSubservice = 3,
|
|
||||||
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)]
|
|
||||||
mod app {
|
mod app {
|
||||||
use super::*;
|
use core::time::Duration;
|
||||||
use core::slice::Iter;
|
|
||||||
use rtic_monotonics::systick::Systick;
|
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use satrs::pus::verification::{TcStateStarted, 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 stm32f3_discovery::switch_hal::OutputSwitch;
|
use super::*;
|
||||||
use stm32f3xx_hal::Switch;
|
use arbitrary_int::u14;
|
||||||
#[allow(dead_code)]
|
use rtic::Mutex;
|
||||||
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
|
use rtic_sync::{
|
||||||
|
channel::{Receiver, Sender},
|
||||||
|
make_channel,
|
||||||
|
};
|
||||||
|
use satrs_stm32f3_disco_rtic::{CcsdsPacketId, LedPinSet, Request, Response};
|
||||||
|
use spacepackets::CcsdsPacketReader;
|
||||||
|
|
||||||
|
systick_monotonic!(Mono, 1000);
|
||||||
|
|
||||||
|
embassy_stm32::bind_interrupts!(struct Irqs {
|
||||||
|
USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>;
|
||||||
|
});
|
||||||
|
|
||||||
#[shared]
|
#[shared]
|
||||||
struct Shared {
|
struct Shared {
|
||||||
blink_freq: MillisDurationU32,
|
blink_freq: Duration,
|
||||||
tx_shared: UartTxShared,
|
|
||||||
rx_transfer: Option<RxDmaTransferType>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[local]
|
#[local]
|
||||||
struct Local {
|
struct Local {
|
||||||
verif_reporter: VerificationReportCreator,
|
leds: satrs_stm32f3_disco_rtic::Leds,
|
||||||
leds: Leds,
|
current_dir: satrs_stm32f3_disco_rtic::Direction,
|
||||||
last_dir: Direction,
|
seq_count: u14,
|
||||||
curr_dir: Iter<'static, Direction>,
|
tx: embassy_stm32::usart::UartTx<'static, embassy_stm32::mode::Async>,
|
||||||
|
rx: embassy_stm32::usart::RingBufferedUartRx<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(cx: init::Context) -> (Shared, Local) {
|
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());
|
||||||
|
|
||||||
|
let (req_sender, req_receiver) = make_channel!(RequestWithTcId, 16);
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
Mono::start(cx.core.SYST, 8_000_000);
|
||||||
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
|
|
||||||
|
|
||||||
let mut flash = cx.device.FLASH.constrain();
|
defmt::info!("sat-rs demo application for the STM32F3-Discovery with RTICv2");
|
||||||
let clocks = rcc
|
let led_pin_set = LedPinSet {
|
||||||
.cfgr
|
pin_n: p.PE8,
|
||||||
.use_hse(8.MHz())
|
pin_ne: p.PE9,
|
||||||
.sysclk(8.MHz())
|
pin_e: p.PE10,
|
||||||
.pclk1(8.MHz())
|
pin_se: p.PE11,
|
||||||
.freeze(&mut flash.acr);
|
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 mut config = embassy_stm32::usart::Config::default();
|
||||||
//let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
|
config.baudrate = UART_BAUD;
|
||||||
|
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 (tx, rx) = uart.split();
|
||||||
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);
|
|
||||||
// USART2 pins
|
|
||||||
let mut pins = (
|
|
||||||
// TX pin: PA2
|
|
||||||
gpioa
|
|
||||||
.pa2
|
|
||||||
.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
|
|
||||||
// RX pin: PA3
|
|
||||||
gpioa
|
|
||||||
.pa3
|
|
||||||
.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
|
|
||||||
);
|
|
||||||
pins.1.internal_pull_up(&mut gpioa.pupdr, true);
|
|
||||||
let mut usart2 = Serial::new(
|
|
||||||
cx.device.USART2,
|
|
||||||
pins,
|
|
||||||
UART_BAUD.Bd(),
|
|
||||||
clocks,
|
|
||||||
&mut rcc.apb1,
|
|
||||||
);
|
|
||||||
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();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
// 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);
|
|
||||||
defmt::info!("Spawning tasks");
|
defmt::info!("Spawning tasks");
|
||||||
blink::spawn().unwrap();
|
blinky::spawn().unwrap();
|
||||||
serial_tx_handler::spawn().unwrap();
|
serial_tx_handler::spawn().unwrap();
|
||||||
|
serial_rx_handler::spawn(req_sender).unwrap();
|
||||||
let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
|
req_handler::spawn(req_receiver).unwrap();
|
||||||
|
|
||||||
(
|
(
|
||||||
Shared {
|
Shared {
|
||||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
blink_freq: Duration::from_millis(DEFAULT_BLINK_FREQ_MS as u64),
|
||||||
tx_shared: UartTxShared {
|
|
||||||
last_completed: None,
|
|
||||||
state: UartTxState::Idle(Some(TxIdle {
|
|
||||||
tx: tx_serial,
|
|
||||||
dma_channel: dma1.ch7,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
rx_transfer: Some(rx_transfer),
|
|
||||||
},
|
},
|
||||||
Local {
|
Local {
|
||||||
verif_reporter,
|
|
||||||
leds,
|
leds,
|
||||||
last_dir: Direction::North,
|
tx,
|
||||||
curr_dir: Direction::iter(),
|
seq_count: u14::new(0),
|
||||||
|
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])]
|
#[task(local = [leds, current_dir], shared=[blink_freq])]
|
||||||
async fn blink(mut cx: blink::Context) {
|
async fn blinky(mut cx: blinky::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;
|
|
||||||
};
|
|
||||||
loop {
|
loop {
|
||||||
match curr_dir.next() {
|
cx.local.leds.blink_next(cx.local.current_dir);
|
||||||
Some(dir) => {
|
|
||||||
toggle_leds(dir);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
*curr_dir = Direction::iter();
|
|
||||||
toggle_leds(curr_dir.next().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
||||||
Systick::delay(current_blink_freq).await;
|
Mono::delay(MillisDurationU32::from_ticks(
|
||||||
}
|
current_blink_freq.as_millis() as u32,
|
||||||
}
|
))
|
||||||
|
.await;
|
||||||
#[task(
|
|
||||||
shared = [tx_shared],
|
|
||||||
)]
|
|
||||||
async fn serial_tx_handler(mut 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..]);
|
|
||||||
// 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;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Nothing to do, and we are idle.
|
|
||||||
Systick::delay(TX_HANDLER_FREQ_MS.millis()).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(
|
#[task(
|
||||||
local = [
|
local = [
|
||||||
verif_reporter,
|
tx,
|
||||||
|
encoded_buf: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN]
|
||||||
|
],
|
||||||
|
shared = [],
|
||||||
|
)]
|
||||||
|
async fn serial_tx_handler(cx: serial_tx_handler::Context) {
|
||||||
|
loop {
|
||||||
|
while let Some(vec) = TM_QUEUE.dequeue() {
|
||||||
|
let encoded_len =
|
||||||
|
cobs::encode_including_sentinels(&vec[0..vec.len()], cx.local.encoded_buf);
|
||||||
|
defmt::debug!("sending {} bytes over UART", encoded_len);
|
||||||
|
cx.local
|
||||||
|
.tx
|
||||||
|
.write(&cx.local.encoded_buf[0..encoded_len])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Mono::delay(TX_HANDLER_FREQ_MS.millis()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(
|
||||||
|
local = [
|
||||||
|
rx,
|
||||||
|
read_buf: [u8; 128] = [0; 128],
|
||||||
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
|
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],
|
|
||||||
],
|
],
|
||||||
shared = [blink_freq]
|
shared = [blink_freq]
|
||||||
)]
|
)]
|
||||||
async fn serial_rx_handler(
|
async fn serial_rx_handler(
|
||||||
mut cx: serial_rx_handler::Context,
|
cx: serial_rx_handler::Context,
|
||||||
received_packet: Vec<u8, MAX_TC_LEN>,
|
mut sender: Sender<'static, RequestWithTcId, 16>,
|
||||||
) {
|
) {
|
||||||
cx.local.timestamp[0] = P_FIELD_BASE;
|
let mut decoder = cobs::CobsDecoder::new(cx.local.decode_buf);
|
||||||
defmt::info!("Received packet with {} bytes", received_packet.len());
|
loop {
|
||||||
let decode_buf = cx.local.decode_buf;
|
match cx.local.rx.read(cx.local.read_buf).await {
|
||||||
let packet = received_packet.as_slice();
|
Ok(bytes) => {
|
||||||
let mut start_idx = None;
|
defmt::debug!("received {} bytes over UART", bytes);
|
||||||
for (idx, byte) in packet.iter().enumerate() {
|
for byte in cx.local.read_buf[0..bytes].iter() {
|
||||||
if *byte != 0 {
|
match decoder.feed(*byte) {
|
||||||
start_idx = Some(idx);
|
Ok(None) => (),
|
||||||
break;
|
Ok(Some(packet_size)) => {
|
||||||
}
|
match CcsdsPacketReader::new_with_checksum(
|
||||||
}
|
&decoder.dest()[0..packet_size],
|
||||||
if start_idx.is_none() {
|
) {
|
||||||
defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
|
Ok(packet) => {
|
||||||
return;
|
let packet_id = packet.packet_id();
|
||||||
}
|
let psc = packet.psc();
|
||||||
let start_idx = start_idx.unwrap();
|
let tc_packet_id = CcsdsPacketId { packet_id, psc };
|
||||||
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
|
if let Ok(request) =
|
||||||
Ok(len) => {
|
postcard::from_bytes::<Request>(packet.packet_data())
|
||||||
defmt::info!("Decoded packet length: {}", len);
|
{
|
||||||
let pus_tc = PusTcReader::new(decode_buf);
|
sender
|
||||||
match pus_tc {
|
.send(RequestWithTcId {
|
||||||
Ok((tc, _tc_len)) => {
|
request,
|
||||||
match convert_pus_tc_to_request(
|
tc_id: tc_packet_id,
|
||||||
&tc,
|
})
|
||||||
cx.local.verif_reporter,
|
.await
|
||||||
cx.local.src_data_buf,
|
.unwrap();
|
||||||
cx.local.timestamp,
|
}
|
||||||
) {
|
|
||||||
Ok(request_with_token) => {
|
|
||||||
let started_token = handle_start_verification(
|
|
||||||
request_with_token.token,
|
|
||||||
cx.local.verif_reporter,
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
cx.local.timestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
match request_with_token.request {
|
|
||||||
Request::Ping => {
|
|
||||||
handle_ping_request(cx.local.timestamp);
|
|
||||||
}
|
}
|
||||||
Request::ChangeBlinkFrequency(new_freq_ms) => {
|
Err(e) => {
|
||||||
defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
|
defmt::error!("error unpacking ccsds packet: {}", e);
|
||||||
cx.shared.blink_freq.lock(|blink_freq| {
|
|
||||||
*blink_freq =
|
|
||||||
MillisDurationU32::from_ticks(new_freq_ms);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handle_completion_verification(
|
|
||||||
started_token,
|
|
||||||
cx.local.verif_reporter,
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
cx.local.timestamp,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// TODO: Error handling: Send verification failure based on request error.
|
defmt::error!("cobs decoding error: {}", e);
|
||||||
defmt::warn!("request error {}", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
}
|
||||||
defmt::warn!("Error unpacking PUS TC: {}", e);
|
Err(e) => {
|
||||||
}
|
defmt::error!("uart read error: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
defmt::warn!("decoding error, can only process cobs encoded frames")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_ping_request(timestamp: &[u8]) {
|
#[task(shared = [blink_freq], local = [seq_count])]
|
||||||
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
|
async fn req_handler(
|
||||||
let sp_header =
|
mut cx: req_handler::Context,
|
||||||
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
|
mut receiver: Receiver<'static, RequestWithTcId, 16>,
|
||||||
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
|
|
||||||
let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
|
|
||||||
let mut tm_packet = TmPacket::new();
|
|
||||||
tm_packet
|
|
||||||
.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() {
|
|
||||||
defmt::warn!("TC queue full");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_start_verification(
|
|
||||||
accepted_token: VerificationToken<TcStateAccepted>,
|
|
||||||
verif_reporter: &mut VerificationReportCreator,
|
|
||||||
src_data_buf: &mut [u8],
|
|
||||||
timestamp: &[u8],
|
|
||||||
) -> VerificationToken<TcStateStarted> {
|
|
||||||
let (tm_creator, started_token) = verif_reporter
|
|
||||||
.start_success(
|
|
||||||
src_data_buf,
|
|
||||||
accepted_token,
|
|
||||||
SEQ_COUNT_PROVIDER.get(),
|
|
||||||
0,
|
|
||||||
×tamp,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let result = send_tm(tm_creator);
|
|
||||||
if let Err(e) = result {
|
|
||||||
handle_tm_send_error(e);
|
|
||||||
}
|
|
||||||
started_token
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_completion_verification(
|
|
||||||
started_token: VerificationToken<TcStateStarted>,
|
|
||||||
verif_reporter: &mut VerificationReportCreator,
|
|
||||||
src_data_buf: &mut [u8],
|
|
||||||
timestamp: &[u8],
|
|
||||||
) {
|
) {
|
||||||
let result = send_tm(
|
loop {
|
||||||
verif_reporter
|
match receiver.recv().await {
|
||||||
.completion_success(
|
Ok(request_with_tc_id) => {
|
||||||
src_data_buf,
|
let tm_send_result = match request_with_tc_id.request {
|
||||||
started_token,
|
Request::Ping => handle_ping_request(&mut cx, request_with_tc_id.tc_id),
|
||||||
SEQ_COUNT_PROVIDER.get(),
|
Request::ChangeBlinkFrequency(duration) => {
|
||||||
0,
|
handle_change_blink_frequency_request(
|
||||||
timestamp,
|
&mut cx,
|
||||||
)
|
request_with_tc_id.tc_id,
|
||||||
.unwrap(),
|
duration,
|
||||||
);
|
)
|
||||||
if let Err(e) = result {
|
}
|
||||||
handle_tm_send_error(e);
|
};
|
||||||
|
if let Err(e) = tm_send_result {
|
||||||
|
defmt::error!("error sending TM response: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_e) => defmt::error!("request receive error"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
|
fn handle_ping_request(
|
||||||
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
|
cx: &mut req_handler::Context,
|
||||||
let mut tc_packet = TcPacket::new();
|
tc_packet_id: CcsdsPacketId,
|
||||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
) -> Result<(), TmSendError> {
|
||||||
let rx_ref = rx_transfer.as_ref().unwrap();
|
defmt::info!("Received PUS ping telecommand, sending ping reply");
|
||||||
if rx_ref.is_complete() {
|
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
|
||||||
let uart_rx_owned = rx_transfer.take().unwrap();
|
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
||||||
let (buf, c, rx) = uart_rx_owned.stop();
|
Ok(())
|
||||||
// The received data is transferred to another task now to avoid any processing overhead
|
|
||||||
// during the interrupt. There are multiple ways to do this, we use a stack allocaed vector here
|
|
||||||
// to do this.
|
|
||||||
tc_packet.resize(buf.len(), 0).expect("vec resize failed");
|
|
||||||
tc_packet.copy_from_slice(buf);
|
|
||||||
|
|
||||||
// Start the next transfer as soon as possible.
|
|
||||||
*rx_transfer = Some(rx.read_exact(buf, c));
|
|
||||||
|
|
||||||
// Send the vector to a regular task.
|
|
||||||
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler task failed");
|
|
||||||
// If this happens, there is a high chance that the maximum packet length was
|
|
||||||
// exceeded. Circular mode is not used here, so data might be missed.
|
|
||||||
defmt::warn!(
|
|
||||||
"rx transfer with maximum length {}, might miss data",
|
|
||||||
TC_BUF_LEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
|
fn handle_change_blink_frequency_request(
|
||||||
fn serial_isr(mut cx: serial_isr::Context) {
|
cx: &mut req_handler::Context,
|
||||||
|
tc_packet_id: CcsdsPacketId,
|
||||||
|
duration: Duration,
|
||||||
|
) -> Result<(), TmSendError> {
|
||||||
|
defmt::info!(
|
||||||
|
"Received ChangeBlinkFrequency request, new frequency: {} ms",
|
||||||
|
duration.as_millis()
|
||||||
|
);
|
||||||
cx.shared
|
cx.shared
|
||||||
.tx_shared
|
.blink_freq
|
||||||
.lock(|tx_shared| match &mut tx_shared.state {
|
.lock(|blink_freq| *blink_freq = duration);
|
||||||
UartTxState::Idle(_) => (),
|
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
|
||||||
UartTxState::Transmitting(transfer) => {
|
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
||||||
let transfer_ref = transfer.as_ref().unwrap();
|
Ok(())
|
||||||
if transfer_ref.is_complete() {
|
}
|
||||||
let transfer = transfer.take().unwrap();
|
}
|
||||||
let (_, dma_channel, mut tx) = transfer.stop();
|
|
||||||
tx.clear_event(TxEvent::TransmissionComplete);
|
fn send_tm(
|
||||||
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
|
tc_packet_id: CcsdsPacketId,
|
||||||
// We cache the last completed time to ensure that there is a minimum delay between consecutive
|
response: Response,
|
||||||
// transferred packets.
|
current_seq_count: u14,
|
||||||
tx_shared.last_completed = Some(Systick::now());
|
) -> Result<(), TmSendError> {
|
||||||
}
|
let sp_header = SpHeader::new_for_unseg_tc(PUS_APID, current_seq_count, 0);
|
||||||
}
|
let tm_header = satrs_stm32f3_disco_rtic::TmHeader {
|
||||||
});
|
tc_packet_id: Some(tc_packet_id),
|
||||||
let mut tc_packet = TcPacket::new();
|
uptime_millis: Mono::now().duration_since_epoch().to_millis(),
|
||||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
};
|
||||||
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
|
let mut tm_packet = TmPacket::new();
|
||||||
// Received a partial packet.
|
let tm_size = tm_size(&tm_header, &response);
|
||||||
if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
|
tm_packet.resize(tm_size, 0).expect("vec resize failed");
|
||||||
let rx_transfer_owned = rx_transfer.take().unwrap();
|
create_tm_packet(&mut tm_packet, sp_header, tm_header, response)?;
|
||||||
let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
|
if TM_QUEUE.enqueue(tm_packet).is_err() {
|
||||||
// The received data is transferred to another task now to avoid any processing overhead
|
defmt::warn!("TC queue full");
|
||||||
// during the interrupt. There are multiple ways to do this, we use a stack
|
return Err(TmSendError::Queue);
|
||||||
// allocated vector to do this.
|
}
|
||||||
tc_packet
|
Ok(())
|
||||||
.resize(rx_len as usize, 0)
|
}
|
||||||
.expect("vec resize failed");
|
|
||||||
tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
|
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
rx.clear_event(RxEvent::Idle);
|
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||||
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
|
#[defmt::panic_handler]
|
||||||
*rx_transfer = Some(rx.read_exact(buf, ch));
|
fn panic() -> ! {
|
||||||
}
|
cortex_m::asm::udf()
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
/// with status code 0.
|
||||||
|
pub fn exit() -> ! {
|
||||||
|
loop {
|
||||||
|
debug::exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hardfault handler.
|
||||||
|
///
|
||||||
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
/// with an error. This seems better than the default, which is to spin in a
|
||||||
|
/// loop.
|
||||||
|
#[cortex_m_rt::exception]
|
||||||
|
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||||
|
loop {
|
||||||
|
debug::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||||
|
// once within a crate. the module can be in any file but there can only be at most
|
||||||
|
// one `#[tests]` module in this library crate
|
||||||
|
#[cfg(test)]
|
||||||
|
#[defmt_test::tests]
|
||||||
|
mod unit_tests {
|
||||||
|
use defmt::assert;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
defmt = "0.3"
|
defmt = "1"
|
||||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
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"
|
cortex-m-semihosting = "0.5.0"
|
||||||
|
# TODO: Replace with embassy-hal.
|
||||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
||||||
embedded-alloc = "0.5"
|
embedded-alloc = "0.6"
|
||||||
rtic-sync = { version = "1", features = ["defmt-03"] }
|
rtic-sync = { version = "1", features = ["defmt-03"] }
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
[dependencies.smoltcp]
|
||||||
version = "0.11.0"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
||||||
|
|
||||||
@@ -34,17 +35,16 @@ version = "2"
|
|||||||
features = ["thumbv7-backend"]
|
features = ["thumbv7-backend"]
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
[dependencies.rtic-monotonics]
|
||||||
version = "1"
|
version = "2"
|
||||||
features = ["cortex-m-systick"]
|
features = ["cortex-m-systick"]
|
||||||
|
|
||||||
[dependencies.satrs]
|
[dependencies.satrs]
|
||||||
path = "../../satrs"
|
path = "../../satrs"
|
||||||
version = "0.2"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["defmt", "heapless"]
|
features = ["defmt", "heapless"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
defmt-test = "0.3"
|
defmt-test = "0.4"
|
||||||
|
|
||||||
# cargo build/run
|
# cargo build/run
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
sat-rs example for the STM32F3-Discovery board
|
sat-rs example for the STM32H73ZI-Nucleo board
|
||||||
=======
|
=======
|
||||||
|
|
||||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
use rtic::app;
|
use rtic::app;
|
||||||
use rtic_monotonics::systick::Systick;
|
use rtic_monotonics::systick::prelude::*;
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
||||||
use satrs::static_subpool;
|
use satrs::static_subpool;
|
||||||
// global logger + panicking-behavior + memory layout
|
// global logger + panicking-behavior + memory layout
|
||||||
@@ -13,7 +12,7 @@ use smoltcp::socket::udp::UdpMetadata;
|
|||||||
use smoltcp::socket::{dhcpv4, udp};
|
use smoltcp::socket::{dhcpv4, udp};
|
||||||
|
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use embedded_alloc::Heap;
|
use embedded_alloc::LlffHeap as Heap;
|
||||||
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
|
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
|
||||||
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
|
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
|
||||||
use stm32h7xx_hal::ethernet;
|
use stm32h7xx_hal::ethernet;
|
||||||
@@ -32,6 +31,8 @@ pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_
|
|||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static HEAP: Heap = Heap::empty();
|
static HEAP: Heap = Heap::empty();
|
||||||
|
|
||||||
|
systick_monotonic!(Mono, 1000);
|
||||||
|
|
||||||
// We place the memory pool buffers inside the larger AXISRAM.
|
// We place the memory pool buffers inside the larger AXISRAM.
|
||||||
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
|
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
|
||||||
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
|
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
|
||||||
@@ -72,7 +73,7 @@ impl Net {
|
|||||||
let mut iface = Interface::new(
|
let mut iface = Interface::new(
|
||||||
config,
|
config,
|
||||||
&mut ethdev,
|
&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
|
// Create sockets
|
||||||
let dhcp_socket = dhcpv4::Socket::new();
|
let dhcp_socket = dhcpv4::Socket::new();
|
||||||
@@ -91,7 +92,7 @@ impl Net {
|
|||||||
/// Polls on the ethernet interface. You should refer to the smoltcp
|
/// Polls on the ethernet interface. You should refer to the smoltcp
|
||||||
/// documentation for poll() to understand how to call poll efficiently
|
/// documentation for poll() to understand how to call poll efficiently
|
||||||
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
|
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());
|
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
|
||||||
|
|
||||||
self.iface.poll(timestamp, &mut self.ethdev, sockets)
|
self.iface.poll(timestamp, &mut self.ethdev, sockets)
|
||||||
@@ -203,8 +204,7 @@ mod app {
|
|||||||
use core::ptr::addr_of_mut;
|
use core::ptr::addr_of_mut;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use rtic_monotonics::systick::fugit::MillisDurationU32;
|
use rtic_monotonics::fugit::MillisDurationU32;
|
||||||
use rtic_monotonics::systick::Systick;
|
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
||||||
use stm32h7xx_hal::gpio::{Output, Pin};
|
use stm32h7xx_hal::gpio::{Output, Pin};
|
||||||
@@ -256,12 +256,7 @@ mod app {
|
|||||||
.freeze(pwrcfg, &cx.device.SYSCFG);
|
.freeze(pwrcfg, &cx.device.SYSCFG);
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz());
|
||||||
Systick::start(
|
|
||||||
cx.core.SYST,
|
|
||||||
ccdr.clocks.sys_ck().to_Hz(),
|
|
||||||
systick_mono_token,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
|
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
|
||||||
// good for.
|
// good for.
|
||||||
@@ -377,24 +372,24 @@ mod app {
|
|||||||
|
|
||||||
shared_pool
|
shared_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { SUBPOOL_SMALL.assume_init_mut() },
|
SUBPOOL_SMALL.get_mut().unwrap(),
|
||||||
unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
|
SUBPOOL_SMALL_SIZES.get_mut().unwrap(),
|
||||||
SUBPOOL_SMALL_NUM_BLOCKS,
|
SUBPOOL_SMALL_NUM_BLOCKS,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.expect("growing heapless memory pool failed");
|
.expect("growing heapless memory pool failed");
|
||||||
shared_pool
|
shared_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
|
SUBPOOL_MEDIUM.get_mut().unwrap(),
|
||||||
unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
|
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(),
|
||||||
SUBPOOL_MEDIUM_NUM_BLOCKS,
|
SUBPOOL_MEDIUM_NUM_BLOCKS,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.expect("growing heapless memory pool failed");
|
.expect("growing heapless memory pool failed");
|
||||||
shared_pool
|
shared_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { SUBPOOL_LARGE.assume_init_mut() },
|
SUBPOOL_LARGE.get_mut().unwrap(),
|
||||||
unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
|
SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
|
||||||
SUBPOOL_LARGE_NUM_BLOCKS,
|
SUBPOOL_LARGE_NUM_BLOCKS,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@@ -403,7 +398,7 @@ mod app {
|
|||||||
// Set up global allocator. Use AXISRAM for the heap.
|
// Set up global allocator. Use AXISRAM for the heap.
|
||||||
#[link_section = ".axisram"]
|
#[link_section = ".axisram"]
|
||||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
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");
|
eth_link_check::spawn().expect("eth link check failed");
|
||||||
blinky::spawn().expect("spawning blink task failed");
|
blinky::spawn().expect("spawning blink task failed");
|
||||||
@@ -435,7 +430,7 @@ mod app {
|
|||||||
leds.led1.toggle();
|
leds.led1.toggle();
|
||||||
leds.led2.toggle();
|
leds.led2.toggle();
|
||||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
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);
|
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
|
||||||
defmt::info!("Ethernet link down");
|
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);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
images/minisim-arch/minisim-arch.graphml
Normal file
260
images/minisim-arch/minisim-arch.graphml
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
|
<!--Created by yEd 3.23.2-->
|
||||||
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
|
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||||
|
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||||
|
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||||
|
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||||
|
<graph edgedefault="directed" id="G">
|
||||||
|
<data key="d0" xml:space="preserve"/>
|
||||||
|
<node id="n0">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="360.0" width="479.0" x="771.3047672479152" y="458.0"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="237.5" y="178.0">
|
||||||
|
<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="177.64799999999997" width="200.75199999999973" x="1037.5527672479152" y="470.15200000000027"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.919921875" x="13.264464667588754" xml:space="preserve" y="8.302185845943427">Simulation<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.433926114471642" nodeRatioY="-0.45326608886143704" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="84.39999999999986" x="1068.8351781652768" y="508.2800000000002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.638671875" x="23.380664062499818" xml:space="preserve" y="8.015625">PCDU<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n3">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="550.4800000000001"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.453125" x="13.973437499999818" xml:space="preserve" y="8.015625">Magnetometer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n4">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="594.9000000000001"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="88.83203125" x="15.783984374999818" xml:space="preserve" y="8.015625">Magnetorquer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n5">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="783.4063563305535" y="545.2800000000002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="85.931640625" x="17.234179687499932" xml:space="preserve" y="8.015625">SimController<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n6">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="840.5407126611072" y="677.8000000000002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.05078125" x="7.674609374999932" xml:space="preserve" y="8.015625">UDP TC Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n7">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="1005.2814253222144" y="677.8000000000002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.111328125" x="11.644335937499932" xml:space="preserve" y="8.015625">UDP TM Sender<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n8">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="34.0" width="120.39999999999986" x="931.6174253222144" y="775.5920000000002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.740234375" x="40.82988281249993" xml:space="preserve" y="8.015625">Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<edge id="e0" source="n5" target="n3">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="-5.199999999999932"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e1" source="n5" target="n2">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="60.19999999999993" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1023.8695890826383" y="562.2800000000002"/>
|
||||||
|
<y:Point x="1023.8695890826383" y="525.2800000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e2" source="n5" target="n4">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="48.72964366944643" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1023.8695890826383" y="562.2800000000002"/>
|
||||||
|
<y:Point x="1023.8695890826383" y="611.9000000000001"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.955078125" x="12.686124396959713" xml:space="preserve" y="-22.50440429687478">schedule_event<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="13.519999999999978" distanceToCenter="true" position="left" ratio="0.11621274698385183" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e3" source="n6" target="n5">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="-5.329643669446341" ty="0.0">
|
||||||
|
<y:Point x="900.7407126611072" y="628.5400000000002"/>
|
||||||
|
<y:Point x="838.2767126611071" y="628.5400000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.923828125" x="-87.89792405764274" xml:space="preserve" y="-40.606550292968564">SimRequest<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="49.935999999999936" distanceToCenter="true" position="left" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e4" source="n4" target="n7">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="60.200000000000045" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1223.8814253222142" y="611.9000000000001"/>
|
||||||
|
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e5" source="n3" target="n7">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1223.8814253222142" y="567.4800000000001"/>
|
||||||
|
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e6" source="n2" target="n7">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="11.514125426161627" sy="-2.5781798912005343" tx="45.553752843062284" ty="0.0">
|
||||||
|
<y:Point x="1223.8814253222142" y="522.7018201087997"/>
|
||||||
|
<y:Point x="1223.8814253222142" y="694.8000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="60.4140625" x="-2.4087265765670054" xml:space="preserve" y="145.1356018470808">SimReply<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.97817989120062" distanceToCenter="true" position="right" ratio="0.679561684469248" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e7" source="n8" target="n6">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-25.212712661107275" sy="0.0" tx="-11.264000000000124" ty="0.0">
|
||||||
|
<y:Point x="966.6047126611071" y="731.8000000000002"/>
|
||||||
|
<y:Point x="889.4767126611071" y="731.8000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="119.751953125" x="-132.27600022951788" xml:space="preserve" y="-32.03587548828091">SimRequest in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="20.73181250000017" distanceToCenter="true" position="left" ratio="0.9386993050513424" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e8" source="n7" target="n8">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="29.18399999999997" sy="0.0" tx="24.28800000000001" ty="0.0">
|
||||||
|
<y:Point x="1094.6654253222143" y="731.8000000000002"/>
|
||||||
|
<y:Point x="1016.1054253222144" y="731.8000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="104.2421875" x="-62.15307370122309" xml:space="preserve" y="34.80927001953137">SimReply in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="23.81218750000005" distanceToCenter="true" position="left" ratio="0.12769857433808468" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e9" source="n5" target="n1">
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="23.921741802203996" sy="-3.0501798912007416" tx="0.0" ty="-56.27417989120056">
|
||||||
|
<y:Point x="867.5280981327575" y="502.70182010879967"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.95703125" x="73.38950633588263" xml:space="preserve" y="-62.699758016200235">step<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="11.126187499999986" distanceToCenter="true" position="left" ratio="0.5889387894625147" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
</graph>
|
||||||
|
<data key="d7">
|
||||||
|
<y:Resources/>
|
||||||
|
</data>
|
||||||
|
</graphml>
|
||||||
BIN
images/minisim-arch/minisim-arch.png
Normal file
BIN
images/minisim-arch/minisim-arch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
29
justfile
Normal file
29
justfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
all: check build embedded test check-fmt clippy docs
|
||||||
|
|
||||||
|
check:
|
||||||
|
cargo check
|
||||||
|
cargo check -p satrs-example --no-default-features
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo nextest run --all-features
|
||||||
|
cargo test --doc --all-features
|
||||||
|
|
||||||
|
embedded:
|
||||||
|
cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
|
||||||
|
|
||||||
|
check-fmt:
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
cargo fmt --all
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
docs-satrs:
|
||||||
|
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p satrs --all-features
|
||||||
|
|
||||||
|
docs: docs-satrs
|
||||||
@@ -3,20 +3,20 @@
|
|||||||
Software for space systems oftentimes has different requirements than the software for host
|
Software for space systems oftentimes has different requirements than the software for host
|
||||||
systems or servers. Currently, most space systems are considered embedded systems.
|
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
|
For these systems, the computation power and the available heap are important resources
|
||||||
which are constrained. This might make completeley heap based memory management schemes which
|
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
|
are oftentimes used on host and server based systems unfeasable. Still, completely forbidding
|
||||||
heap allocations might make software development unnecessarilly difficult, especially in a
|
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.
|
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
|
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
|
running out of memory (something even Rust can not protect from) or heap fragmentation on systems
|
||||||
without a MMU.
|
without a MMU.
|
||||||
|
|
||||||
# Using pre-allocated pool structures
|
# 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
|
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
|
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
|
another solution to avoid run-time allocations by offering pre-allocated static
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ This book is the primary information resource for the [sat-rs library](https://e
|
|||||||
in addition to the regular API documentation. It contains the following resources:
|
in addition to the regular API documentation. It contains the following resources:
|
||||||
|
|
||||||
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
||||||
2. General information on how to build On-Board Software and how `sat-rs` can help to fulfill
|
2. General information on how to build on-board Software and how `sat-rs` can help to fulfill
|
||||||
the unique requirements of writing software for remote systems.
|
the unique requirements of writing software for remote systems.
|
||||||
2. A Getting-Started workshop where a small On-Board Software is built from scratch using
|
|
||||||
sat-rs components.
|
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
@@ -31,7 +29,9 @@ and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-
|
|||||||
The [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
The [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
||||||
provides various practical usage examples of the `sat-rs` framework. If you are more interested in
|
provides various practical usage examples of the `sat-rs` framework. If you are more interested in
|
||||||
the practical application of `sat-rs` inside an application, it is recommended to have a look at
|
the practical application of `sat-rs` inside an application, it is recommended to have a look at
|
||||||
the example application.
|
the example application. The [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim)
|
||||||
|
applicatin complements the example application and can be used to simulate some physical devices
|
||||||
|
for the `satrs-example` device handlers.
|
||||||
|
|
||||||
# Flight Heritage
|
# Flight Heritage
|
||||||
|
|
||||||
@@ -43,3 +43,7 @@ Currently this library has the following flight heritage:
|
|||||||
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
|
||||||
The application is strongly based on the sat-rs example application. You can find the repository
|
The application is strongly based on the sat-rs example application. You can find the repository
|
||||||
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
|
||||||
|
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
|
||||||
|
alongside a Flight System Simulator in the context of a
|
||||||
|
[Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
|
||||||
|
at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
|
||||||
|
|||||||
@@ -7,3 +7,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
|
# [v0.1.1] 2024-02-21
|
||||||
|
|
||||||
|
satrs v0.2.0-rc.0
|
||||||
|
satrs-mib v0.1.1
|
||||||
|
|
||||||
|
# [v0.1.0] 2024-02-13
|
||||||
|
|
||||||
|
satrs v0.1.1
|
||||||
|
satrs-mib v0.1.0
|
||||||
|
|||||||
@@ -8,32 +8,32 @@ homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
|||||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fern = "0.6"
|
fern = "0.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
delegate = "0.10"
|
delegate = "0.13"
|
||||||
zerocopy = "0.6"
|
zerocopy = "0.8"
|
||||||
csv = "1"
|
csv = "1"
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
thiserror = "1"
|
thiserror = "2"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
derive-new = "0.5"
|
derive-new = "0.7"
|
||||||
|
cfg-if = "1"
|
||||||
|
arbitrary-int = "2"
|
||||||
|
bitbybit = "1.4"
|
||||||
|
postcard = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
[dependencies.satrs]
|
satrs = { path = "../satrs", features = ["test_util"] }
|
||||||
path = "../satrs"
|
satrs-minisim = { path = "../satrs-minisim" }
|
||||||
features = ["test_util"]
|
satrs-mib = { path = "../satrs-mib" }
|
||||||
|
|
||||||
[dependencies.satrs-mib]
|
|
||||||
version = "0.1.1"
|
|
||||||
path = "../satrs-mib"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dyn_tmtc = []
|
# default = ["heap_tmtc"]
|
||||||
default = ["dyn_tmtc"]
|
# heap_tmtc = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ You can run the application using `cargo run`.
|
|||||||
|
|
||||||
# Features
|
# 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
|
TMTC packets are exchanged using the heap as the backing memory instead of pre-allocated static
|
||||||
stores.
|
stores.
|
||||||
|
|
||||||
@@ -48,16 +48,17 @@ It is recommended to use a virtual environment to do this. To set up one in the
|
|||||||
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
|
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
|
||||||
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
|
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
|
||||||
on how to activate the environment and then use the following command to install the required
|
on how to activate the environment and then use the following command to install the required
|
||||||
dependency:
|
dependency interactively:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install -r requirements.txt
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, if you would like to use the GUI functionality provided by `tmtccmd`, you can also
|
Alternatively, if you would like to use the GUI functionality provided by `tmtccmd`, you can also
|
||||||
install it manually with
|
install it manually with
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
pip install -e .
|
||||||
pip install tmtccmd[gui]
|
pip install tmtccmd[gui]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -72,3 +73,22 @@ the `simpleclient`:
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can also simply call the script without any arguments to view the command tree.
|
You can also simply call the script without any arguments to view the command tree.
|
||||||
|
|
||||||
|
## Adding the mini simulator application
|
||||||
|
|
||||||
|
This example application features a few device handlers. The
|
||||||
|
[`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim)
|
||||||
|
can be used to simulate the physical devices managed by these device handlers.
|
||||||
|
|
||||||
|
The example application will attempt communication with the mini simulator on UDP port 7303.
|
||||||
|
If this works, the device handlers will use communication interfaces dedicated to the communication
|
||||||
|
with the mini simulator. Otherwise, they will be replaced by dummy interfaces which either
|
||||||
|
return constant values or behave like ideal devices.
|
||||||
|
|
||||||
|
In summary, you can use the following command command to run the mini-simulator first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -p satrs-minisim
|
||||||
|
```
|
||||||
|
|
||||||
|
and then start the example using `cargo run -p satrs-example`.
|
||||||
|
|||||||
134
satrs-example/pytmtc/.gitignore
vendored
134
satrs-example/pytmtc/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
/tmtc_conf.json
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
/venv
|
/venv
|
||||||
@@ -7,3 +8,136 @@ __pycache__
|
|||||||
|
|
||||||
/seqcnt.txt
|
/seqcnt.txt
|
||||||
/.tmtc-history.txt
|
/.tmtc-history.txt
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Example client for the sat-rs example application"""
|
"""Example client for the sat-rs example application"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Any, Optional
|
|
||||||
from prompt_toolkit.history import History
|
|
||||||
from prompt_toolkit.history import FileHistory
|
|
||||||
|
|
||||||
from spacepackets.ccsds import PacketId, PacketType
|
|
||||||
import tmtccmd
|
import tmtccmd
|
||||||
from spacepackets.ecss import PusTelemetry, PusVerificator
|
from spacepackets.ecss import PusVerificator
|
||||||
from spacepackets.ecss.pus_17_test import Service17Tm
|
|
||||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
|
|
||||||
from spacepackets.ccsds.time import CdsShortTimestamp
|
|
||||||
|
|
||||||
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
from tmtccmd import ProcedureParamsWrapper
|
||||||
from tmtccmd.core.base import BackendRequest
|
from tmtccmd.core.base import BackendRequest
|
||||||
from tmtccmd.pus import VerificationWrapper
|
from tmtccmd.pus import VerificationWrapper
|
||||||
from tmtccmd.tmtc import CcsdsTmHandler, GenericApidHandlerBase
|
from tmtccmd.tmtc import CcsdsTmHandler
|
||||||
from tmtccmd.com import ComInterface
|
|
||||||
from tmtccmd.config import (
|
from tmtccmd.config import (
|
||||||
CmdTreeNode,
|
|
||||||
default_json_path,
|
default_json_path,
|
||||||
SetupParams,
|
SetupParams,
|
||||||
HookBase,
|
|
||||||
params_to_procedure_conversion,
|
params_to_procedure_conversion,
|
||||||
)
|
)
|
||||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
||||||
@@ -33,193 +24,20 @@ from tmtccmd.logging.pus import (
|
|||||||
RawTmtcTimedLogWrapper,
|
RawTmtcTimedLogWrapper,
|
||||||
TimedLogWhen,
|
TimedLogWhen,
|
||||||
)
|
)
|
||||||
from tmtccmd.tmtc import (
|
from spacepackets.seqcount import PusFileSeqCountProvider
|
||||||
TcQueueEntryType,
|
|
||||||
ProcedureWrapper,
|
|
||||||
TcProcedureType,
|
|
||||||
FeedWrapper,
|
|
||||||
SendCbParams,
|
|
||||||
DefaultPusQueueHelper,
|
|
||||||
QueueWrapper,
|
|
||||||
)
|
|
||||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
|
|
||||||
from tmtccmd.util.obj_id import ObjectIdDictT
|
|
||||||
|
|
||||||
|
|
||||||
import pus_tc
|
from pytmtc.config import SatrsConfigHook
|
||||||
from common import Apid, EventU32
|
from pytmtc.pus_tc import TcHandler
|
||||||
|
from pytmtc.pus_tm import PusHandler
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
_LOGGER = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
class SatRsConfigHook(HookBase):
|
|
||||||
def __init__(self, json_cfg_path: str):
|
|
||||||
super().__init__(json_cfg_path=json_cfg_path)
|
|
||||||
|
|
||||||
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
|
||||||
from tmtccmd.config.com import (
|
|
||||||
create_com_interface_default,
|
|
||||||
create_com_interface_cfg_default,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert self.cfg_path is not None
|
|
||||||
packet_id_list = []
|
|
||||||
for apid in Apid:
|
|
||||||
packet_id_list.append(PacketId(PacketType.TM, True, apid))
|
|
||||||
cfg = create_com_interface_cfg_default(
|
|
||||||
com_if_key=com_if_key,
|
|
||||||
json_cfg_path=self.cfg_path,
|
|
||||||
space_packet_ids=packet_id_list,
|
|
||||||
)
|
|
||||||
assert cfg is not None
|
|
||||||
return create_com_interface_default(cfg)
|
|
||||||
|
|
||||||
def get_command_definitions(self) -> CmdTreeNode:
|
|
||||||
"""This function should return the root node of the command definition tree."""
|
|
||||||
return pus_tc.create_cmd_definition_tree()
|
|
||||||
|
|
||||||
def get_cmd_history(self) -> Optional[History]:
|
|
||||||
"""Optionlly return a history class for the past command paths which will be used
|
|
||||||
when prompting a command path from the user in CLI mode."""
|
|
||||||
return FileHistory(".tmtc-history.txt")
|
|
||||||
|
|
||||||
def get_object_ids(self) -> ObjectIdDictT:
|
|
||||||
from tmtccmd.config.objects import get_core_object_ids
|
|
||||||
|
|
||||||
return get_core_object_ids()
|
|
||||||
|
|
||||||
|
|
||||||
class PusHandler(GenericApidHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
file_logger: logging.Logger,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
raw_logger: RawTmtcTimedLogWrapper,
|
|
||||||
):
|
|
||||||
super().__init__(None)
|
|
||||||
self.file_logger = file_logger
|
|
||||||
self.raw_logger = raw_logger
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
|
|
||||||
def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
|
|
||||||
try:
|
|
||||||
pus_tm = PusTelemetry.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
_LOGGER.warning("Could not generate PUS TM object from raw data")
|
|
||||||
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
|
||||||
raise e
|
|
||||||
service = pus_tm.service
|
|
||||||
if service == 1:
|
|
||||||
tm_packet = Service1Tm.unpack(
|
|
||||||
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
|
|
||||||
)
|
|
||||||
res = self.verif_wrapper.add_tm(tm_packet)
|
|
||||||
if res is None:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
|
||||||
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
|
||||||
)
|
|
||||||
_LOGGER.warning(
|
|
||||||
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.verif_wrapper.log_to_console(tm_packet, res)
|
|
||||||
self.verif_wrapper.log_to_file(tm_packet, res)
|
|
||||||
elif service == 3:
|
|
||||||
_LOGGER.info("No handling for HK packets implemented")
|
|
||||||
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
|
|
||||||
pus_tm = PusTelemetry.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
if pus_tm.subservice == 25:
|
|
||||||
if len(pus_tm.source_data) < 8:
|
|
||||||
raise ValueError("No addressable ID in HK packet")
|
|
||||||
json_str = pus_tm.source_data[8:]
|
|
||||||
_LOGGER.info(json_str)
|
|
||||||
elif service == 5:
|
|
||||||
tm_packet = PusTelemetry.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
src_data = tm_packet.source_data
|
|
||||||
event_u32 = EventU32.unpack(src_data)
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received event packet. Source APID: {Apid(tm_packet.apid)!r}, Event: {event_u32}"
|
|
||||||
)
|
|
||||||
if event_u32.group_id == 0 and event_u32.unique_id == 0:
|
|
||||||
_LOGGER.info("Received test event")
|
|
||||||
elif service == 17:
|
|
||||||
tm_packet = Service17Tm.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
if tm_packet.subservice == 2:
|
|
||||||
self.file_logger.info("Received Ping Reply TM[17,2]")
|
|
||||||
_LOGGER.info("Received Ping Reply TM[17,2]")
|
|
||||||
else:
|
|
||||||
self.file_logger.info(
|
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
|
||||||
)
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"The service {service} is not implemented in Telemetry Factory"
|
|
||||||
)
|
|
||||||
tm_packet = PusTelemetry.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
self.raw_logger.log_tm(pus_tm)
|
|
||||||
|
|
||||||
|
|
||||||
class TcHandler(TcHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
seq_count_provider: FileSeqCountProvider,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
):
|
|
||||||
super(TcHandler, self).__init__()
|
|
||||||
self.seq_count_provider = seq_count_provider
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
self.queue_helper = DefaultPusQueueHelper(
|
|
||||||
queue_wrapper=QueueWrapper.empty(),
|
|
||||||
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
|
|
||||||
seq_cnt_provider=seq_count_provider,
|
|
||||||
pus_verificator=self.verif_wrapper.pus_verificator,
|
|
||||||
default_pus_apid=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_cb(self, send_params: SendCbParams):
|
|
||||||
entry_helper = send_params.entry
|
|
||||||
if entry_helper.is_tc:
|
|
||||||
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
|
|
||||||
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
|
|
||||||
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
|
||||||
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
|
||||||
send_params.com_if.send(raw_tc)
|
|
||||||
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
|
||||||
log_entry = entry_helper.to_log_entry()
|
|
||||||
_LOGGER.info(log_entry.log_str)
|
|
||||||
|
|
||||||
def queue_finished_cb(self, info: ProcedureWrapper):
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
|
|
||||||
|
|
||||||
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
|
|
||||||
q = self.queue_helper
|
|
||||||
q.queue_wrapper = wrapper.queue_wrapper
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
assert def_proc.cmd_path is not None
|
|
||||||
pus_tc.pack_pus_telecommands(q, def_proc.cmd_path)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
add_colorlog_console_logger(_LOGGER)
|
add_colorlog_console_logger(_LOGGER)
|
||||||
tmtccmd.init_printout(False)
|
tmtccmd.init_printout(False)
|
||||||
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
|
hook_obj = SatrsConfigHook(json_cfg_path=default_json_path())
|
||||||
parser_wrapper = PreArgsParsingWrapper()
|
parser_wrapper = PreArgsParsingWrapper()
|
||||||
parser_wrapper.create_default_parent_parser()
|
parser_wrapper.create_default_parent_parser()
|
||||||
parser_wrapper.create_default_parser()
|
parser_wrapper.create_default_parser()
|
||||||
|
|||||||
27
satrs-example/pytmtc/pyproject.toml
Normal file
27
satrs-example/pytmtc/pyproject.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pytmtc"
|
||||||
|
description = "Python TMTC client for OPS-SAT"
|
||||||
|
readme = "README.md"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
authors = [
|
||||||
|
{name = "Robin Mueller", email = "robin.mueller.m@gmail.com"},
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"tmtccmd~=8.1",
|
||||||
|
"pydantic~=2.7"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages]
|
||||||
|
find = {}
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
extend-exclude = ["archive"]
|
||||||
|
[tool.ruff.lint]
|
||||||
|
ignore = ["E501"]
|
||||||
|
[tool.ruff.lint.extend-per-file-ignores]
|
||||||
|
"__init__.py" = ["F401"]
|
||||||
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
|
||||||
45
satrs-example/pytmtc/pytmtc/acs/mgms.py
Normal file
45
satrs-example/pytmtc/pytmtc/acs/mgms.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
import enum
|
||||||
|
from typing import List
|
||||||
|
from spacepackets.ecss import PusTm
|
||||||
|
from tmtccmd.tmtc import DefaultPusQueueHelper
|
||||||
|
|
||||||
|
from pytmtc.common import AcsId, Apid
|
||||||
|
from pytmtc.hk_common import create_request_one_shot_hk_cmd
|
||||||
|
from pytmtc.mode import handle_set_mode_cmd
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SetId(enum.IntEnum):
|
||||||
|
SENSOR_SET = 0
|
||||||
|
|
||||||
|
|
||||||
|
def create_mgm_cmds(q: DefaultPusQueueHelper, cmd_path: List[str]):
|
||||||
|
assert len(cmd_path) >= 3
|
||||||
|
if cmd_path[2] == "hk":
|
||||||
|
if cmd_path[3] == "one_shot_hk":
|
||||||
|
q.add_log_cmd("Sending HK one shot request")
|
||||||
|
q.add_pus_tc(
|
||||||
|
create_request_one_shot_hk_cmd(Apid.ACS, AcsId.MGM_0, SetId.SENSOR_SET)
|
||||||
|
)
|
||||||
|
|
||||||
|
if cmd_path[2] == "mode":
|
||||||
|
if cmd_path[3] == "set_mode":
|
||||||
|
handle_set_mode_cmd(q, "MGM 0", cmd_path[4], Apid.ACS, AcsId.MGM_0)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_mgm_hk_report(pus_tm: PusTm, set_id: int, hk_data: bytes):
|
||||||
|
if set_id == SetId.SENSOR_SET:
|
||||||
|
if len(hk_data) != 13:
|
||||||
|
raise ValueError(f"invalid HK data length, expected 13, got {len(hk_data)}")
|
||||||
|
data_valid = hk_data[0]
|
||||||
|
mgm_x = struct.unpack("!f", hk_data[1:5])[0]
|
||||||
|
mgm_y = struct.unpack("!f", hk_data[5:9])[0]
|
||||||
|
mgm_z = struct.unpack("!f", hk_data[9:13])[0]
|
||||||
|
_LOGGER.info(
|
||||||
|
f"received MGM HK set in uT: Valid {data_valid} X {mgm_x} Y {mgm_y} Z {mgm_z}"
|
||||||
|
)
|
||||||
|
pass
|
||||||
@@ -39,7 +39,10 @@ class EventU32:
|
|||||||
|
|
||||||
|
|
||||||
class AcsId(enum.IntEnum):
|
class AcsId(enum.IntEnum):
|
||||||
MGM_0 = 0
|
SUBSYSTEM = 1
|
||||||
|
MGM_ASSEMBLY = 2
|
||||||
|
MGM_0 = 3
|
||||||
|
MGM_1 = 4
|
||||||
|
|
||||||
|
|
||||||
class AcsHkIds(enum.IntEnum):
|
class AcsHkIds(enum.IntEnum):
|
||||||
47
satrs-example/pytmtc/pytmtc/config.py
Normal file
47
satrs-example/pytmtc/pytmtc/config.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from prompt_toolkit.history import FileHistory, History
|
||||||
|
from spacepackets.ccsds import PacketId, PacketType
|
||||||
|
from tmtccmd import HookBase
|
||||||
|
from tmtccmd.com import ComInterface
|
||||||
|
from tmtccmd.config import CmdTreeNode
|
||||||
|
from tmtccmd.util.obj_id import ObjectIdDictT
|
||||||
|
|
||||||
|
from pytmtc.common import Apid
|
||||||
|
from pytmtc.pus_tc import create_cmd_definition_tree
|
||||||
|
|
||||||
|
|
||||||
|
class SatrsConfigHook(HookBase):
|
||||||
|
def __init__(self, json_cfg_path: str):
|
||||||
|
super().__init__(json_cfg_path)
|
||||||
|
|
||||||
|
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
||||||
|
from tmtccmd.config.com import (
|
||||||
|
create_com_interface_default,
|
||||||
|
create_com_interface_cfg_default,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.cfg_path is not None
|
||||||
|
packet_id_list = []
|
||||||
|
for apid in Apid:
|
||||||
|
packet_id_list.append(PacketId(PacketType.TM, True, apid))
|
||||||
|
cfg = create_com_interface_cfg_default(
|
||||||
|
com_if_key=com_if_key,
|
||||||
|
json_cfg_path=self.cfg_path,
|
||||||
|
space_packet_ids=packet_id_list,
|
||||||
|
)
|
||||||
|
assert cfg is not None
|
||||||
|
return create_com_interface_default(cfg)
|
||||||
|
|
||||||
|
def get_command_definitions(self) -> CmdTreeNode:
|
||||||
|
"""This function should return the root node of the command definition tree."""
|
||||||
|
return create_cmd_definition_tree()
|
||||||
|
|
||||||
|
def get_cmd_history(self) -> Optional[History]:
|
||||||
|
"""Optionlly return a history class for the past command paths which will be used
|
||||||
|
when prompting a command path from the user in CLI mode."""
|
||||||
|
return FileHistory(".tmtc-history.txt")
|
||||||
|
|
||||||
|
def get_object_ids(self) -> ObjectIdDictT:
|
||||||
|
from tmtccmd.config.objects import get_core_object_ids
|
||||||
|
|
||||||
|
return get_core_object_ids()
|
||||||
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
42
satrs-example/pytmtc/pytmtc/hk.py
Normal file
42
satrs-example/pytmtc/pytmtc/hk.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
from spacepackets.ecss.pus_3_hk import Subservice
|
||||||
|
from spacepackets.ecss import PusTm
|
||||||
|
|
||||||
|
from pytmtc.common import AcsId, Apid
|
||||||
|
from pytmtc.acs.mgms import handle_mgm_hk_report
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_hk_packet(pus_tm: PusTm):
|
||||||
|
if len(pus_tm.source_data) < 4:
|
||||||
|
raise ValueError("no unique ID in HK packet")
|
||||||
|
unique_id = struct.unpack("!I", pus_tm.source_data[:4])[0]
|
||||||
|
if (
|
||||||
|
pus_tm.subservice == Subservice.TM_HK_REPORT
|
||||||
|
or pus_tm.subservice == Subservice.TM_DIAGNOSTICS_REPORT
|
||||||
|
):
|
||||||
|
if len(pus_tm.source_data) < 8:
|
||||||
|
raise ValueError("no set ID in HK packet")
|
||||||
|
set_id = struct.unpack("!I", pus_tm.source_data[4:8])[0]
|
||||||
|
handle_hk_report(pus_tm, unique_id, set_id)
|
||||||
|
_LOGGER.warning(
|
||||||
|
f"handling for HK packet with subservice {pus_tm.subservice} not implemented yet"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_hk_report(pus_tm: PusTm, unique_id: int, set_id: int):
|
||||||
|
hk_data = pus_tm.source_data[8:]
|
||||||
|
if pus_tm.apid == Apid.ACS:
|
||||||
|
if unique_id == AcsId.MGM_0:
|
||||||
|
handle_mgm_hk_report(pus_tm, set_id, hk_data)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
f"handling for HK report with unique ID {unique_id} not implemented yet"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
f"handling for HK report with apid {pus_tm.apid} not implemented yet"
|
||||||
|
)
|
||||||
16
satrs-example/pytmtc/pytmtc/hk_common.py
Normal file
16
satrs-example/pytmtc/pytmtc/hk_common.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
from spacepackets.ecss import PusService, PusTc
|
||||||
|
from spacepackets.ecss.pus_3_hk import Subservice
|
||||||
|
|
||||||
|
|
||||||
|
def create_request_one_shot_hk_cmd(apid: int, unique_id: int, set_id: int) -> PusTc:
|
||||||
|
app_data = bytearray()
|
||||||
|
app_data.extend(struct.pack("!I", unique_id))
|
||||||
|
app_data.extend(struct.pack("!I", set_id))
|
||||||
|
return PusTc(
|
||||||
|
service=PusService.S3_HOUSEKEEPING,
|
||||||
|
subservice=Subservice.TC_GENERATE_ONE_PARAMETER_REPORT,
|
||||||
|
apid=apid,
|
||||||
|
app_data=app_data,
|
||||||
|
)
|
||||||
31
satrs-example/pytmtc/pytmtc/mode.py
Normal file
31
satrs-example/pytmtc/pytmtc/mode.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import struct
|
||||||
|
from spacepackets.ecss import PusTc
|
||||||
|
from tmtccmd.pus.s200_fsfw_mode import Mode, Subservice
|
||||||
|
from tmtccmd.tmtc import DefaultPusQueueHelper
|
||||||
|
|
||||||
|
|
||||||
|
def create_set_mode_cmd(apid: int, unique_id: int, mode: int, submode: int) -> PusTc:
|
||||||
|
app_data = bytearray()
|
||||||
|
app_data.extend(struct.pack("!I", unique_id))
|
||||||
|
app_data.extend(struct.pack("!I", mode))
|
||||||
|
app_data.extend(struct.pack("!H", submode))
|
||||||
|
return PusTc(
|
||||||
|
service=200,
|
||||||
|
subservice=Subservice.TC_MODE_COMMAND,
|
||||||
|
apid=apid,
|
||||||
|
app_data=app_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_set_mode_cmd(
|
||||||
|
q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
|
||||||
|
):
|
||||||
|
if mode_str == "off":
|
||||||
|
q.add_log_cmd(f"Sending Mode OFF to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
|
||||||
|
elif mode_str == "on":
|
||||||
|
q.add_log_cmd(f"Sending Mode ON to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
|
||||||
|
elif mode_str == "normal":
|
||||||
|
q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))
|
||||||
@@ -1,37 +1,73 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import struct
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from spacepackets.ccsds import CdsShortTimestamp
|
from spacepackets.ccsds import CdsShortTimestamp
|
||||||
from spacepackets.ecss import PusTelecommand
|
from spacepackets.ecss import PusTelecommand
|
||||||
|
from spacepackets.seqcount import FileSeqCountProvider
|
||||||
|
from tmtccmd import ProcedureWrapper, TcHandlerBase
|
||||||
from tmtccmd.config import CmdTreeNode
|
from tmtccmd.config import CmdTreeNode
|
||||||
from tmtccmd.pus.tc.s200_fsfw_mode import Mode
|
from tmtccmd.pus import VerificationWrapper
|
||||||
from tmtccmd.tmtc import DefaultPusQueueHelper
|
from tmtccmd.tmtc import (
|
||||||
|
DefaultPusQueueHelper,
|
||||||
|
FeedWrapper,
|
||||||
|
QueueWrapper,
|
||||||
|
SendCbParams,
|
||||||
|
TcProcedureType,
|
||||||
|
TcQueueEntryType,
|
||||||
|
)
|
||||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
|
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
|
||||||
from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice
|
|
||||||
|
|
||||||
from common import AcsId, Apid
|
from pytmtc.acs import create_acs_node
|
||||||
|
from pytmtc.common import Apid
|
||||||
|
from pytmtc.acs.mgms import create_mgm_cmds
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_set_mode_cmd(
|
class TcHandler(TcHandlerBase):
|
||||||
apid: int, unique_id: int, mode: int, submode: int
|
def __init__(
|
||||||
) -> PusTelecommand:
|
self,
|
||||||
app_data = bytearray()
|
seq_count_provider: FileSeqCountProvider,
|
||||||
app_data.extend(struct.pack("!I", unique_id))
|
verif_wrapper: VerificationWrapper,
|
||||||
app_data.extend(struct.pack("!I", mode))
|
):
|
||||||
app_data.extend(struct.pack("!H", submode))
|
super(TcHandler, self).__init__()
|
||||||
return PusTelecommand(
|
self.seq_count_provider = seq_count_provider
|
||||||
service=200,
|
self.verif_wrapper = verif_wrapper
|
||||||
subservice=ModeSubservice.TC_MODE_COMMAND,
|
self.queue_helper = DefaultPusQueueHelper(
|
||||||
apid=apid,
|
queue_wrapper=QueueWrapper.empty(),
|
||||||
app_data=app_data,
|
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
|
||||||
)
|
seq_cnt_provider=seq_count_provider,
|
||||||
|
pus_verificator=self.verif_wrapper.pus_verificator,
|
||||||
|
default_pus_apid=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_cb(self, send_params: SendCbParams):
|
||||||
|
entry_helper = send_params.entry
|
||||||
|
if entry_helper.is_tc:
|
||||||
|
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
|
||||||
|
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
|
||||||
|
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
||||||
|
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
||||||
|
send_params.com_if.send(raw_tc)
|
||||||
|
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
||||||
|
log_entry = entry_helper.to_log_entry()
|
||||||
|
_LOGGER.info(log_entry.log_str)
|
||||||
|
|
||||||
|
def queue_finished_cb(self, info: ProcedureWrapper):
|
||||||
|
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
||||||
|
def_proc = info.to_tree_commanding_procedure()
|
||||||
|
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
|
||||||
|
|
||||||
|
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
|
||||||
|
q = self.queue_helper
|
||||||
|
q.queue_wrapper = wrapper.queue_wrapper
|
||||||
|
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
||||||
|
def_proc = info.to_tree_commanding_procedure()
|
||||||
|
assert def_proc.cmd_path is not None
|
||||||
|
pack_pus_telecommands(q, def_proc.cmd_path)
|
||||||
|
|
||||||
|
|
||||||
def create_cmd_definition_tree() -> CmdTreeNode:
|
def create_cmd_definition_tree() -> CmdTreeNode:
|
||||||
|
|
||||||
root_node = CmdTreeNode.root_node()
|
root_node = CmdTreeNode.root_node()
|
||||||
|
|
||||||
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
|
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
|
||||||
@@ -65,15 +101,7 @@ def create_cmd_definition_tree() -> CmdTreeNode:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
root_node.add_child(scheduler_node)
|
root_node.add_child(scheduler_node)
|
||||||
|
root_node.add_child(create_acs_node(mode_node, hk_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)
|
|
||||||
|
|
||||||
return root_node
|
return root_node
|
||||||
|
|
||||||
|
|
||||||
@@ -112,32 +140,4 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
|
|||||||
if cmd_path_list[0] == "acs":
|
if cmd_path_list[0] == "acs":
|
||||||
assert len(cmd_path_list) >= 2
|
assert len(cmd_path_list) >= 2
|
||||||
if cmd_path_list[1] == "mgms":
|
if cmd_path_list[1] == "mgms":
|
||||||
assert len(cmd_path_list) >= 3
|
create_mgm_cmds(q, cmd_path_list)
|
||||||
if cmd_path_list[2] == "hk":
|
|
||||||
if cmd_path_list[3] == "one_shot_hk":
|
|
||||||
q.add_log_cmd("Sending HK one shot request")
|
|
||||||
# TODO: Fix
|
|
||||||
# q.add_pus_tc(
|
|
||||||
# create_request_one_hk_command(
|
|
||||||
# make_addressable_id(Apid.ACS, AcsId.MGM_SET)
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
if cmd_path_list[2] == "mode":
|
|
||||||
if cmd_path_list[3] == "set_mode":
|
|
||||||
handle_set_mode_cmd(
|
|
||||||
q, "MGM 0", cmd_path_list[4], Apid.ACS, AcsId.MGM_0
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_set_mode_cmd(
|
|
||||||
q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
|
|
||||||
):
|
|
||||||
if mode_str == "off":
|
|
||||||
q.add_log_cmd(f"Sending Mode OFF to {target_str}")
|
|
||||||
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
|
|
||||||
elif mode_str == "on":
|
|
||||||
q.add_log_cmd(f"Sending Mode ON to {target_str}")
|
|
||||||
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
|
|
||||||
elif mode_str == "normal":
|
|
||||||
q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
|
|
||||||
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))
|
|
||||||
93
satrs-example/pytmtc/pytmtc/pus_tm.py
Normal file
93
satrs-example/pytmtc/pytmtc/pus_tm.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
from spacepackets.ccsds.time import CdsShortTimestamp
|
||||||
|
from spacepackets.ecss import PusTm
|
||||||
|
from spacepackets.ecss.pus_17_test import Service17Tm
|
||||||
|
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
|
||||||
|
from tmtccmd.logging.pus import RawTmtcTimedLogWrapper
|
||||||
|
from tmtccmd.pus import VerificationWrapper
|
||||||
|
from tmtccmd.tmtc import GenericApidHandlerBase
|
||||||
|
|
||||||
|
from pytmtc.common import Apid, EventU32
|
||||||
|
from pytmtc.hk import handle_hk_packet
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PusHandler(GenericApidHandlerBase):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_logger: logging.Logger,
|
||||||
|
verif_wrapper: VerificationWrapper,
|
||||||
|
raw_logger: RawTmtcTimedLogWrapper,
|
||||||
|
):
|
||||||
|
super().__init__(None)
|
||||||
|
self.file_logger = file_logger
|
||||||
|
self.raw_logger = raw_logger
|
||||||
|
self.verif_wrapper = verif_wrapper
|
||||||
|
|
||||||
|
def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
|
||||||
|
try:
|
||||||
|
pus_tm = PusTm.unpack(
|
||||||
|
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
_LOGGER.warning("Could not generate PUS TM object from raw data")
|
||||||
|
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
||||||
|
raise e
|
||||||
|
service = pus_tm.service
|
||||||
|
if service == 1:
|
||||||
|
tm_packet = Service1Tm.unpack(
|
||||||
|
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
|
||||||
|
)
|
||||||
|
res = self.verif_wrapper.add_tm(tm_packet)
|
||||||
|
if res is None:
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
||||||
|
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
||||||
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.verif_wrapper.log_to_console(tm_packet, res)
|
||||||
|
self.verif_wrapper.log_to_file(tm_packet, res)
|
||||||
|
elif service == 3:
|
||||||
|
pus_tm = PusTm.unpack(
|
||||||
|
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
||||||
|
)
|
||||||
|
handle_hk_packet(pus_tm)
|
||||||
|
elif service == 5:
|
||||||
|
tm_packet = PusTm.unpack(
|
||||||
|
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
||||||
|
)
|
||||||
|
src_data = tm_packet.source_data
|
||||||
|
event_u32 = EventU32.unpack(src_data)
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Received event packet. Source APID: {Apid(tm_packet.apid)!r}, Event: {event_u32}"
|
||||||
|
)
|
||||||
|
if event_u32.group_id == 0 and event_u32.unique_id == 0:
|
||||||
|
_LOGGER.info("Received test event")
|
||||||
|
elif service == 17:
|
||||||
|
tm_packet = Service17Tm.unpack(
|
||||||
|
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
||||||
|
)
|
||||||
|
if tm_packet.subservice == 2:
|
||||||
|
self.file_logger.info("Received Ping Reply TM[17,2]")
|
||||||
|
_LOGGER.info("Received Ping Reply TM[17,2]")
|
||||||
|
else:
|
||||||
|
self.file_logger.info(
|
||||||
|
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
||||||
|
)
|
||||||
|
_LOGGER.info(
|
||||||
|
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.info(
|
||||||
|
f"The service {service} is not implemented in Telemetry Factory"
|
||||||
|
)
|
||||||
|
tm_packet = PusTm.unpack(
|
||||||
|
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
||||||
|
)
|
||||||
|
self.raw_logger.log_tm(pus_tm)
|
||||||
@@ -1,2 +1 @@
|
|||||||
tmtccmd == 8.0.0rc2
|
.
|
||||||
# -e git+https://github.com/robamu-org/tmtccmd@97e5e51101a08b21472b3ddecc2063359f7e307a#egg=tmtccmd
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
from tmtccmd.config import OpCodeEntry, TmtcDefinitionWrapper, CoreServiceList
|
|
||||||
from tmtccmd.config.globals import get_default_tmtc_defs
|
|
||||||
|
|
||||||
from common import HkOpCodes
|
|
||||||
|
|
||||||
|
|
||||||
def tc_definitions() -> TmtcDefinitionWrapper:
|
|
||||||
defs = get_default_tmtc_defs()
|
|
||||||
srv_5 = OpCodeEntry()
|
|
||||||
srv_5.add("0", "Event Test")
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_5.value,
|
|
||||||
info="PUS Service 5 Event",
|
|
||||||
op_code_entry=srv_5,
|
|
||||||
)
|
|
||||||
srv_17 = OpCodeEntry()
|
|
||||||
srv_17.add("ping", "Ping Test")
|
|
||||||
srv_17.add("trigger_event", "Trigger Event")
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_17_ALT,
|
|
||||||
info="PUS Service 17 Test",
|
|
||||||
op_code_entry=srv_17,
|
|
||||||
)
|
|
||||||
srv_3 = OpCodeEntry()
|
|
||||||
srv_3.add(HkOpCodes.GENERATE_ONE_SHOT, "Generate AOCS one shot HK")
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_3,
|
|
||||||
info="PUS Service 3 Housekeeping",
|
|
||||||
op_code_entry=srv_3,
|
|
||||||
)
|
|
||||||
srv_11 = OpCodeEntry()
|
|
||||||
srv_11.add("0", "Scheduled TC Test")
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_11,
|
|
||||||
info="PUS Service 11 TC Scheduling",
|
|
||||||
op_code_entry=srv_11,
|
|
||||||
)
|
|
||||||
return defs
|
|
||||||
0
satrs-example/pytmtc/tests/__init__.py
Normal file
0
satrs-example/pytmtc/tests/__init__.py
Normal file
48
satrs-example/pytmtc/tests/test_tc_mods.py
Normal file
48
satrs-example/pytmtc/tests/test_tc_mods.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from spacepackets.ccsds import CdsShortTimestamp
|
||||||
|
from tmtccmd.tmtc import DefaultPusQueueHelper, QueueEntryHelper
|
||||||
|
from tmtccmd.tmtc.queue import QueueWrapper
|
||||||
|
|
||||||
|
from pytmtc.config import SatrsConfigHook
|
||||||
|
from pytmtc.pus_tc import pack_pus_telecommands
|
||||||
|
|
||||||
|
|
||||||
|
class TestTcModules(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.hook = SatrsConfigHook(json_cfg_path="tmtc_conf.json")
|
||||||
|
self.queue_helper = DefaultPusQueueHelper(
|
||||||
|
queue_wrapper=QueueWrapper.empty(),
|
||||||
|
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
|
||||||
|
seq_cnt_provider=None,
|
||||||
|
pus_verificator=None,
|
||||||
|
default_pus_apid=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cmd_tree_creation_works_without_errors(self):
|
||||||
|
cmd_defs = self.hook.get_command_definitions()
|
||||||
|
self.assertIsNotNone(cmd_defs)
|
||||||
|
|
||||||
|
def test_ping_cmd_generation(self):
|
||||||
|
pack_pus_telecommands(self.queue_helper, "/test/ping")
|
||||||
|
queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
|
||||||
|
entry_helper = QueueEntryHelper(queue_entry)
|
||||||
|
log_queue = entry_helper.to_log_entry()
|
||||||
|
self.assertEqual(log_queue.log_str, "Sending PUS ping telecommand")
|
||||||
|
queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
|
||||||
|
entry_helper.entry = queue_entry
|
||||||
|
pus_tc_entry = entry_helper.to_pus_tc_entry()
|
||||||
|
self.assertEqual(pus_tc_entry.pus_tc.service, 17)
|
||||||
|
self.assertEqual(pus_tc_entry.pus_tc.subservice, 1)
|
||||||
|
|
||||||
|
def test_event_trigger_generation(self):
|
||||||
|
pack_pus_telecommands(self.queue_helper, "/test/trigger_event")
|
||||||
|
queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
|
||||||
|
entry_helper = QueueEntryHelper(queue_entry)
|
||||||
|
log_queue = entry_helper.to_log_entry()
|
||||||
|
self.assertEqual(log_queue.log_str, "Triggering test event")
|
||||||
|
queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
|
||||||
|
entry_helper.entry = queue_entry
|
||||||
|
pus_tc_entry = entry_helper.to_pus_tc_entry()
|
||||||
|
self.assertEqual(pus_tc_entry.pus_tc.service, 17)
|
||||||
|
self.assertEqual(pus_tc_entry.pus_tc.subservice, 128)
|
||||||
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,52 +1,126 @@
|
|||||||
|
/*
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs::hk::{HkRequest, HkRequestVariant};
|
use satrs::hk::{HkRequest, HkRequestVariant};
|
||||||
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
|
use satrs::mode_tree::{ModeChild, ModeNode};
|
||||||
use satrs::spacepackets::ecss::hk;
|
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
|
||||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
use satrs_example::ids::generic_pus::PUS_MODE;
|
||||||
use satrs::spacepackets::SpHeader;
|
use satrs_example::{DeviceMode, TimestampHelper};
|
||||||
use satrs_example::{DeviceMode, TimeStampHelper};
|
use satrs_minisim::acs::lis3mdl::{
|
||||||
|
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
|
||||||
|
};
|
||||||
|
use satrs_minisim::acs::MgmRequestLis3Mdl;
|
||||||
|
use satrs_minisim::eps::PcduSwitch;
|
||||||
|
use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest};
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::sync::mpsc::{self};
|
use std::sync::mpsc::{self};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use satrs::mode::{
|
use satrs::mode::{
|
||||||
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
|
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||||
|
ModeRequestHandlerMpscBounded,
|
||||||
};
|
};
|
||||||
use satrs::pus::{EcssTmSender, PusTmVariant};
|
use satrs::pus::{EcssTmSender, PusTmVariant};
|
||||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||||
use satrs_example::config::components::PUS_MODE_SERVICE;
|
use satrs_example::config::components::NO_SENDER;
|
||||||
|
|
||||||
|
use crate::hk::PusHkHelper;
|
||||||
use crate::pus::hk::{HkReply, HkReplyVariant};
|
use crate::pus::hk::{HkReply, HkReplyVariant};
|
||||||
use crate::requests::CompositeRequest;
|
use crate::requests::CompositeRequest;
|
||||||
|
use crate::spi::SpiInterface;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0;
|
pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14;
|
||||||
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
|
|
||||||
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
|
|
||||||
|
|
||||||
pub trait SpiInterface {
|
// Register adresses to access various bytes from the raw reply.
|
||||||
type Error;
|
pub const X_LOWBYTE_IDX: usize = 9;
|
||||||
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
|
pub const Y_LOWBYTE_IDX: usize = 11;
|
||||||
|
pub const Z_LOWBYTE_IDX: usize = 13;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum SetId {
|
||||||
|
SensorData = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TransitionState {
|
||||||
|
#[default]
|
||||||
|
Idle,
|
||||||
|
PowerSwitching,
|
||||||
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SpiDummyInterface {
|
pub struct SpiDummyInterface {
|
||||||
pub dummy_val_0: i16,
|
pub dummy_values: MgmLis3RawValues,
|
||||||
pub dummy_val_1: i16,
|
|
||||||
pub dummy_val_2: i16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpiInterface for SpiDummyInterface {
|
impl SpiInterface for SpiDummyInterface {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
rx[0..2].copy_from_slice(&self.dummy_val_0.to_be_bytes());
|
rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.x.to_le_bytes());
|
||||||
rx[2..4].copy_from_slice(&self.dummy_val_1.to_be_bytes());
|
rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.y.to_be_bytes());
|
||||||
rx[4..6].copy_from_slice(&self.dummy_val_2.to_be_bytes());
|
rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.z.to_be_bytes());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SpiSimInterface {
|
||||||
|
pub sim_request_tx: mpsc::Sender<SimRequest>,
|
||||||
|
pub sim_reply_rx: mpsc::Receiver<SimReply>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiInterface for SpiSimInterface {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
// Right now, we only support requesting sensor data and not configuration of the sensor.
|
||||||
|
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
let mgm_sensor_request = MgmRequestLis3Mdl::RequestSensorData;
|
||||||
|
if let Err(e) = self
|
||||||
|
.sim_request_tx
|
||||||
|
.send(SimRequest::new_with_epoch_time(mgm_sensor_request))
|
||||||
|
{
|
||||||
|
log::error!("failed to send MGM LIS3 request: {e}");
|
||||||
|
}
|
||||||
|
match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) {
|
||||||
|
Ok(sim_reply) => {
|
||||||
|
let sim_reply_lis3 = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||||
|
.expect("failed to parse LIS3 reply");
|
||||||
|
rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&sim_reply_lis3.raw.x.to_le_bytes());
|
||||||
|
rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&sim_reply_lis3.raw.y.to_le_bytes());
|
||||||
|
rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("MGM LIS3 SIM reply timeout: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SpiSimInterfaceWrapper {
|
||||||
|
Dummy(SpiDummyInterface),
|
||||||
|
Sim(SpiSimInterface),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiInterface for SpiSimInterfaceWrapper {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
SpiSimInterfaceWrapper::Dummy(dummy) => dummy.transfer(tx, rx),
|
||||||
|
SpiSimInterfaceWrapper::Sim(sim_if) => sim_if.transfer(tx, rx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
pub struct MgmData {
|
pub struct MgmData {
|
||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
@@ -55,63 +129,79 @@ pub struct MgmData {
|
|||||||
pub z: f32,
|
pub z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MpscModeLeafInterface {
|
#[derive(Default)]
|
||||||
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
|
pub struct BufWrapper {
|
||||||
pub reply_tx_to_pus: mpsc::Sender<GenericMessage<ModeReply>>,
|
tx_buf: [u8; 32],
|
||||||
pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>,
|
rx_buf: [u8; 32],
|
||||||
|
tm_buf: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModeHelpers {
|
||||||
|
current: ModeAndSubmode,
|
||||||
|
target: Option<ModeAndSubmode>,
|
||||||
|
requestor_info: Option<MessageMetadata>,
|
||||||
|
transition_state: TransitionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ModeHelpers {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
current: ModeAndSubmode::new(DeviceMode::Off as u32, 0),
|
||||||
|
target: Default::default(),
|
||||||
|
requestor_info: Default::default(),
|
||||||
|
transition_state: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender> {
|
pub struct MgmHandlerLis3Mdl<
|
||||||
|
ComInterface: SpiInterface,
|
||||||
|
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||||
|
> {
|
||||||
id: UniqueApidTargetId,
|
id: UniqueApidTargetId,
|
||||||
dev_str: &'static str,
|
dev_str: &'static str,
|
||||||
mode_interface: MpscModeLeafInterface,
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
|
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||||
tm_sender: TmSender,
|
switch_helper: SwitchHelper,
|
||||||
com_interface: ComInterface,
|
tm_sender: TmTcSender,
|
||||||
|
pub com_interface: ComInterface,
|
||||||
shared_mgm_set: Arc<Mutex<MgmData>>,
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
|
#[new(value = "PusHkHelper::new(id)")]
|
||||||
mode_and_submode: ModeAndSubmode,
|
hk_helper: PusHkHelper,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
tx_buf: [u8; 12],
|
mode_helpers: ModeHelpers,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
rx_buf: [u8; 12],
|
bufs: BufWrapper,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
tm_buf: [u8; 16],
|
stamp_helper: TimestampHelper,
|
||||||
#[new(default)]
|
|
||||||
stamp_helper: TimeStampHelper,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
|
impl<
|
||||||
|
ComInterface: SpiInterface,
|
||||||
|
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||||
|
> MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||||
|
{
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
self.stamp_helper.update_from_now();
|
self.stamp_helper.update_from_now();
|
||||||
// Handle requests.
|
// Handle requests.
|
||||||
self.handle_composite_requests();
|
self.handle_composite_requests();
|
||||||
self.handle_mode_requests();
|
self.handle_mode_requests();
|
||||||
|
if let Some(target_mode_submode) = self.mode_helpers.target {
|
||||||
|
self.handle_mode_transition(target_mode_submode);
|
||||||
|
}
|
||||||
if self.mode() == DeviceMode::Normal as u32 {
|
if self.mode() == DeviceMode::Normal as u32 {
|
||||||
log::trace!("polling LIS3MDL sensor {}", self.dev_str);
|
log::trace!("polling LIS3MDL sensor {}", self.dev_str);
|
||||||
// Communicate with the device.
|
self.poll_sensor();
|
||||||
let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
// Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
|
|
||||||
let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap());
|
|
||||||
let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap());
|
|
||||||
let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap());
|
|
||||||
// Simple scaling to retrieve the float value, assuming a sensor resolution of
|
|
||||||
let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
|
|
||||||
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
|
||||||
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
|
||||||
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
|
||||||
drop(mgm_guard);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_composite_requests(&mut self) {
|
pub fn handle_composite_requests(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
match self.composite_request_receiver.try_recv() {
|
match self.composite_request_rx.try_recv() {
|
||||||
Ok(ref msg) => match &msg.message {
|
Ok(ref msg) => match &msg.message {
|
||||||
CompositeRequest::Hk(hk_request) => {
|
CompositeRequest::Hk(hk_request) => {
|
||||||
self.handle_hk_request(&msg.requestor_info, hk_request)
|
self.handle_hk_request(&msg.requestor_info, hk_request)
|
||||||
@@ -139,34 +229,33 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
|
|||||||
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
||||||
match hk_request.variant {
|
match hk_request.variant {
|
||||||
HkRequestVariant::OneShot => {
|
HkRequestVariant::OneShot => {
|
||||||
self.hk_reply_sender
|
|
||||||
.send(GenericMessage::new(
|
|
||||||
*requestor_info,
|
|
||||||
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
|
||||||
))
|
|
||||||
.expect("failed to send HK reply");
|
|
||||||
let sec_header = PusTmSecondaryHeader::new(
|
|
||||||
3,
|
|
||||||
hk::Subservice::TmHkPacket as u8,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
self.stamp_helper.stamp(),
|
|
||||||
);
|
|
||||||
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
|
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
|
||||||
// Use binary serialization here. We want the data to be tightly packed.
|
if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
|
||||||
self.tm_buf[0] = mgm_snapshot.valid as u8;
|
self.stamp_helper.stamp(),
|
||||||
self.tm_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
|
SetId::SensorData as u32,
|
||||||
self.tm_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
|
&mut |hk_buf| {
|
||||||
self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
|
hk_buf[0] = mgm_snapshot.valid as u8;
|
||||||
let hk_tm = PusTmCreator::new(
|
hk_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
|
||||||
SpHeader::new_from_apid(self.id.apid),
|
hk_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
|
||||||
sec_header,
|
hk_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
|
||||||
&self.tm_buf[0..12],
|
Ok(13)
|
||||||
true,
|
},
|
||||||
);
|
&mut self.bufs.tm_buf,
|
||||||
self.tm_sender
|
) {
|
||||||
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
// TODO: If sending the TM fails, we should also send a failure reply.
|
||||||
.expect("failed to send HK TM");
|
self.tm_sender
|
||||||
|
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
||||||
|
.expect("failed to send HK TM");
|
||||||
|
self.hk_reply_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
*requestor_info,
|
||||||
|
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
||||||
|
))
|
||||||
|
.expect("failed to send HK reply");
|
||||||
|
} else {
|
||||||
|
// TODO: Send back failure reply. Need result code for this.
|
||||||
|
log::error!("TM buffer too small to generate HK data");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HkRequestVariant::EnablePeriodic => todo!(),
|
HkRequestVariant::EnablePeriodic => todo!(),
|
||||||
HkRequestVariant::DisablePeriodic => todo!(),
|
HkRequestVariant::DisablePeriodic => todo!(),
|
||||||
@@ -177,54 +266,137 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
|
|||||||
pub fn handle_mode_requests(&mut self) {
|
pub fn handle_mode_requests(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
// TODO: Only allow one set mode request per cycle?
|
// TODO: Only allow one set mode request per cycle?
|
||||||
match self.mode_interface.request_rx.try_recv() {
|
match self.mode_node.try_recv_mode_request() {
|
||||||
Ok(msg) => {
|
Ok(opt_msg) => {
|
||||||
let result = self.handle_mode_request(msg);
|
if let Some(msg) = opt_msg {
|
||||||
// TODO: Trigger event?
|
let result = self.handle_mode_request(msg);
|
||||||
if result.is_err() {
|
// TODO: Trigger event?
|
||||||
log::warn!(
|
if result.is_err() {
|
||||||
"{}: mode request failed with error {:?}",
|
log::warn!(
|
||||||
self.dev_str,
|
"{}: mode request failed with error {:?}",
|
||||||
result.err().unwrap()
|
self.dev_str,
|
||||||
);
|
result.err().unwrap()
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
if e != mpsc::TryRecvError::Empty {
|
|
||||||
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_sensor(&mut self) {
|
||||||
|
// Communicate with the device. This is actually how to read the data from the LIS3 device
|
||||||
|
// SPI interface.
|
||||||
|
self.com_interface
|
||||||
|
.transfer(
|
||||||
|
&self.bufs.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
|
||||||
|
&mut self.bufs.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
|
||||||
|
)
|
||||||
|
.expect("failed to transfer data");
|
||||||
|
let x_raw = i16::from_le_bytes(
|
||||||
|
self.bufs.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let y_raw = i16::from_le_bytes(
|
||||||
|
self.bufs.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let z_raw = i16::from_le_bytes(
|
||||||
|
self.bufs.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
// Simple scaling to retrieve the float value, assuming the best sensor resolution.
|
||||||
|
let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
|
||||||
|
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
mgm_guard.valid = true;
|
||||||
|
drop(mgm_guard);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_mode_transition(&mut self, target_mode_submode: ModeAndSubmode) {
|
||||||
|
if target_mode_submode.mode() == DeviceMode::On as u32
|
||||||
|
|| target_mode_submode.mode() == DeviceMode::Normal as u32
|
||||||
|
{
|
||||||
|
if self.mode_helpers.transition_state == TransitionState::Idle {
|
||||||
|
let result = self
|
||||||
|
.switch_helper
|
||||||
|
.send_switch_on_cmd(MessageMetadata::new(0, self.id.id()), PcduSwitch::Mgm);
|
||||||
|
if result.is_err() {
|
||||||
|
// Could not send switch command.. still continue with transition.
|
||||||
|
log::error!("failed to send switch on command");
|
||||||
|
}
|
||||||
|
self.mode_helpers.transition_state = TransitionState::PowerSwitching;
|
||||||
|
}
|
||||||
|
if self.mode_helpers.transition_state == TransitionState::PowerSwitching
|
||||||
|
&& self
|
||||||
|
.switch_helper
|
||||||
|
.is_switch_on(PcduSwitch::Mgm)
|
||||||
|
.expect("switch info error")
|
||||||
|
{
|
||||||
|
self.mode_helpers.transition_state = TransitionState::Done;
|
||||||
|
}
|
||||||
|
if self.mode_helpers.transition_state == TransitionState::Done {
|
||||||
|
self.mode_helpers.current = self.mode_helpers.target.unwrap();
|
||||||
|
self.handle_mode_reached(self.mode_helpers.requestor_info)
|
||||||
|
.expect("failed to handle mode reached");
|
||||||
|
self.mode_helpers.transition_state = TransitionState::Idle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeProvider
|
impl<
|
||||||
for MgmHandlerLis3Mdl<ComInterface, TmSender>
|
ComInterface: SpiInterface,
|
||||||
|
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||||
|
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||||
{
|
{
|
||||||
fn mode_and_submode(&self) -> ModeAndSubmode {
|
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||||
self.mode_and_submode
|
self.mode_helpers.current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
impl<
|
||||||
for MgmHandlerLis3Mdl<ComInterface, TmSender>
|
ComInterface: SpiInterface,
|
||||||
|
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
|
||||||
|
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
|
||||||
{
|
{
|
||||||
type Error = ModeError;
|
type Error = ModeError;
|
||||||
|
|
||||||
fn start_transition(
|
fn start_transition(
|
||||||
&mut self,
|
&mut self,
|
||||||
requestor: MessageMetadata,
|
requestor: MessageMetadata,
|
||||||
mode_and_submode: ModeAndSubmode,
|
mode_and_submode: ModeAndSubmode,
|
||||||
|
_forced: bool,
|
||||||
) -> Result<(), satrs::mode::ModeError> {
|
) -> Result<(), satrs::mode::ModeError> {
|
||||||
log::info!(
|
log::info!(
|
||||||
"{}: transitioning to mode {:?}",
|
"{}: transitioning to mode {:?}",
|
||||||
self.dev_str,
|
self.dev_str,
|
||||||
mode_and_submode
|
mode_and_submode
|
||||||
);
|
);
|
||||||
self.mode_and_submode = mode_and_submode;
|
self.mode_helpers.current = mode_and_submode;
|
||||||
self.handle_mode_reached(Some(requestor))?;
|
if mode_and_submode.mode() == DeviceMode::Off as u32 {
|
||||||
|
self.shared_mgm_set.lock().unwrap().valid = false;
|
||||||
|
self.handle_mode_reached(Some(requestor))?;
|
||||||
|
} else if mode_and_submode.mode() == DeviceMode::Normal as u32
|
||||||
|
|| mode_and_submode.mode() == DeviceMode::On as u32
|
||||||
|
{
|
||||||
|
// TODO: Write helper method for the struct? Might help for other handlers as well..
|
||||||
|
self.mode_helpers.transition_state = TransitionState::Idle;
|
||||||
|
self.mode_helpers.requestor_info = Some(requestor);
|
||||||
|
self.mode_helpers.target = Some(mode_and_submode);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +404,7 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
|||||||
log::info!(
|
log::info!(
|
||||||
"{} announcing mode: {:?}",
|
"{} announcing mode: {:?}",
|
||||||
self.dev_str,
|
self.dev_str,
|
||||||
self.mode_and_submode
|
self.mode_and_submode()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,11 +412,15 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
|||||||
&mut self,
|
&mut self,
|
||||||
requestor: Option<MessageMetadata>,
|
requestor: Option<MessageMetadata>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
|
self.mode_helpers.target = None;
|
||||||
self.announce_mode(requestor, false);
|
self.announce_mode(requestor, false);
|
||||||
if let Some(requestor) = requestor {
|
if let Some(requestor) = requestor {
|
||||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
if requestor.sender_id() == NO_SENDER {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if requestor.sender_id() != PUS_MODE.id() {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {}",
|
"can not send back mode reply to sender {:x}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -259,16 +435,15 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
|||||||
requestor: MessageMetadata,
|
requestor: MessageMetadata,
|
||||||
reply: ModeReply,
|
reply: ModeReply,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
if requestor.sender_id() != PUS_MODE.id() {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"can not send back mode reply to sender {}",
|
"can not send back mode reply to sender {}",
|
||||||
requestor.sender_id()
|
requestor.sender_id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.mode_interface
|
self.mode_node
|
||||||
.reply_tx_to_pus
|
.send_mode_reply(requestor, reply)
|
||||||
.send(GenericMessage::new(requestor, reply))
|
.map_err(ModeError::Send)?;
|
||||||
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,3 +455,267 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{mpsc, Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
use arbitrary_int::u21;
|
||||||
|
use satrs::{
|
||||||
|
mode::{ModeReply, ModeRequest},
|
||||||
|
mode_tree::ModeParent,
|
||||||
|
power::SwitchStateBinary,
|
||||||
|
request::{GenericMessage, UniqueApidTargetId},
|
||||||
|
tmtc::PacketAsVec,
|
||||||
|
ComponentId,
|
||||||
|
};
|
||||||
|
use satrs_example::ids::{acs::ASSEMBLY, Apid};
|
||||||
|
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
|
||||||
|
|
||||||
|
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TestSpiInterface {
|
||||||
|
pub call_count: u32,
|
||||||
|
pub next_mgm_data: MgmLis3RawValues,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiInterface for TestSpiInterface {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&self.next_mgm_data.x.to_le_bytes());
|
||||||
|
rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&self.next_mgm_data.y.to_le_bytes());
|
||||||
|
rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
|
||||||
|
.copy_from_slice(&self.next_mgm_data.z.to_le_bytes());
|
||||||
|
self.call_count += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct MgmTestbench {
|
||||||
|
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, 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::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 id = UniqueApidTargetId::new(Apid::Acs.raw_value(), u21::new(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::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_handler() {
|
||||||
|
let mut testbench = MgmTestbench::new();
|
||||||
|
assert_eq!(testbench.handler.com_interface.call_count, 0);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Off as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
|
||||||
|
testbench.handler.periodic_operation();
|
||||||
|
// Handler is OFF, no changes expected.
|
||||||
|
assert_eq!(testbench.handler.com_interface.call_count, 0);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Off as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normal_handler() {
|
||||||
|
let mut testbench = MgmTestbench::new();
|
||||||
|
testbench
|
||||||
|
.mode_request_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
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();
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Normal as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
|
||||||
|
|
||||||
|
// Verify power switch handling.
|
||||||
|
let mut switch_requests = testbench.handler.switch_helper.switch_requests.borrow_mut();
|
||||||
|
assert_eq!(switch_requests.len(), 1);
|
||||||
|
let switch_req = switch_requests.pop_front().expect("no switch request");
|
||||||
|
assert_eq!(switch_req.target_state, SwitchStateBinary::On);
|
||||||
|
assert_eq!(switch_req.switch_id, PcduSwitch::Mgm);
|
||||||
|
let mut switch_info_requests = testbench
|
||||||
|
.handler
|
||||||
|
.switch_helper
|
||||||
|
.switch_info_requests
|
||||||
|
.borrow_mut();
|
||||||
|
assert_eq!(switch_info_requests.len(), 1);
|
||||||
|
let switch_info_req = switch_info_requests.pop_front().expect("no switch request");
|
||||||
|
assert_eq!(switch_info_req, PcduSwitch::Mgm);
|
||||||
|
|
||||||
|
let mode_reply = testbench
|
||||||
|
.mode_reply_rx_to_pus
|
||||||
|
.try_recv()
|
||||||
|
.expect("no mode reply generated");
|
||||||
|
match mode_reply.message {
|
||||||
|
ModeReply::ModeReply(mode) => {
|
||||||
|
assert_eq!(mode.mode(), DeviceMode::Normal as u32);
|
||||||
|
assert_eq!(mode.submode(), 0);
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected mode reply"),
|
||||||
|
}
|
||||||
|
// The device should have been polled once.
|
||||||
|
assert_eq!(testbench.handler.com_interface.call_count, 1);
|
||||||
|
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
||||||
|
assert!(mgm_set.x < 0.001);
|
||||||
|
assert!(mgm_set.y < 0.001);
|
||||||
|
assert!(mgm_set.z < 0.001);
|
||||||
|
assert!(mgm_set.valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normal_handler_mgm_set_conversion() {
|
||||||
|
let mut testbench = MgmTestbench::new();
|
||||||
|
let raw_values = MgmLis3RawValues {
|
||||||
|
x: 1000,
|
||||||
|
y: -1000,
|
||||||
|
z: 1000,
|
||||||
|
};
|
||||||
|
testbench.handler.com_interface.next_mgm_data = raw_values;
|
||||||
|
testbench
|
||||||
|
.mode_request_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
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();
|
||||||
|
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
||||||
|
let expected_x =
|
||||||
|
raw_values.x as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
let expected_y =
|
||||||
|
raw_values.y as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
let expected_z =
|
||||||
|
raw_values.z as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
let x_diff = (mgm_set.x - expected_x).abs();
|
||||||
|
let y_diff = (mgm_set.y - expected_y).abs();
|
||||||
|
let z_diff = (mgm_set.z - expected_z).abs();
|
||||||
|
assert!(x_diff < 0.001, "x diff too large: {}", x_diff);
|
||||||
|
assert!(y_diff < 0.001, "y diff too large: {}", y_diff);
|
||||||
|
assert!(z_diff < 0.001, "z diff too large: {}", z_diff);
|
||||||
|
assert!(mgm_set.valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
|
pub mod assembly;
|
||||||
|
pub mod ctrl;
|
||||||
pub mod mgm;
|
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,9 @@
|
|||||||
|
use arbitrary_int::u11;
|
||||||
use satrs::pus::verification::RequestId;
|
use satrs::pus::verification::RequestId;
|
||||||
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
||||||
use satrs::spacepackets::ecss::tm::PusTmReader;
|
use satrs::spacepackets::ecss::tm::PusTmReader;
|
||||||
use satrs::{
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
spacepackets::ecss::{PusPacket, WritablePusPacket},
|
use satrs::spacepackets::SpHeader;
|
||||||
spacepackets::SpHeader,
|
|
||||||
};
|
|
||||||
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
||||||
use std::net::{IpAddr, SocketAddr, UdpSocket};
|
use std::net::{IpAddr, SocketAddr, UdpSocket};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -12,7 +11,12 @@ use std::time::Duration;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut buf = [0; 32];
|
let mut buf = [0; 32];
|
||||||
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
|
let pus_tc = PusTcCreator::new_simple(
|
||||||
|
SpHeader::new_from_apid(u11::new(0x02)),
|
||||||
|
MessageTypeId::new(17, 1),
|
||||||
|
&[],
|
||||||
|
CreatorConfig::default(),
|
||||||
|
);
|
||||||
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
||||||
let tc_req_id = RequestId::new(&pus_tc);
|
let tc_req_id = RequestId::new(&pus_tc);
|
||||||
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
|
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
|
||||||
@@ -29,10 +33,10 @@ fn main() {
|
|||||||
let res = client.recv(&mut buf);
|
let res = client.recv(&mut buf);
|
||||||
match res {
|
match res {
|
||||||
Ok(_len) => {
|
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 {
|
if pus_tm.service_type_id() == 17 && pus_tm.message_subtype_id() == 2 {
|
||||||
println!("Received PUS Ping Reply TM[17,2]")
|
println!("Received PUS Ping Reply TM[17,2]")
|
||||||
} else if pus_tm.service() == 1 {
|
} else if pus_tm.service_type_id() == 1 {
|
||||||
if pus_tm.source_data().is_empty() {
|
if pus_tm.source_data().is_empty() {
|
||||||
println!("Invalid verification TM, no source data");
|
println!("Invalid verification TM, no source data");
|
||||||
}
|
}
|
||||||
@@ -41,28 +45,29 @@ fn main() {
|
|||||||
println!("Invalid verification TM source data, less than 4 bytes")
|
println!("Invalid verification TM source data, less than 4 bytes")
|
||||||
}
|
}
|
||||||
let req_id = RequestId::from_bytes(src_data).unwrap();
|
let req_id = RequestId::from_bytes(src_data).unwrap();
|
||||||
if pus_tm.subservice() == 1 {
|
let subtype_id = pus_tm.message_subtype_id();
|
||||||
|
if subtype_id == 1 {
|
||||||
println!("Received TM[1,1] acceptance success for request ID {req_id}")
|
println!("Received TM[1,1] acceptance success for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 2 {
|
} else if subtype_id == 2 {
|
||||||
println!("Received TM[1,2] acceptance failure for request ID {req_id}")
|
println!("Received TM[1,2] acceptance failure for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 3 {
|
} else if subtype_id == 3 {
|
||||||
println!("Received TM[1,3] start success for request ID {req_id}")
|
println!("Received TM[1,3] start success for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 4 {
|
} else if subtype_id == 4 {
|
||||||
println!("Received TM[1,2] start failure for request ID {req_id}")
|
println!("Received TM[1,2] start failure for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 5 {
|
} else if subtype_id == 5 {
|
||||||
println!("Received TM[1,5] step success for request ID {req_id}")
|
println!("Received TM[1,5] step success for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 6 {
|
} else if subtype_id == 6 {
|
||||||
println!("Received TM[1,6] step failure for request ID {req_id}")
|
println!("Received TM[1,6] step failure for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 7 {
|
} else if subtype_id == 7 {
|
||||||
println!("Received TM[1,7] completion success for request ID {req_id}")
|
println!("Received TM[1,7] completion success for request ID {req_id}")
|
||||||
} else if pus_tm.subservice() == 8 {
|
} else if subtype_id == 8 {
|
||||||
println!("Received TM[1,8] completion failure for request ID {req_id}");
|
println!("Received TM[1,8] completion failure for request ID {req_id}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Received TM[{}, {}] with {} bytes",
|
"Received TM[{}, {}] with {} bytes",
|
||||||
pus_tm.service(),
|
pus_tm.service_type_id(),
|
||||||
pus_tm.subservice(),
|
pus_tm.message_subtype_id(),
|
||||||
size
|
size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
|
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
|
||||||
|
|
||||||
trait FieldDataProvider: Send {
|
trait FieldDataProvider: Send {
|
||||||
fn get_data(&self) -> &[u8];
|
fn get_data(&self) -> &[u8];
|
||||||
@@ -35,7 +35,7 @@ struct ExampleMgmSet {
|
|||||||
temperature: u16,
|
temperature: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromBytes, AsBytes, Unaligned)]
|
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct ExampleMgmSetZc {
|
struct ExampleMgmSetZc {
|
||||||
mgm_vec: [u8; 12],
|
mgm_vec: [u8; 12],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use arbitrary_int::u11;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
res_code::ResultU16,
|
res_code::ResultU16,
|
||||||
@@ -10,7 +11,7 @@ use strum::IntoEnumIterator;
|
|||||||
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
events::{EventU32TypedSev, SeverityInfo},
|
events_legacy::{EventU32TypedSev, SeverityInfo},
|
||||||
pool::{StaticMemoryPool, StaticPoolConfig},
|
pool::{StaticMemoryPool, StaticPoolConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,14 +44,14 @@ pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<Severi
|
|||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for id in components::Apid::iter() {
|
for id in crate::ids::Apid::iter() {
|
||||||
set.insert(PacketId::new(PacketType::Tc, true, id as u16));
|
set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16)));
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
};
|
};
|
||||||
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for id in components::Apid::iter() {
|
for id in crate::ids::Apid::iter() {
|
||||||
set.insert(id as u16);
|
set.insert(id as u16);
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -122,60 +123,9 @@ pub mod mode_err {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod components {
|
pub mod components {
|
||||||
use satrs::request::UniqueApidTargetId;
|
use satrs::ComponentId;
|
||||||
use strum::EnumIter;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
|
pub const NO_SENDER: ComponentId = ComponentId::MAX;
|
||||||
pub enum Apid {
|
|
||||||
Sched = 1,
|
|
||||||
GenericPus = 2,
|
|
||||||
Acs = 3,
|
|
||||||
Cfdp = 4,
|
|
||||||
Tmtc = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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 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 mod pool {
|
pub mod pool {
|
||||||
@@ -224,7 +174,7 @@ pub mod pool {
|
|||||||
|
|
||||||
pub mod tasks {
|
pub mod tasks {
|
||||||
pub const FREQ_MS_UDP_TMTC: u64 = 200;
|
pub const FREQ_MS_UDP_TMTC: u64 = 200;
|
||||||
pub const FREQ_MS_EVENT_HANDLING: u64 = 400;
|
|
||||||
pub const FREQ_MS_AOCS: u64 = 500;
|
pub const FREQ_MS_AOCS: u64 = 500;
|
||||||
pub const FREQ_MS_PUS_STACK: u64 = 200;
|
pub const FREQ_MS_PUS_STACK: u64 = 200;
|
||||||
|
pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
197
satrs-example/src/eps/mod.rs
Normal file
197
satrs-example/src/eps/mod.rs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
use satrs::{
|
||||||
|
power::{
|
||||||
|
PowerSwitchInfo, PowerSwitcherCommandSender, SwitchRequest, SwitchState, SwitchStateBinary,
|
||||||
|
},
|
||||||
|
queue::GenericSendError,
|
||||||
|
request::{GenericMessage, MessageMetadata},
|
||||||
|
};
|
||||||
|
use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use self::pcdu::SharedSwitchSet;
|
||||||
|
|
||||||
|
pub mod pcdu;
|
||||||
|
|
||||||
|
#[derive(new, Clone)]
|
||||||
|
pub struct PowerSwitchHelper {
|
||||||
|
switcher_tx: mpsc::SyncSender<GenericMessage<SwitchRequest>>,
|
||||||
|
shared_switch_set: SharedSwitchSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SwitchCommandingError {
|
||||||
|
#[error("send error: {0}")]
|
||||||
|
Send(#[from] GenericSendError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SwitchInfoError {
|
||||||
|
/// This is a configuration error which should not occur.
|
||||||
|
#[error("switch ID not in map")]
|
||||||
|
SwitchIdNotInMap(PcduSwitch),
|
||||||
|
#[error("switch set invalid")]
|
||||||
|
SwitchSetInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
|
||||||
|
type Error = SwitchInfoError;
|
||||||
|
|
||||||
|
fn switch_state(
|
||||||
|
&self,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<satrs::power::SwitchState, Self::Error> {
|
||||||
|
let switch_set = self
|
||||||
|
.shared_switch_set
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock switch set");
|
||||||
|
if !switch_set.valid {
|
||||||
|
return Err(SwitchInfoError::SwitchSetInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(state) = switch_set.switch_map.get(&switch_id) {
|
||||||
|
return Ok(*state);
|
||||||
|
}
|
||||||
|
Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_delay_ms(&self) -> Duration {
|
||||||
|
// Here, we could set device specific switch delays theoretically. Set it to this value
|
||||||
|
// for now.
|
||||||
|
Duration::from_millis(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
|
||||||
|
type Error = SwitchCommandingError;
|
||||||
|
|
||||||
|
fn send_switch_on_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.switcher_tx
|
||||||
|
.send_switch_on_cmd(requestor_info, switch_id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_switch_off_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: satrs::request::MessageMetadata,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.switcher_tx
|
||||||
|
.send_switch_off_cmd(requestor_info, switch_id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct SwitchRequestInfo {
|
||||||
|
pub requestor_info: MessageMetadata,
|
||||||
|
pub switch_id: PcduSwitch,
|
||||||
|
pub target_state: satrs::power::SwitchStateBinary,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test switch helper which can be used for unittests.
|
||||||
|
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>,
|
||||||
|
pub switch_map_valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TestSwitchHelper {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
switch_requests: Default::default(),
|
||||||
|
switch_info_requests: Default::default(),
|
||||||
|
switch_delay_request_count: Default::default(),
|
||||||
|
next_switch_delay: Duration::from_millis(1000),
|
||||||
|
switch_map: Default::default(),
|
||||||
|
switch_map_valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
|
||||||
|
type Error = SwitchInfoError;
|
||||||
|
|
||||||
|
fn switch_state(
|
||||||
|
&self,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<satrs::power::SwitchState, Self::Error> {
|
||||||
|
let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut();
|
||||||
|
switch_info_requests_mut.push_back(switch_id);
|
||||||
|
if !self.switch_map_valid {
|
||||||
|
return Err(SwitchInfoError::SwitchSetInvalid);
|
||||||
|
}
|
||||||
|
let switch_map_mut = self.switch_map.borrow_mut();
|
||||||
|
if let Some(state) = switch_map_mut.0.get(&switch_id) {
|
||||||
|
return Ok(*state);
|
||||||
|
}
|
||||||
|
Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_delay_ms(&self) -> Duration {
|
||||||
|
self.next_switch_delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
|
||||||
|
type Error = SwitchCommandingError;
|
||||||
|
|
||||||
|
fn send_switch_on_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: MessageMetadata,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
||||||
|
switch_requests_mut.push_back(SwitchRequestInfo {
|
||||||
|
requestor_info,
|
||||||
|
switch_id,
|
||||||
|
target_state: SwitchStateBinary::On,
|
||||||
|
});
|
||||||
|
// By default, the test helper immediately acknowledges the switch request by setting
|
||||||
|
// the appropriate switch state in the internal switch map.
|
||||||
|
let mut switch_map_mut = self.switch_map.borrow_mut();
|
||||||
|
if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
|
||||||
|
*switch_state = SwitchState::On;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_switch_off_cmd(
|
||||||
|
&self,
|
||||||
|
requestor_info: MessageMetadata,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut switch_requests_mut = self.switch_requests.borrow_mut();
|
||||||
|
switch_requests_mut.push_back(SwitchRequestInfo {
|
||||||
|
requestor_info,
|
||||||
|
switch_id,
|
||||||
|
target_state: SwitchStateBinary::Off,
|
||||||
|
});
|
||||||
|
// By default, the test helper immediately acknowledges the switch request by setting
|
||||||
|
// the appropriate switch state in the internal switch map.
|
||||||
|
let mut switch_map_mut = self.switch_map.borrow_mut();
|
||||||
|
if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
|
||||||
|
*switch_state = SwitchState::Off;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl TestSwitchHelper {
|
||||||
|
// Helper function which can be used to force a switch to another state for test purposes.
|
||||||
|
pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) {
|
||||||
|
self.switch_map.get_mut().0.insert(switch, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
747
satrs-example/src/eps/pcdu.rs
Normal file
747
satrs-example/src/eps/pcdu.rs
Normal file
@@ -0,0 +1,747 @@
|
|||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::VecDeque,
|
||||||
|
sync::{mpsc, Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_new::new;
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use satrs::{
|
||||||
|
hk::{HkRequest, HkRequestVariant},
|
||||||
|
mode::{
|
||||||
|
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
|
||||||
|
ModeRequestHandlerMpscBounded,
|
||||||
|
},
|
||||||
|
mode_tree::{ModeChild, ModeNode},
|
||||||
|
power::SwitchRequest,
|
||||||
|
pus::{EcssTmSender, PusTmVariant},
|
||||||
|
queue::GenericSendError,
|
||||||
|
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
|
||||||
|
spacepackets::ByteConversionError,
|
||||||
|
};
|
||||||
|
use satrs_example::{
|
||||||
|
config::components::NO_SENDER,
|
||||||
|
ids::{eps::PCDU, generic_pus::PUS_MODE},
|
||||||
|
CcsdsTmPacketOwned, DeviceMode, TimestampHelper,
|
||||||
|
};
|
||||||
|
use satrs_minisim::{
|
||||||
|
eps::{
|
||||||
|
PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper,
|
||||||
|
},
|
||||||
|
SerializableSimMsgPayload, SimReply, SimRequest,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{hk::PusHkHelper, requests::CompositeRequest};
|
||||||
|
|
||||||
|
pub trait SerialInterface {
|
||||||
|
type Error: core::fmt::Debug;
|
||||||
|
|
||||||
|
/// Send some data via the serial interface.
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), Self::Error>;
|
||||||
|
/// Receive all replies received on the serial interface so far. This function takes a closure
|
||||||
|
/// and call its for each received packet, passing the received packet into it.
|
||||||
|
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
|
||||||
|
&self,
|
||||||
|
f: ReplyHandler,
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct SerialInterfaceToSim {
|
||||||
|
pub sim_request_tx: mpsc::Sender<SimRequest>,
|
||||||
|
pub sim_reply_rx: mpsc::Receiver<SimReply>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum SetId {
|
||||||
|
SwitcherSet = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialInterface for SerialInterfaceToSim {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let request: PcduRequest = serde_json::from_slice(data).expect("expected a PCDU request");
|
||||||
|
self.sim_request_tx
|
||||||
|
.send(SimRequest::new_with_epoch_time(request))
|
||||||
|
.expect("failed to send request to simulation");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
|
||||||
|
&self,
|
||||||
|
mut f: ReplyHandler,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
loop {
|
||||||
|
match self.sim_reply_rx.try_recv() {
|
||||||
|
Ok(reply) => {
|
||||||
|
let reply = serde_json::to_string(&reply).unwrap();
|
||||||
|
f(reply.as_bytes());
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => break,
|
||||||
|
mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("sim reply sender has disconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SerialInterfaceDummy {
|
||||||
|
// Need interior mutability here for both fields.
|
||||||
|
pub switch_map: RefCell<SwitchMapBinaryWrapper>,
|
||||||
|
pub reply_deque: RefCell<VecDeque<SimReply>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialInterface for SerialInterfaceDummy {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let pcdu_req: PcduRequest = serde_json::from_slice(data).unwrap();
|
||||||
|
let switch_map_mut = &mut self.switch_map.borrow_mut().0;
|
||||||
|
match pcdu_req {
|
||||||
|
PcduRequest::SwitchDevice { switch, state } => {
|
||||||
|
match switch_map_mut.entry(switch) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(mut val) => {
|
||||||
|
*val.get_mut() = state;
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Vacant(vacant) => {
|
||||||
|
vacant.insert(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PcduRequest::RequestSwitchInfo => {
|
||||||
|
let mut reply_deque_mut = self.reply_deque.borrow_mut();
|
||||||
|
reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo(
|
||||||
|
switch_map_mut.clone(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
|
||||||
|
&self,
|
||||||
|
mut f: ReplyHandler,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if self.reply_queue_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let reply = self.get_next_reply_as_string();
|
||||||
|
f(reply.as_bytes());
|
||||||
|
if self.reply_queue_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialInterfaceDummy {
|
||||||
|
fn get_next_reply_as_string(&self) -> String {
|
||||||
|
let mut reply_deque_mut = self.reply_deque.borrow_mut();
|
||||||
|
let next_reply = reply_deque_mut.pop_front().unwrap();
|
||||||
|
serde_json::to_string(&next_reply).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply_queue_empty(&self) -> bool {
|
||||||
|
self.reply_deque.borrow().is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SerialSimInterfaceWrapper {
|
||||||
|
Dummy(SerialInterfaceDummy),
|
||||||
|
Sim(SerialInterfaceToSim),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialInterface for SerialSimInterfaceWrapper {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
SerialSimInterfaceWrapper::Dummy(dummy) => dummy.send(data),
|
||||||
|
SerialSimInterfaceWrapper::Sim(sim) => sim.send(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
|
||||||
|
&self,
|
||||||
|
f: ReplyHandler,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
SerialSimInterfaceWrapper::Dummy(dummy) => dummy.try_recv_replies(f),
|
||||||
|
SerialSimInterfaceWrapper::Sim(sim) => sim.try_recv_replies(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum OpCode {
|
||||||
|
RegularOp = 0,
|
||||||
|
PollAndRecvReplies = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct SwitchSet {
|
||||||
|
pub valid: bool,
|
||||||
|
pub switch_map: SwitchMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
|
||||||
|
|
||||||
|
/// Example PCDU device handler.
|
||||||
|
#[derive(new)]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub struct PcduHandler<ComInterface: SerialInterface> {
|
||||||
|
id: UniqueApidTargetId,
|
||||||
|
dev_str: &'static str,
|
||||||
|
mode_node: ModeRequestHandlerMpscBounded,
|
||||||
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
|
//hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
|
||||||
|
hk_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
|
||||||
|
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
|
pub com_interface: ComInterface,
|
||||||
|
shared_switch_map: Arc<Mutex<SwitchSet>>,
|
||||||
|
#[new(value = "PusHkHelper::new(id)")]
|
||||||
|
hk_helper: PusHkHelper,
|
||||||
|
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
|
||||||
|
mode_and_submode: ModeAndSubmode,
|
||||||
|
#[new(default)]
|
||||||
|
stamp_helper: TimestampHelper,
|
||||||
|
#[new(value = "[0; 256]")]
|
||||||
|
tm_buf: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
|
||||||
|
pub fn periodic_operation(&mut self, op_code: OpCode) {
|
||||||
|
match op_code {
|
||||||
|
OpCode::RegularOp => {
|
||||||
|
self.stamp_helper.update_from_now();
|
||||||
|
// Handle requests.
|
||||||
|
self.handle_composite_requests();
|
||||||
|
self.handle_mode_requests();
|
||||||
|
self.handle_switch_requests();
|
||||||
|
// Poll the switch states and/or telemetry regularly here.
|
||||||
|
if self.mode() == DeviceMode::Normal as u32 || self.mode() == DeviceMode::On as u32
|
||||||
|
{
|
||||||
|
self.handle_periodic_commands();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OpCode::PollAndRecvReplies => {
|
||||||
|
self.poll_and_handle_replies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_composite_requests(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.composite_request_rx.try_recv() {
|
||||||
|
Ok(ref msg) => match &msg.message {
|
||||||
|
CompositeRequest::Hk(hk_request) => {
|
||||||
|
self.handle_hk_request(&msg.requestor_info, hk_request)
|
||||||
|
}
|
||||||
|
// TODO: This object does not have actions (yet).. Still send back completion failure
|
||||||
|
// reply.
|
||||||
|
CompositeRequest::Action(_action_req) => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
if e != mpsc::TryRecvError::Empty {
|
||||||
|
log::warn!(
|
||||||
|
"{}: failed to receive composite request: {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_hk_request(&mut self, _requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
||||||
|
match hk_request.variant {
|
||||||
|
HkRequestVariant::OneShot => {
|
||||||
|
if hk_request.unique_id == SetId::SwitcherSet as u32 {
|
||||||
|
if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
|
||||||
|
self.stamp_helper.stamp(),
|
||||||
|
SetId::SwitcherSet as u32,
|
||||||
|
&mut |hk_buf| {
|
||||||
|
// Send TM down as JSON.
|
||||||
|
let switch_map_snapshot = self
|
||||||
|
.shared_switch_map
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock switch map")
|
||||||
|
.clone();
|
||||||
|
let switch_map_json = serde_json::to_string(&switch_map_snapshot)
|
||||||
|
.expect("failed to serialize switch map");
|
||||||
|
if switch_map_json.len() > hk_buf.len() {
|
||||||
|
log::error!("switch map JSON too large for HK buffer");
|
||||||
|
return Err(ByteConversionError::ToSliceTooSmall {
|
||||||
|
found: hk_buf.len(),
|
||||||
|
expected: switch_map_json.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(switch_map_json.len())
|
||||||
|
},
|
||||||
|
&mut self.tm_buf,
|
||||||
|
) {
|
||||||
|
// TODO: Fix
|
||||||
|
/*
|
||||||
|
self.tm_sender
|
||||||
|
.send(self.id.id(), PusTmVariant::Direct(hk_tm))
|
||||||
|
.expect("failed to send HK TM");
|
||||||
|
self.hk_reply_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
*requestor_info,
|
||||||
|
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
||||||
|
))
|
||||||
|
.expect("failed to send HK reply");
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HkRequestVariant::EnablePeriodic => todo!(),
|
||||||
|
HkRequestVariant::DisablePeriodic => todo!(),
|
||||||
|
HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_periodic_commands(&self) {
|
||||||
|
let pcdu_req = PcduRequest::RequestSwitchInfo;
|
||||||
|
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
|
||||||
|
if let Err(_e) = self.com_interface.send(pcdu_req_ser.as_bytes()) {
|
||||||
|
log::warn!("polling PCDU switch info failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_mode_requests(&mut self) {
|
||||||
|
loop {
|
||||||
|
// TODO: Only allow one set mode request per cycle?
|
||||||
|
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() {
|
||||||
|
log::warn!(
|
||||||
|
"{}: mode request failed with error {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
result.err().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_switch_requests(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.switch_request_rx.try_recv() {
|
||||||
|
Ok(switch_req) => match PcduSwitch::try_from(switch_req.message.switch_id()) {
|
||||||
|
Ok(pcdu_switch) => {
|
||||||
|
let pcdu_req = PcduRequest::SwitchDevice {
|
||||||
|
switch: pcdu_switch,
|
||||||
|
state: switch_req.message.target_state(),
|
||||||
|
};
|
||||||
|
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
|
||||||
|
self.com_interface
|
||||||
|
.send(pcdu_req_ser.as_bytes())
|
||||||
|
.expect("failed to send switch request to PCDU");
|
||||||
|
}
|
||||||
|
Err(e) => todo!("failed to convert switch ID {:?} to typed PCDU switch", e),
|
||||||
|
},
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => break,
|
||||||
|
mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("switch request receiver has disconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_and_handle_replies(&mut self) {
|
||||||
|
if let Err(e) = self.com_interface.try_recv_replies(|reply| {
|
||||||
|
let sim_reply: SimReply = serde_json::from_slice(reply).expect("invalid reply format");
|
||||||
|
let pcdu_reply = PcduReply::from_sim_message(&sim_reply).expect("invalid reply format");
|
||||||
|
match pcdu_reply {
|
||||||
|
PcduReply::SwitchInfo(switch_info) => {
|
||||||
|
let switch_map_wrapper =
|
||||||
|
SwitchMapWrapper::from_binary_switch_map_ref(&switch_info);
|
||||||
|
let mut shared_switch_map = self
|
||||||
|
.shared_switch_map
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock switch map");
|
||||||
|
shared_switch_map.switch_map = switch_map_wrapper.0;
|
||||||
|
shared_switch_map.valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
log::warn!("receiving PCDU replies failed: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> {
|
||||||
|
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||||
|
self.mode_and_submode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
mode_and_submode
|
||||||
|
);
|
||||||
|
self.mode_and_submode = mode_and_submode;
|
||||||
|
if mode_and_submode.mode() == DeviceMode::Off as u32 {
|
||||||
|
self.shared_switch_map.lock().unwrap().valid = false;
|
||||||
|
}
|
||||||
|
self.handle_mode_reached(Some(requestor))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
|
||||||
|
log::info!(
|
||||||
|
"{} announcing mode: {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
self.mode_and_submode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mode_reached(
|
||||||
|
&mut self,
|
||||||
|
requestor: Option<MessageMetadata>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.announce_mode(requestor, false);
|
||||||
|
if let Some(requestor) = requestor {
|
||||||
|
if requestor.sender_id() == NO_SENDER {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if requestor.sender_id() != PUS_MODE.id() {
|
||||||
|
log::warn!(
|
||||||
|
"can not send back mode reply to sender {}",
|
||||||
|
requestor.sender_id()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_mode_reply(
|
||||||
|
&self,
|
||||||
|
requestor: MessageMetadata,
|
||||||
|
reply: ModeReply,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if requestor.sender_id() != PUS_MODE.id() {
|
||||||
|
log::warn!(
|
||||||
|
"can not send back mode reply to sender {}",
|
||||||
|
requestor.sender_id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.mode_node
|
||||||
|
.send_mode_reply(requestor, reply)
|
||||||
|
.map_err(|_| GenericSendError::RxDisconnected)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mode_info(
|
||||||
|
&mut self,
|
||||||
|
_requestor_info: MessageMetadata,
|
||||||
|
_info: ModeAndSubmode,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
use arbitrary_int::u21;
|
||||||
|
use satrs::{mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage};
|
||||||
|
use satrs_example::ids::{self, Apid};
|
||||||
|
use satrs_minisim::eps::SwitchMapBinary;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SerialInterfaceTest {
|
||||||
|
pub inner: SerialInterfaceDummy,
|
||||||
|
pub send_queue: RefCell<VecDeque<Vec<u8>>>,
|
||||||
|
pub reply_queue: RefCell<VecDeque<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialInterface for SerialInterfaceTest {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let mut send_queue_mut = self.send_queue.borrow_mut();
|
||||||
|
send_queue_mut.push_back(data.to_vec());
|
||||||
|
self.inner.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
|
||||||
|
&self,
|
||||||
|
mut f: ReplyHandler,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if self.inner.reply_queue_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let reply = self.inner.get_next_reply_as_string();
|
||||||
|
self.reply_queue.borrow_mut().push_back(reply.clone());
|
||||||
|
f(reply.as_bytes());
|
||||||
|
if self.inner.reply_queue_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PcduTestbench {
|
||||||
|
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 hk_rx: std::sync::mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
|
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
|
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
|
||||||
|
pub handler: PcduHandler<SerialInterfaceTest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PcduTestbench {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
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_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx);
|
||||||
|
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
||||||
|
let (hk_tx, hk_rx) = mpsc::sync_channel(10);
|
||||||
|
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
|
||||||
|
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
|
||||||
|
let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
|
||||||
|
let mut handler = PcduHandler::new(
|
||||||
|
UniqueApidTargetId::new(Apid::Eps.raw_value(), u21::new(0)),
|
||||||
|
"TEST_PCDU",
|
||||||
|
mode_node,
|
||||||
|
composite_request_rx,
|
||||||
|
hk_tx,
|
||||||
|
switch_reqest_rx,
|
||||||
|
tm_tx.clone(),
|
||||||
|
//TmTcSender::Normal(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,
|
||||||
|
mode_reply_rx_to_parent,
|
||||||
|
composite_request_tx,
|
||||||
|
hk_rx,
|
||||||
|
tm_rx,
|
||||||
|
switch_request_tx,
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_switch_info_req_was_sent(&self, expected_queue_len: usize) {
|
||||||
|
// Check that there is now communication happening.
|
||||||
|
let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
|
||||||
|
assert_eq!(send_queue_mut.len(), expected_queue_len);
|
||||||
|
let packet_sent = send_queue_mut.pop_front().unwrap();
|
||||||
|
drop(send_queue_mut);
|
||||||
|
let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
|
||||||
|
assert_eq!(pcdu_req, PcduRequest::RequestSwitchInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_switch_req_was_sent(
|
||||||
|
&self,
|
||||||
|
expected_queue_len: usize,
|
||||||
|
switch_id: PcduSwitch,
|
||||||
|
target_state: SwitchStateBinary,
|
||||||
|
) {
|
||||||
|
// Check that there is now communication happening.
|
||||||
|
let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
|
||||||
|
assert_eq!(send_queue_mut.len(), expected_queue_len);
|
||||||
|
let packet_sent = send_queue_mut.pop_front().unwrap();
|
||||||
|
drop(send_queue_mut);
|
||||||
|
let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
pcdu_req,
|
||||||
|
PcduRequest::SwitchDevice {
|
||||||
|
switch: switch_id,
|
||||||
|
state: target_state
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_switch_reply_received(
|
||||||
|
&self,
|
||||||
|
expected_queue_len: usize,
|
||||||
|
expected_map: SwitchMapBinary,
|
||||||
|
) {
|
||||||
|
// Check that a switch reply was read back.
|
||||||
|
let mut reply_received_mut = self.handler.com_interface.reply_queue.borrow_mut();
|
||||||
|
assert_eq!(reply_received_mut.len(), expected_queue_len);
|
||||||
|
let reply_received = reply_received_mut.pop_front().unwrap();
|
||||||
|
let sim_reply: SimReply = serde_json::from_str(&reply_received).unwrap();
|
||||||
|
let pcdu_reply = PcduReply::from_sim_message(&sim_reply).unwrap();
|
||||||
|
assert_eq!(pcdu_reply, PcduReply::SwitchInfo(expected_map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_handler() {
|
||||||
|
let mut testbench = PcduTestbench::new();
|
||||||
|
assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.com_interface.reply_queue.borrow().len(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Off as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
|
||||||
|
testbench.handler.periodic_operation(OpCode::RegularOp);
|
||||||
|
testbench
|
||||||
|
.handler
|
||||||
|
.periodic_operation(OpCode::PollAndRecvReplies);
|
||||||
|
// Handler is OFF, no changes expected.
|
||||||
|
assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.com_interface.reply_queue.borrow().len(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Off as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normal_mode() {
|
||||||
|
let mut testbench = PcduTestbench::new();
|
||||||
|
testbench
|
||||||
|
.mode_request_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
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();
|
||||||
|
assert!(!switch_map_shared.valid);
|
||||||
|
drop(switch_map_shared);
|
||||||
|
testbench.handler.periodic_operation(OpCode::RegularOp);
|
||||||
|
testbench
|
||||||
|
.handler
|
||||||
|
.periodic_operation(OpCode::PollAndRecvReplies);
|
||||||
|
// Check correctness of mode.
|
||||||
|
assert_eq!(
|
||||||
|
testbench.handler.mode_and_submode().mode(),
|
||||||
|
DeviceMode::Normal as u32
|
||||||
|
);
|
||||||
|
assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
|
||||||
|
|
||||||
|
testbench.verify_switch_info_req_was_sent(1);
|
||||||
|
testbench.verify_switch_reply_received(1, SwitchMapBinaryWrapper::default().0);
|
||||||
|
|
||||||
|
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
|
||||||
|
assert!(switch_map_shared.valid);
|
||||||
|
drop(switch_map_shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_switch_request_handling() {
|
||||||
|
let mut testbench = PcduTestbench::new();
|
||||||
|
testbench
|
||||||
|
.mode_request_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
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, ids::acs::MGM0.id()),
|
||||||
|
SwitchRequest::new(0, SwitchStateBinary::On),
|
||||||
|
))
|
||||||
|
.expect("failed to send switch request");
|
||||||
|
testbench.handler.periodic_operation(OpCode::RegularOp);
|
||||||
|
testbench
|
||||||
|
.handler
|
||||||
|
.periodic_operation(OpCode::PollAndRecvReplies);
|
||||||
|
|
||||||
|
testbench.verify_switch_req_was_sent(2, PcduSwitch::Mgm, SwitchStateBinary::On);
|
||||||
|
testbench.verify_switch_info_req_was_sent(1);
|
||||||
|
let mut switch_map = SwitchMapBinaryWrapper::default().0;
|
||||||
|
*switch_map
|
||||||
|
.get_mut(&PcduSwitch::Mgm)
|
||||||
|
.expect("switch state setting failed") = SwitchStateBinary::On;
|
||||||
|
testbench.verify_switch_reply_received(1, switch_map);
|
||||||
|
|
||||||
|
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
|
||||||
|
assert!(switch_map_shared.valid);
|
||||||
|
drop(switch_map_shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
use std::sync::mpsc::{self};
|
use std::sync::mpsc::{self};
|
||||||
|
|
||||||
use crate::pus::create_verification_reporter;
|
//use crate::pus::create_verification_reporter;
|
||||||
use satrs::event_man::{EventMessageU32, EventRoutingError};
|
use arbitrary_int::traits::Integer as _;
|
||||||
use satrs::pus::event::EventTmHookProvider;
|
use arbitrary_int::u11;
|
||||||
|
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
|
||||||
|
use satrs::pus::event::EventTmHook;
|
||||||
use satrs::pus::verification::VerificationReporter;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs::pus::EcssTmSender;
|
use satrs::pus::EcssTmSender;
|
||||||
use satrs::request::UniqueApidTargetId;
|
use satrs::request::UniqueApidTargetId;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
event_man::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
|
event_man_legacy::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
|
||||||
pus::{
|
pus::{
|
||||||
event_man::{
|
event_man::{
|
||||||
DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
|
DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
|
||||||
@@ -16,22 +18,23 @@ use satrs::{
|
|||||||
},
|
},
|
||||||
spacepackets::time::cds::CdsTime,
|
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;
|
use crate::update_time;
|
||||||
|
|
||||||
// This helper sets the APID of the event sender for the PUS telemetry.
|
// This helper sets the APID of the event sender for the PUS telemetry.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EventApidSetter {
|
pub struct EventApidSetter {
|
||||||
pub next_apid: u16,
|
pub next_apid: u11,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventTmHookProvider for EventApidSetter {
|
impl EventTmHook for EventApidSetter {
|
||||||
fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
|
fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
|
||||||
tm.set_apid(self.next_apid);
|
tm.set_apid(self.next_apid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
|
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
|
||||||
/// packets. It also handles the verification completion of PUS event service requests.
|
/// packets. It also handles the verification completion of PUS event service requests.
|
||||||
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
||||||
@@ -59,12 +62,11 @@ impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
|
|||||||
// telemetry for each event.
|
// telemetry for each event.
|
||||||
let event_reporter = EventReporter::new_with_hook(
|
let event_reporter = EventReporter::new_with_hook(
|
||||||
PUS_EVENT_MANAGEMENT.raw(),
|
PUS_EVENT_MANAGEMENT.raw(),
|
||||||
0,
|
u11::ZERO,
|
||||||
0,
|
0,
|
||||||
128,
|
128,
|
||||||
EventApidSetter::default(),
|
EventApidSetter::default(),
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
let pus_event_dispatcher =
|
let pus_event_dispatcher =
|
||||||
DefaultPusEventU32TmCreator::new_with_default_backend(event_reporter);
|
DefaultPusEventU32TmCreator::new_with_default_backend(event_reporter);
|
||||||
let pus_event_man_send_provider = EventU32SenderMpscBounded::new(
|
let pus_event_man_send_provider = EventU32SenderMpscBounded::new(
|
||||||
@@ -217,20 +219,18 @@ impl<TmSender: EcssTmSender> EventHandler<TmSender> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use arbitrary_int::u21;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
events::EventU32,
|
events_legacy::EventU32,
|
||||||
pus::verification::VerificationReporterCfg,
|
pus::verification::VerificationReporterConfig,
|
||||||
spacepackets::{
|
spacepackets::ecss::{tm::PusTmReader, PusPacket},
|
||||||
ecss::{tm::PusTmReader, PusPacket},
|
|
||||||
CcsdsPacket,
|
|
||||||
},
|
|
||||||
tmtc::PacketAsVec,
|
tmtc::PacketAsVec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(1, 2);
|
const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(u11::new(1), u21::new(2));
|
||||||
const TEST_EVENT: EventU32 = EventU32::new(satrs::events::Severity::Info, 1, 1);
|
const TEST_EVENT: EventU32 = EventU32::new(satrs::events_legacy::Severity::Info, 1, 1);
|
||||||
|
|
||||||
pub struct EventManagementTestbench {
|
pub struct EventManagementTestbench {
|
||||||
pub event_tx: mpsc::SyncSender<EventMessageU32>,
|
pub event_tx: mpsc::SyncSender<EventMessageU32>,
|
||||||
@@ -244,7 +244,7 @@ mod tests {
|
|||||||
let (event_tx, event_rx) = mpsc::sync_channel(10);
|
let (event_tx, event_rx) = mpsc::sync_channel(10);
|
||||||
let (_event_req_tx, event_req_rx) = mpsc::sync_channel(10);
|
let (_event_req_tx, event_req_rx) = mpsc::sync_channel(10);
|
||||||
let (tm_sender, tm_receiver) = mpsc::channel();
|
let (tm_sender, tm_receiver) = mpsc::channel();
|
||||||
let verif_reporter_cfg = VerificationReporterCfg::new(0x05, 2, 2, 128).unwrap();
|
let verif_reporter_cfg = VerificationReporterConfig::new(u11::new(0x05), 2, 2, 128);
|
||||||
let verif_reporter =
|
let verif_reporter =
|
||||||
VerificationReporter::new(PUS_EVENT_MANAGEMENT.id(), &verif_reporter_cfg);
|
VerificationReporter::new(PUS_EVENT_MANAGEMENT.id(), &verif_reporter_cfg);
|
||||||
let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
|
let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
|
||||||
@@ -270,7 +270,7 @@ mod tests {
|
|||||||
.event_tx
|
.event_tx
|
||||||
.send(EventMessageU32::new(
|
.send(EventMessageU32::new(
|
||||||
TEST_CREATOR_ID.id(),
|
TEST_CREATOR_ID.id(),
|
||||||
EventU32::new(satrs::events::Severity::Info, 1, 1),
|
EventU32::new(satrs::events_legacy::Severity::Info, 1, 1),
|
||||||
))
|
))
|
||||||
.expect("failed to send event");
|
.expect("failed to send event");
|
||||||
testbench.pus_event_handler.handle_event_requests();
|
testbench.pus_event_handler.handle_event_requests();
|
||||||
@@ -281,9 +281,7 @@ mod tests {
|
|||||||
.try_recv()
|
.try_recv()
|
||||||
.expect("failed to receive TM packet");
|
.expect("failed to receive TM packet");
|
||||||
assert_eq!(tm_packet.sender_id, PUS_EVENT_MANAGEMENT.id());
|
assert_eq!(tm_packet.sender_id, PUS_EVENT_MANAGEMENT.id());
|
||||||
let tm_reader = PusTmReader::new(&tm_packet.packet, 7)
|
let tm_reader = PusTmReader::new(&tm_packet.packet, 7).expect("failed to create TM reader");
|
||||||
.expect("failed to create TM reader")
|
|
||||||
.0;
|
|
||||||
assert_eq!(tm_reader.apid(), TEST_CREATOR_ID.apid);
|
assert_eq!(tm_reader.apid(), TEST_CREATOR_ID.apid);
|
||||||
assert_eq!(tm_reader.user_data().len(), 4);
|
assert_eq!(tm_reader.user_data().len(), 4);
|
||||||
let event_read_back = EventU32::from_be_bytes(tm_reader.user_data().try_into().unwrap());
|
let event_read_back = EventU32::from_be_bytes(tm_reader.user_data().try_into().unwrap());
|
||||||
@@ -295,3 +293,4 @@ mod tests {
|
|||||||
// TODO: Add test.
|
// TODO: Add test.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use arbitrary_int::traits::Integer as _;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs::hk::UniqueId;
|
use satrs::hk::UniqueId;
|
||||||
use satrs::request::UniqueApidTargetId;
|
use satrs::request::UniqueApidTargetId;
|
||||||
use satrs::spacepackets::ByteConversionError;
|
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
||||||
|
use satrs::spacepackets::ecss::{hk, CreatorConfig, MessageTypeId};
|
||||||
|
use satrs::spacepackets::{ByteConversionError, SpHeader};
|
||||||
|
|
||||||
#[derive(Debug, new, Copy, Clone)]
|
#[derive(Debug, new, Copy, Clone)]
|
||||||
pub struct HkUniqueId {
|
pub struct HkUniqueId {
|
||||||
@@ -27,9 +30,45 @@ impl HkUniqueId {
|
|||||||
expected: 8,
|
expected: 8,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
buf[0..4].copy_from_slice(&self.target_id.unique_id.to_be_bytes());
|
buf[0..4].copy_from_slice(&self.target_id.unique_id.as_u32().to_be_bytes());
|
||||||
buf[4..8].copy_from_slice(&self.set_id.to_be_bytes());
|
buf[4..8].copy_from_slice(&self.set_id.to_be_bytes());
|
||||||
|
|
||||||
Ok(8)
|
Ok(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct PusHkHelper {
|
||||||
|
component_id: UniqueApidTargetId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PusHkHelper {
|
||||||
|
pub fn generate_hk_report_packet<
|
||||||
|
'a,
|
||||||
|
'b,
|
||||||
|
HkWriter: FnMut(&mut [u8]) -> Result<usize, ByteConversionError>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
timestamp: &'a [u8],
|
||||||
|
set_id: u32,
|
||||||
|
hk_data_writer: &mut HkWriter,
|
||||||
|
buf: &'b mut [u8],
|
||||||
|
) -> Result<PusTmCreator<'a, 'b>, ByteConversionError> {
|
||||||
|
let sec_header = PusTmSecondaryHeader::new(
|
||||||
|
MessageTypeId::new(3, hk::MessageSubtypeId::TmHkPacket as u8),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
timestamp,
|
||||||
|
);
|
||||||
|
buf[0..4].copy_from_slice(&self.component_id.unique_id.as_u32().to_be_bytes());
|
||||||
|
buf[4..8].copy_from_slice(&set_id.to_be_bytes());
|
||||||
|
let (_, second_half) = buf.split_at_mut(8);
|
||||||
|
let hk_data_len = hk_data_writer(second_half)?;
|
||||||
|
Ok(PusTmCreator::new(
|
||||||
|
SpHeader::new_from_apid(self.component_id.apid),
|
||||||
|
sec_header,
|
||||||
|
&buf[0..8 + hk_data_len],
|
||||||
|
CreatorConfig::default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
109
satrs-example/src/ids.rs
Normal file
109
satrs-example/src/ids.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//! This is an auto-generated configuration module.
|
||||||
|
use satrs::request::UniqueApidTargetId;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
|
||||||
|
#[bitbybit::bitenum(u11)]
|
||||||
|
pub enum Apid {
|
||||||
|
Sched = 1,
|
||||||
|
GenericPus = 2,
|
||||||
|
Acs = 3,
|
||||||
|
Cfdp = 4,
|
||||||
|
Tmtc = 5,
|
||||||
|
Eps = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod acs {
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u21, exhaustive = false)]
|
||||||
|
pub enum Id {
|
||||||
|
Subsystem = 1,
|
||||||
|
Assembly = 2,
|
||||||
|
Mgm0 = 3,
|
||||||
|
Mgm1 = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Subsystem.raw_value());
|
||||||
|
pub const ASSEMBLY: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Assembly.raw_value());
|
||||||
|
pub const MGM0: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm0.raw_value());
|
||||||
|
pub const MGM1: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm1.raw_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod eps {
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u21, exhaustive = false)]
|
||||||
|
pub enum Id {
|
||||||
|
Pcdu = 0,
|
||||||
|
Subsystem = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PCDU: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Pcdu.raw_value());
|
||||||
|
pub const SUBSYSTEM: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Subsystem.raw_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod generic_pus {
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u21, exhaustive = false)]
|
||||||
|
pub enum Id {
|
||||||
|
PusEventManagement = 0,
|
||||||
|
PusRouting = 1,
|
||||||
|
PusTest = 2,
|
||||||
|
PusAction = 3,
|
||||||
|
PusMode = 4,
|
||||||
|
PusHk = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PUS_EVENT_MANAGEMENT: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
||||||
|
super::Apid::GenericPus.raw_value(),
|
||||||
|
Id::PusEventManagement.raw_value(),
|
||||||
|
);
|
||||||
|
pub const PUS_ROUTING: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
||||||
|
super::Apid::GenericPus.raw_value(),
|
||||||
|
Id::PusRouting.raw_value(),
|
||||||
|
);
|
||||||
|
pub const PUS_TEST: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
||||||
|
super::Apid::GenericPus.raw_value(),
|
||||||
|
Id::PusTest.raw_value(),
|
||||||
|
);
|
||||||
|
pub const PUS_ACTION: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
||||||
|
super::Apid::GenericPus.raw_value(),
|
||||||
|
Id::PusAction.raw_value(),
|
||||||
|
);
|
||||||
|
pub const PUS_MODE: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
|
||||||
|
super::Apid::GenericPus.raw_value(),
|
||||||
|
Id::PusMode.raw_value(),
|
||||||
|
);
|
||||||
|
pub const PUS_HK: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::GenericPus.raw_value(), Id::PusHk.raw_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod sched {
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u21, exhaustive = false)]
|
||||||
|
pub enum Id {
|
||||||
|
PusSched = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PUS_SCHED: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Sched.raw_value(), Id::PusSched.raw_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod tmtc {
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u21, exhaustive = false)]
|
||||||
|
pub enum Id {
|
||||||
|
UdpServer = 0,
|
||||||
|
TcpServer = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UDP_SERVER: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::UdpServer.raw_value());
|
||||||
|
pub const TCP_SERVER: super::UniqueApidTargetId =
|
||||||
|
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::TcpServer.raw_value());
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
//! This module contains all component related to the direct interface of the example.
|
//! This module contains all component related to the direct interface of the example.
|
||||||
|
pub mod sim_client_udp;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
|
|||||||
420
satrs-example/src/interface/sim_client_udp.rs
Normal file
420
satrs-example/src/interface/sim_client_udp.rs
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket},
|
||||||
|
sync::mpsc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use satrs::pus::HandlingStatus;
|
||||||
|
use satrs_minisim::{
|
||||||
|
udp::SIM_CTRL_PORT, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimReply,
|
||||||
|
SimRequest,
|
||||||
|
};
|
||||||
|
use satrs_minisim::{SimCtrlReply, SimCtrlRequest};
|
||||||
|
|
||||||
|
struct SimReplyMap(pub HashMap<SimComponent, mpsc::Sender<SimReply>>);
|
||||||
|
|
||||||
|
pub fn create_sim_client(sim_request_rx: mpsc::Receiver<SimRequest>) -> Option<SimClientUdp> {
|
||||||
|
match SimClientUdp::new(
|
||||||
|
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, SIM_CTRL_PORT)),
|
||||||
|
sim_request_rx,
|
||||||
|
) {
|
||||||
|
Ok(sim_client) => {
|
||||||
|
log::info!("simulator client connection success");
|
||||||
|
return Some(sim_client);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("sim client creation error: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum SimClientCreationError {
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("timeout when trying to connect to sim UDP server")]
|
||||||
|
Timeout,
|
||||||
|
#[error("invalid ping reply when trying connection to UDP sim server")]
|
||||||
|
InvalidReplyJsonError(#[from] serde_json::Error),
|
||||||
|
#[error("invalid sim reply, not pong reply as expected: {0:?}")]
|
||||||
|
ReplyIsNotPong(SimReply),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SimClientUdp {
|
||||||
|
udp_client: UdpSocket,
|
||||||
|
simulator_addr: SocketAddr,
|
||||||
|
sim_request_rx: mpsc::Receiver<SimRequest>,
|
||||||
|
reply_map: SimReplyMap,
|
||||||
|
reply_buf: [u8; 4096],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimClientUdp {
|
||||||
|
pub fn new(
|
||||||
|
simulator_addr: SocketAddr,
|
||||||
|
sim_request_rx: mpsc::Receiver<SimRequest>,
|
||||||
|
) -> Result<Self, SimClientCreationError> {
|
||||||
|
let mut reply_buf: [u8; 4096] = [0; 4096];
|
||||||
|
let mut udp_client = UdpSocket::bind("127.0.0.1:0")?;
|
||||||
|
udp_client.set_read_timeout(Some(Duration::from_millis(100)))?;
|
||||||
|
Self::attempt_connection(&mut udp_client, simulator_addr, &mut reply_buf)?;
|
||||||
|
udp_client.set_nonblocking(true)?;
|
||||||
|
Ok(Self {
|
||||||
|
udp_client,
|
||||||
|
simulator_addr,
|
||||||
|
sim_request_rx,
|
||||||
|
reply_map: SimReplyMap(HashMap::new()),
|
||||||
|
reply_buf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attempt_connection(
|
||||||
|
udp_client: &mut UdpSocket,
|
||||||
|
simulator_addr: SocketAddr,
|
||||||
|
reply_buf: &mut [u8],
|
||||||
|
) -> Result<(), SimClientCreationError> {
|
||||||
|
let sim_req = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping);
|
||||||
|
let sim_req_json = serde_json::to_string(&sim_req).expect("failed to serialize SimRequest");
|
||||||
|
udp_client.send_to(sim_req_json.as_bytes(), simulator_addr)?;
|
||||||
|
match udp_client.recv(reply_buf) {
|
||||||
|
Ok(reply_len) => {
|
||||||
|
let sim_reply: SimReply = serde_json::from_slice(&reply_buf[0..reply_len])?;
|
||||||
|
if sim_reply.component() != SimComponent::SimCtrl {
|
||||||
|
return Err(SimClientCreationError::ReplyIsNotPong(sim_reply));
|
||||||
|
}
|
||||||
|
let sim_ctrl_reply =
|
||||||
|
SimCtrlReply::from_sim_message(&sim_reply).expect("invalid SIM reply");
|
||||||
|
match sim_ctrl_reply {
|
||||||
|
SimCtrlReply::InvalidRequest(_) => {
|
||||||
|
panic!("received invalid request reply from UDP sim server")
|
||||||
|
}
|
||||||
|
SimCtrlReply::Pong => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == std::io::ErrorKind::TimedOut
|
||||||
|
|| e.kind() == std::io::ErrorKind::WouldBlock
|
||||||
|
{
|
||||||
|
Err(SimClientCreationError::Timeout)
|
||||||
|
} else {
|
||||||
|
Err(SimClientCreationError::Io(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operation(&mut self) -> HandlingStatus {
|
||||||
|
let mut no_sim_requests_handled = true;
|
||||||
|
let mut no_data_from_udp_server_received = true;
|
||||||
|
loop {
|
||||||
|
match self.sim_request_rx.try_recv() {
|
||||||
|
Ok(request) => {
|
||||||
|
let request_json =
|
||||||
|
serde_json::to_string(&request).expect("failed to serialize SimRequest");
|
||||||
|
if let Err(e) = self
|
||||||
|
.udp_client
|
||||||
|
.send_to(request_json.as_bytes(), self.simulator_addr)
|
||||||
|
{
|
||||||
|
log::error!("error sending data to UDP SIM server: {e}");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
no_sim_requests_handled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mpsc::TryRecvError::Disconnected => {
|
||||||
|
log::warn!("SIM request sender disconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
match self.udp_client.recv(&mut self.reply_buf) {
|
||||||
|
Ok(recvd_bytes) => {
|
||||||
|
no_data_from_udp_server_received = false;
|
||||||
|
let sim_reply_result: serde_json::Result<SimReply> =
|
||||||
|
serde_json::from_slice(&self.reply_buf[0..recvd_bytes]);
|
||||||
|
match sim_reply_result {
|
||||||
|
Ok(sim_reply) => {
|
||||||
|
if let Some(sender) = self.reply_map.0.get(&sim_reply.component()) {
|
||||||
|
sender.send(sim_reply).expect("failed to send SIM reply");
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"no recipient for SIM reply from component {:?}",
|
||||||
|
sim_reply.component()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to deserialize SIM reply: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == std::io::ErrorKind::WouldBlock
|
||||||
|
|| e.kind() == std::io::ErrorKind::TimedOut
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log::error!("error receiving data from UDP SIM server: {e}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if no_sim_requests_handled && no_data_from_udp_server_received {
|
||||||
|
return HandlingStatus::Empty;
|
||||||
|
}
|
||||||
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_reply_recipient(
|
||||||
|
&mut self,
|
||||||
|
component: SimComponent,
|
||||||
|
reply_sender: mpsc::Sender<SimReply>,
|
||||||
|
) {
|
||||||
|
self.reply_map.0.insert(component, reply_sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::{SocketAddr, UdpSocket},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
mpsc, Arc,
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use satrs_minisim::{
|
||||||
|
eps::{PcduReply, PcduRequest},
|
||||||
|
SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider,
|
||||||
|
SimReply, SimRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::SimClientUdp;
|
||||||
|
|
||||||
|
struct UdpSimTestServer {
|
||||||
|
udp_server: UdpSocket,
|
||||||
|
request_tx: mpsc::Sender<SimRequest>,
|
||||||
|
reply_rx: mpsc::Receiver<SimReply>,
|
||||||
|
last_sender: Option<SocketAddr>,
|
||||||
|
stop_signal: Arc<AtomicBool>,
|
||||||
|
recv_buf: [u8; 1024],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UdpSimTestServer {
|
||||||
|
pub fn new(
|
||||||
|
request_tx: mpsc::Sender<SimRequest>,
|
||||||
|
reply_rx: mpsc::Receiver<SimReply>,
|
||||||
|
stop_signal: Arc<AtomicBool>,
|
||||||
|
) -> Self {
|
||||||
|
let udp_server = UdpSocket::bind("127.0.0.1:0").expect("creating UDP server failed");
|
||||||
|
udp_server
|
||||||
|
.set_nonblocking(true)
|
||||||
|
.expect("failed to set UDP server to non-blocking");
|
||||||
|
Self {
|
||||||
|
udp_server,
|
||||||
|
request_tx,
|
||||||
|
reply_rx,
|
||||||
|
last_sender: None,
|
||||||
|
stop_signal,
|
||||||
|
recv_buf: [0; 1024],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operation(&mut self) {
|
||||||
|
loop {
|
||||||
|
let mut no_sim_replies_handled = true;
|
||||||
|
let mut no_data_received = true;
|
||||||
|
if self.stop_signal.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(last_sender) = self.last_sender {
|
||||||
|
loop {
|
||||||
|
match self.reply_rx.try_recv() {
|
||||||
|
Ok(sim_reply) => {
|
||||||
|
let sim_reply_json = serde_json::to_string(&sim_reply)
|
||||||
|
.expect("failed to serialize SimReply");
|
||||||
|
self.udp_server
|
||||||
|
.send_to(sim_reply_json.as_bytes(), last_sender)
|
||||||
|
.expect("failed to send reply to client from UDP server");
|
||||||
|
no_sim_replies_handled = false;
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => break,
|
||||||
|
mpsc::TryRecvError::Disconnected => {
|
||||||
|
panic!("reply sender disconnected")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.udp_server.recv_from(&mut self.recv_buf) {
|
||||||
|
Ok((read_bytes, from)) => {
|
||||||
|
let sim_request: SimRequest =
|
||||||
|
serde_json::from_slice(&self.recv_buf[0..read_bytes])
|
||||||
|
.expect("failed to deserialize SimRequest");
|
||||||
|
if sim_request.component() == SimComponent::SimCtrl {
|
||||||
|
// For a ping, we perform the reply handling here directly
|
||||||
|
let sim_ctrl_request =
|
||||||
|
SimCtrlRequest::from_sim_message(&sim_request)
|
||||||
|
.expect("failed to convert SimRequest to SimCtrlRequest");
|
||||||
|
match sim_ctrl_request {
|
||||||
|
SimCtrlRequest::Ping => {
|
||||||
|
no_data_received = false;
|
||||||
|
self.last_sender = Some(from);
|
||||||
|
let sim_reply = SimReply::new(&SimCtrlReply::Pong);
|
||||||
|
let sim_reply_json = serde_json::to_string(&sim_reply)
|
||||||
|
.expect("failed to serialize SimReply");
|
||||||
|
self.udp_server
|
||||||
|
.send_to(sim_reply_json.as_bytes(), from)
|
||||||
|
.expect(
|
||||||
|
"failed to send reply to client from UDP server",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Forward each SIM request for testing purposes.
|
||||||
|
self.request_tx
|
||||||
|
.send(sim_request)
|
||||||
|
.expect("failed to send request");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() != std::io::ErrorKind::WouldBlock
|
||||||
|
&& e.kind() != std::io::ErrorKind::TimedOut
|
||||||
|
{
|
||||||
|
panic!("UDP server error: {}", e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if no_sim_replies_handled && no_data_received {
|
||||||
|
std::thread::sleep(Duration::from_millis(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.udp_server.local_addr().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_connection_test() {
|
||||||
|
let (server_sim_request_tx, server_sim_request_rx) = mpsc::channel();
|
||||||
|
let (_server_sim_reply_tx, server_sim_reply_rx) = mpsc::channel();
|
||||||
|
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||||
|
let mut udp_server = UdpSimTestServer::new(
|
||||||
|
server_sim_request_tx,
|
||||||
|
server_sim_reply_rx,
|
||||||
|
stop_signal.clone(),
|
||||||
|
);
|
||||||
|
let server_addr = udp_server.local_addr();
|
||||||
|
let (_client_sim_req_tx, client_sim_req_rx) = mpsc::channel();
|
||||||
|
// Need to spawn the simulator UDP server before calling the client constructor.
|
||||||
|
let jh0 = std::thread::spawn(move || {
|
||||||
|
udp_server.operation();
|
||||||
|
});
|
||||||
|
// Creating the client also performs the connection test.
|
||||||
|
SimClientUdp::new(server_addr, client_sim_req_rx).unwrap();
|
||||||
|
let sim_request = server_sim_request_rx
|
||||||
|
.recv_timeout(Duration::from_millis(50))
|
||||||
|
.expect("no SIM request received");
|
||||||
|
let ping_request = SimCtrlRequest::from_sim_message(&sim_request)
|
||||||
|
.expect("failed to create SimCtrlRequest");
|
||||||
|
assert_eq!(ping_request, SimCtrlRequest::Ping);
|
||||||
|
// Stop the server.
|
||||||
|
stop_signal.store(true, Ordering::Relaxed);
|
||||||
|
jh0.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_request_reply_test() {
|
||||||
|
let (server_sim_request_tx, server_sim_request_rx) = mpsc::channel();
|
||||||
|
let (server_sim_reply_tx, sever_sim_reply_rx) = mpsc::channel();
|
||||||
|
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||||
|
let mut udp_server = UdpSimTestServer::new(
|
||||||
|
server_sim_request_tx,
|
||||||
|
sever_sim_reply_rx,
|
||||||
|
stop_signal.clone(),
|
||||||
|
);
|
||||||
|
let server_addr = udp_server.local_addr();
|
||||||
|
let (client_sim_req_tx, client_sim_req_rx) = mpsc::channel();
|
||||||
|
let (client_pcdu_reply_tx, client_pcdu_reply_rx) = mpsc::channel();
|
||||||
|
// Need to spawn the simulator UDP server before calling the client constructor.
|
||||||
|
let jh0 = std::thread::spawn(move || {
|
||||||
|
udp_server.operation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Creating the client also performs the connection test.
|
||||||
|
let mut client = SimClientUdp::new(server_addr, client_sim_req_rx).unwrap();
|
||||||
|
client.add_reply_recipient(SimComponent::Pcdu, client_pcdu_reply_tx);
|
||||||
|
|
||||||
|
let sim_request = server_sim_request_rx
|
||||||
|
.recv_timeout(Duration::from_millis(50))
|
||||||
|
.expect("no SIM request received");
|
||||||
|
let ping_request = SimCtrlRequest::from_sim_message(&sim_request)
|
||||||
|
.expect("failed to create SimCtrlRequest");
|
||||||
|
assert_eq!(ping_request, SimCtrlRequest::Ping);
|
||||||
|
|
||||||
|
let pcdu_req = PcduRequest::RequestSwitchInfo;
|
||||||
|
client_sim_req_tx
|
||||||
|
.send(SimRequest::new_with_epoch_time(pcdu_req))
|
||||||
|
.expect("send failed");
|
||||||
|
client.operation();
|
||||||
|
|
||||||
|
// Check that the request arrives properly at the server.
|
||||||
|
let sim_request = server_sim_request_rx
|
||||||
|
.recv_timeout(Duration::from_millis(50))
|
||||||
|
.expect("no SIM request received");
|
||||||
|
let req_recvd_on_server =
|
||||||
|
PcduRequest::from_sim_message(&sim_request).expect("failed to create SimCtrlRequest");
|
||||||
|
matches!(req_recvd_on_server, PcduRequest::RequestSwitchInfo);
|
||||||
|
|
||||||
|
// We inject the reply ourselves.
|
||||||
|
let pcdu_reply = PcduReply::SwitchInfo(HashMap::new());
|
||||||
|
server_sim_reply_tx
|
||||||
|
.send(SimReply::new(&pcdu_reply))
|
||||||
|
.expect("sending PCDU reply failed");
|
||||||
|
|
||||||
|
// Now we verify that the reply is sent by the UDP server back to the client, and then
|
||||||
|
// forwarded by the clients internal map.
|
||||||
|
let mut pcdu_reply_received = false;
|
||||||
|
for _ in 0..3 {
|
||||||
|
client.operation();
|
||||||
|
|
||||||
|
match client_pcdu_reply_rx.try_recv() {
|
||||||
|
Ok(sim_reply) => {
|
||||||
|
assert_eq!(sim_reply.component(), SimComponent::Pcdu);
|
||||||
|
let pcdu_reply_from_client = PcduReply::from_sim_message(&sim_reply)
|
||||||
|
.expect("failed to create PcduReply");
|
||||||
|
assert_eq!(pcdu_reply_from_client, pcdu_reply);
|
||||||
|
pcdu_reply_received = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => std::thread::sleep(Duration::from_millis(10)),
|
||||||
|
mpsc::TryRecvError::Disconnected => panic!("reply sender disconnected"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pcdu_reply_received {
|
||||||
|
panic!("no reply received");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the server.
|
||||||
|
stop_signal.store(true, Ordering::Relaxed);
|
||||||
|
jh0.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{HashSet, VecDeque},
|
||||||
fmt::Debug,
|
|
||||||
marker::PhantomData,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use satrs::hal::std::tcp_spacepackets_server::CcsdsPacketParser;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
||||||
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
|
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
|
||||||
spacepackets::{CcsdsPacket, PacketId},
|
spacepackets::{CcsdsPacket, PacketId},
|
||||||
tmtc::{PacketSenderRaw, PacketSource},
|
tmtc::PacketSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ConnectionFinishedHandler {}
|
pub struct ConnectionFinishedHandler {}
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ impl SpacePacketValidator for SimplePacketValidator {
|
|||||||
if self.valid_ids.contains(&sp_header.packet_id()) {
|
if self.valid_ids.contains(&sp_header.packet_id()) {
|
||||||
return SpValidity::Valid;
|
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
|
// We could perform a CRC check.. but lets keep this simple and assume that TCP ensures
|
||||||
// data integrity.
|
// data integrity.
|
||||||
SpValidity::Skip
|
SpValidity::Skip
|
||||||
@@ -102,40 +103,29 @@ impl PacketSource for SyncTcpTmSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
|
pub type TcpServer<ReceivesTc> = TcpSpacepacketsServer<
|
||||||
SyncTcpTmSource,
|
SyncTcpTmSource,
|
||||||
ReceivesTc,
|
ReceivesTc,
|
||||||
SimplePacketValidator,
|
SimplePacketValidator,
|
||||||
ConnectionFinishedHandler,
|
ConnectionFinishedHandler,
|
||||||
(),
|
|
||||||
SendError,
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
|
pub struct TcpTask(pub TcpServer<TmTcSender>);
|
||||||
pub TcpServer<TcSender, SendError>,
|
|
||||||
PhantomData<SendError>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
impl TcpTask {
|
||||||
TcpTask<TcSender, SendError>
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cfg: ServerConfig,
|
cfg: ServerConfig,
|
||||||
tm_source: SyncTcpTmSource,
|
tm_source: SyncTcpTmSource,
|
||||||
tc_sender: TcSender,
|
tc_sender: TmTcSender,
|
||||||
valid_ids: HashSet<PacketId>,
|
valid_ids: HashSet<PacketId>,
|
||||||
) -> Result<Self, std::io::Error> {
|
) -> Result<Self, std::io::Error> {
|
||||||
Ok(Self(
|
Ok(Self(TcpSpacepacketsServer::new(
|
||||||
TcpSpacepacketsServer::new(
|
cfg,
|
||||||
cfg,
|
tm_source,
|
||||||
tm_source,
|
CcsdsPacketParser::new(cfg.id, 2048, tc_sender, SimplePacketValidator { valid_ids }),
|
||||||
tc_sender,
|
ConnectionFinishedHandler::default(),
|
||||||
SimplePacketValidator { valid_ids },
|
None,
|
||||||
ConnectionFinishedHandler::default(),
|
)?))
|
||||||
None,
|
|
||||||
)?,
|
|
||||||
PhantomData,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use core::fmt::Debug;
|
#![allow(dead_code)]
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
use std::sync::mpsc;
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||||
use satrs::pus::HandlingStatus;
|
use satrs::pus::HandlingStatus;
|
||||||
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
|
use satrs::queue::GenericSendError;
|
||||||
use satrs::{
|
|
||||||
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
|
||||||
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait UdpTmHandler {
|
use satrs_example::CcsdsTmPacketOwned;
|
||||||
|
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
|
pub trait UdpTmHandlerProvider {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub struct StaticUdpTmHandler {
|
pub struct StaticUdpTmHandler {
|
||||||
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
||||||
pub tm_store: SharedStaticMemoryPool,
|
pub tm_store: SharedStaticMemoryPool,
|
||||||
@@ -42,22 +45,17 @@ impl UdpTmHandler for StaticUdpTmHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub struct DynamicUdpTmHandler {
|
pub struct UdpTmHandlerWithChannel {
|
||||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpTmHandler for DynamicUdpTmHandler {
|
impl UdpTmHandlerProvider for UdpTmHandlerWithChannel {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||||
while let Ok(tm) = self.tm_rx.try_recv() {
|
while let Ok(tm) = self.tm_rx.try_recv() {
|
||||||
if tm.packet.len() > 9 {
|
info!("Sending PUS TM with header {:?}", tm.tm_header);
|
||||||
let service = tm.packet[7];
|
let result = socket.send_to(&tm.to_vec(), recv_addr);
|
||||||
let subservice = tm.packet[8];
|
|
||||||
info!("Sending PUS TM[{service},{subservice}]")
|
|
||||||
} else {
|
|
||||||
info!("Sending PUS TM");
|
|
||||||
}
|
|
||||||
let result = socket.send_to(&tm.packet, recv_addr);
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
warn!("Sending TM with UDP socket failed: {e}")
|
warn!("Sending TM with UDP socket failed: {e}")
|
||||||
}
|
}
|
||||||
@@ -65,21 +63,49 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UdpTmtcServer<
|
#[derive(Default, Debug, Clone)]
|
||||||
TcSender: PacketSenderRaw<Error = SendError>,
|
pub struct TestTmHandler {
|
||||||
TmHandler: UdpTmHandler,
|
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
|
||||||
SendError,
|
|
||||||
> {
|
|
||||||
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
|
|
||||||
pub tm_handler: TmHandler,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl UdpTmHandlerProvider for TestTmHandler {
|
||||||
TcSender: PacketSenderRaw<Error = SendError>,
|
fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||||
TmHandler: UdpTmHandler,
|
self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr);
|
||||||
SendError: Debug + 'static,
|
}
|
||||||
> UdpTmtcServer<TcSender, TmHandler, SendError>
|
}
|
||||||
{
|
|
||||||
|
pub enum UdpTmHandler {
|
||||||
|
Normal(UdpTmHandlerWithChannel),
|
||||||
|
Test(TestTmHandler),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UdpTmHandlerWithChannel> for UdpTmHandler {
|
||||||
|
fn from(handler: UdpTmHandlerWithChannel) -> Self {
|
||||||
|
UdpTmHandler::Normal(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TestTmHandler> for UdpTmHandler {
|
||||||
|
fn from(handler: TestTmHandler) -> Self {
|
||||||
|
UdpTmHandler::Test(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UdpTmHandlerProvider for UdpTmHandler {
|
||||||
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||||
|
match self {
|
||||||
|
UdpTmHandler::Normal(handler) => handler.send_tm_to_udp_client(socket, recv_addr),
|
||||||
|
UdpTmHandler::Test(handler) => handler.send_tm_to_udp_client(socket, recv_addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UdpTmtcServer {
|
||||||
|
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>,
|
||||||
|
pub tm_handler: UdpTmHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UdpTmtcServer {
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if self.poll_tc_server() == HandlingStatus::Empty {
|
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||||
@@ -113,69 +139,48 @@ impl<
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::VecDeque,
|
|
||||||
net::IpAddr,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use arbitrary_int::traits::Integer as _;
|
||||||
|
use arbitrary_int::u14;
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||||
SpHeader,
|
SpHeader,
|
||||||
},
|
},
|
||||||
tmtc::PacketSenderRaw,
|
|
||||||
ComponentId,
|
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;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
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>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpTmHandler for TestTmHandler {
|
|
||||||
fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) {
|
|
||||||
self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||||
let test_receiver = TestSender::default();
|
let test_receiver = TmTcSender::Mock(MockSender::default());
|
||||||
// let tc_queue = test_receiver.tc_vec.clone();
|
|
||||||
let udp_tc_server =
|
let udp_tc_server =
|
||||||
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||||
let tm_handler = TestTmHandler::default();
|
let tm_handler = TestTmHandler::default();
|
||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
let mut udp_dyn_server = UdpTmtcServer {
|
let mut udp_dyn_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler,
|
tm_handler: tm_handler.into(),
|
||||||
};
|
};
|
||||||
udp_dyn_server.periodic_operation();
|
udp_dyn_server.periodic_operation();
|
||||||
let queue = udp_dyn_server.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!(queue.is_empty());
|
||||||
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
||||||
}
|
}
|
||||||
@@ -183,8 +188,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_transactions() {
|
fn test_transactions() {
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
|
let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
|
||||||
let test_receiver = TestSender::default();
|
let test_receiver = TmTcSender::Mock(MockSender::default());
|
||||||
// let tc_queue = test_receiver.tc_vec.clone();
|
|
||||||
let udp_tc_server =
|
let udp_tc_server =
|
||||||
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||||
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
||||||
@@ -192,19 +196,30 @@ mod tests {
|
|||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
let mut udp_dyn_server = UdpTmtcServer {
|
let mut udp_dyn_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler,
|
tm_handler: tm_handler.into(),
|
||||||
};
|
};
|
||||||
let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
|
let sph = SpHeader::new_for_unseg_tc(ids::Apid::GenericPus.raw_value(), u14::ZERO, 0);
|
||||||
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
|
let ping_tc = PusTcCreator::new_simple(
|
||||||
.to_vec()
|
sph,
|
||||||
.unwrap();
|
MessageTypeId::new(17, 1),
|
||||||
|
&[],
|
||||||
|
CreatorConfig::default(),
|
||||||
|
)
|
||||||
|
.to_vec()
|
||||||
|
.unwrap();
|
||||||
let client = UdpSocket::bind("127.0.0.1:0").expect("Connecting to UDP server failed");
|
let client = UdpSocket::bind("127.0.0.1:0").expect("Connecting to UDP server failed");
|
||||||
let client_addr = client.local_addr().unwrap();
|
let client_addr = client.local_addr().unwrap();
|
||||||
println!("{}", server_addr);
|
println!("{}", server_addr);
|
||||||
client.send_to(&ping_tc, server_addr).unwrap();
|
client.send_to(&ping_tc, server_addr).unwrap();
|
||||||
udp_dyn_server.periodic_operation();
|
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());
|
assert!(!queue.is_empty());
|
||||||
let packet_with_sender = queue.pop_front().unwrap();
|
let packet_with_sender = queue.pop_front().unwrap();
|
||||||
assert_eq!(packet_with_sender.packet, ping_tc);
|
assert_eq!(packet_with_sender.packet, ping_tc);
|
||||||
@@ -219,7 +234,13 @@ mod tests {
|
|||||||
assert_eq!(received_addr, client_addr);
|
assert_eq!(received_addr, client_addr);
|
||||||
}
|
}
|
||||||
udp_dyn_server.periodic_operation();
|
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!(queue.is_empty());
|
||||||
drop(queue);
|
drop(queue);
|
||||||
// Still tries to send to the same client.
|
// Still tries to send to the same client.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use satrs::action::{ActionRequest, ActionRequestVariant};
|
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||||
use satrs::pool::SharedStaticMemoryPool;
|
|
||||||
use satrs::pus::action::{
|
use satrs::pus::action::{
|
||||||
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
|
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
|
||||||
};
|
};
|
||||||
@@ -10,21 +9,21 @@ use satrs::pus::verification::{
|
|||||||
VerificationReportingProvider, VerificationToken,
|
VerificationReportingProvider, VerificationToken,
|
||||||
};
|
};
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
ActiveRequest, EcssTcAndToken, EcssTcCacher, EcssTmSender, EcssTmtcError,
|
||||||
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
|
GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler,
|
||||||
MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper,
|
PusServiceHelper, PusTcToRequestConverter,
|
||||||
PusTcToRequestConverter,
|
|
||||||
};
|
};
|
||||||
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId};
|
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::config::tmtc_err;
|
||||||
|
use satrs_example::ids;
|
||||||
|
use satrs_example::ids::generic_pus::PUS_ACTION;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::requests::GenericRequestRouter;
|
use crate::requests::GenericRequestRouter;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
||||||
@@ -162,7 +161,7 @@ impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for Actio
|
|||||||
verif_reporter: &impl VerificationReportingProvider,
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
time_stamp: &[u8],
|
time_stamp: &[u8],
|
||||||
) -> Result<(ActivePusActionRequestStd, ActionRequest), Self::Error> {
|
) -> Result<(ActivePusActionRequestStd, ActionRequest), Self::Error> {
|
||||||
let subservice = tc.subservice();
|
let subservice = tc.message_subtype_id();
|
||||||
let user_data = tc.user_data();
|
let user_data = tc.user_data();
|
||||||
if user_data.len() < 8 {
|
if user_data.len() < 8 {
|
||||||
verif_reporter
|
verif_reporter
|
||||||
@@ -207,20 +206,20 @@ impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for Actio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_action_service_static(
|
pub fn create_action_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_in_mem_converter: EcssTcCacher,
|
||||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
action_router: GenericRequestRouter,
|
action_router: GenericRequestRouter,
|
||||||
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||||
) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> ActionServiceWrapper {
|
||||||
let action_request_handler = PusTargetedRequestService::new(
|
let action_request_handler = PusTargetedRequestService::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
PUS_ACTION_SERVICE.id(),
|
ids::generic_pus::PUS_ACTION.id(),
|
||||||
pus_action_rx,
|
pus_action_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
create_verification_reporter(PUS_ACTION.id(), PUS_ACTION.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
tc_in_mem_converter,
|
||||||
),
|
),
|
||||||
ActionRequestConverter::default(),
|
ActionRequestConverter::default(),
|
||||||
// TODO: Implementation which does not use run-time allocation? Maybe something like
|
// 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(
|
pub struct ActionServiceWrapper {
|
||||||
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(crate) service: PusTargetedRequestService<
|
pub(crate) service: PusTargetedRequestService<
|
||||||
MpscTcReceiver,
|
MpscTcReceiver,
|
||||||
TmSender,
|
|
||||||
TcInMemConverter,
|
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
ActionRequestConverter,
|
ActionRequestConverter,
|
||||||
ActionReplyHandler,
|
ActionReplyHandler,
|
||||||
@@ -275,9 +247,7 @@ pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTc
|
|||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
impl TargetedPusService for ActionServiceWrapper {
|
||||||
for ActionServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
const SERVICE_ID: u8 = PusServiceId::Action as u8;
|
const SERVICE_ID: u8 = PusServiceId::Action as u8;
|
||||||
const SERVICE_STR: &'static str = "action";
|
const SERVICE_STR: &'static str = "action";
|
||||||
|
|
||||||
@@ -300,12 +270,15 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPus
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use arbitrary_int::traits::Integer as _;
|
||||||
use satrs::pus::test_util::{
|
use satrs::pus::test_util::{
|
||||||
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
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::test_util::TestVerificationReporter;
|
||||||
|
use satrs::pus::{verification, EcssTcVecCacher};
|
||||||
use satrs::request::MessageMetadata;
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
|
use satrs::tmtc::PacketAsVec;
|
||||||
use satrs::ComponentId;
|
use satrs::ComponentId;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
res_code::ResultU16,
|
res_code::ResultU16,
|
||||||
@@ -338,10 +311,10 @@ mod tests {
|
|||||||
{
|
{
|
||||||
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
|
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
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 (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||||
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||||
let (action_req_tx, action_req_rx) = mpsc::channel();
|
let (action_req_tx, action_req_rx) = mpsc::sync_channel(10);
|
||||||
let verif_reporter = TestVerificationReporter::new(owner_id);
|
let verif_reporter = TestVerificationReporter::new(owner_id);
|
||||||
let mut generic_req_router = GenericRequestRouter::default();
|
let mut generic_req_router = GenericRequestRouter::default();
|
||||||
generic_req_router
|
generic_req_router
|
||||||
@@ -352,9 +325,9 @@ mod tests {
|
|||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
owner_id,
|
owner_id,
|
||||||
pus_action_rx,
|
pus_action_rx,
|
||||||
tm_funnel_tx.clone(),
|
TmTcSender::Heap(tm_funnel_tx.clone()),
|
||||||
verif_reporter,
|
verif_reporter,
|
||||||
EcssTcInVecConverter::default(),
|
EcssTcCacher::Heap(EcssTcVecCacher::default()),
|
||||||
),
|
),
|
||||||
ActionRequestConverter::default(),
|
ActionRequestConverter::default(),
|
||||||
DefaultActiveActionRequestMap::default(),
|
DefaultActiveActionRequestMap::default(),
|
||||||
@@ -397,7 +370,7 @@ mod tests {
|
|||||||
if let Err(mpsc::TryRecvError::Empty) = packet {
|
if let Err(mpsc::TryRecvError::Empty) = packet {
|
||||||
} else {
|
} else {
|
||||||
let tm = packet.unwrap();
|
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:?}");
|
panic!("unexpected TM packet {unexpected_tm:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,7 +413,11 @@ mod tests {
|
|||||||
|
|
||||||
pub fn add_tc(&mut self, tc: &PusTcCreator) {
|
pub fn add_tc(&mut self, tc: &PusTcCreator) {
|
||||||
self.request_id = Some(verification::RequestId::new(tc).into());
|
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
|
let accepted_token = self
|
||||||
.service
|
.service
|
||||||
.service_helper
|
.service_helper
|
||||||
@@ -473,12 +450,13 @@ mod tests {
|
|||||||
);
|
);
|
||||||
// Create a basic action request and verify forwarding.
|
// Create a basic action request and verify forwarding.
|
||||||
let sp_header = SpHeader::new_from_apid(TEST_APID);
|
let sp_header = SpHeader::new_from_apid(TEST_APID);
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(8, 128));
|
||||||
let action_id = 5_u32;
|
let action_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
let pus8_packet = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
let pus8_packet =
|
||||||
|
PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
||||||
testbench.add_tc(&pus8_packet);
|
testbench.add_tc(&pus8_packet);
|
||||||
let time_stamp: [u8; 7] = [0; 7];
|
let time_stamp: [u8; 7] = [0; 7];
|
||||||
testbench.verify_next_tc_is_handled_properly(&time_stamp);
|
testbench.verify_next_tc_is_handled_properly(&time_stamp);
|
||||||
@@ -514,7 +492,7 @@ mod tests {
|
|||||||
TEST_COMPONENT_ID_1.id(),
|
TEST_COMPONENT_ID_1.id(),
|
||||||
);
|
);
|
||||||
// Create a basic action request and verify forwarding.
|
// Create a basic action request and verify forwarding.
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(8, 128));
|
||||||
let action_id = 5_u32;
|
let action_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
// Invalid ID, routing should fail.
|
// Invalid ID, routing should fail.
|
||||||
@@ -524,7 +502,7 @@ mod tests {
|
|||||||
SpHeader::new_from_apid(TEST_APID),
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
sec_header,
|
sec_header,
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
testbench.add_tc(&pus8_packet);
|
testbench.add_tc(&pus8_packet);
|
||||||
let time_stamp: [u8; 7] = [0; 7];
|
let time_stamp: [u8; 7] = [0; 7];
|
||||||
@@ -540,17 +518,17 @@ mod tests {
|
|||||||
TEST_COMPONENT_ID_0.raw(),
|
TEST_COMPONENT_ID_0.raw(),
|
||||||
ActionRequestConverter::default(),
|
ActionRequestConverter::default(),
|
||||||
);
|
);
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(8, 128));
|
||||||
let action_id = 5_u32;
|
let action_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
// Invalid ID, routing should fail.
|
// Invalid ID, routing should fail.
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
let pus8_packet = PusTcCreator::new(
|
let pus8_packet = PusTcCreator::new(
|
||||||
SpHeader::new_from_apid(TEST_APID),
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
sec_header,
|
sec_header,
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
let token = testbench.add_tc(&pus8_packet);
|
let token = testbench.add_tc(&pus8_packet);
|
||||||
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||||
@@ -576,11 +554,11 @@ mod tests {
|
|||||||
fn converter_action_req_with_data() {
|
fn converter_action_req_with_data() {
|
||||||
let mut testbench =
|
let mut testbench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ActionRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ActionRequestConverter::default());
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(8, 128));
|
||||||
let action_id = 5_u32;
|
let action_id = 5_u32;
|
||||||
let mut app_data: [u8; 16] = [0; 16];
|
let mut app_data: [u8; 16] = [0; 16];
|
||||||
// Invalid ID, routing should fail.
|
// Invalid ID, routing should fail.
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
app_data[i + 8] = i as u8;
|
app_data[i + 8] = i as u8;
|
||||||
@@ -589,7 +567,7 @@ mod tests {
|
|||||||
SpHeader::new_from_apid(TEST_APID),
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
sec_header,
|
sec_header,
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
let token = testbench.add_tc(&pus8_packet);
|
let token = testbench.add_tc(&pus8_packet);
|
||||||
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||||
@@ -719,7 +697,7 @@ mod tests {
|
|||||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
let action_reply = ActionReplyPus::new(5_u32, ActionReplyVariant::Completed);
|
let action_reply = ActionReplyPus::new(5_u32, ActionReplyVariant::Completed);
|
||||||
let unrequested_reply =
|
let unrequested_reply =
|
||||||
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u32), action_reply);
|
||||||
// Right now this function does not do a lot. We simply check that it does not panic or do
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
// weird stuff.
|
// weird stuff.
|
||||||
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use crate::pus::create_verification_reporter;
|
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_man::EventRequestWithToken;
|
||||||
use satrs::pus::event_srv::PusEventServiceHandler;
|
use satrs::pus::event_srv::PusEventServiceHandler;
|
||||||
use satrs::pus::verification::VerificationReporter;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
|
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher, MpscTcReceiver,
|
||||||
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
|
PartialPusHandlingError, PusServiceHelper,
|
||||||
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
|
|
||||||
};
|
};
|
||||||
use satrs::spacepackets::ecss::PusServiceId;
|
use satrs::spacepackets::ecss::PusServiceId;
|
||||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT;
|
||||||
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
|
|
||||||
|
|
||||||
use super::{DirectPusService, HandlingStatus};
|
use super::{DirectPusService, HandlingStatus};
|
||||||
|
|
||||||
pub fn create_event_service_static(
|
pub fn create_event_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tm_in_pool_converter: EcssTcCacher,
|
||||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||||
) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> EventServiceWrapper {
|
||||||
let pus_5_handler = PusEventServiceHandler::new(
|
let pus_5_handler = PusEventServiceHandler::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
PUS_EVENT_MANAGEMENT.id(),
|
PUS_EVENT_MANAGEMENT.id(),
|
||||||
pus_event_rx,
|
pus_event_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
tm_in_pool_converter,
|
||||||
),
|
),
|
||||||
event_request_tx,
|
event_request_tx,
|
||||||
);
|
);
|
||||||
@@ -37,34 +35,12 @@ pub fn create_event_service_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_event_service_dynamic(
|
pub struct EventServiceWrapper {
|
||||||
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 handler:
|
pub handler:
|
||||||
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
PusEventServiceHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
impl DirectPusService for EventServiceWrapper {
|
||||||
for EventServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
const SERVICE_ID: u8 = PusServiceId::Event as u8;
|
const SERVICE_ID: u8 = PusServiceId::Event as u8;
|
||||||
|
|
||||||
const SERVICE_STR: &'static str = "events";
|
const SERVICE_STR: &'static str = "events";
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
|
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
|
||||||
use satrs::pool::SharedStaticMemoryPool;
|
|
||||||
use satrs::pus::verification::{
|
use satrs::pus::verification::{
|
||||||
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||||
VerificationReportingProvider, VerificationToken,
|
VerificationReportingProvider, VerificationToken,
|
||||||
};
|
};
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
|
ActivePusRequestStd, ActiveRequest, DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher,
|
||||||
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
|
EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, PusPacketHandlingError,
|
||||||
EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
|
PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
|
||||||
PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
|
|
||||||
};
|
};
|
||||||
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||||
|
use satrs::res_code::ResultU16;
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId};
|
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::config::{hk_err, tmtc_err};
|
||||||
|
use satrs_example::ids::generic_pus::PUS_HK;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
|
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
|
||||||
use crate::requests::GenericRequestRouter;
|
use crate::requests::GenericRequestRouter;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
|
|
||||||
use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService};
|
use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService};
|
||||||
|
|
||||||
@@ -32,8 +31,10 @@ pub struct HkReply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum HkReplyVariant {
|
pub enum HkReplyVariant {
|
||||||
Ack,
|
Ack,
|
||||||
|
Failed(ResultU16),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -69,6 +70,15 @@ impl PusReplyHandler<ActivePusRequestStd, HkReply> for HkReplyHandler {
|
|||||||
.completion_success(tm_sender, started_token, time_stamp)
|
.completion_success(tm_sender, started_token, time_stamp)
|
||||||
.expect("sending completion success verification failed");
|
.expect("sending completion success verification failed");
|
||||||
}
|
}
|
||||||
|
HkReplyVariant::Failed(failure_code) => {
|
||||||
|
verification_handler
|
||||||
|
.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
started_token,
|
||||||
|
FailParams::new(time_stamp, &failure_code, &[]),
|
||||||
|
)
|
||||||
|
.expect("sending completion success verification failed");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -154,11 +164,11 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
|
|||||||
found: 4,
|
found: 4,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let subservice = tc.subservice();
|
let subservice = tc.message_subtype_id();
|
||||||
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).expect("invalid tc format");
|
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).expect("invalid tc format");
|
||||||
let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
|
let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
|
||||||
|
|
||||||
let standard_subservice = hk::Subservice::try_from(subservice);
|
let standard_subservice = hk::MessageSubtypeId::try_from(subservice);
|
||||||
if standard_subservice.is_err() {
|
if standard_subservice.is_err() {
|
||||||
verif_reporter
|
verif_reporter
|
||||||
.start_failure(
|
.start_failure(
|
||||||
@@ -170,19 +180,22 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
|
|||||||
return Err(GenericConversionError::InvalidSubservice(subservice));
|
return Err(GenericConversionError::InvalidSubservice(subservice));
|
||||||
}
|
}
|
||||||
let request = match standard_subservice.unwrap() {
|
let request = match standard_subservice.unwrap() {
|
||||||
hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
|
hk::MessageSubtypeId::TcEnableHkGeneration
|
||||||
|
| hk::MessageSubtypeId::TcEnableDiagGeneration => {
|
||||||
HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic)
|
HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic)
|
||||||
}
|
}
|
||||||
hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
|
hk::MessageSubtypeId::TcDisableHkGeneration
|
||||||
|
| hk::MessageSubtypeId::TcDisableDiagGeneration => {
|
||||||
HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic)
|
HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic)
|
||||||
}
|
}
|
||||||
hk::Subservice::TcReportHkReportStructures => todo!(),
|
hk::MessageSubtypeId::TcReportHkReportStructures => todo!(),
|
||||||
hk::Subservice::TmHkPacket => todo!(),
|
hk::MessageSubtypeId::TmHkPacket => todo!(),
|
||||||
hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
|
hk::MessageSubtypeId::TcGenerateOneShotHk
|
||||||
|
| hk::MessageSubtypeId::TcGenerateOneShotDiag => {
|
||||||
HkRequest::new(unique_id, HkRequestVariant::OneShot)
|
HkRequest::new(unique_id, HkRequestVariant::OneShot)
|
||||||
}
|
}
|
||||||
hk::Subservice::TcModifyDiagCollectionInterval
|
hk::MessageSubtypeId::TcModifyDiagCollectionInterval
|
||||||
| hk::Subservice::TcModifyHkCollectionInterval => {
|
| hk::MessageSubtypeId::TcModifyHkCollectionInterval => {
|
||||||
if user_data.len() < 12 {
|
if user_data.len() < 12 {
|
||||||
verif_reporter
|
verif_reporter
|
||||||
.start_failure(
|
.start_failure(
|
||||||
@@ -230,20 +243,20 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_hk_service_static(
|
pub fn create_hk_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_in_mem_converter: EcssTcCacher,
|
||||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
request_router: GenericRequestRouter,
|
request_router: GenericRequestRouter,
|
||||||
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||||
) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> HkServiceWrapper {
|
||||||
let pus_3_handler = PusTargetedRequestService::new(
|
let pus_3_handler = PusTargetedRequestService::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
PUS_HK_SERVICE.id(),
|
PUS_HK.id(),
|
||||||
pus_hk_rx,
|
pus_hk_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
|
create_verification_reporter(PUS_HK.id(), PUS_HK.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
tc_in_mem_converter,
|
||||||
),
|
),
|
||||||
HkRequestConverter::default(),
|
HkRequestConverter::default(),
|
||||||
DefaultActiveRequestMap::default(),
|
DefaultActiveRequestMap::default(),
|
||||||
@@ -256,36 +269,9 @@ pub fn create_hk_service_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_hk_service_dynamic(
|
pub struct HkServiceWrapper {
|
||||||
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(crate) service: PusTargetedRequestService<
|
pub(crate) service: PusTargetedRequestService<
|
||||||
MpscTcReceiver,
|
MpscTcReceiver,
|
||||||
TmSender,
|
|
||||||
TcInMemConverter,
|
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
HkRequestConverter,
|
HkRequestConverter,
|
||||||
HkReplyHandler,
|
HkReplyHandler,
|
||||||
@@ -296,9 +282,7 @@ pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMe
|
|||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
impl TargetedPusService for HkServiceWrapper {
|
||||||
for HkServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8;
|
const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8;
|
||||||
const SERVICE_STR: &'static str = "housekeeping";
|
const SERVICE_STR: &'static str = "housekeeping";
|
||||||
|
|
||||||
@@ -321,16 +305,19 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPus
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use arbitrary_int::traits::Integer as _;
|
||||||
|
use arbitrary_int::{u14, u21};
|
||||||
use satrs::pus::test_util::{
|
use satrs::pus::test_util::{
|
||||||
TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
||||||
};
|
};
|
||||||
use satrs::request::MessageMetadata;
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
hk::HkRequestVariant,
|
hk::HkRequestVariant,
|
||||||
pus::test_util::TEST_APID,
|
pus::test_util::TEST_APID,
|
||||||
request::GenericMessage,
|
request::GenericMessage,
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{hk::Subservice, tc::PusTcCreator},
|
ecss::{hk::MessageSubtypeId, tc::PusTcCreator},
|
||||||
SpHeader,
|
SpHeader,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -347,19 +334,18 @@ mod tests {
|
|||||||
fn hk_converter_one_shot_req() {
|
fn hk_converter_one_shot_req() {
|
||||||
let mut hk_bench =
|
let mut hk_bench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let target_id = TEST_UNIQUE_ID_0;
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
let unique_id = 5_u32;
|
let unique_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
app_data[0..4].copy_from_slice(&target_id.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
|
|
||||||
let hk_req = PusTcCreator::new_simple(
|
let hk_req = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcGenerateOneShotHk as u8),
|
||||||
Subservice::TcGenerateOneShotHk as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
let accepted_token = hk_bench.add_tc(&hk_req);
|
let accepted_token = hk_bench.add_tc(&hk_req);
|
||||||
let (_active_req, req) = hk_bench
|
let (_active_req, req) = hk_bench
|
||||||
@@ -377,11 +363,11 @@ mod tests {
|
|||||||
fn hk_converter_enable_periodic_generation() {
|
fn hk_converter_enable_periodic_generation() {
|
||||||
let mut hk_bench =
|
let mut hk_bench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let target_id = TEST_UNIQUE_ID_0;
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
let unique_id = 5_u32;
|
let unique_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
app_data[0..4].copy_from_slice(&target_id.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
let mut generic_check = |tc: &PusTcCreator| {
|
let mut generic_check = |tc: &PusTcCreator| {
|
||||||
let accepted_token = hk_bench.add_tc(tc);
|
let accepted_token = hk_bench.add_tc(tc);
|
||||||
@@ -396,18 +382,16 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let tc0 = PusTcCreator::new_simple(
|
let tc0 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcEnableHkGeneration as u8),
|
||||||
Subservice::TcEnableHkGeneration as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc0);
|
generic_check(&tc0);
|
||||||
let tc1 = PusTcCreator::new_simple(
|
let tc1 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcEnableDiagGeneration as u8),
|
||||||
Subservice::TcEnableDiagGeneration as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc1);
|
generic_check(&tc1);
|
||||||
}
|
}
|
||||||
@@ -416,11 +400,11 @@ mod tests {
|
|||||||
fn hk_conversion_disable_periodic_generation() {
|
fn hk_conversion_disable_periodic_generation() {
|
||||||
let mut hk_bench =
|
let mut hk_bench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let target_id = TEST_UNIQUE_ID_0;
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
let unique_id = 5_u32;
|
let unique_id = 5_u32;
|
||||||
let mut app_data: [u8; 8] = [0; 8];
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
app_data[0..4].copy_from_slice(&target_id.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
let mut generic_check = |tc: &PusTcCreator| {
|
let mut generic_check = |tc: &PusTcCreator| {
|
||||||
let accepted_token = hk_bench.add_tc(tc);
|
let accepted_token = hk_bench.add_tc(tc);
|
||||||
@@ -435,18 +419,16 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let tc0 = PusTcCreator::new_simple(
|
let tc0 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcDisableHkGeneration as u8),
|
||||||
Subservice::TcDisableHkGeneration as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc0);
|
generic_check(&tc0);
|
||||||
let tc1 = PusTcCreator::new_simple(
|
let tc1 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcDisableDiagGeneration as u8),
|
||||||
Subservice::TcDisableDiagGeneration as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc1);
|
generic_check(&tc1);
|
||||||
}
|
}
|
||||||
@@ -455,12 +437,12 @@ mod tests {
|
|||||||
fn hk_conversion_modify_interval() {
|
fn hk_conversion_modify_interval() {
|
||||||
let mut hk_bench =
|
let mut hk_bench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let target_id = TEST_UNIQUE_ID_0;
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
let unique_id = 5_u32;
|
let unique_id = 5_u32;
|
||||||
let mut app_data: [u8; 12] = [0; 12];
|
let mut app_data: [u8; 12] = [0; 12];
|
||||||
let collection_interval_factor = 5_u32;
|
let collection_interval_factor = 5_u32;
|
||||||
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
app_data[0..4].copy_from_slice(&target_id.as_u32().to_be_bytes());
|
||||||
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
app_data[8..12].copy_from_slice(&collection_interval_factor.to_be_bytes());
|
app_data[8..12].copy_from_slice(&collection_interval_factor.to_be_bytes());
|
||||||
|
|
||||||
@@ -478,18 +460,16 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let tc0 = PusTcCreator::new_simple(
|
let tc0 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcModifyHkCollectionInterval as u8),
|
||||||
Subservice::TcModifyHkCollectionInterval as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc0);
|
generic_check(&tc0);
|
||||||
let tc1 = PusTcCreator::new_simple(
|
let tc1 = PusTcCreator::new_simple(
|
||||||
sp_header,
|
sp_header,
|
||||||
3,
|
MessageTypeId::new(3, MessageSubtypeId::TcModifyDiagCollectionInterval as u8),
|
||||||
Subservice::TcModifyDiagCollectionInterval as u8,
|
|
||||||
&app_data,
|
&app_data,
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
);
|
);
|
||||||
generic_check(&tc1);
|
generic_check(&tc1);
|
||||||
}
|
}
|
||||||
@@ -498,8 +478,8 @@ mod tests {
|
|||||||
fn hk_reply_handler() {
|
fn hk_reply_handler() {
|
||||||
let mut reply_testbench =
|
let mut reply_testbench =
|
||||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), HkReplyHandler::default());
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), HkReplyHandler::default());
|
||||||
let sender_id = 2_u64;
|
let sender_id = 2_u32;
|
||||||
let apid_target_id = 3_u32;
|
let apid_target_id = u21::new(3);
|
||||||
let unique_id = 5_u32;
|
let unique_id = 5_u32;
|
||||||
let (req_id, active_req) = reply_testbench.add_tc(TEST_APID, apid_target_id, &[]);
|
let (req_id, active_req) = reply_testbench.add_tc(TEST_APID, apid_target_id, &[]);
|
||||||
let reply = GenericMessage::new(
|
let reply = GenericMessage::new(
|
||||||
@@ -520,7 +500,7 @@ mod tests {
|
|||||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
|
||||||
let action_reply = HkReply::new(5_u32, HkReplyVariant::Ack);
|
let action_reply = HkReply::new(5_u32, HkReplyVariant::Ack);
|
||||||
let unrequested_reply =
|
let unrequested_reply =
|
||||||
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u32), action_reply);
|
||||||
// Right now this function does not do a lot. We simply check that it does not panic or do
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
// weird stuff.
|
// weird stuff.
|
||||||
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::requests::GenericRequestRouter;
|
use crate::requests::GenericRequestRouter;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use satrs::pool::PoolAddr;
|
use satrs::pool::PoolAddr;
|
||||||
use satrs::pus::verification::{
|
use satrs::pus::verification::{
|
||||||
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||||
VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
|
VerificationReporterConfig, VerificationReportingProvider, VerificationToken,
|
||||||
};
|
};
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
|
ActiveRequest, ActiveRequestStore, CacheAndReadRawEcssTc, EcssTcAndToken, EcssTcCacher,
|
||||||
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
|
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
|
||||||
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
|
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
|
||||||
PusTcToRequestConverter, TcInMemory,
|
PusTcToRequestConverter, TcInMemory,
|
||||||
@@ -17,11 +18,11 @@ use satrs::spacepackets::ecss::tc::PusTcReader;
|
|||||||
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
||||||
use satrs::tmtc::{PacketAsVec, PacketInPool};
|
use satrs::tmtc::{PacketAsVec, PacketInPool};
|
||||||
use satrs::ComponentId;
|
use satrs::ComponentId;
|
||||||
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
|
||||||
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
||||||
use satrs_example::TimeStampHelper;
|
use satrs_example::ids::generic_pus::PUS_ROUTING;
|
||||||
|
use satrs_example::TimestampHelper;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::mpsc::{self, Sender};
|
use std::sync::mpsc;
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
@@ -32,41 +33,41 @@ pub mod stack;
|
|||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> VerificationReporter {
|
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);
|
||||||
// Every software component which needs to generate verification telemetry, gets a cloned
|
// Every software component which needs to generate verification telemetry, gets a cloned
|
||||||
// verification reporter.
|
// verification reporter.
|
||||||
VerificationReporter::new(owner_id, &verif_cfg)
|
VerificationReporter::new(owner_id, &verif_cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
||||||
pub struct PusTcMpscRouter {
|
pub struct PusTcMpscRouter {
|
||||||
pub test_tc_sender: Sender<EcssTcAndToken>,
|
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||||
pub event_tc_sender: Sender<EcssTcAndToken>,
|
pub event_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||||
pub sched_tc_sender: Sender<EcssTcAndToken>,
|
pub sched_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||||
pub hk_tc_sender: Sender<EcssTcAndToken>,
|
pub hk_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
|
||||||
pub action_tc_sender: Sender<EcssTcAndToken>,
|
#[allow(dead_code)]
|
||||||
pub mode_tc_sender: Sender<EcssTcAndToken>,
|
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 id: ComponentId,
|
||||||
pub tm_sender: TmSender,
|
pub tm_sender: TmTcSender,
|
||||||
pub verif_reporter: VerificationReporter,
|
pub verif_reporter: VerificationReporter,
|
||||||
pub pus_router: PusTcMpscRouter,
|
pub pus_router: PusTcMpscRouter,
|
||||||
stamp_helper: TimeStampHelper,
|
stamp_helper: TimestampHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
impl PusTcDistributor {
|
||||||
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
pub fn new(tm_sender: TmTcSender, pus_router: PusTcMpscRouter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PUS_ROUTING_SERVICE.raw(),
|
id: PUS_ROUTING.raw(),
|
||||||
tm_sender,
|
tm_sender,
|
||||||
verif_reporter: create_verification_reporter(
|
verif_reporter: create_verification_reporter(PUS_ROUTING.id(), PUS_ROUTING.apid),
|
||||||
PUS_ROUTING_SERVICE.id(),
|
|
||||||
PUS_ROUTING_SERVICE.apid,
|
|
||||||
),
|
|
||||||
pus_router,
|
pus_router,
|
||||||
stamp_helper: TimeStampHelper::default(),
|
stamp_helper: TimestampHelper::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,18 +103,18 @@ impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
|||||||
sender_id,
|
sender_id,
|
||||||
pus_tc_result.unwrap_err()
|
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?
|
// TODO: Shouldn't this be an error?
|
||||||
return Ok(HandlingStatus::HandledOne);
|
return Ok(HandlingStatus::HandledOne);
|
||||||
}
|
}
|
||||||
let pus_tc = pus_tc_result.unwrap().0;
|
let pus_tc = pus_tc_result.unwrap();
|
||||||
let init_token = self.verif_reporter.add_tc(&pus_tc);
|
let init_token = self.verif_reporter.start_verification(&pus_tc);
|
||||||
self.stamp_helper.update_from_now();
|
self.stamp_helper.update_from_now();
|
||||||
let accepted_token = self
|
let accepted_token = self
|
||||||
.verif_reporter
|
.verif_reporter
|
||||||
.acceptance_success(&self.tm_sender, init_token, self.stamp_helper.stamp())
|
.acceptance_success(&self.tm_sender, init_token, self.stamp_helper.stamp())
|
||||||
.expect("Acceptance success failure");
|
.expect("Acceptance success failure");
|
||||||
let service = PusServiceId::try_from(pus_tc.service());
|
let service = PusServiceId::try_from(pus_tc.service_type_id());
|
||||||
let tc_in_memory: TcInMemory = if let Some(store_addr) = addr_opt {
|
let tc_in_memory: TcInMemory = if let Some(store_addr) = addr_opt {
|
||||||
PacketInPool::new(sender_id, store_addr).into()
|
PacketInPool::new(sender_id, store_addr).into()
|
||||||
} else {
|
} else {
|
||||||
@@ -187,6 +188,7 @@ impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
|||||||
Ok(HandlingStatus::HandledOne)
|
Ok(HandlingStatus::HandledOne)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub trait TargetedPusService {
|
pub trait TargetedPusService {
|
||||||
const SERVICE_ID: u8;
|
const SERVICE_ID: u8;
|
||||||
@@ -266,21 +268,19 @@ pub trait DirectPusService {
|
|||||||
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
|
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
|
||||||
pub struct PusTargetedRequestService<
|
pub struct PusTargetedRequestService<
|
||||||
TcReceiver: EcssTcReceiver,
|
TcReceiver: EcssTcReceiver,
|
||||||
TmSender: EcssTmSender,
|
|
||||||
TcInMemConverter: EcssTcInMemConverter,
|
|
||||||
VerificationReporter: VerificationReportingProvider,
|
VerificationReporter: VerificationReportingProvider,
|
||||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
RequestType,
|
RequestType,
|
||||||
ReplyType,
|
ReplyType,
|
||||||
> {
|
> {
|
||||||
pub service_helper:
|
pub service_helper:
|
||||||
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
PusServiceHelper<TcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||||
pub request_router: GenericRequestRouter,
|
pub request_router: GenericRequestRouter,
|
||||||
pub request_converter: RequestConverter,
|
pub request_converter: RequestConverter,
|
||||||
pub active_request_map: ActiveRequestMap,
|
pub active_request_map: ActiveRequestMapInstance,
|
||||||
pub reply_handler: ReplyHandler,
|
pub reply_handler: ReplyHandler,
|
||||||
pub reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
pub reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||||
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
|
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
|
||||||
@@ -288,24 +288,20 @@ pub struct PusTargetedRequestService<
|
|||||||
|
|
||||||
impl<
|
impl<
|
||||||
TcReceiver: EcssTcReceiver,
|
TcReceiver: EcssTcReceiver,
|
||||||
TmSender: EcssTmSender,
|
|
||||||
TcInMemConverter: EcssTcInMemConverter,
|
|
||||||
VerificationReporter: VerificationReportingProvider,
|
VerificationReporter: VerificationReportingProvider,
|
||||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
RequestType,
|
RequestType,
|
||||||
ReplyType,
|
ReplyType,
|
||||||
>
|
>
|
||||||
PusTargetedRequestService<
|
PusTargetedRequestService<
|
||||||
TcReceiver,
|
TcReceiver,
|
||||||
TmSender,
|
|
||||||
TcInMemConverter,
|
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
RequestConverter,
|
RequestConverter,
|
||||||
ReplyHandler,
|
ReplyHandler,
|
||||||
ActiveRequestMap,
|
ActiveRequestMapInstance,
|
||||||
ActiveRequestInfo,
|
ActiveRequestInfo,
|
||||||
RequestType,
|
RequestType,
|
||||||
ReplyType,
|
ReplyType,
|
||||||
@@ -316,12 +312,12 @@ where
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
service_helper: PusServiceHelper<
|
service_helper: PusServiceHelper<
|
||||||
TcReceiver,
|
TcReceiver,
|
||||||
TmSender,
|
TmTcSender,
|
||||||
TcInMemConverter,
|
EcssTcCacher,
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
>,
|
>,
|
||||||
request_converter: RequestConverter,
|
request_converter: RequestConverter,
|
||||||
active_request_map: ActiveRequestMap,
|
active_request_map: ActiveRequestMapInstance,
|
||||||
reply_hook: ReplyHandler,
|
reply_hook: ReplyHandler,
|
||||||
request_router: GenericRequestRouter,
|
request_router: GenericRequestRouter,
|
||||||
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||||
@@ -515,7 +511,7 @@ where
|
|||||||
/// and also log the error.
|
/// and also log the error.
|
||||||
pub fn generic_pus_request_timeout_handler(
|
pub fn generic_pus_request_timeout_handler(
|
||||||
sender: &(impl EcssTmSender + ?Sized),
|
sender: &(impl EcssTmSender + ?Sized),
|
||||||
active_request: &(impl ActiveRequestProvider + Debug),
|
active_request: &(impl ActiveRequest + Debug),
|
||||||
verification_handler: &impl VerificationReportingProvider,
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
time_stamp: &[u8],
|
time_stamp: &[u8],
|
||||||
service_str: &'static str,
|
service_str: &'static str,
|
||||||
@@ -537,13 +533,15 @@ pub fn generic_pus_request_timeout_handler(
|
|||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use arbitrary_int::{u11, u21};
|
||||||
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
|
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
|
||||||
use satrs::pus::{MpscTmAsVecSender, PusTmVariant};
|
use satrs::pus::{MpscTmAsVecSender, PusTmVariant};
|
||||||
use satrs::request::RequestId;
|
use satrs::request::RequestId;
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
pus::{
|
pus::{
|
||||||
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
|
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
|
||||||
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
|
ActiveRequestStore, MpscTcReceiver,
|
||||||
},
|
},
|
||||||
request::UniqueApidTargetId,
|
request::UniqueApidTargetId,
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
@@ -562,7 +560,7 @@ pub(crate) mod tests {
|
|||||||
// Testbench dedicated to the testing of [PusReplyHandler]s
|
// Testbench dedicated to the testing of [PusReplyHandler]s
|
||||||
pub struct ReplyHandlerTestbench<
|
pub struct ReplyHandlerTestbench<
|
||||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
Reply,
|
Reply,
|
||||||
> {
|
> {
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
@@ -576,7 +574,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
impl<
|
impl<
|
||||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
Reply,
|
Reply,
|
||||||
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
|
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
|
||||||
{
|
{
|
||||||
@@ -596,17 +594,17 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
pub fn add_tc(
|
pub fn add_tc(
|
||||||
&mut self,
|
&mut self,
|
||||||
apid: u16,
|
apid: u11,
|
||||||
apid_target: u32,
|
apid_target: u21,
|
||||||
time_stamp: &[u8],
|
time_stamp: &[u8],
|
||||||
) -> (verification::RequestId, ActivePusRequestStd) {
|
) -> (verification::RequestId, ActivePusRequestStd) {
|
||||||
let sp_header = SpHeader::new_from_apid(apid);
|
let sp_header = SpHeader::new_from_apid(apid);
|
||||||
let sec_header_dummy = PusTcSecondaryHeader::new_simple(0, 0);
|
let sec_header_dummy = PusTcSecondaryHeader::new_simple(MessageTypeId::new(0, 0));
|
||||||
let init = self.verif_reporter.add_tc(&PusTcCreator::new(
|
let init = self.verif_reporter.start_verification(&PusTcCreator::new(
|
||||||
sp_header,
|
sp_header,
|
||||||
sec_header_dummy,
|
sec_header_dummy,
|
||||||
&[],
|
&[],
|
||||||
true,
|
CreatorConfig::default(),
|
||||||
));
|
));
|
||||||
let accepted = self
|
let accepted = self
|
||||||
.verif_reporter
|
.verif_reporter
|
||||||
@@ -677,7 +675,7 @@ pub(crate) mod tests {
|
|||||||
// Testbench dedicated to the testing of [PusTcToRequestConverter]s
|
// Testbench dedicated to the testing of [PusTcToRequestConverter]s
|
||||||
pub struct PusConverterTestbench<
|
pub struct PusConverterTestbench<
|
||||||
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
Request,
|
Request,
|
||||||
> {
|
> {
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
@@ -691,7 +689,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
impl<
|
impl<
|
||||||
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
Request,
|
Request,
|
||||||
> PusConverterTestbench<Converter, ActiveRequestInfo, Request>
|
> PusConverterTestbench<Converter, ActiveRequestInfo, Request>
|
||||||
{
|
{
|
||||||
@@ -709,7 +707,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
|
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_request_id = Some(verification::RequestId::new(tc));
|
||||||
self.current_packet = Some(tc.to_vec().unwrap());
|
self.current_packet = Some(tc.to_vec().unwrap());
|
||||||
self.verif_reporter
|
self.verif_reporter
|
||||||
@@ -725,8 +723,8 @@ pub(crate) mod tests {
|
|||||||
&mut self,
|
&mut self,
|
||||||
token: VerificationToken<TcStateAccepted>,
|
token: VerificationToken<TcStateAccepted>,
|
||||||
time_stamp: &[u8],
|
time_stamp: &[u8],
|
||||||
expected_apid: u16,
|
expected_apid: u11,
|
||||||
expected_apid_target: u32,
|
expected_apid_target: u21,
|
||||||
) -> Result<(ActiveRequestInfo, Request), Converter::Error> {
|
) -> Result<(ActiveRequestInfo, Request), Converter::Error> {
|
||||||
if self.current_packet.is_none() {
|
if self.current_packet.is_none() {
|
||||||
return Err(GenericConversionError::InvalidAppData(
|
return Err(GenericConversionError::InvalidAppData(
|
||||||
@@ -737,7 +735,7 @@ pub(crate) mod tests {
|
|||||||
let tc_reader = PusTcReader::new(¤t_packet).unwrap();
|
let tc_reader = PusTcReader::new(¤t_packet).unwrap();
|
||||||
let (active_info, request) = self.converter.convert(
|
let (active_info, request) = self.converter.convert(
|
||||||
token,
|
token,
|
||||||
&tc_reader.0,
|
&tc_reader,
|
||||||
&self.dummy_sender,
|
&self.dummy_sender,
|
||||||
&self.verif_reporter,
|
&self.verif_reporter,
|
||||||
time_stamp,
|
time_stamp,
|
||||||
@@ -757,19 +755,17 @@ pub(crate) mod tests {
|
|||||||
pub struct TargetedPusRequestTestbench<
|
pub struct TargetedPusRequestTestbench<
|
||||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>,
|
||||||
ActiveRequestInfo: ActiveRequestProvider,
|
ActiveRequestInfo: ActiveRequest,
|
||||||
RequestType,
|
RequestType,
|
||||||
ReplyType,
|
ReplyType,
|
||||||
> {
|
> {
|
||||||
pub service: PusTargetedRequestService<
|
pub service: PusTargetedRequestService<
|
||||||
MpscTcReceiver,
|
MpscTcReceiver,
|
||||||
MpscTmAsVecSender,
|
|
||||||
EcssTcInVecConverter,
|
|
||||||
TestVerificationReporter,
|
TestVerificationReporter,
|
||||||
RequestConverter,
|
RequestConverter,
|
||||||
ReplyHandler,
|
ReplyHandler,
|
||||||
ActiveRequestMap,
|
ActiveRequestMapInstance,
|
||||||
ActiveRequestInfo,
|
ActiveRequestInfo,
|
||||||
RequestType,
|
RequestType,
|
||||||
ReplyType,
|
ReplyType,
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
|
use arbitrary_int::traits::Integer as _;
|
||||||
|
use arbitrary_int::u14;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
use satrs::mode_tree::{ModeNode, ModeParent};
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
|
use satrs_example::ids;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::requests::GenericRequestRouter;
|
use crate::requests::GenericRequestRouter;
|
||||||
use satrs::pool::SharedStaticMemoryPool;
|
use crate::tmtc::sender::TmTcSender;
|
||||||
use satrs::pus::verification::VerificationReporter;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher, MpscTcReceiver, PusPacketHandlingError,
|
||||||
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError,
|
|
||||||
PusServiceHelper,
|
PusServiceHelper,
|
||||||
};
|
};
|
||||||
use satrs::request::GenericMessage;
|
use satrs::request::GenericMessage;
|
||||||
@@ -20,8 +23,8 @@ use satrs::{
|
|||||||
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
|
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
|
||||||
VerificationToken,
|
VerificationToken,
|
||||||
},
|
},
|
||||||
ActivePusRequestStd, ActiveRequestProvider, EcssTmSender, EcssTmtcError,
|
ActivePusRequestStd, ActiveRequest, EcssTmSender, EcssTmtcError, GenericConversionError,
|
||||||
GenericConversionError, PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
|
PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
|
||||||
},
|
},
|
||||||
request::UniqueApidTargetId,
|
request::UniqueApidTargetId,
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
@@ -34,7 +37,6 @@ use satrs::{
|
|||||||
},
|
},
|
||||||
ComponentId,
|
ComponentId,
|
||||||
};
|
};
|
||||||
use satrs_example::config::components::PUS_MODE_SERVICE;
|
|
||||||
use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId};
|
use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -78,10 +80,19 @@ impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
|
|||||||
.write_to_be_bytes(&mut source_data)
|
.write_to_be_bytes(&mut source_data)
|
||||||
.expect("writing mode reply failed");
|
.expect("writing mode reply failed");
|
||||||
let req_id = verification::RequestId::from(reply.request_id());
|
let req_id = verification::RequestId::from(reply.request_id());
|
||||||
let sp_header = SpHeader::new_for_unseg_tm(req_id.packet_id().apid(), 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tm(req_id.packet_id().apid(), u14::ZERO, 0);
|
||||||
let sec_header =
|
let sec_header = PusTmSecondaryHeader::new(
|
||||||
PusTmSecondaryHeader::new(200, Subservice::TmModeReply as u8, 0, 0, time_stamp);
|
MessageTypeId::new(200, Subservice::TmModeReply as u8),
|
||||||
let pus_tm = PusTmCreator::new(sp_header, sec_header, &source_data, true);
|
0,
|
||||||
|
0,
|
||||||
|
time_stamp,
|
||||||
|
);
|
||||||
|
let pus_tm = PusTmCreator::new(
|
||||||
|
sp_header,
|
||||||
|
sec_header,
|
||||||
|
&source_data,
|
||||||
|
CreatorConfig::default(),
|
||||||
|
);
|
||||||
tm_sender.send_tm(self.owner_id, PusTmVariant::Direct(pus_tm))?;
|
tm_sender.send_tm(self.owner_id, PusTmVariant::Direct(pus_tm))?;
|
||||||
verification_handler.completion_success(tm_sender, started_token, time_stamp)?;
|
verification_handler.completion_success(tm_sender, started_token, time_stamp)?;
|
||||||
}
|
}
|
||||||
@@ -110,6 +121,7 @@ impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
|
|||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
ModeReply::ModeInfo(_mode_and_submode) => (),
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -146,7 +158,7 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
|
|||||||
verif_reporter: &impl VerificationReportingProvider,
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
time_stamp: &[u8],
|
time_stamp: &[u8],
|
||||||
) -> Result<(ActivePusRequestStd, ModeRequest), Self::Error> {
|
) -> Result<(ActivePusRequestStd, ModeRequest), Self::Error> {
|
||||||
let subservice = tc.subservice();
|
let subservice = tc.message_subtype_id();
|
||||||
let user_data = tc.user_data();
|
let user_data = tc.user_data();
|
||||||
let not_enough_app_data = |expected: usize| {
|
let not_enough_app_data = |expected: usize| {
|
||||||
verif_reporter
|
verif_reporter
|
||||||
@@ -190,7 +202,13 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
|
|||||||
}
|
}
|
||||||
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
|
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
|
||||||
.expect("mode and submode extraction failed");
|
.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::TcReadMode => Ok((active_request, ModeRequest::ReadMode)),
|
||||||
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
|
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
|
||||||
@@ -202,24 +220,27 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_mode_service_static(
|
pub fn create_mode_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_in_mem_converter: EcssTcCacher,
|
||||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
mode_router: GenericRequestRouter,
|
mode_router: GenericRequestRouter,
|
||||||
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> ModeServiceWrapper {
|
||||||
let mode_request_handler = PusTargetedRequestService::new(
|
let mode_request_handler = PusTargetedRequestService::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
PUS_MODE_SERVICE.id(),
|
ids::generic_pus::PUS_MODE.id(),
|
||||||
pus_action_rx,
|
pus_action_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
|
create_verification_reporter(
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
ids::generic_pus::PUS_MODE.id(),
|
||||||
|
ids::generic_pus::PUS_MODE.apid,
|
||||||
|
),
|
||||||
|
tc_in_mem_converter,
|
||||||
),
|
),
|
||||||
ModeRequestConverter::default(),
|
ModeRequestConverter::default(),
|
||||||
DefaultActiveRequestMap::default(),
|
DefaultActiveRequestMap::default(),
|
||||||
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
|
ModeReplyHandler::new(ids::generic_pus::PUS_MODE.id()),
|
||||||
mode_router,
|
mode_router,
|
||||||
reply_receiver,
|
reply_receiver,
|
||||||
);
|
);
|
||||||
@@ -228,36 +249,9 @@ pub fn create_mode_service_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_mode_service_dynamic(
|
pub struct ModeServiceWrapper {
|
||||||
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(crate) service: PusTargetedRequestService<
|
pub(crate) service: PusTargetedRequestService<
|
||||||
MpscTcReceiver,
|
MpscTcReceiver,
|
||||||
TmSender,
|
|
||||||
TcInMemConverter,
|
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
ModeRequestConverter,
|
ModeRequestConverter,
|
||||||
ModeReplyHandler,
|
ModeReplyHandler,
|
||||||
@@ -268,9 +262,24 @@ pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcIn
|
|||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
impl ModeNode for ModeServiceWrapper {
|
||||||
for ModeServiceWrapper<TmSender, TcInMemConverter>
|
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_ID: u8 = CustomPusServiceId::Mode as u8;
|
||||||
const SERVICE_STR: &'static str = "mode";
|
const SERVICE_STR: &'static str = "mode";
|
||||||
|
|
||||||
@@ -293,8 +302,11 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPus
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use arbitrary_int::traits::Integer;
|
||||||
|
use arbitrary_int::u14;
|
||||||
use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0, TEST_UNIQUE_ID_0};
|
use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0, TEST_UNIQUE_ID_0};
|
||||||
use satrs::request::MessageMetadata;
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
mode::{ModeAndSubmode, ModeReply, ModeRequest},
|
mode::{ModeAndSubmode, ModeReply, ModeRequest},
|
||||||
pus::mode::Subservice,
|
pus::mode::Subservice,
|
||||||
@@ -317,11 +329,12 @@ mod tests {
|
|||||||
fn mode_converter_read_mode_request() {
|
fn mode_converter_read_mode_request() {
|
||||||
let mut testbench =
|
let mut testbench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcReadMode as u8);
|
let sec_header =
|
||||||
|
PusTcSecondaryHeader::new_simple(MessageTypeId::new(200, Subservice::TcReadMode as u8));
|
||||||
let mut app_data: [u8; 4] = [0; 4];
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
||||||
let token = testbench.add_tc(&tc);
|
let token = testbench.add_tc(&tc);
|
||||||
let (_active_req, req) = testbench
|
let (_active_req, req) = testbench
|
||||||
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
@@ -333,31 +346,41 @@ mod tests {
|
|||||||
fn mode_converter_set_mode_request() {
|
fn mode_converter_set_mode_request() {
|
||||||
let mut testbench =
|
let mut testbench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcSetMode as u8);
|
let sec_header =
|
||||||
|
PusTcSecondaryHeader::new_simple(MessageTypeId::new(200, Subservice::TcSetMode as u8));
|
||||||
let mut app_data: [u8; 4 + ModeAndSubmode::RAW_LEN] = [0; 4 + ModeAndSubmode::RAW_LEN];
|
let mut app_data: [u8; 4 + ModeAndSubmode::RAW_LEN] = [0; 4 + ModeAndSubmode::RAW_LEN];
|
||||||
let mode_and_submode = ModeAndSubmode::new(2, 1);
|
let mode_and_submode = ModeAndSubmode::new(2, 1);
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
mode_and_submode
|
mode_and_submode
|
||||||
.write_to_be_bytes(&mut app_data[4..])
|
.write_to_be_bytes(&mut app_data[4..])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
||||||
let token = testbench.add_tc(&tc);
|
let token = testbench.add_tc(&tc);
|
||||||
let (_active_req, req) = testbench
|
let (_active_req, req) = testbench
|
||||||
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
.expect("conversion has failed");
|
.expect("conversion has failed");
|
||||||
assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
|
assert_eq!(
|
||||||
|
req,
|
||||||
|
ModeRequest::SetMode {
|
||||||
|
mode_and_submode,
|
||||||
|
forced: false
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mode_converter_announce_mode() {
|
fn mode_converter_announce_mode() {
|
||||||
let mut testbench =
|
let mut testbench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceMode as u8);
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(
|
||||||
|
200,
|
||||||
|
Subservice::TcAnnounceMode as u8,
|
||||||
|
));
|
||||||
let mut app_data: [u8; 4] = [0; 4];
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
||||||
let token = testbench.add_tc(&tc);
|
let token = testbench.add_tc(&tc);
|
||||||
let (_active_req, req) = testbench
|
let (_active_req, req) = testbench
|
||||||
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
@@ -369,12 +392,14 @@ mod tests {
|
|||||||
fn mode_converter_announce_mode_recursively() {
|
fn mode_converter_announce_mode_recursively() {
|
||||||
let mut testbench =
|
let mut testbench =
|
||||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, u14::ZERO, 0);
|
||||||
let sec_header =
|
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(
|
||||||
PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceModeRecursive as u8);
|
200,
|
||||||
|
Subservice::TcAnnounceModeRecursive as u8,
|
||||||
|
));
|
||||||
let mut app_data: [u8; 4] = [0; 4];
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.as_u32().to_be_bytes());
|
||||||
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default());
|
||||||
let token = testbench.add_tc(&tc);
|
let token = testbench.add_tc(&tc);
|
||||||
let (_active_req, req) = testbench
|
let (_active_req, req) = testbench
|
||||||
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
@@ -390,7 +415,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let mode_reply = ModeReply::ModeReply(ModeAndSubmode::new(5, 1));
|
let mode_reply = ModeReply::ModeReply(ModeAndSubmode::new(5, 1));
|
||||||
let unrequested_reply =
|
let unrequested_reply =
|
||||||
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), mode_reply);
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u32), mode_reply);
|
||||||
// Right now this function does not do a lot. We simply check that it does not panic or do
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
// weird stuff.
|
// weird stuff.
|
||||||
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
@@ -2,28 +2,28 @@ use std::sync::mpsc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::pus::create_verification_reporter;
|
use crate::pus::create_verification_reporter;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
use log::info;
|
use log::info;
|
||||||
use satrs::pool::{PoolProvider, StaticMemoryPool};
|
use satrs::pool::{PoolProvider, StaticMemoryPool};
|
||||||
use satrs::pus::scheduler::{PusScheduler, TcInfo};
|
use satrs::pus::scheduler::{PusSchedulerAlloc, TcInfo};
|
||||||
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
|
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
|
||||||
use satrs::pus::verification::VerificationReporter;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
|
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher, MpscTcReceiver,
|
||||||
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
|
PartialPusHandlingError, PusServiceHelper,
|
||||||
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
|
|
||||||
};
|
};
|
||||||
use satrs::spacepackets::ecss::PusServiceId;
|
use satrs::spacepackets::ecss::PusServiceId;
|
||||||
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
|
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
|
||||||
use satrs::ComponentId;
|
use satrs::ComponentId;
|
||||||
use satrs_example::config::components::PUS_SCHED_SERVICE;
|
use satrs_example::ids::sched::PUS_SCHED;
|
||||||
|
|
||||||
use super::{DirectPusService, HandlingStatus};
|
use super::{DirectPusService, HandlingStatus};
|
||||||
|
|
||||||
pub trait TcReleaser {
|
pub trait TcReleaseProvider {
|
||||||
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
|
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcReleaser for PacketSenderWithSharedPool {
|
impl TcReleaseProvider for PacketSenderWithSharedPool {
|
||||||
fn release(
|
fn release(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender_id: ComponentId,
|
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(
|
fn release(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender_id: ComponentId,
|
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<
|
pub pus_11_handler: PusSchedServiceHandler<
|
||||||
MpscTcReceiver,
|
MpscTcReceiver,
|
||||||
TmSender,
|
TmTcSender,
|
||||||
TcInMemConverter,
|
EcssTcCacher,
|
||||||
VerificationReporter,
|
VerificationReporter,
|
||||||
PusScheduler,
|
PusSchedulerAlloc,
|
||||||
>,
|
>,
|
||||||
pub sched_tc_pool: StaticMemoryPool,
|
pub sched_tc_pool: StaticMemoryPool,
|
||||||
pub releaser_buf: [u8; 4096],
|
pub releaser_buf: [u8; 4096],
|
||||||
pub tc_releaser: Box<dyn TcReleaser + Send>,
|
pub tc_releaser: TcReleaser,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
impl DirectPusService for SchedulingServiceWrapper {
|
||||||
for SchedulingServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
const SERVICE_ID: u8 = PusServiceId::Verification as u8;
|
const SERVICE_ID: u8 = PusServiceId::Verification as u8;
|
||||||
|
|
||||||
const SERVICE_STR: &'static str = "verification";
|
const SERVICE_STR: &'static str = "verification";
|
||||||
@@ -134,9 +146,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
impl SchedulingServiceWrapper {
|
||||||
SchedulingServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
pub fn release_tcs(&mut self) {
|
pub fn release_tcs(&mut self) {
|
||||||
let id = self.pus_11_handler.service_helper.id();
|
let id = self.pus_11_handler.service_helper.id();
|
||||||
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
||||||
@@ -162,21 +172,22 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_scheduler_service_static(
|
pub fn create_scheduler_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_releaser: PacketSenderWithSharedPool,
|
tc_in_mem_converter: EcssTcCacher,
|
||||||
|
tc_releaser: TcReleaser,
|
||||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
sched_tc_pool: StaticMemoryPool,
|
sched_tc_pool: StaticMemoryPool,
|
||||||
) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> SchedulingServiceWrapper {
|
||||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
let scheduler = PusSchedulerAlloc::new_with_current_init_time(Duration::from_secs(5))
|
||||||
.expect("Creating PUS Scheduler failed");
|
.expect("Creating PUS Scheduler failed");
|
||||||
let pus_11_handler = PusSchedServiceHandler::new(
|
let pus_11_handler = PusSchedServiceHandler::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
PUS_SCHED_SERVICE.id(),
|
PUS_SCHED.id(),
|
||||||
pus_sched_rx,
|
pus_sched_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
|
create_verification_reporter(PUS_SCHED.id(), PUS_SCHED.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
|
tc_in_mem_converter,
|
||||||
),
|
),
|
||||||
scheduler,
|
scheduler,
|
||||||
);
|
);
|
||||||
@@ -184,34 +195,6 @@ pub fn create_scheduler_service_static(
|
|||||||
pus_11_handler,
|
pus_11_handler,
|
||||||
sched_tc_pool,
|
sched_tc_pool,
|
||||||
releaser_buf: [0; 4096],
|
releaser_buf: [0; 4096],
|
||||||
tc_releaser: Box::new(tc_releaser),
|
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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
use crate::pus::mode::ModeServiceWrapper;
|
use crate::pus::mode::ModeServiceWrapper;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs::{
|
use satrs::spacepackets::time::{cds, TimeWriter};
|
||||||
pus::{EcssTcInMemConverter, EcssTmSender},
|
|
||||||
spacepackets::time::{cds, TimeWriter},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
|
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
|
||||||
@@ -11,21 +8,17 @@ use super::{
|
|||||||
HandlingStatus, TargetedPusService,
|
HandlingStatus, TargetedPusService,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one
|
|
||||||
// for targeted services..
|
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
pub struct PusStack {
|
||||||
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
|
pub test_srv: TestCustomServiceWrapper,
|
||||||
hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
|
pub hk_srv_wrapper: HkServiceWrapper,
|
||||||
event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
|
pub event_srv: EventServiceWrapper,
|
||||||
action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
|
pub action_srv_wrapper: ActionServiceWrapper,
|
||||||
schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
|
pub schedule_srv: SchedulingServiceWrapper,
|
||||||
mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
|
pub mode_srv: ModeServiceWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
impl PusStack {
|
||||||
PusStack<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
// Release all telecommands which reached their release time before calling the service
|
// Release all telecommands which reached their release time before calling the service
|
||||||
// handlers.
|
// handlers.
|
||||||
@@ -1,35 +1,34 @@
|
|||||||
use crate::pus::create_verification_reporter;
|
use crate::pus::create_verification_reporter;
|
||||||
|
use crate::tmtc::sender::TmTcSender;
|
||||||
use log::info;
|
use log::info;
|
||||||
use satrs::event_man::{EventMessage, EventMessageU32};
|
use satrs::event_man_legacy::{EventMessage, EventMessageU32};
|
||||||
use satrs::pool::SharedStaticMemoryPool;
|
|
||||||
use satrs::pus::test::PusService17TestHandler;
|
use satrs::pus::test::PusService17TestHandler;
|
||||||
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
|
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
|
||||||
|
use satrs::pus::PartialPusHandlingError;
|
||||||
use satrs::pus::{
|
use satrs::pus::{
|
||||||
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter,
|
CacheAndReadRawEcssTc, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher,
|
||||||
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper,
|
MpscTcReceiver, PusServiceHelper,
|
||||||
};
|
};
|
||||||
use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError};
|
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
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::config::{tmtc_err, TEST_EVENT};
|
||||||
|
use satrs_example::ids::generic_pus::PUS_TEST;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use super::{DirectPusService, HandlingStatus};
|
use super::{DirectPusService, HandlingStatus};
|
||||||
|
|
||||||
pub fn create_test_service_static(
|
pub fn create_test_service(
|
||||||
tm_sender: PacketSenderWithSharedPool,
|
tm_sender: TmTcSender,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_in_mem_converter: EcssTcCacher,
|
||||||
event_sender: mpsc::SyncSender<EventMessageU32>,
|
event_sender: mpsc::SyncSender<EventMessageU32>,
|
||||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
) -> TestCustomServiceWrapper {
|
||||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||||
PUS_TEST_SERVICE.id(),
|
PUS_TEST.id(),
|
||||||
pus_test_rx,
|
pus_test_rx,
|
||||||
tm_sender,
|
tm_sender,
|
||||||
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
|
create_verification_reporter(PUS_TEST.id(), PUS_TEST.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
tc_in_mem_converter,
|
||||||
));
|
));
|
||||||
TestCustomServiceWrapper {
|
TestCustomServiceWrapper {
|
||||||
handler: pus17_handler,
|
handler: pus17_handler,
|
||||||
@@ -37,34 +36,13 @@ pub fn create_test_service_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_test_service_dynamic(
|
pub struct TestCustomServiceWrapper {
|
||||||
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 handler:
|
pub handler:
|
||||||
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
PusService17TestHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>,
|
||||||
pub event_tx: mpsc::SyncSender<EventMessageU32>,
|
pub event_tx: mpsc::SyncSender<EventMessageU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
|
impl DirectPusService for TestCustomServiceWrapper {
|
||||||
for TestCustomServiceWrapper<TmSender, TcInMemConverter>
|
|
||||||
{
|
|
||||||
const SERVICE_ID: u8 = PusServiceId::Test as u8;
|
const SERVICE_ID: u8 = PusServiceId::Test as u8;
|
||||||
|
|
||||||
const SERVICE_STR: &'static str = "test";
|
const SERVICE_STR: &'static str = "test";
|
||||||
@@ -108,7 +86,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
DirectPusPacketHandlerResult::CustomSubservice(subservice, token) => {
|
DirectPusPacketHandlerResult::CustomSubservice(subservice, token) => {
|
||||||
let (tc, _) = PusTcReader::new(
|
let tc = PusTcReader::new(
|
||||||
self.handler
|
self.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.tc_in_mem_converter
|
.tc_in_mem_converter
|
||||||
@@ -118,7 +96,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
|||||||
if subservice == 128 {
|
if subservice == 128 {
|
||||||
info!("generating test event");
|
info!("generating test event");
|
||||||
self.event_tx
|
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");
|
.expect("Sending test event failed");
|
||||||
match self.handler.service_helper.verif_reporter().start_success(
|
match self.handler.service_helper.verif_reporter().start_success(
|
||||||
self.handler.service_helper.tm_sender(),
|
self.handler.service_helper.tm_sender(),
|
||||||
@@ -144,7 +122,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let fail_data = [tc.subservice()];
|
let fail_data = [tc.message_subtype_id()];
|
||||||
self.handler
|
self.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.verif_reporter()
|
.verif_reporter()
|
||||||
@@ -1,6 +1,128 @@
|
|||||||
use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
|
extern crate alloc;
|
||||||
|
|
||||||
|
use satrs::spacepackets::{
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum, time::cds::CdsTime, CcsdsPacketCreationError,
|
||||||
|
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod ids;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
serde::Serialize,
|
||||||
|
serde::Deserialize,
|
||||||
|
num_enum::TryFromPrimitive,
|
||||||
|
num_enum::IntoPrimitive,
|
||||||
|
)]
|
||||||
|
#[repr(u64)]
|
||||||
|
pub enum ComponentId {
|
||||||
|
Pcdu,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum MessageType {
|
||||||
|
Ping,
|
||||||
|
Mode,
|
||||||
|
Hk,
|
||||||
|
Action,
|
||||||
|
Verification,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unserialized owned TM packet which can be cloned and sent around.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CcsdsTmPacketOwned {
|
||||||
|
pub sp_header: SpacePacketHeader,
|
||||||
|
pub tm_header: TmHeader,
|
||||||
|
pub payload: alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple type modelling packet stored in the heap. This structure is intended to
|
||||||
|
/// be used when sending a packet via a message queue, so it also contains the sender ID.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct PacketAsVec {
|
||||||
|
pub sender_id: ComponentId,
|
||||||
|
pub packet: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketAsVec {
|
||||||
|
pub fn new(sender_id: ComponentId, packet: Vec<u8>) -> Self {
|
||||||
|
Self { sender_id, packet }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CcsdsCreationError {
|
||||||
|
#[error("CCSDS packet creation error: {0}")]
|
||||||
|
CcsdsPacketCreation(#[from] CcsdsPacketCreationError),
|
||||||
|
#[error("postcard error: {0}")]
|
||||||
|
Postcard(#[from] postcard::Error),
|
||||||
|
#[error("timestamp generation error")]
|
||||||
|
Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CcsdsTmPacketOwned {
|
||||||
|
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
|
||||||
|
let response_len =
|
||||||
|
postcard::experimental::serialized_size(&self.tm_header)? + self.payload.len();
|
||||||
|
let mut ccsds_tm = CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(
|
||||||
|
self.sp_header,
|
||||||
|
response_len,
|
||||||
|
buf,
|
||||||
|
)?;
|
||||||
|
let user_data = ccsds_tm.packet_data_mut();
|
||||||
|
let ser_len = postcard::to_slice(&self.tm_header, user_data)?.len();
|
||||||
|
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
|
||||||
|
let ccsds_packet_len = ccsds_tm.finish();
|
||||||
|
Ok(ccsds_packet_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len_written(&self) -> usize {
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum(
|
||||||
|
postcard::experimental::serialized_size(&self.tm_header).unwrap() as usize
|
||||||
|
+ postcard::experimental::serialized_size(&self.payload).unwrap() as usize,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
|
||||||
|
let mut buf = alloc::vec![0u8; self.len_written()];
|
||||||
|
let len = self.write_to_bytes(&mut buf).unwrap();
|
||||||
|
buf.truncate(len);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct TmHeader {
|
||||||
|
pub sender_id: ComponentId,
|
||||||
|
pub target_id: ComponentId,
|
||||||
|
pub message_type: MessageType,
|
||||||
|
/// Telemetry can either be sent unsolicited, or as a response to telecommands.
|
||||||
|
pub tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
/// Raw CDS short timestamp.
|
||||||
|
pub timestamp: Option<[u8; 7]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct TcHeader {
|
||||||
|
pub target_id: ComponentId,
|
||||||
|
pub request_type: MessageType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CcsdsTcPacketOwned {
|
||||||
|
pub sp_header: SpacePacketHeader,
|
||||||
|
pub tc_header: TcHeader,
|
||||||
|
pub payload: alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum DeviceMode {
|
pub enum DeviceMode {
|
||||||
@@ -9,12 +131,12 @@ pub enum DeviceMode {
|
|||||||
Normal = 2,
|
Normal = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TimeStampHelper {
|
pub struct TimestampHelper {
|
||||||
stamper: CdsTime,
|
stamper: CdsTime,
|
||||||
time_stamp: [u8; 7],
|
time_stamp: [u8; 7],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimeStampHelper {
|
impl TimestampHelper {
|
||||||
pub fn stamp(&self) -> &[u8] {
|
pub fn stamp(&self) -> &[u8] {
|
||||||
&self.time_stamp
|
&self.time_stamp
|
||||||
}
|
}
|
||||||
@@ -29,7 +151,7 @@ impl TimeStampHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TimeStampHelper {
|
impl Default for TimestampHelper {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"),
|
stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"),
|
||||||
|
|||||||
@@ -1,174 +1,129 @@
|
|||||||
|
use std::{
|
||||||
|
net::{IpAddr, SocketAddr},
|
||||||
|
sync::{mpsc, Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use eps::{
|
||||||
|
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
|
||||||
|
PowerSwitchHelper,
|
||||||
|
};
|
||||||
|
use interface::{
|
||||||
|
sim_client_udp::create_sim_client,
|
||||||
|
tcp::{SyncTcpTmSource, TcpTask},
|
||||||
|
udp::UdpTmtcServer,
|
||||||
|
};
|
||||||
|
use log::info;
|
||||||
|
use logger::setup_logger;
|
||||||
|
use requests::GenericRequestRouter;
|
||||||
|
use satrs::{
|
||||||
|
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
||||||
|
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
|
||||||
|
pus::{event_man::EventRequestWithToken, HandlingStatus},
|
||||||
|
request::{GenericMessage, MessageMetadata},
|
||||||
|
spacepackets::time::cds::CdsTime,
|
||||||
|
};
|
||||||
|
use satrs_example::{
|
||||||
|
config::{
|
||||||
|
components::NO_SENDER,
|
||||||
|
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};
|
||||||
|
|
||||||
|
use crate::{interface::udp::UdpTmHandlerWithChannel, tmtc::tc_source::CcsdsDistributor};
|
||||||
|
|
||||||
mod acs;
|
mod acs;
|
||||||
|
mod eps;
|
||||||
mod events;
|
mod events;
|
||||||
mod hk;
|
mod hk;
|
||||||
mod interface;
|
mod interface;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod pus;
|
//mod pus;
|
||||||
mod requests;
|
mod requests;
|
||||||
|
mod spi;
|
||||||
mod tmtc;
|
mod tmtc;
|
||||||
|
|
||||||
use crate::events::EventHandler;
|
fn main() {
|
||||||
use crate::interface::udp::DynamicUdpTmHandler;
|
setup_logger().expect("setting up logging with fern failed");
|
||||||
use crate::pus::stack::PusStack;
|
println!("Running OBSW example");
|
||||||
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::request::GenericMessage;
|
|
||||||
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_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
|
|
||||||
};
|
|
||||||
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
|
|
||||||
|
|
||||||
use crate::acs::mgm::{MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface};
|
|
||||||
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::ModeRequest;
|
|
||||||
use satrs::pus::event_man::EventRequestWithToken;
|
|
||||||
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
|
|
||||||
use satrs_example::config::components::{MGM_HANDLER_0, TCP_SERVER, UDP_SERVER};
|
|
||||||
use std::net::{IpAddr, SocketAddr};
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn static_tmtc_pool_main() {
|
|
||||||
let (tm_pool, tc_pool) = create_static_pools();
|
|
||||||
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
|
|
||||||
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
|
|
||||||
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
|
|
||||||
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
|
|
||||||
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||||
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
|
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
|
||||||
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
||||||
|
|
||||||
let tm_sink_tx_sender =
|
let (sim_request_tx, sim_request_rx) = mpsc::channel();
|
||||||
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
|
//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) =
|
let (mgm_0_handler_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10);
|
||||||
mpsc::channel::<GenericMessage<CompositeRequest>>();
|
let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10);
|
||||||
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
|
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.
|
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||||
let mut request_map = GenericRequestRouter::default();
|
let mut request_map = GenericRequestRouter::default();
|
||||||
request_map
|
request_map
|
||||||
.composite_router_map
|
.composite_router_map
|
||||||
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
|
.insert(MGM0.id(), mgm_0_handler_composite_tx);
|
||||||
request_map
|
request_map
|
||||||
.mode_router_map
|
.composite_router_map
|
||||||
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
|
.insert(MGM1.id(), mgm_1_handler_composite_tx);
|
||||||
|
request_map
|
||||||
// This helper structure is used by all telecommand providers which need to send telecommands
|
.composite_router_map
|
||||||
// to the TC source.
|
.insert(PCDU.id(), pcdu_handler_composite_tx);
|
||||||
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
|
|
||||||
|
|
||||||
// Create event handling components
|
// Create event handling components
|
||||||
// These sender handles are used to send event requests, for example to enable or disable
|
// These sender handles are used to send event requests, for example to enable or disable
|
||||||
// certain events.
|
// certain events.
|
||||||
let (event_tx, event_rx) = mpsc::sync_channel(100);
|
//let (event_tx, event_rx) = mpsc::sync_channel(100);
|
||||||
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
|
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
|
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||||
// in the sat-rs documentation.
|
// in the sat-rs documentation.
|
||||||
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
|
//let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
|
||||||
|
|
||||||
let (pus_test_tx, pus_test_rx) = mpsc::channel();
|
//let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20);
|
||||||
let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
//let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10);
|
||||||
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
//let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50);
|
||||||
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
//let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50);
|
||||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
//let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50);
|
||||||
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
//let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50);
|
||||||
|
|
||||||
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
//let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||||
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
|
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50);
|
||||||
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
|
//let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30);
|
||||||
|
|
||||||
let pus_router = PusTcMpscRouter {
|
let mut ccsds_distributor = CcsdsDistributor::default();
|
||||||
test_tc_sender: pus_test_tx,
|
let mut tmtc_task = TcSourceTask::new(
|
||||||
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_static(
|
|
||||||
tm_sink_tx_sender.clone(),
|
|
||||||
shared_tc_pool.clone(),
|
|
||||||
event_tx.clone(),
|
|
||||||
pus_test_rx,
|
|
||||||
);
|
|
||||||
let pus_scheduler_service = create_scheduler_service_static(
|
|
||||||
tm_sink_tx_sender.clone(),
|
|
||||||
tc_source.clone(),
|
|
||||||
pus_sched_rx,
|
|
||||||
create_sched_tc_pool(),
|
|
||||||
);
|
|
||||||
let pus_event_service = create_event_service_static(
|
|
||||||
tm_sink_tx_sender.clone(),
|
|
||||||
shared_tc_pool.clone(),
|
|
||||||
pus_event_rx,
|
|
||||||
event_request_tx,
|
|
||||||
);
|
|
||||||
let pus_action_service = create_action_service_static(
|
|
||||||
tm_sink_tx_sender.clone(),
|
|
||||||
shared_tc_pool.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(),
|
|
||||||
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(),
|
|
||||||
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 = TcSourceTaskStatic::new(
|
|
||||||
shared_tc_pool_wrapper.clone(),
|
|
||||||
tc_source_rx,
|
tc_source_rx,
|
||||||
PusTcDistributor::new(tm_sink_tx_sender, pus_router),
|
ccsds_distributor,
|
||||||
|
//PusTcDistributor::new(tm_sender.clone(), pus_router),
|
||||||
);
|
);
|
||||||
|
let tc_sender = TmTcSender::Normal(tc_source_tx.clone());
|
||||||
|
let udp_tm_handler = UdpTmHandlerWithChannel {
|
||||||
|
tm_rx: tm_server_rx,
|
||||||
|
};
|
||||||
|
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
|
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone())
|
||||||
.expect("creating UDP TMTC server failed");
|
.expect("creating UDP TMTC server failed");
|
||||||
let mut udp_tmtc_server = UdpTmtcServer {
|
let mut udp_tmtc_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler: StaticUdpTmHandler {
|
tm_handler: udp_tm_handler.into(),
|
||||||
tm_rx: tm_server_rx,
|
|
||||||
tm_store: shared_tm_pool.clone(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let tcp_server_cfg = ServerConfig::new(
|
let tcp_server_cfg = ServerConfig::new(
|
||||||
@@ -182,38 +137,121 @@ fn static_tmtc_pool_main() {
|
|||||||
let mut tcp_server = TcpTask::new(
|
let mut tcp_server = TcpTask::new(
|
||||||
tcp_server_cfg,
|
tcp_server_cfg,
|
||||||
sync_tm_tcp_source.clone(),
|
sync_tm_tcp_source.clone(),
|
||||||
tc_source.clone(),
|
tc_sender,
|
||||||
PACKET_ID_VALIDATOR.clone(),
|
PACKET_ID_VALIDATOR.clone(),
|
||||||
)
|
)
|
||||||
.expect("tcp server creation failed");
|
.expect("tcp server creation failed");
|
||||||
|
|
||||||
let mut tm_sink = TmSinkStatic::new(
|
let mut tm_sink = TmSink::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
|
||||||
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) =
|
let shared_switch_set = Arc::new(Mutex::default());
|
||||||
mpsc::channel();
|
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
|
||||||
|
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
|
||||||
|
|
||||||
let dummy_spi_interface = SpiDummyInterface::default();
|
//let shared_mgm_0_set = Arc::default();
|
||||||
let shared_mgm_set = Arc::default();
|
//let shared_mgm_1_set = Arc::default();
|
||||||
let mode_leaf_interface = MpscModeLeafInterface {
|
let mgm_0_mode_node = ModeRequestHandlerMpscBounded::new(MGM0.into(), mgm_0_handler_mode_rx);
|
||||||
request_rx: mgm_handler_mode_rx,
|
let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), mgm_1_handler_mode_rx);
|
||||||
reply_tx_to_pus: pus_mode_reply_tx,
|
/*
|
||||||
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
|
let (mgm_0_spi_interface, mgm_1_spi_interface) =
|
||||||
|
if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||||
|
sim_client
|
||||||
|
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
|
||||||
|
sim_client
|
||||||
|
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
|
||||||
|
(
|
||||||
|
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||||
|
sim_request_tx: sim_request_tx.clone(),
|
||||||
|
sim_reply_rx: mgm_0_sim_reply_rx,
|
||||||
|
}),
|
||||||
|
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
|
||||||
|
sim_request_tx: sim_request_tx.clone(),
|
||||||
|
sim_reply_rx: mgm_1_sim_reply_rx,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||||
|
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
|
||||||
|
MGM0,
|
||||||
|
"MGM_0",
|
||||||
|
mgm_0_mode_node,
|
||||||
|
mgm_0_handler_composite_rx,
|
||||||
|
pus_hk_reply_tx.clone(),
|
||||||
|
switch_helper.clone(),
|
||||||
|
tm_sender.clone(),
|
||||||
|
mgm_0_spi_interface,
|
||||||
|
shared_mgm_0_set,
|
||||||
|
);
|
||||||
|
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
|
||||||
|
MGM1,
|
||||||
|
"MGM_1",
|
||||||
|
mgm_1_mode_node,
|
||||||
|
mgm_1_handler_composite_rx,
|
||||||
|
pus_hk_reply_tx.clone(),
|
||||||
|
switch_helper.clone(),
|
||||||
|
tm_sender.clone(),
|
||||||
|
mgm_1_spi_interface,
|
||||||
|
shared_mgm_1_set,
|
||||||
|
);
|
||||||
|
// Connect PUS service to device handlers.
|
||||||
|
connect_mode_nodes(
|
||||||
|
&mut pus_stack.mode_srv,
|
||||||
|
mgm_0_handler_mode_tx,
|
||||||
|
&mut mgm_0_handler,
|
||||||
|
pus_mode_reply_tx.clone(),
|
||||||
|
);
|
||||||
|
connect_mode_nodes(
|
||||||
|
&mut pus_stack.mode_srv,
|
||||||
|
mgm_1_handler_mode_tx,
|
||||||
|
&mut mgm_1_handler,
|
||||||
|
pus_mode_reply_tx.clone(),
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
|
||||||
|
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
|
||||||
|
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
|
||||||
|
sim_request_tx.clone(),
|
||||||
|
pcdu_sim_reply_rx,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
|
||||||
};
|
};
|
||||||
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
let pcdu_mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), pcdu_handler_mode_rx);
|
||||||
MGM_HANDLER_0,
|
let mut pcdu_handler = PcduHandler::new(
|
||||||
"MGM_0",
|
PCDU,
|
||||||
mode_leaf_interface,
|
"PCDU",
|
||||||
mgm_handler_composite_rx,
|
pcdu_mode_node,
|
||||||
|
pcdu_handler_composite_rx,
|
||||||
pus_hk_reply_tx,
|
pus_hk_reply_tx,
|
||||||
tm_sink_tx,
|
switch_request_rx,
|
||||||
dummy_spi_interface,
|
tm_sink_tx.clone(),
|
||||||
shared_mgm_set,
|
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 {
|
||||||
|
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as Mode, 0),
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.expect("sending initial mode request failed");
|
||||||
|
|
||||||
info!("Starting TMTC and UDP task");
|
info!("Starting TMTC and UDP task");
|
||||||
let jh_udp_tmtc = thread::Builder::new()
|
let jh_udp_tmtc = thread::Builder::new()
|
||||||
@@ -247,29 +285,54 @@ fn static_tmtc_pool_main() {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting event handling task");
|
let mut opt_jh_sim_client = None;
|
||||||
let jh_event_handling = thread::Builder::new()
|
if let Some(mut sim_client) = opt_sim_client {
|
||||||
.name("sat-rs events".to_string())
|
info!("Starting UDP sim client task");
|
||||||
.spawn(move || loop {
|
opt_jh_sim_client = Some(
|
||||||
event_handler.periodic_operation();
|
thread::Builder::new()
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
.name("sat-rs sim adapter".to_string())
|
||||||
})
|
.spawn(move || loop {
|
||||||
.unwrap();
|
if sim_client.operation() == HandlingStatus::Empty {
|
||||||
|
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
info!("Starting AOCS thread");
|
info!("Starting AOCS thread");
|
||||||
let jh_aocs = thread::Builder::new()
|
let jh_aocs = thread::Builder::new()
|
||||||
.name("sat-rs aocs".to_string())
|
.name("sat-rs aocs".to_string())
|
||||||
.spawn(move || loop {
|
.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));
|
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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.
|
||||||
|
//
|
||||||
|
// 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(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
|
||||||
|
thread::sleep(Duration::from_millis(300));
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting PUS handler thread");
|
info!("Starting PUS handler thread");
|
||||||
let jh_pus_handler = thread::Builder::new()
|
let jh_pus_handler = thread::Builder::new()
|
||||||
.name("sat-rs pus".to_string())
|
.name("sat-rs pus".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
pus_stack.periodic_operation();
|
//event_handler.periodic_operation();
|
||||||
|
//pus_stack.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -283,241 +346,18 @@ fn static_tmtc_pool_main() {
|
|||||||
jh_tm_funnel
|
jh_tm_funnel
|
||||||
.join()
|
.join()
|
||||||
.expect("Joining TM Funnel thread failed");
|
.expect("Joining TM Funnel thread failed");
|
||||||
jh_event_handling
|
if let Some(jh_sim_client) = opt_jh_sim_client {
|
||||||
.join()
|
jh_sim_client
|
||||||
.expect("Joining Event Manager thread failed");
|
.join()
|
||||||
|
.expect("Joining SIM client thread failed");
|
||||||
|
}
|
||||||
jh_aocs.join().expect("Joining AOCS thread failed");
|
jh_aocs.join().expect("Joining AOCS thread failed");
|
||||||
|
jh_eps.join().expect("Joining EPS thread failed");
|
||||||
jh_pus_handler
|
jh_pus_handler
|
||||||
.join()
|
.join()
|
||||||
.expect("Joining PUS handler thread failed");
|
.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_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
|
||||||
let (tm_server_tx, tm_server_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
// 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::channel::<GenericMessage<CompositeRequest>>();
|
|
||||||
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
|
|
||||||
|
|
||||||
// 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.raw(), mgm_handler_composite_tx);
|
|
||||||
request_map
|
|
||||||
.mode_router_map
|
|
||||||
.insert(MGM_HANDLER_0.raw(), mgm_handler_mode_tx);
|
|
||||||
|
|
||||||
// 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_funnel_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_funnel_tx.clone(), event_tx.clone(), pus_test_rx);
|
|
||||||
let pus_scheduler_service = create_scheduler_service_dynamic(
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
tc_source_tx.clone(),
|
|
||||||
pus_sched_rx,
|
|
||||||
create_sched_tc_pool(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let pus_event_service =
|
|
||||||
create_event_service_dynamic(tm_funnel_tx.clone(), pus_event_rx, event_request_tx);
|
|
||||||
let pus_action_service = create_action_service_dynamic(
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
pus_action_rx,
|
|
||||||
request_map.clone(),
|
|
||||||
pus_action_reply_rx,
|
|
||||||
);
|
|
||||||
let pus_hk_service = create_hk_service_dynamic(
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
pus_hk_rx,
|
|
||||||
request_map.clone(),
|
|
||||||
pus_hk_reply_rx,
|
|
||||||
);
|
|
||||||
let pus_mode_service = create_mode_service_dynamic(
|
|
||||||
tm_funnel_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_funnel_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_funnel_rx, tm_server_tx);
|
|
||||||
|
|
||||||
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
|
|
||||||
mpsc::channel();
|
|
||||||
let dummy_spi_interface = SpiDummyInterface::default();
|
|
||||||
let shared_mgm_set = Arc::default();
|
|
||||||
let mode_leaf_interface = MpscModeLeafInterface {
|
|
||||||
request_rx: mgm_handler_mode_rx,
|
|
||||||
reply_tx_to_pus: pus_mode_reply_tx,
|
|
||||||
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
|
|
||||||
};
|
|
||||||
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
|
||||||
MGM_HANDLER_0,
|
|
||||||
"MGM_0",
|
|
||||||
mode_leaf_interface,
|
|
||||||
mgm_handler_composite_rx,
|
|
||||||
pus_hk_reply_tx,
|
|
||||||
tm_funnel_tx,
|
|
||||||
dummy_spi_interface,
|
|
||||||
shared_mgm_set,
|
|
||||||
);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
info!("Starting event handling task");
|
|
||||||
let jh_event_handling = thread::Builder::new()
|
|
||||||
.name("sat-rs events".to_string())
|
|
||||||
.spawn(move || loop {
|
|
||||||
event_handler.periodic_operation();
|
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
|
||||||
})
|
|
||||||
.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 PUS handler thread");
|
|
||||||
let jh_pus_handler = thread::Builder::new()
|
|
||||||
.name("sat-rs pus".to_string())
|
|
||||||
.spawn(move || loop {
|
|
||||||
pus_stack.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");
|
|
||||||
jh_event_handling
|
|
||||||
.join()
|
|
||||||
.expect("Joining Event Manager thread failed");
|
|
||||||
jh_aocs.join().expect("Joining AOCS 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]) {
|
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
|
||||||
time_provider
|
time_provider
|
||||||
.update_from_now()
|
.update_from_now()
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ use satrs::mode::ModeRequest;
|
|||||||
use satrs::pus::verification::{
|
use satrs::pus::verification::{
|
||||||
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||||
};
|
};
|
||||||
use satrs::pus::{ActiveRequestProvider, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
use satrs::pus::{ActiveRequest, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
||||||
use satrs::queue::GenericSendError;
|
use satrs::queue::GenericSendError;
|
||||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs::spacepackets::ecss::PusPacket;
|
use satrs::spacepackets::ecss::PusPacket;
|
||||||
use satrs::ComponentId;
|
use satrs::ComponentId;
|
||||||
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
|
||||||
use satrs_example::config::tmtc_err;
|
use satrs_example::config::tmtc_err;
|
||||||
|
use satrs_example::ids;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@@ -26,16 +26,18 @@ pub enum CompositeRequest {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GenericRequestRouter {
|
pub struct GenericRequestRouter {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
// All messages which do not have a dedicated queue.
|
// All messages which do not have a dedicated queue.
|
||||||
pub composite_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<CompositeRequest>>>,
|
pub composite_router_map:
|
||||||
pub mode_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<ModeRequest>>>,
|
HashMap<ComponentId, mpsc::SyncSender<GenericMessage<CompositeRequest>>>,
|
||||||
|
pub mode_router_map: HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GenericRequestRouter {
|
impl Default for GenericRequestRouter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PUS_ROUTING_SERVICE.raw(),
|
id: ids::generic_pus::PUS_ROUTING.raw(),
|
||||||
composite_router_map: Default::default(),
|
composite_router_map: Default::default(),
|
||||||
mode_router_map: Default::default(),
|
mode_router_map: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -44,7 +46,7 @@ impl Default for GenericRequestRouter {
|
|||||||
impl GenericRequestRouter {
|
impl GenericRequestRouter {
|
||||||
pub(crate) fn handle_error_generic(
|
pub(crate) fn handle_error_generic(
|
||||||
&self,
|
&self,
|
||||||
active_request: &impl ActiveRequestProvider,
|
active_request: &impl ActiveRequest,
|
||||||
tc: &PusTcReader,
|
tc: &PusTcReader,
|
||||||
error: GenericRoutingError,
|
error: GenericRoutingError,
|
||||||
tm_sender: &(impl EcssTmSender + ?Sized),
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
@@ -53,7 +55,7 @@ impl GenericRequestRouter {
|
|||||||
) {
|
) {
|
||||||
warn!(
|
warn!(
|
||||||
"Routing request for service {} failed: {error:?}",
|
"Routing request for service {} failed: {error:?}",
|
||||||
tc.service()
|
tc.service_type_id()
|
||||||
);
|
);
|
||||||
let accepted_token: VerificationToken<TcStateAccepted> = active_request
|
let accepted_token: VerificationToken<TcStateAccepted> = active_request
|
||||||
.token()
|
.token()
|
||||||
@@ -64,7 +66,8 @@ impl GenericRequestRouter {
|
|||||||
let apid_target_id = UniqueApidTargetId::from(id);
|
let apid_target_id = UniqueApidTargetId::from(id);
|
||||||
warn!("Target APID for request: {}", apid_target_id.apid);
|
warn!("Target APID for request: {}", apid_target_id.apid);
|
||||||
warn!("Target Unique ID for request: {}", apid_target_id.unique_id);
|
warn!("Target Unique ID for request: {}", apid_target_id.unique_id);
|
||||||
let mut fail_data: [u8; 8] = [0; 8];
|
let mut fail_data: [u8; core::mem::size_of::<ComponentId>()] =
|
||||||
|
[0; core::mem::size_of::<ComponentId>()];
|
||||||
fail_data.copy_from_slice(&id.to_be_bytes());
|
fail_data.copy_from_slice(&id.to_be_bytes());
|
||||||
verif_reporter
|
verif_reporter
|
||||||
.completion_failure(
|
.completion_failure(
|
||||||
|
|||||||
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 tc_source;
|
||||||
pub mod tm_sink;
|
pub mod tm_sink;
|
||||||
|
|||||||
76
satrs-example/src/tmtc/sender.rs
Normal file
76
satrs-example/src/tmtc/sender.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
|
||||||
|
|
||||||
|
use satrs::{
|
||||||
|
queue::GenericSendError,
|
||||||
|
tmtc::{PacketAsVec, PacketHandler},
|
||||||
|
ComponentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct MockSender(pub RefCell<VecDeque<PacketAsVec>>);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TmTcSender {
|
||||||
|
Normal(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 PacketHandler for TmTcSender {
|
||||||
|
type Error = GenericSendError;
|
||||||
|
|
||||||
|
fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
TmTcSender::Normal(sync_sender) => {
|
||||||
|
if let Err(e) = sync_sender.send(PacketAsVec::new(sender_id, packet.to_vec())) {
|
||||||
|
log::error!("Error sending packet via Heap TM/TC sender: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TmTcSender::Mock(sender) => {
|
||||||
|
sender.handle_packet(sender_id, packet).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketHandler for MockSender {
|
||||||
|
type Error = GenericSendError;
|
||||||
|
|
||||||
|
fn handle_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,36 @@
|
|||||||
use satrs::{
|
use satrs::{
|
||||||
pool::PoolProvider,
|
|
||||||
pus::HandlingStatus,
|
pus::HandlingStatus,
|
||||||
tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
|
spacepackets::{CcsdsPacketReader, ChecksumType},
|
||||||
|
tmtc::PacketAsVec,
|
||||||
|
};
|
||||||
|
use satrs_example::{CcsdsTcPacketOwned, ComponentId, TcHeader};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::mpsc::{self, TryRecvError},
|
||||||
};
|
};
|
||||||
use std::sync::mpsc::{self, TryRecvError};
|
|
||||||
|
|
||||||
use satrs::pus::MpscTmAsVecSender;
|
|
||||||
|
|
||||||
use crate::pus::PusTcDistributor;
|
|
||||||
|
|
||||||
// TC source components where static pools are the backing memory of the received telecommands.
|
// TC source components where static pools are the backing memory of the received telecommands.
|
||||||
|
/*
|
||||||
pub struct TcSourceTaskStatic {
|
pub struct TcSourceTaskStatic {
|
||||||
shared_tc_pool: SharedPacketPool,
|
shared_tc_pool: SharedPacketPool,
|
||||||
tc_receiver: mpsc::Receiver<PacketInPool>,
|
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||||
tc_buf: [u8; 4096],
|
/// We allocate this buffer from the heap to avoid a clippy warning on large enum variant
|
||||||
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
|
/// differences.
|
||||||
|
tc_buf: Box<[u8; 4096]>,
|
||||||
|
pus_distributor: PusTcDistributor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl TcSourceTaskStatic {
|
impl TcSourceTaskStatic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
shared_tc_pool: SharedPacketPool,
|
shared_tc_pool: SharedPacketPool,
|
||||||
tc_receiver: mpsc::Receiver<PacketInPool>,
|
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||||
pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
|
pus_receiver: PusTcDistributor,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shared_tc_pool,
|
shared_tc_pool,
|
||||||
tc_receiver,
|
tc_receiver,
|
||||||
tc_buf: [0; 4096],
|
tc_buf: Box::new([0; 4096]),
|
||||||
pus_distributor: pus_receiver,
|
pus_distributor: pus_receiver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,11 +49,11 @@ impl TcSourceTaskStatic {
|
|||||||
.0
|
.0
|
||||||
.read()
|
.read()
|
||||||
.expect("locking tc pool failed");
|
.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");
|
.expect("reading pool failed");
|
||||||
drop(pool);
|
drop(pool);
|
||||||
self.pus_distributor
|
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();
|
.ok();
|
||||||
HandlingStatus::HandledOne
|
HandlingStatus::HandledOne
|
||||||
}
|
}
|
||||||
@@ -63,21 +67,25 @@ impl TcSourceTaskStatic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<CcsdsTcPacketOwned>>;
|
||||||
|
|
||||||
// TC source components where the heap is the backing memory of the received telecommands.
|
// TC source components where the heap is the backing memory of the received telecommands.
|
||||||
pub struct TcSourceTaskDynamic {
|
pub struct TcSourceTask {
|
||||||
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
|
ccsds_distributor: CcsdsDistributor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcSourceTaskDynamic {
|
//#[allow(dead_code)]
|
||||||
|
impl TcSourceTask {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
ccsds_distributor: CcsdsDistributor,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tc_receiver,
|
tc_receiver,
|
||||||
pus_distributor: pus_receiver,
|
ccsds_distributor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +97,41 @@ impl TcSourceTaskDynamic {
|
|||||||
// Right now, we only expect ECSS PUS packets.
|
// Right now, we only expect ECSS PUS packets.
|
||||||
// If packets like CFDP are expected, we might have to check the APID first.
|
// If packets like CFDP are expected, we might have to check the APID first.
|
||||||
match self.tc_receiver.try_recv() {
|
match self.tc_receiver.try_recv() {
|
||||||
Ok(packet_as_vec) => {
|
Ok(packet) => {
|
||||||
self.pus_distributor
|
let ccsds_tc_reader_result =
|
||||||
.handle_tc_packet_vec(packet_as_vec)
|
CcsdsPacketReader::new(&packet.packet, Some(ChecksumType::WithCrc16));
|
||||||
.ok();
|
if ccsds_tc_reader_result.is_err() {
|
||||||
|
log::warn!(
|
||||||
|
"received invalid CCSDS TC packet: {:?}",
|
||||||
|
ccsds_tc_reader_result.err()
|
||||||
|
);
|
||||||
|
// TODO: Send a dedicated TM packet.
|
||||||
|
return HandlingStatus::HandledOne;
|
||||||
|
}
|
||||||
|
let ccsds_tc_reader = ccsds_tc_reader_result.unwrap();
|
||||||
|
let tc_header_result =
|
||||||
|
postcard::take_from_bytes::<TcHeader>(ccsds_tc_reader.user_data());
|
||||||
|
if tc_header_result.is_err() {
|
||||||
|
log::warn!(
|
||||||
|
"received CCSDS TC packet with invalid TC header: {:?}",
|
||||||
|
tc_header_result.err()
|
||||||
|
);
|
||||||
|
// TODO: Send a dedicated TM packet.
|
||||||
|
return HandlingStatus::HandledOne;
|
||||||
|
}
|
||||||
|
let (tc_header, payload) = tc_header_result.unwrap();
|
||||||
|
if let Some(sender) = self.ccsds_distributor.get(&tc_header.target_id) {
|
||||||
|
sender
|
||||||
|
.send(CcsdsTcPacketOwned {
|
||||||
|
sp_header: *ccsds_tc_reader.sp_header(),
|
||||||
|
tc_header,
|
||||||
|
payload: payload.to_vec(),
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
} else {
|
||||||
|
log::warn!("no TC handler for target ID {:?}", tc_header.target_id);
|
||||||
|
// TODO: Send a dedicated TM packet.
|
||||||
|
}
|
||||||
HandlingStatus::HandledOne
|
HandlingStatus::HandledOne
|
||||||
}
|
}
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
|
|||||||
@@ -3,30 +3,32 @@ use std::{
|
|||||||
sync::mpsc::{self},
|
sync::mpsc::{self},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use arbitrary_int::{u11, u14};
|
||||||
use log::info;
|
use log::info;
|
||||||
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
|
|
||||||
use satrs::{
|
use satrs::{
|
||||||
pool::PoolProvider,
|
|
||||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||||
time::cds::MIN_CDS_FIELD_LEN,
|
seq_count::{SequenceCounter, SequenceCounterCcsdsSimple},
|
||||||
CcsdsPacket,
|
CcsdsPacket,
|
||||||
},
|
},
|
||||||
|
tmtc::PacketAsVec,
|
||||||
};
|
};
|
||||||
|
use satrs_example::CcsdsTmPacketOwned;
|
||||||
|
|
||||||
use crate::interface::tcp::SyncTcpTmSource;
|
use crate::interface::tcp::SyncTcpTmSource;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CcsdsSeqCounterMap {
|
pub struct CcsdsSeqCounterMap {
|
||||||
apid_seq_counter_map: HashMap<u16, CcsdsSimpleSeqCountProvider>,
|
apid_seq_counter_map: HashMap<u11, SequenceCounterCcsdsSimple>,
|
||||||
}
|
}
|
||||||
impl CcsdsSeqCounterMap {
|
impl CcsdsSeqCounterMap {
|
||||||
pub fn get_and_increment(&mut self, apid: u16) -> u16 {
|
pub fn get_and_increment(&mut self, apid: u11) -> u14 {
|
||||||
self.apid_seq_counter_map
|
u14::new(
|
||||||
.entry(apid)
|
self.apid_seq_counter_map
|
||||||
.or_default()
|
.entry(apid)
|
||||||
.get_and_increment()
|
.or_default()
|
||||||
|
.get_and_increment(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ impl TmFunnelCommon {
|
|||||||
);
|
);
|
||||||
let entry = self
|
let entry = self
|
||||||
.msg_counter_map
|
.msg_counter_map
|
||||||
.entry(zero_copy_writer.service())
|
.entry(zero_copy_writer.service_type_id())
|
||||||
.or_insert(0);
|
.or_insert(0);
|
||||||
zero_copy_writer.set_msg_count(*entry);
|
zero_copy_writer.set_msg_count(*entry);
|
||||||
if *entry == u16::MAX {
|
if *entry == u16::MAX {
|
||||||
@@ -72,13 +74,14 @@ impl TmFunnelCommon {
|
|||||||
fn packet_printout(tm: &PusTmZeroCopyWriter) {
|
fn packet_printout(tm: &PusTmZeroCopyWriter) {
|
||||||
info!(
|
info!(
|
||||||
"Sending PUS TM[{},{}] with APID {}",
|
"Sending PUS TM[{},{}] with APID {}",
|
||||||
tm.service(),
|
tm.service_type_id(),
|
||||||
tm.subservice(),
|
tm.message_subtype_id(),
|
||||||
tm.apid()
|
tm.apid()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub struct TmSinkStatic {
|
pub struct TmSinkStatic {
|
||||||
common: TmFunnelCommon,
|
common: TmFunnelCommon,
|
||||||
shared_tm_store: SharedPacketPool,
|
shared_tm_store: SharedPacketPool,
|
||||||
@@ -86,6 +89,7 @@ pub struct TmSinkStatic {
|
|||||||
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl TmSinkStatic {
|
impl TmSinkStatic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
shared_tm_store: SharedPacketPool,
|
shared_tm_store: SharedPacketPool,
|
||||||
@@ -110,7 +114,7 @@ impl TmSinkStatic {
|
|||||||
let mut tm_copy = Vec::new();
|
let mut tm_copy = Vec::new();
|
||||||
pool_guard
|
pool_guard
|
||||||
.modify(&pus_tm_in_pool.store_addr, |buf| {
|
.modify(&pus_tm_in_pool.store_addr, |buf| {
|
||||||
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
|
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN, true)
|
||||||
.expect("Creating TM zero copy writer failed");
|
.expect("Creating TM zero copy writer failed");
|
||||||
self.common.apply_packet_processing(zero_copy_writer);
|
self.common.apply_packet_processing(zero_copy_writer);
|
||||||
tm_copy = buf.to_vec()
|
tm_copy = buf.to_vec()
|
||||||
@@ -125,18 +129,20 @@ impl TmSinkStatic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub struct TmSinkDynamic {
|
pub struct TmSink {
|
||||||
common: TmFunnelCommon,
|
common: TmFunnelCommon,
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
tm_server_tx: mpsc::Sender<PacketAsVec>,
|
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TmSinkDynamic {
|
//#[allow(dead_code)]
|
||||||
|
impl TmSink {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
sync_tm_tcp_source: SyncTcpTmSource,
|
||||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
tm_server_tx: mpsc::Sender<PacketAsVec>,
|
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||||
@@ -147,15 +153,41 @@ impl TmSinkDynamic {
|
|||||||
|
|
||||||
pub fn operation(&mut self) {
|
pub fn operation(&mut self) {
|
||||||
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
||||||
|
//let tm_raw = tm.to_vec();
|
||||||
|
tm.sp_header.set_seq_count(
|
||||||
|
self.common
|
||||||
|
.seq_counter_map
|
||||||
|
.get_and_increment(tm.sp_header.apid()),
|
||||||
|
);
|
||||||
// Read the TM, set sequence counter and message counter, and finally update
|
// Read the TM, set sequence counter and message counter, and finally update
|
||||||
// the CRC.
|
// the CRC.
|
||||||
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
|
/*
|
||||||
.expect("Creating TM zero copy writer failed");
|
let zero_copy_writer =
|
||||||
|
PusTmZeroCopyWriter::new(&mut tm_raw, MIN_CDS_FIELD_LEN, true)
|
||||||
|
.expect("Creating TM zero copy writer failed");
|
||||||
self.common.apply_packet_processing(zero_copy_writer);
|
self.common.apply_packet_processing(zero_copy_writer);
|
||||||
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
|
*/
|
||||||
|
self.common.sync_tm_tcp_source.add_tm(&tm.to_vec());
|
||||||
self.tm_server_tx
|
self.tm_server_tx
|
||||||
.send(tm)
|
.send(tm)
|
||||||
.expect("Sending TM to server failed");
|
.expect("Sending TM to server failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[allow(dead_code)]
|
||||||
|
//pub enum TmSink {
|
||||||
|
//Static(TmSinkStatic),
|
||||||
|
//Heap(TmSinkDynamic),
|
||||||
|
//}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl TmSink {
|
||||||
|
pub fn operation(&mut self) {
|
||||||
|
match self {
|
||||||
|
TmSink::Static(static_sink) => static_sink.operation(),
|
||||||
|
TmSink::Heap(dynamic_sink) => dynamic_sink.operation(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
|
# [v0.1.3] 2024-08-26
|
||||||
|
|
||||||
|
Bump `satrs-shared`.
|
||||||
|
|
||||||
# [v0.1.2] 2024-04-17
|
# [v0.1.2] 2024-04-17
|
||||||
|
|
||||||
Allow `satrs-shared` from `v0.1.3` to `<v0.2`.
|
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
|
# [v0.1.0] 2024-02-12
|
||||||
|
|
||||||
Initial release containing the `resultcode` macro.
|
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]
|
[package]
|
||||||
name = "satrs-mib"
|
name = "satrs-mib"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.61"
|
rust-version = "1.61"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
@@ -23,7 +23,8 @@ version = "1"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.satrs-shared]
|
[dependencies.satrs-shared]
|
||||||
version = ">=0.1.3, <0.2"
|
version = "0.2"
|
||||||
|
path = "../satrs-shared"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
[dependencies.satrs-mib-codegen]
|
[dependencies.satrs-mib-codegen]
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ features = ["full"]
|
|||||||
trybuild = { version = "1", features = ["diff"] }
|
trybuild = { version = "1", features = ["diff"] }
|
||||||
|
|
||||||
[dev-dependencies.satrs-shared]
|
[dev-dependencies.satrs-shared]
|
||||||
version = ">=0.1.3, <0.2"
|
version = "0.2"
|
||||||
|
path = "../../satrs-shared"
|
||||||
|
|
||||||
[dev-dependencies.satrs-mib]
|
[dev-dependencies.satrs-mib]
|
||||||
path = ".."
|
path = ".."
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
extern crate alloc;
|
|
||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ edition = "2021"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "2"
|
||||||
fern = "0.5"
|
fern = "0.7"
|
||||||
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
|
num_enum = "0.7"
|
||||||
humantime = "2"
|
humantime = "2"
|
||||||
|
tai-time = { version = "0.3", features = ["serde"] }
|
||||||
|
|
||||||
[dependencies.asynchronix]
|
[dependencies.nexosim]
|
||||||
version = "0.2.1"
|
version = "0.3.1"
|
||||||
git = "https://github.com/asynchronics/asynchronix.git"
|
|
||||||
branch = "main"
|
|
||||||
features = ["serde"]
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
[dependencies.satrs]
|
||||||
path = "../satrs"
|
path = "../satrs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
delegate = "0.12"
|
delegate = "0.13"
|
||||||
|
|||||||
32
satrs-minisim/README.md
Normal file
32
satrs-minisim/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
sat-rs minisim
|
||||||
|
======
|
||||||
|
|
||||||
|
This crate contains a mini-simulator based on the open-source discrete-event simulation framework
|
||||||
|
[asynchronix](https://github.com/asynchronics/asynchronix).
|
||||||
|
|
||||||
|
Right now, this crate is primarily used together with the
|
||||||
|
[`satrs-example` application](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
||||||
|
to simulate the devices connected to the example application.
|
||||||
|
|
||||||
|
You can simply run this application using
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -p satrs-minisim
|
||||||
|
```
|
||||||
|
|
||||||
|
in the workspace. The mini simulator uses the UDP port 7303 to exchange simulation requests and
|
||||||
|
simulation replies with any other application.
|
||||||
|
|
||||||
|
The simulator was designed in a modular way to be scalable and adaptable to other communication
|
||||||
|
schemes. This might allow it to serve a mini-simulator for other example applications which
|
||||||
|
still have similar device handlers.
|
||||||
|
|
||||||
|
The following graph shows the high-level architecture of the mini-simulator.
|
||||||
|
|
||||||
|
<img src="../images/minisim-arch/minisim-arch.png" alt="Mini simulator architecture" width="500" class="center"/>
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
use std::{f32::consts::PI, sync::mpsc, time::Duration};
|
use std::{f32::consts::PI, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
use asynchronix::{
|
use nexosim::{
|
||||||
model::{Model, Output},
|
model::{Context, Model},
|
||||||
time::Scheduler,
|
ports::Output,
|
||||||
};
|
};
|
||||||
use satrs::power::SwitchStateBinary;
|
use satrs::power::SwitchStateBinary;
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{MgmReply, MgmSensorValues, MgtDipole, MgtHkSet, MgtReply, MGT_GEN_MAGNETIC_FIELD},
|
acs::{
|
||||||
|
lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla,
|
||||||
|
MgtDipole, MgtHkSet, MgtReply, MGT_GEN_MAGNETIC_FIELD,
|
||||||
|
},
|
||||||
SimReply,
|
SimReply,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::time::current_millis;
|
use crate::time::current_millis;
|
||||||
|
|
||||||
// Earth magnetic field varies between -30 uT and 30 uT
|
// Earth magnetic field varies between roughly -30 uT and 30 uT
|
||||||
const AMPLITUDE_MGM: f32 = 0.03;
|
const AMPLITUDE_MGM_UT: f32 = 30.0;
|
||||||
// Lets start with a simple frequency here.
|
// Lets start with a simple frequency here.
|
||||||
const FREQUENCY_MGM: f32 = 1.0;
|
const FREQUENCY_MGM: f32 = 1.0;
|
||||||
const PHASE_X: f32 = 0.0;
|
const PHASE_X: f32 = 0.0;
|
||||||
@@ -23,38 +26,38 @@ const PHASE_Z: f32 = 0.2;
|
|||||||
|
|
||||||
/// Simple model for a magnetometer where the measure magnetic fields are modeled with sine waves.
|
/// Simple model for a magnetometer where the measure magnetic fields are modeled with sine waves.
|
||||||
///
|
///
|
||||||
/// Please note that that a more realistic MGM model wouold include the following components
|
/// An ideal sensor would sample the magnetic field at a high fixed rate. This might not be
|
||||||
/// which are not included here to simplify the model:
|
/// possible for a general purpose OS, but self self-sampling at a relatively high rate (20-40 ms)
|
||||||
///
|
/// might still be possible and is probably sufficient for many OBSW needs.
|
||||||
/// 1. It would probably generate signed [i16] values which need to be converted to SI units
|
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
|
||||||
/// because it is a digital sensor
|
|
||||||
/// 2. It would sample the magnetic field at a high fixed rate. This might not be possible for
|
|
||||||
/// a general purpose OS, but self self-sampling at a relatively high rate (20-40 ms) might
|
|
||||||
/// stil lbe possible.
|
|
||||||
pub struct MagnetometerModel {
|
|
||||||
pub switch_state: SwitchStateBinary,
|
pub switch_state: SwitchStateBinary,
|
||||||
|
#[allow(dead_code)]
|
||||||
pub periodicity: Duration,
|
pub periodicity: Duration,
|
||||||
pub external_mag_field: Option<MgmSensorValues>,
|
pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
|
||||||
pub reply_sender: mpsc::Sender<SimReply>,
|
pub reply_sender: mpsc::Sender<SimReply>,
|
||||||
|
pub phatom: std::marker::PhantomData<ReplyProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MagnetometerModel {
|
impl MagnetometerModel<MgmLis3MdlReply> {
|
||||||
pub fn new(periodicity: Duration, reply_sender: mpsc::Sender<SimReply>) -> Self {
|
pub fn new_for_lis3mdl(periodicity: Duration, reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
switch_state: SwitchStateBinary::Off,
|
switch_state: SwitchStateBinary::Off,
|
||||||
periodicity,
|
periodicity,
|
||||||
external_mag_field: None,
|
external_mag_field: None,
|
||||||
reply_sender,
|
reply_sender,
|
||||||
|
phatom: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ReplyProvider: MgmReplyProvider> MagnetometerModel<ReplyProvider> {
|
||||||
pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) {
|
pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) {
|
||||||
self.switch_state = switch_state;
|
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
|
self.reply_sender
|
||||||
.send(SimReply::new(MgmReply {
|
.send(ReplyProvider::create_mgm_reply(MgmReplyCommon {
|
||||||
switch_state: self.switch_state,
|
switch_state: self.switch_state,
|
||||||
sensor_values: self.calculate_current_mgm_tuple(current_millis(scheduler.time())),
|
sensor_values: self.calculate_current_mgm_tuple(current_millis(scheduler.time())),
|
||||||
}))
|
}))
|
||||||
@@ -63,23 +66,23 @@ impl MagnetometerModel {
|
|||||||
|
|
||||||
// Devices like magnetorquers generate a strong magnetic field which overrides the default
|
// Devices like magnetorquers generate a strong magnetic field which overrides the default
|
||||||
// model for the measured magnetic field.
|
// model for the measured magnetic field.
|
||||||
pub async fn apply_external_magnetic_field(&mut self, field: MgmSensorValues) {
|
pub async fn apply_external_magnetic_field(&mut self, field: MgmSensorValuesMicroTesla) {
|
||||||
self.external_mag_field = Some(field);
|
self.external_mag_field = Some(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_current_mgm_tuple(&self, time_ms: u64) -> MgmSensorValues {
|
fn calculate_current_mgm_tuple(&self, time_ms: u64) -> MgmSensorValuesMicroTesla {
|
||||||
if SwitchStateBinary::On == self.switch_state {
|
if SwitchStateBinary::On == self.switch_state {
|
||||||
if let Some(ext_field) = self.external_mag_field {
|
if let Some(ext_field) = self.external_mag_field {
|
||||||
return ext_field;
|
return ext_field;
|
||||||
}
|
}
|
||||||
let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0);
|
let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0);
|
||||||
return MgmSensorValues {
|
return MgmSensorValuesMicroTesla {
|
||||||
x: AMPLITUDE_MGM * (base_sin_val + PHASE_X).sin(),
|
x: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_X).sin(),
|
||||||
y: AMPLITUDE_MGM * (base_sin_val + PHASE_Y).sin(),
|
y: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Y).sin(),
|
||||||
z: AMPLITUDE_MGM * (base_sin_val + PHASE_Z).sin(),
|
z: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Z).sin(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
MgmSensorValues {
|
MgmSensorValuesMicroTesla {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0,
|
y: 0.0,
|
||||||
z: 0.0,
|
z: 0.0,
|
||||||
@@ -87,13 +90,13 @@ impl MagnetometerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for MagnetometerModel {}
|
impl<ReplyProvider: MgmReplyProvider> Model for MagnetometerModel<ReplyProvider> {}
|
||||||
|
|
||||||
pub struct MagnetorquerModel {
|
pub struct MagnetorquerModel {
|
||||||
switch_state: SwitchStateBinary,
|
switch_state: SwitchStateBinary,
|
||||||
torquing: bool,
|
torquing: bool,
|
||||||
torque_dipole: MgtDipole,
|
torque_dipole: MgtDipole,
|
||||||
pub gen_magnetic_field: Output<MgmSensorValues>,
|
pub gen_magnetic_field: Output<MgmSensorValuesMicroTesla>,
|
||||||
reply_sender: mpsc::Sender<SimReply>,
|
reply_sender: mpsc::Sender<SimReply>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +114,11 @@ impl MagnetorquerModel {
|
|||||||
pub async fn apply_torque(
|
pub async fn apply_torque(
|
||||||
&mut self,
|
&mut self,
|
||||||
duration_and_dipole: (Duration, MgtDipole),
|
duration_and_dipole: (Duration, MgtDipole),
|
||||||
scheduler: &Scheduler<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.torque_dipole = duration_and_dipole.1;
|
self.torque_dipole = duration_and_dipole.1;
|
||||||
self.torquing = true;
|
self.torquing = true;
|
||||||
if scheduler
|
if cx
|
||||||
.schedule_event(duration_and_dipole.0, Self::clear_torque, ())
|
.schedule_event(duration_and_dipole.0, Self::clear_torque, ())
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
@@ -135,25 +138,24 @@ impl MagnetorquerModel {
|
|||||||
self.generate_magnetic_field(()).await;
|
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 {
|
if self.switch_state != SwitchStateBinary::On {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduler
|
cx.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
|
||||||
.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
|
|
||||||
.expect("requesting housekeeping data failed")
|
.expect("requesting housekeeping data failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_housekeeping_data(&mut self) {
|
pub fn send_housekeeping_data(&mut self) {
|
||||||
self.reply_sender
|
self.reply_sender
|
||||||
.send(SimReply::new(MgtReply::Hk(MgtHkSet {
|
.send(SimReply::new(&MgtReply::Hk(MgtHkSet {
|
||||||
dipole: self.torque_dipole,
|
dipole: self.torque_dipole,
|
||||||
torquing: self.torquing,
|
torquing: self.torquing,
|
||||||
})))
|
})))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValues {
|
fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValuesMicroTesla {
|
||||||
// Simplified model: Just returns some fixed magnetic field for now.
|
// Simplified model: Just returns some fixed magnetic field for now.
|
||||||
// Later, we could make this more fancy by incorporating the commanded dipole.
|
// Later, we could make this more fancy by incorporating the commanded dipole.
|
||||||
MGT_GEN_MAGNETIC_FIELD
|
MGT_GEN_MAGNETIC_FIELD
|
||||||
@@ -179,9 +181,12 @@ pub mod tests {
|
|||||||
|
|
||||||
use satrs::power::SwitchStateBinary;
|
use satrs::power::SwitchStateBinary;
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{MgmReply, MgmRequest, MgtDipole, MgtHkSet, MgtReply, MgtRequest},
|
acs::{
|
||||||
|
lis3mdl::{self, MgmLis3MdlReply},
|
||||||
|
MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest,
|
||||||
|
},
|
||||||
eps::PcduSwitch,
|
eps::PcduSwitch,
|
||||||
SerializableSimMsgPayload, SimMessageProvider, SimRequest, SimTarget,
|
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{eps::tests::switch_device_on, test_helpers::SimTestbench};
|
use crate::{eps::tests::switch_device_on, test_helpers::SimTestbench};
|
||||||
@@ -189,22 +194,22 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic_mgm_request() {
|
fn test_basic_mgm_request() {
|
||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
let request = SimRequest::new_with_epoch_time(MgmRequest::RequestSensorData);
|
let request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
||||||
sim_testbench
|
sim_testbench
|
||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply.is_some());
|
assert!(sim_reply.is_some());
|
||||||
let sim_reply = sim_reply.unwrap();
|
let sim_reply = sim_reply.unwrap();
|
||||||
assert_eq!(sim_reply.target(), SimTarget::Mgm);
|
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
|
||||||
let reply = MgmReply::from_sim_message(&sim_reply)
|
let reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize MGM sensor values");
|
.expect("failed to deserialize MGM sensor values");
|
||||||
assert_eq!(reply.switch_state, SwitchStateBinary::Off);
|
assert_eq!(reply.common.switch_state, SwitchStateBinary::Off);
|
||||||
assert_eq!(reply.sensor_values.x, 0.0);
|
assert_eq!(reply.common.sensor_values.x, 0.0);
|
||||||
assert_eq!(reply.sensor_values.y, 0.0);
|
assert_eq!(reply.common.sensor_values.y, 0.0);
|
||||||
assert_eq!(reply.sensor_values.z, 0.0);
|
assert_eq!(reply.common.sensor_values.z, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -212,32 +217,48 @@ pub mod tests {
|
|||||||
let mut sim_testbench = SimTestbench::new();
|
let mut sim_testbench = SimTestbench::new();
|
||||||
switch_device_on(&mut sim_testbench, PcduSwitch::Mgm);
|
switch_device_on(&mut sim_testbench, PcduSwitch::Mgm);
|
||||||
|
|
||||||
let mut request = SimRequest::new_with_epoch_time(MgmRequest::RequestSensorData);
|
let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
||||||
sim_testbench
|
sim_testbench
|
||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
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();
|
let mut sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply_res.is_some());
|
assert!(sim_reply_res.is_some());
|
||||||
let mut sim_reply = sim_reply_res.unwrap();
|
let mut sim_reply = sim_reply_res.unwrap();
|
||||||
assert_eq!(sim_reply.target(), SimTarget::Mgm);
|
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
|
||||||
let first_reply = MgmReply::from_sim_message(&sim_reply)
|
let first_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize MGM sensor values");
|
.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(MgmRequest::RequestSensorData);
|
request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
|
||||||
sim_testbench
|
sim_testbench
|
||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
sim_reply_res = sim_testbench.try_receive_next_reply();
|
sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply_res.is_some());
|
assert!(sim_reply_res.is_some());
|
||||||
sim_reply = sim_reply_res.unwrap();
|
sim_reply = sim_reply_res.unwrap();
|
||||||
|
|
||||||
let second_reply = MgmReply::from_sim_message(&sim_reply)
|
let second_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize MGM sensor values");
|
.expect("failed to deserialize MGM sensor values");
|
||||||
|
let x_conv_back = second_reply.raw.x as f32
|
||||||
|
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
|
||||||
|
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
|
||||||
|
let y_conv_back = second_reply.raw.y as f32
|
||||||
|
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
|
||||||
|
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
|
||||||
|
let z_conv_back = second_reply.raw.z as f32
|
||||||
|
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
|
||||||
|
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
|
||||||
|
let diff_x = (second_reply.common.sensor_values.x - x_conv_back).abs();
|
||||||
|
assert!(diff_x < 0.01, "diff x too large: {}", diff_x);
|
||||||
|
let diff_y = (second_reply.common.sensor_values.y - y_conv_back).abs();
|
||||||
|
assert!(diff_y < 0.01, "diff y too large: {}", diff_y);
|
||||||
|
let diff_z = (second_reply.common.sensor_values.z - z_conv_back).abs();
|
||||||
|
assert!(diff_z < 0.01, "diff z too large: {}", diff_z);
|
||||||
|
// assert_eq!(second_reply.raw_reply, SwitchStateBinary::On);
|
||||||
// Check that the values are changing.
|
// Check that the values are changing.
|
||||||
assert!(first_reply != second_reply);
|
assert!(first_reply != second_reply);
|
||||||
}
|
}
|
||||||
@@ -250,7 +271,7 @@ pub mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply_res.is_none());
|
assert!(sim_reply_res.is_none());
|
||||||
}
|
}
|
||||||
@@ -265,7 +286,7 @@ pub mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply_res.is_some());
|
assert!(sim_reply_res.is_some());
|
||||||
let sim_reply = sim_reply_res.unwrap();
|
let sim_reply = sim_reply_res.unwrap();
|
||||||
@@ -286,7 +307,7 @@ pub mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
let sim_reply_res = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply_res.is_some());
|
assert!(sim_reply_res.is_some());
|
||||||
let sim_reply = sim_reply_res.unwrap();
|
let sim_reply = sim_reply_res.unwrap();
|
||||||
@@ -317,7 +338,7 @@ pub mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
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(
|
check_mgt_hk(
|
||||||
&mut sim_testbench,
|
&mut sim_testbench,
|
||||||
@@ -326,7 +347,9 @@ pub mod tests {
|
|||||||
torquing: true,
|
torquing: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
sim_testbench.step_by(Duration::from_millis(100));
|
sim_testbench
|
||||||
|
.step_until(Duration::from_millis(100))
|
||||||
|
.unwrap();
|
||||||
check_mgt_hk(
|
check_mgt_hk(
|
||||||
&mut sim_testbench,
|
&mut sim_testbench,
|
||||||
MgtHkSet {
|
MgtHkSet {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::{sync::mpsc, time::Duration};
|
use std::{sync::mpsc, time::Duration};
|
||||||
|
|
||||||
use asynchronix::{
|
use nexosim::{
|
||||||
simulation::{Address, Simulation},
|
simulation::{Address, Scheduler, Simulation},
|
||||||
time::{Clock, MonotonicTime, SystemClock},
|
time::{Clock, MonotonicTime, SystemClock},
|
||||||
};
|
};
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
acs::{MgmRequest, MgtRequest},
|
acs::{lis3mdl::MgmLis3MdlReply, MgmRequestLis3Mdl, MgtRequest},
|
||||||
eps::PcduRequest,
|
eps::PcduRequest,
|
||||||
SerializableSimMsgPayload, SimCtrlReply, SimCtrlRequest, SimMessageProvider, SimReply,
|
SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider,
|
||||||
SimRequest, SimRequestError, SimTarget,
|
SimReply, SimRequest, SimRequestError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,35 +16,62 @@ use crate::{
|
|||||||
eps::PcduModel,
|
eps::PcduModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const WARNING_FOR_STALE_DATA: bool = false;
|
||||||
|
|
||||||
|
const SIM_CTRL_REQ_WIRETAPPING: bool = false;
|
||||||
|
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.
|
// The simulation controller processes requests and drives the simulation.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct SimController {
|
pub struct SimController {
|
||||||
pub sys_clock: SystemClock,
|
pub sys_clock: SystemClock,
|
||||||
pub request_receiver: mpsc::Receiver<SimRequest>,
|
pub request_receiver: mpsc::Receiver<SimRequest>,
|
||||||
pub reply_sender: mpsc::Sender<SimReply>,
|
pub reply_sender: mpsc::Sender<SimReply>,
|
||||||
pub simulation: Simulation,
|
pub simulation: Simulation,
|
||||||
pub mgm_addr: Address<MagnetometerModel>,
|
pub scheduler: Scheduler,
|
||||||
pub pcdu_addr: Address<PcduModel>,
|
pub addr_wrapper: ModelAddrWrapper,
|
||||||
pub mgt_addr: Address<MagnetorquerModel>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl SimController {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sys_clock: SystemClock,
|
sys_clock: SystemClock,
|
||||||
request_receiver: mpsc::Receiver<SimRequest>,
|
request_receiver: mpsc::Receiver<SimRequest>,
|
||||||
reply_sender: mpsc::Sender<SimReply>,
|
reply_sender: mpsc::Sender<SimReply>,
|
||||||
simulation: Simulation,
|
simulation: Simulation,
|
||||||
mgm_addr: Address<MagnetometerModel>,
|
scheduler: Scheduler,
|
||||||
pcdu_addr: Address<PcduModel>,
|
addr_wrapper: ModelAddrWrapper,
|
||||||
mgt_addr: Address<MagnetorquerModel>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sys_clock,
|
sys_clock,
|
||||||
request_receiver,
|
request_receiver,
|
||||||
reply_sender,
|
reply_sender,
|
||||||
simulation,
|
simulation,
|
||||||
mgm_addr,
|
scheduler,
|
||||||
pcdu_addr,
|
addr_wrapper,
|
||||||
mgt_addr,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +82,7 @@ impl SimController {
|
|||||||
// Check for UDP requests every millisecond. Shift the simulator ahead here to prevent
|
// Check for UDP requests every millisecond. Shift the simulator ahead here to prevent
|
||||||
// replies lying in the past.
|
// replies lying in the past.
|
||||||
t += Duration::from_millis(udp_polling_interval_ms);
|
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.handle_sim_requests(t_old);
|
||||||
self.simulation
|
self.simulation
|
||||||
.step_until(t)
|
.step_until(t)
|
||||||
@@ -67,14 +94,15 @@ impl SimController {
|
|||||||
loop {
|
loop {
|
||||||
match self.request_receiver.try_recv() {
|
match self.request_receiver.try_recv() {
|
||||||
Ok(request) => {
|
Ok(request) => {
|
||||||
if request.timestamp < old_timestamp {
|
if request.timestamp < old_timestamp && WARNING_FOR_STALE_DATA {
|
||||||
log::warn!("stale data with timestamp {:?} received", request.timestamp);
|
log::warn!("stale data with timestamp {:?} received", request.timestamp);
|
||||||
}
|
}
|
||||||
if let Err(e) = match request.target() {
|
if let Err(e) = match request.component() {
|
||||||
SimTarget::SimCtrl => self.handle_ctrl_request(&request),
|
SimComponent::SimCtrl => self.handle_ctrl_request(&request),
|
||||||
SimTarget::Mgm => self.handle_mgm_request(&request),
|
SimComponent::Mgm0Lis3Mdl => self.handle_mgm_request(0, &request),
|
||||||
SimTarget::Mgt => self.handle_mgt_request(&request),
|
SimComponent::Mgm1Lis3Mdl => self.handle_mgm_request(1, &request),
|
||||||
SimTarget::Pcdu => self.handle_pcdu_request(&request),
|
SimComponent::Mgt => self.handle_mgt_request(&request),
|
||||||
|
SimComponent::Pcdu => self.handle_pcdu_request(&request),
|
||||||
} {
|
} {
|
||||||
self.handle_invalid_request_with_valid_target(e, &request)
|
self.handle_invalid_request_with_valid_target(e, &request)
|
||||||
}
|
}
|
||||||
@@ -91,24 +119,39 @@ impl SimController {
|
|||||||
|
|
||||||
fn handle_ctrl_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
fn handle_ctrl_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||||
let sim_ctrl_request = SimCtrlRequest::from_sim_message(request)?;
|
let sim_ctrl_request = SimCtrlRequest::from_sim_message(request)?;
|
||||||
|
if SIM_CTRL_REQ_WIRETAPPING {
|
||||||
|
log::info!("received sim ctrl request: {sim_ctrl_request:?}");
|
||||||
|
}
|
||||||
match sim_ctrl_request {
|
match sim_ctrl_request {
|
||||||
SimCtrlRequest::Ping => {
|
SimCtrlRequest::Ping => {
|
||||||
self.reply_sender
|
self.reply_sender
|
||||||
.send(SimReply::new(SimCtrlReply::Pong))
|
.send(SimReply::new(&SimCtrlReply::Pong))
|
||||||
.expect("sending reply from sim controller failed");
|
.expect("sending reply from sim controller failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
|
||||||
let mgm_request = MgmRequest::from_sim_message(request)?;
|
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:?}");
|
||||||
|
}
|
||||||
match mgm_request {
|
match mgm_request {
|
||||||
MgmRequest::RequestSensorData => {
|
MgmRequestLis3Mdl::RequestSensorData => {
|
||||||
self.simulation.send_event(
|
let addr = match mgm_idx {
|
||||||
MagnetometerModel::send_sensor_values,
|
0 => &self.addr_wrapper.mgm_0_addr,
|
||||||
(),
|
1 => &self.addr_wrapper.mgm_1_addr,
|
||||||
&self.mgm_addr,
|
|
||||||
);
|
_ => panic!("invalid mgm index"),
|
||||||
|
};
|
||||||
|
self.simulation
|
||||||
|
.process_event(MagnetometerModel::send_sensor_values, (), addr)
|
||||||
|
.expect("event execution error for mgm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -116,17 +159,27 @@ impl SimController {
|
|||||||
|
|
||||||
fn handle_pcdu_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
fn handle_pcdu_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||||
let pcdu_request = PcduRequest::from_sim_message(request)?;
|
let pcdu_request = PcduRequest::from_sim_message(request)?;
|
||||||
|
if PCDU_REQ_WIRETAPPING {
|
||||||
|
log::info!("received PCDU request: {pcdu_request:?}");
|
||||||
|
}
|
||||||
match pcdu_request {
|
match pcdu_request {
|
||||||
PcduRequest::RequestSwitchInfo => {
|
PcduRequest::RequestSwitchInfo => {
|
||||||
self.simulation
|
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 } => {
|
PcduRequest::SwitchDevice { switch, state } => {
|
||||||
self.simulation.send_event(
|
self.simulation
|
||||||
PcduModel::switch_device,
|
.process_event(
|
||||||
(switch, state),
|
PcduModel::switch_device,
|
||||||
&self.pcdu_addr,
|
(switch, state),
|
||||||
);
|
&self.addr_wrapper.pcdu_addr,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -134,18 +187,27 @@ impl SimController {
|
|||||||
|
|
||||||
fn handle_mgt_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
fn handle_mgt_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
|
||||||
let mgt_request = MgtRequest::from_sim_message(request)?;
|
let mgt_request = MgtRequest::from_sim_message(request)?;
|
||||||
match mgt_request {
|
if MGT_REQ_WIRETAPPING {
|
||||||
MgtRequest::ApplyTorque { duration, dipole } => self.simulation.send_event(
|
log::info!("received MGT request: {mgt_request:?}");
|
||||||
MagnetorquerModel::apply_torque,
|
|
||||||
(duration, dipole),
|
|
||||||
&self.mgt_addr,
|
|
||||||
),
|
|
||||||
MgtRequest::RequestHk => self.simulation.send_event(
|
|
||||||
MagnetorquerModel::request_housekeeping_data,
|
|
||||||
(),
|
|
||||||
&self.mgt_addr,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
match mgt_request {
|
||||||
|
MgtRequest::ApplyTorque { duration, dipole } => self
|
||||||
|
.simulation
|
||||||
|
.process_event(
|
||||||
|
MagnetorquerModel::apply_torque,
|
||||||
|
(duration, dipole),
|
||||||
|
&self.addr_wrapper.mgt_addr,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
MgtRequest::RequestHk => self
|
||||||
|
.simulation
|
||||||
|
.process_event(
|
||||||
|
MagnetorquerModel::request_housekeeping_data,
|
||||||
|
(),
|
||||||
|
&self.addr_wrapper.mgt_addr,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,11 +218,11 @@ impl SimController {
|
|||||||
) {
|
) {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"received invalid {:?} request: {:?}",
|
"received invalid {:?} request: {:?}",
|
||||||
request.target(),
|
request.component(),
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
self.reply_sender
|
self.reply_sender
|
||||||
.send(SimReply::new(SimCtrlReply::from(error)))
|
.send(SimReply::new(&SimCtrlReply::from(error)))
|
||||||
.expect("sending reply from sim controller failed");
|
.expect("sending reply from sim controller failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,11 +241,11 @@ mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending sim ctrl request failed");
|
.expect("sending sim ctrl request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply.is_some());
|
assert!(sim_reply.is_some());
|
||||||
let sim_reply = sim_reply.unwrap();
|
let sim_reply = sim_reply.unwrap();
|
||||||
assert_eq!(sim_reply.target(), SimTarget::SimCtrl);
|
assert_eq!(sim_reply.component(), SimComponent::SimCtrl);
|
||||||
let reply = SimCtrlReply::from_sim_message(&sim_reply)
|
let reply = SimCtrlReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize MGM sensor values");
|
.expect("failed to deserialize MGM sensor values");
|
||||||
assert_eq!(reply, SimCtrlReply::Pong);
|
assert_eq!(reply, SimCtrlReply::Pong);
|
||||||
|
|||||||
@@ -1,50 +1,47 @@
|
|||||||
use std::{collections::HashMap, sync::mpsc, time::Duration};
|
use std::{sync::mpsc, time::Duration};
|
||||||
|
|
||||||
use asynchronix::{
|
use nexosim::{
|
||||||
model::{Model, Output},
|
model::{Context, Model},
|
||||||
time::Scheduler,
|
ports::Output,
|
||||||
};
|
};
|
||||||
use satrs::power::SwitchStateBinary;
|
use satrs::power::SwitchStateBinary;
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
eps::{PcduReply, PcduSwitch, SwitchMap},
|
eps::{PcduReply, PcduSwitch, SwitchMapBinaryWrapper},
|
||||||
SimReply,
|
SimReply,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
pub const SWITCH_INFO_DELAY_MS: u64 = 10;
|
||||||
|
|
||||||
pub struct PcduModel {
|
pub struct PcduModel {
|
||||||
pub switcher_map: SwitchMap,
|
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 mgt_switch: Output<SwitchStateBinary>,
|
||||||
pub reply_sender: mpsc::Sender<SimReply>,
|
pub reply_sender: mpsc::Sender<SimReply>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PcduModel {
|
impl PcduModel {
|
||||||
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
|
||||||
let mut switcher_map = HashMap::new();
|
|
||||||
switcher_map.insert(PcduSwitch::Mgm, SwitchStateBinary::Off);
|
|
||||||
switcher_map.insert(PcduSwitch::Mgt, SwitchStateBinary::Off);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
switcher_map,
|
switcher_map: Default::default(),
|
||||||
mgm_switch: Output::new(),
|
mgm_0_switch: Output::new(),
|
||||||
|
mgm_1_switch: Output::new(),
|
||||||
mgt_switch: Output::new(),
|
mgt_switch: Output::new(),
|
||||||
reply_sender,
|
reply_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_switch_info(&mut self, _: (), scheduler: &Scheduler<Self>) {
|
pub async fn request_switch_info(&mut self, _: (), cx: &mut Context<Self>) {
|
||||||
scheduler
|
cx.schedule_event(
|
||||||
.schedule_event(
|
Duration::from_millis(SWITCH_INFO_DELAY_MS),
|
||||||
Duration::from_millis(SWITCH_INFO_DELAY_MS),
|
Self::send_switch_info,
|
||||||
Self::send_switch_info,
|
(),
|
||||||
(),
|
)
|
||||||
)
|
.expect("requesting switch info failed");
|
||||||
.expect("requesting switch info failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_switch_info(&mut self) {
|
pub fn send_switch_info(&mut self) {
|
||||||
let reply = SimReply::new(PcduReply::SwitchInfo(self.switcher_map.clone()));
|
let reply = SimReply::new(&PcduReply::SwitchInfo(self.switcher_map.0.clone()));
|
||||||
self.reply_sender.send(reply).unwrap();
|
self.reply_sender.send(reply).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +51,13 @@ impl PcduModel {
|
|||||||
) {
|
) {
|
||||||
let val = self
|
let val = self
|
||||||
.switcher_map
|
.switcher_map
|
||||||
|
.0
|
||||||
.get_mut(&switch_and_target_state.0)
|
.get_mut(&switch_and_target_state.0)
|
||||||
.unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0));
|
.unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0));
|
||||||
*val = switch_and_target_state.1;
|
*val = switch_and_target_state.1;
|
||||||
match switch_and_target_state.0 {
|
match switch_and_target_state.0 {
|
||||||
PcduSwitch::Mgm => {
|
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 => {
|
PcduSwitch::Mgt => {
|
||||||
self.mgt_switch.send(switch_and_target_state.1).await;
|
self.mgt_switch.send(switch_and_target_state.1).await;
|
||||||
@@ -76,7 +74,8 @@ pub(crate) mod tests {
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use satrs_minisim::{
|
use satrs_minisim::{
|
||||||
eps::PcduRequest, SerializableSimMsgPayload, SimMessageProvider, SimRequest, SimTarget,
|
eps::{PcduRequest, SwitchMapBinary},
|
||||||
|
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::test_helpers::SimTestbench;
|
use crate::test_helpers::SimTestbench;
|
||||||
@@ -94,7 +93,7 @@ pub(crate) mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM switch request failed");
|
.expect("sending MGM switch request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -105,24 +104,21 @@ pub(crate) mod tests {
|
|||||||
switch_device(sim_testbench, switch, SwitchStateBinary::On);
|
switch_device(sim_testbench, switch, SwitchStateBinary::On);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_all_off_switch_map() -> SwitchMap {
|
pub(crate) fn get_all_off_switch_map() -> SwitchMapBinary {
|
||||||
let mut switcher_map = SwitchMap::new();
|
SwitchMapBinaryWrapper::default().0
|
||||||
switcher_map.insert(super::PcduSwitch::Mgm, super::SwitchStateBinary::Off);
|
|
||||||
switcher_map.insert(super::PcduSwitch::Mgt, super::SwitchStateBinary::Off);
|
|
||||||
switcher_map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMap) {
|
fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMapBinary) {
|
||||||
let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo);
|
let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo);
|
||||||
sim_testbench
|
sim_testbench
|
||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
sim_testbench.handle_sim_requests_time_agnostic();
|
||||||
sim_testbench.step();
|
sim_testbench.step().unwrap();
|
||||||
let sim_reply = sim_testbench.try_receive_next_reply();
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply.is_some());
|
assert!(sim_reply.is_some());
|
||||||
let sim_reply = sim_reply.unwrap();
|
let sim_reply = sim_reply.unwrap();
|
||||||
assert_eq!(sim_reply.target(), SimTarget::Pcdu);
|
assert_eq!(sim_reply.component(), SimComponent::Pcdu);
|
||||||
let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
|
let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize PCDU switch info");
|
.expect("failed to deserialize PCDU switch info");
|
||||||
match pcdu_reply {
|
match pcdu_reply {
|
||||||
@@ -148,16 +144,16 @@ pub(crate) mod tests {
|
|||||||
.send_request(request)
|
.send_request(request)
|
||||||
.expect("sending MGM request failed");
|
.expect("sending MGM request failed");
|
||||||
sim_testbench.handle_sim_requests_time_agnostic();
|
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();
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply.is_none());
|
assert!(sim_reply.is_none());
|
||||||
// Reply takes 20ms
|
// 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();
|
let sim_reply = sim_testbench.try_receive_next_reply();
|
||||||
assert!(sim_reply.is_some());
|
assert!(sim_reply.is_some());
|
||||||
let sim_reply = sim_reply.unwrap();
|
let sim_reply = sim_reply.unwrap();
|
||||||
assert_eq!(sim_reply.target(), SimTarget::Pcdu);
|
assert_eq!(sim_reply.component(), SimComponent::Pcdu);
|
||||||
let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
|
let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
|
||||||
.expect("failed to deserialize PCDU switch info");
|
.expect("failed to deserialize PCDU switch info");
|
||||||
match pcdu_reply {
|
match pcdu_reply {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
use asynchronix::time::MonotonicTime;
|
use nexosim::time::MonotonicTime;
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
pub const SIM_CTRL_UDP_PORT: u16 = 7303;
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
|
pub enum SimComponent {
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum SimTarget {
|
|
||||||
SimCtrl,
|
SimCtrl,
|
||||||
Mgm,
|
Mgm0Lis3Mdl,
|
||||||
|
Mgm1Lis3Mdl,
|
||||||
Mgt,
|
Mgt,
|
||||||
Pcdu,
|
Pcdu,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SimMessage {
|
pub struct SimMessage {
|
||||||
pub target: SimTarget,
|
pub target: SimComponent,
|
||||||
pub payload: String,
|
pub payload: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,10 +37,10 @@ pub enum SimMessageType {
|
|||||||
pub trait SerializableSimMsgPayload<P: SimMessageProvider>:
|
pub trait SerializableSimMsgPayload<P: SimMessageProvider>:
|
||||||
Serialize + DeserializeOwned + Sized
|
Serialize + DeserializeOwned + Sized
|
||||||
{
|
{
|
||||||
const TARGET: SimTarget;
|
const TARGET: SimComponent;
|
||||||
|
|
||||||
fn from_sim_message(sim_message: &P) -> Result<Self, SimMessageError<P>> {
|
fn from_sim_message(sim_message: &P) -> Result<Self, SimMessageError<P>> {
|
||||||
if sim_message.target() == Self::TARGET {
|
if sim_message.component() == Self::TARGET {
|
||||||
return Ok(serde_json::from_str(sim_message.payload())?);
|
return Ok(serde_json::from_str(sim_message.payload())?);
|
||||||
}
|
}
|
||||||
Err(SimMessageError::TargetRequestMissmatch(sim_message.clone()))
|
Err(SimMessageError::TargetRequestMissmatch(sim_message.clone()))
|
||||||
@@ -49,7 +49,7 @@ pub trait SerializableSimMsgPayload<P: SimMessageProvider>:
|
|||||||
|
|
||||||
pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized {
|
pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized {
|
||||||
fn msg_type(&self) -> SimMessageType;
|
fn msg_type(&self) -> SimMessageType;
|
||||||
fn target(&self) -> SimTarget;
|
fn component(&self) -> SimComponent;
|
||||||
fn payload(&self) -> &String;
|
fn payload(&self) -> &String;
|
||||||
fn from_raw_data(data: &[u8]) -> serde_json::Result<Self> {
|
fn from_raw_data(data: &[u8]) -> serde_json::Result<Self> {
|
||||||
serde_json::from_slice(data)
|
serde_json::from_slice(data)
|
||||||
@@ -78,7 +78,7 @@ impl SimRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SimMessageProvider for SimRequest {
|
impl SimMessageProvider for SimRequest {
|
||||||
fn target(&self) -> SimTarget {
|
fn component(&self) -> SimComponent {
|
||||||
self.inner.target
|
self.inner.target
|
||||||
}
|
}
|
||||||
fn payload(&self) -> &String {
|
fn payload(&self) -> &String {
|
||||||
@@ -91,25 +91,25 @@ impl SimMessageProvider for SimRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A generic simulation reply type. Right now, the payload data is expected to be
|
/// A generic simulation reply type. Right now, the payload data is expected to be
|
||||||
/// JSON, which might be changed inthe future.
|
/// JSON, which might be changed in the future.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SimReply {
|
pub struct SimReply {
|
||||||
inner: SimMessage,
|
inner: SimMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimReply {
|
impl SimReply {
|
||||||
pub fn new<T: SerializableSimMsgPayload<SimReply>>(serializable_reply: T) -> Self {
|
pub fn new<T: SerializableSimMsgPayload<SimReply>>(serializable_reply: &T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: SimMessage {
|
inner: SimMessage {
|
||||||
target: T::TARGET,
|
target: T::TARGET,
|
||||||
payload: serde_json::to_string(&serializable_reply).unwrap(),
|
payload: serde_json::to_string(serializable_reply).unwrap(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimMessageProvider for SimReply {
|
impl SimMessageProvider for SimReply {
|
||||||
fn target(&self) -> SimTarget {
|
fn component(&self) -> SimComponent {
|
||||||
self.inner.target
|
self.inner.target
|
||||||
}
|
}
|
||||||
fn payload(&self) -> &String {
|
fn payload(&self) -> &String {
|
||||||
@@ -126,7 +126,7 @@ pub enum SimCtrlRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimRequest> for SimCtrlRequest {
|
impl SerializableSimMsgPayload<SimRequest> for SimCtrlRequest {
|
||||||
const TARGET: SimTarget = SimTarget::SimCtrl;
|
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SimReplyError = SimMessageError<SimReply>;
|
pub type SimReplyError = SimMessageError<SimReply>;
|
||||||
@@ -151,7 +151,7 @@ pub enum SimCtrlReply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimReply> for SimCtrlReply {
|
impl SerializableSimMsgPayload<SimReply> for SimCtrlReply {
|
||||||
const TARGET: SimTarget = SimTarget::SimCtrl;
|
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SimRequestError> for SimCtrlReply {
|
impl From<SimRequestError> for SimCtrlReply {
|
||||||
@@ -162,19 +162,82 @@ impl From<SimRequestError> for SimCtrlReply {
|
|||||||
|
|
||||||
pub mod eps {
|
pub mod eps {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use satrs::power::{SwitchState, SwitchStateBinary};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use strum::{EnumIter, IntoEnumIterator};
|
||||||
|
|
||||||
use satrs::power::SwitchStateBinary;
|
pub type SwitchMap = HashMap<PcduSwitch, SwitchState>;
|
||||||
|
pub type SwitchMapBinary = HashMap<PcduSwitch, SwitchStateBinary>;
|
||||||
|
|
||||||
pub type SwitchMap = HashMap<PcduSwitch, SwitchStateBinary>;
|
pub struct SwitchMapWrapper(pub SwitchMap);
|
||||||
|
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Hash,
|
||||||
|
EnumIter,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
)]
|
||||||
|
#[repr(u16)]
|
||||||
pub enum PcduSwitch {
|
pub enum PcduSwitch {
|
||||||
Mgm = 0,
|
Mgm = 0,
|
||||||
Mgt = 1,
|
Mgt = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
impl Default for SwitchMapBinaryWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut switch_map = SwitchMapBinary::default();
|
||||||
|
for entry in PcduSwitch::iter() {
|
||||||
|
switch_map.insert(entry, SwitchStateBinary::Off);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SwitchMapWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut switch_map = SwitchMap::default();
|
||||||
|
for entry in PcduSwitch::iter() {
|
||||||
|
switch_map.insert(entry, SwitchState::Unknown);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchMapWrapper {
|
||||||
|
pub fn new_with_init_switches_off() -> Self {
|
||||||
|
let mut switch_map = SwitchMap::default();
|
||||||
|
for entry in PcduSwitch::iter() {
|
||||||
|
switch_map.insert(entry, SwitchState::Off);
|
||||||
|
}
|
||||||
|
Self(switch_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self {
|
||||||
|
Self(
|
||||||
|
switch_map
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| (*key, SwitchState::from(*value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum PcduRequestId {
|
||||||
|
SwitchDevice = 0,
|
||||||
|
RequestSwitchInfo = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PcduRequest {
|
pub enum PcduRequest {
|
||||||
SwitchDevice {
|
SwitchDevice {
|
||||||
switch: PcduSwitch,
|
switch: PcduSwitch,
|
||||||
@@ -184,16 +247,17 @@ pub mod eps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimRequest> for PcduRequest {
|
impl SerializableSimMsgPayload<SimRequest> for PcduRequest {
|
||||||
const TARGET: SimTarget = SimTarget::Pcdu;
|
const TARGET: SimComponent = SimComponent::Pcdu;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum PcduReply {
|
pub enum PcduReply {
|
||||||
SwitchInfo(SwitchMap),
|
// Ack,
|
||||||
|
SwitchInfo(SwitchMapBinary),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimReply> for PcduReply {
|
impl SerializableSimMsgPayload<SimReply> for PcduReply {
|
||||||
const TARGET: SimTarget = SimTarget::Pcdu;
|
const TARGET: SimComponent = SimComponent::Pcdu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,40 +268,116 @@ pub mod acs {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub trait MgmReplyProvider: Send + 'static {
|
||||||
|
fn create_mgm_reply(common: MgmReplyCommon) -> SimReply;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
pub enum MgmRequest {
|
pub enum MgmRequestLis3Mdl {
|
||||||
RequestSensorData,
|
RequestSensorData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimRequest> for MgmRequest {
|
impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl {
|
||||||
const TARGET: SimTarget = SimTarget::Mgm;
|
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally, small magnetometers generate their output as a signed 16 bit raw format or something
|
// Normally, small magnetometers generate their output as a signed 16 bit raw format or something
|
||||||
// similar which needs to be converted to a signed float value with physical units. We will
|
// similar which needs to be converted to a signed float value with physical units. We will
|
||||||
// simplify this now and generate the signed float values directly.
|
// simplify this now and generate the signed float values directly. The unit is micro tesla.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MgmSensorValues {
|
pub struct MgmSensorValuesMicroTesla {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
pub z: f32,
|
pub z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MgmReply {
|
pub struct MgmReplyCommon {
|
||||||
pub switch_state: SwitchStateBinary,
|
pub switch_state: SwitchStateBinary,
|
||||||
pub sensor_values: MgmSensorValues,
|
pub sensor_values: MgmSensorValuesMicroTesla,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimReply> for MgmReply {
|
pub const MGT_GEN_MAGNETIC_FIELD: MgmSensorValuesMicroTesla = MgmSensorValuesMicroTesla {
|
||||||
const TARGET: SimTarget = SimTarget::Mgm;
|
x: 30.0,
|
||||||
}
|
y: -30.0,
|
||||||
|
z: 30.0,
|
||||||
pub const MGT_GEN_MAGNETIC_FIELD: MgmSensorValues = MgmSensorValues {
|
|
||||||
x: 0.03,
|
|
||||||
y: -0.03,
|
|
||||||
z: 0.03,
|
|
||||||
};
|
};
|
||||||
|
pub const ALL_ONES_SENSOR_VAL: i16 = 0xffff_u16 as i16;
|
||||||
|
|
||||||
|
pub mod lis3mdl {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Field data register scaling
|
||||||
|
pub const GAUSS_TO_MICROTESLA_FACTOR: u32 = 100;
|
||||||
|
pub const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
|
||||||
|
pub const FIELD_LSB_PER_GAUSS_8_SENS: f32 = 1.0 / 3421.0;
|
||||||
|
pub const FIELD_LSB_PER_GAUSS_12_SENS: f32 = 1.0 / 2281.0;
|
||||||
|
pub const FIELD_LSB_PER_GAUSS_16_SENS: f32 = 1.0 / 1711.0;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct MgmLis3RawValues {
|
||||||
|
pub x: i16,
|
||||||
|
pub y: i16,
|
||||||
|
pub z: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct MgmLis3MdlReply {
|
||||||
|
pub common: MgmReplyCommon,
|
||||||
|
// Raw sensor values which are transmitted by the LIS3 device in little-endian
|
||||||
|
// order.
|
||||||
|
pub raw: MgmLis3RawValues,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MgmLis3MdlReply {
|
||||||
|
pub fn new(common: MgmReplyCommon) -> Self {
|
||||||
|
match common.switch_state {
|
||||||
|
SwitchStateBinary::Off => Self {
|
||||||
|
common,
|
||||||
|
raw: MgmLis3RawValues {
|
||||||
|
x: ALL_ONES_SENSOR_VAL,
|
||||||
|
y: ALL_ONES_SENSOR_VAL,
|
||||||
|
z: ALL_ONES_SENSOR_VAL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SwitchStateBinary::On => {
|
||||||
|
let mut raw_reply: [u8; 7] = [0; 7];
|
||||||
|
let raw_x: i16 = (common.sensor_values.x
|
||||||
|
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
|
||||||
|
.round() as i16;
|
||||||
|
let raw_y: i16 = (common.sensor_values.y
|
||||||
|
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
|
||||||
|
.round() as i16;
|
||||||
|
let raw_z: i16 = (common.sensor_values.z
|
||||||
|
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
|
||||||
|
.round() as i16;
|
||||||
|
// The first byte is a dummy byte.
|
||||||
|
raw_reply[1..3].copy_from_slice(&raw_x.to_be_bytes());
|
||||||
|
raw_reply[3..5].copy_from_slice(&raw_y.to_be_bytes());
|
||||||
|
raw_reply[5..7].copy_from_slice(&raw_z.to_be_bytes());
|
||||||
|
Self {
|
||||||
|
common,
|
||||||
|
raw: MgmLis3RawValues {
|
||||||
|
x: raw_x,
|
||||||
|
y: raw_y,
|
||||||
|
z: raw_z,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply {
|
||||||
|
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MgmReplyProvider for MgmLis3MdlReply {
|
||||||
|
fn create_mgm_reply(common: MgmReplyCommon) -> SimReply {
|
||||||
|
SimReply::new(&Self::new(common))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple model using i16 values.
|
// Simple model using i16 values.
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -262,7 +402,7 @@ pub mod acs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimRequest> for MgtRequest {
|
impl SerializableSimMsgPayload<SimRequest> for MgtRequest {
|
||||||
const TARGET: SimTarget = SimTarget::Mgt;
|
const TARGET: SimComponent = SimComponent::Mgt;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -279,78 +419,12 @@ pub mod acs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimReply> for MgtReply {
|
impl SerializableSimMsgPayload<SimReply> for MgtReply {
|
||||||
const TARGET: SimTarget = SimTarget::Mgm;
|
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod udp {
|
pub mod udp {
|
||||||
use std::{
|
pub const SIM_CTRL_PORT: u16 = 7303;
|
||||||
net::{SocketAddr, UdpSocket},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::{SimReply, SimRequest};
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum ReceptionError {
|
|
||||||
#[error("IO error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("Serde JSON error: {0}")]
|
|
||||||
SerdeJson(#[from] serde_json::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimUdpClient {
|
|
||||||
socket: UdpSocket,
|
|
||||||
pub reply_buf: [u8; 4096],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimUdpClient {
|
|
||||||
pub fn new(
|
|
||||||
server_addr: &SocketAddr,
|
|
||||||
non_blocking: bool,
|
|
||||||
read_timeot_ms: Option<u64>,
|
|
||||||
) -> std::io::Result<Self> {
|
|
||||||
let socket = UdpSocket::bind("127.0.0.1:0")?;
|
|
||||||
socket.set_nonblocking(non_blocking)?;
|
|
||||||
socket
|
|
||||||
.connect(server_addr)
|
|
||||||
.expect("could not connect to server addr");
|
|
||||||
if let Some(read_timeout) = read_timeot_ms {
|
|
||||||
// Set a read timeout so the test does not hang on failures.
|
|
||||||
socket.set_read_timeout(Some(Duration::from_millis(read_timeout)))?;
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
socket,
|
|
||||||
reply_buf: [0; 4096],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_nonblocking(&self, non_blocking: bool) -> std::io::Result<()> {
|
|
||||||
self.socket.set_nonblocking(non_blocking)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_read_timeout(&self, read_timeout_ms: u64) -> std::io::Result<()> {
|
|
||||||
self.socket
|
|
||||||
.set_read_timeout(Some(Duration::from_millis(read_timeout_ms)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_request(&self, sim_request: &SimRequest) -> std::io::Result<usize> {
|
|
||||||
self.socket.send(
|
|
||||||
&serde_json::to_vec(sim_request).expect("conversion of request to vector failed"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recv_raw(&mut self) -> std::io::Result<usize> {
|
|
||||||
self.socket.recv(&mut self.reply_buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recv_sim_reply(&mut self) -> Result<SimReply, ReceptionError> {
|
|
||||||
let read_len = self.recv_raw()?;
|
|
||||||
Ok(serde_json::from_slice(&self.reply_buf[0..read_len])?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -363,7 +437,7 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimRequest> for DummyRequest {
|
impl SerializableSimMsgPayload<SimRequest> for DummyRequest {
|
||||||
const TARGET: SimTarget = SimTarget::SimCtrl;
|
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -372,13 +446,13 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializableSimMsgPayload<SimReply> for DummyReply {
|
impl SerializableSimMsgPayload<SimReply> for DummyReply {
|
||||||
const TARGET: SimTarget = SimTarget::SimCtrl;
|
const TARGET: SimComponent = SimComponent::SimCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_request() {
|
fn test_basic_request() {
|
||||||
let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping);
|
let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping);
|
||||||
assert_eq!(sim_request.target(), SimTarget::SimCtrl);
|
assert_eq!(sim_request.component(), SimComponent::SimCtrl);
|
||||||
assert_eq!(sim_request.msg_type(), SimMessageType::Request);
|
assert_eq!(sim_request.msg_type(), SimMessageType::Request);
|
||||||
let dummy_request =
|
let dummy_request =
|
||||||
DummyRequest::from_sim_message(&sim_request).expect("deserialization failed");
|
DummyRequest::from_sim_message(&sim_request).expect("deserialization failed");
|
||||||
@@ -387,8 +461,8 @@ pub mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_reply() {
|
fn test_basic_reply() {
|
||||||
let sim_reply = SimReply::new(DummyReply::Pong);
|
let sim_reply = SimReply::new(&DummyReply::Pong);
|
||||||
assert_eq!(sim_reply.target(), SimTarget::SimCtrl);
|
assert_eq!(sim_reply.component(), SimComponent::SimCtrl);
|
||||||
assert_eq!(sim_reply.msg_type(), SimMessageType::Reply);
|
assert_eq!(sim_reply.msg_type(), SimMessageType::Reply);
|
||||||
let dummy_request =
|
let dummy_request =
|
||||||
DummyReply::from_sim_message(&sim_reply).expect("deserialization failed");
|
DummyReply::from_sim_message(&sim_reply).expect("deserialization failed");
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use acs::{MagnetometerModel, MagnetorquerModel};
|
use acs::{MagnetometerModel, MagnetorquerModel};
|
||||||
use asynchronix::simulation::{Mailbox, SimInit};
|
use controller::{ModelAddrWrapper, SimController};
|
||||||
use asynchronix::time::{MonotonicTime, SystemClock};
|
|
||||||
use controller::SimController;
|
|
||||||
use eps::PcduModel;
|
use eps::PcduModel;
|
||||||
use satrs_minisim::{SimReply, SimRequest, SIM_CTRL_UDP_PORT};
|
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;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
@@ -30,10 +31,15 @@ fn create_sim_controller(
|
|||||||
request_receiver: mpsc::Receiver<SimRequest>,
|
request_receiver: mpsc::Receiver<SimRequest>,
|
||||||
) -> SimController {
|
) -> SimController {
|
||||||
// Instantiate models and their mailboxes.
|
// Instantiate models and their mailboxes.
|
||||||
let mgm_model = MagnetometerModel::new(Duration::from_millis(50), reply_sender.clone());
|
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_0_mailbox = Mailbox::new();
|
||||||
let mgm_addr = mgm_mailbox.address();
|
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_mailbox = Mailbox::new();
|
||||||
let pcdu_addr = pcdu_mailbox.address();
|
let pcdu_addr = pcdu_mailbox.address();
|
||||||
let mgt_mailbox = Mailbox::new();
|
let mgt_mailbox = Mailbox::new();
|
||||||
@@ -41,8 +47,11 @@ fn create_sim_controller(
|
|||||||
|
|
||||||
let mut pcdu_model = PcduModel::new(reply_sender.clone());
|
let mut pcdu_model = PcduModel::new(reply_sender.clone());
|
||||||
pcdu_model
|
pcdu_model
|
||||||
.mgm_switch
|
.mgm_0_switch
|
||||||
.connect(MagnetometerModel::switch_device, &mgm_addr);
|
.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());
|
let mut mgt_model = MagnetorquerModel::new(reply_sender.clone());
|
||||||
// Input connections.
|
// Input connections.
|
||||||
@@ -50,9 +59,14 @@ fn create_sim_controller(
|
|||||||
.mgt_switch
|
.mgt_switch
|
||||||
.connect(MagnetorquerModel::switch_device, &mgt_addr);
|
.connect(MagnetorquerModel::switch_device, &mgt_addr);
|
||||||
// Output connections.
|
// Output connections.
|
||||||
mgt_model
|
mgt_model.gen_magnetic_field.connect(
|
||||||
.gen_magnetic_field
|
MagnetometerModel::apply_external_magnetic_field,
|
||||||
.connect(MagnetometerModel::apply_external_magnetic_field, &mgm_addr);
|
&mgm_0_addr,
|
||||||
|
);
|
||||||
|
mgt_model.gen_magnetic_field.connect(
|
||||||
|
MagnetometerModel::apply_external_magnetic_field,
|
||||||
|
&mgm_1_addr,
|
||||||
|
);
|
||||||
|
|
||||||
// Instantiate the simulator
|
// Instantiate the simulator
|
||||||
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
|
||||||
@@ -61,19 +75,21 @@ fn create_sim_controller(
|
|||||||
} else {
|
} else {
|
||||||
SimInit::new()
|
SimInit::new()
|
||||||
};
|
};
|
||||||
let simulation = sim_init
|
let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr);
|
||||||
.add_model(mgm_model, mgm_mailbox)
|
let (simulation, scheduler) = sim_init
|
||||||
.add_model(pcdu_model, pcdu_mailbox)
|
.add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model")
|
||||||
.add_model(mgt_model, mgt_mailbox)
|
.add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model")
|
||||||
.init(start_time);
|
.add_model(pcdu_model, pcdu_mailbox, "PCDU model")
|
||||||
|
.add_model(mgt_model, mgt_mailbox, "MGT model")
|
||||||
|
.init(start_time)
|
||||||
|
.unwrap();
|
||||||
SimController::new(
|
SimController::new(
|
||||||
sys_clock,
|
sys_clock,
|
||||||
request_receiver,
|
request_receiver,
|
||||||
reply_sender,
|
reply_sender,
|
||||||
simulation,
|
simulation,
|
||||||
mgm_addr,
|
scheduler,
|
||||||
pcdu_addr,
|
addrs,
|
||||||
mgt_addr,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +128,9 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut udp_server =
|
let mut udp_server =
|
||||||
SimUdpServer::new(SIM_CTRL_UDP_PORT, request_sender, reply_receiver, 200, None)
|
SimUdpServer::new(SIM_CTRL_PORT, request_sender, reply_receiver, 200, None)
|
||||||
.expect("could not create UDP request server");
|
.expect("could not create UDP request server");
|
||||||
log::info!("starting UDP server on port {}", SIM_CTRL_UDP_PORT);
|
log::info!("starting UDP server on port {SIM_CTRL_PORT}");
|
||||||
// This thread manages the simulator UDP server.
|
// This thread manages the simulator UDP server.
|
||||||
let udp_tc_thread = thread::spawn(move || {
|
let udp_tc_thread = thread::spawn(move || {
|
||||||
udp_server.run();
|
udp_server.run();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user