Compare commits

..

30 Commits

Author SHA1 Message Date
muellerr 2eaa78dfbc this actually works!
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 13:46:14 +02:00
muellerr a6d9bee5df Merge branch 'sim-mgm-update' into serialization-prototyping
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 13:09:25 +02:00
muellerr a77bbfa953 Merge remote-tracking branch 'origin/main' into serialization-prototyping 2024-05-25 13:08:54 +02:00
muellerr 4c67bcdde1 clean up serializatio ntest code 2024-05-25 13:08:32 +02:00
muellerr a710b30013 Merge remote-tracking branch 'origin/main' into serialization-prototyping
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 12:31:51 +02:00
muellerr 29783b2b07 introduce new HK helper
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-25 12:29:44 +02:00
muellerr 2a2a3a3eab PCDU switch set TM handling
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-22 18:48:46 +02:00
muellerr 2507469e68 continue PCDU integration
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-22 18:34:37 +02:00
muellerr b4febefa33 introduce switch handling for MGM
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-22 16:48:51 +02:00
muellerr fe60cb9ccf continue integrating power subsystem
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-19 17:33:37 +02:00
muellerr 27e88ed7f7 fix tests
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-18 18:45:42 +02:00
muellerr 295fed9a72 continue PCDU handler
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 18:39:25 +02:00
muellerr 8e89c8dd66 compiles again
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 17:58:54 +02:00
muellerr cb0a65c4d4 continue PCDU
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 14:08:42 +02:00
muellerr 3db54da3df Merge remote-tracking branch 'origin/main' into sim-mgm-update
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 12:49:20 +02:00
muellerr 15fcb17363 continue PCDU handler
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-16 16:28:22 +02:00
muellerr 8728c7ebea continued sample PCDU handler
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-12 14:23:42 +02:00
muellerr 7606767f63 the PCDU handler is already required
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-11 19:11:41 +02:00
muellerr 37b32a9008 try to make MGM set HK data work
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-10 17:55:11 +02:00
muellerr 9e096193dd clean up python commander a bit 2024-05-10 17:21:59 +02:00
muellerr 43bd77eef0 check that MGM data conversion works
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-10 15:33:43 +02:00
muellerr a4888bce01 add first MGM device unittests 2024-05-09 21:38:56 +02:00
muellerr 6e5b70af34 basic tests for SIM client
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 13:23:40 +02:00
muellerr d1476eb770 added basic tests for pytmtc app
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 11:41:11 +02:00
muellerr 783388aa6f pytmtc as regular package now
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 11:07:08 +02:00
muellerr 4a8db6b26a fix tests
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-08 21:08:41 +02:00
muellerr b86c2eb1d1 added some test stubs
Rust/sat-rs/pipeline/head Build started...
2024-05-08 21:02:16 +02:00
muellerr fe4126f7e2 first connection success
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-05-08 20:55:56 +02:00
muellerr c20163b10a start integrating sim in example
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-05-08 20:38:45 +02:00
muellerr b970154488 add serialization prototyping
Rust/sat-rs/pipeline/head This commit looks good
2024-04-26 10:01:29 +02:00
179 changed files with 51930 additions and 16193 deletions
+3 -14
View File
@@ -11,12 +11,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: Install libudev-dev on Ubuntu - run: cargo check --release
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
@@ -26,7 +21,6 @@ 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
@@ -43,7 +37,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 --target=${{matrix.target}} --no-default-features - run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
fmt: fmt:
name: Check formatting name: Check formatting
@@ -51,8 +45,6 @@ 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:
@@ -61,7 +53,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features --no-deps - run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
clippy: clippy:
name: Clippy name: Clippy
@@ -69,7 +61,4 @@ 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
+4 -5
View File
@@ -4,14 +4,13 @@ members = [
"satrs", "satrs",
"satrs-mib", "satrs-mib",
"satrs-example", "satrs-example",
"satrs-example/models", "satrs-minisim",
"satrs-example/client",
"satrs-example/minisim",
"satrs-shared", "satrs-shared",
"embedded-examples/embedded-client",
] ]
exclude = [ exclude = [
"embedded-examples/stm32f3-disco-rtic", "embedded-examples/stm32f3-disco-rtic",
"embedded-examples/stm32h7-nucleo-rtic", "embedded-examples/stm32h7-rtic",
"serialization-prototyping",
] ]
+4 -14
View File
@@ -1,10 +1,9 @@
<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>
[![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/) [![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://robamu.github.io/sat-rs/book/) [![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
[![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs) [![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
[![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs) [![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
[![matrix chat](https://img.shields.io/matrix/sat-rs%3Amatrix.org)](https://matrix.to/#/#sat-rs:matrix.org)
sat-rs sat-rs
========= =========
@@ -12,7 +11,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://robamu.github.io/sat-rs/book/) link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
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
@@ -38,16 +37,13 @@ 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/stm32f3-disco-rtic): * [`satrs-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/satrs-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/stm32h7-nucleo-rtic): * [`satrs-stm32h-nucleo-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/satrs-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.
@@ -62,8 +58,6 @@ 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
@@ -75,10 +69,6 @@ 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
View File
@@ -1,3 +0,0 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open
@@ -1,18 +0,0 @@
[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"
@@ -1,2 +0,0 @@
[interface]
serial_port = "/dev/ttyUSB0"
@@ -1,107 +0,0 @@
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()
}
@@ -1,4 +1,4 @@
/target /target
/itm.txt /itm.txt
/.cargo/config.toml /.cargo/config*
/.vscode /.vscode
File diff suppressed because it is too large Load Diff
+39 -17
View File
@@ -9,28 +9,50 @@ 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 = "1" defmt = "0.3"
defmt-rtt = { version = "1" } defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
panic-probe = { version = "1", features = ["print-defmt"] } panic-probe = { version = "0.3", features = ["print-defmt"] }
embedded-hal = "1" embedded-hal = "0.2.7"
cortex-m-semihosting = "0.5.0" cortex-m-semihosting = "0.5.0"
embassy-stm32 = { version = "0.6", features = ["defmt", "stm32f303vc", "memory-x", "unstable-pac", "time-driver-any"] }
embassy-time = { version = "0.5", features = ["defmt", "generic-queue-16", "defmt-timestamp-uptime-ms"]}
enumset = "1" enumset = "1"
heapless = "0.9" heapless = "0.8"
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"] }
rtic = { version = "2", features = ["thumbv7-backend"] } [dependencies.rtic]
rtic-sync = { version = "1" } version = "2"
features = ["thumbv7-backend"]
[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.5" defmt-test = "0.3"
# cargo test # cargo test
[profile.test] [profile.test]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,12 @@
# 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]
@@ -0,0 +1,42 @@
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
@@ -0,0 +1,33 @@
/* Linker script for the STM32F303VCT6 */
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 40K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* You may want to use this variable to locate the call stack and static
variables in different memory regions. Below is shown the default value */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
/* You can use this symbol to customize the location of the .text section */
/* If omitted the .text section will be placed right after the .vector_table
section */
/* This is required only on microcontrollers that store some configuration right
after the vector table */
/* _stext = ORIGIN(FLASH) + 0x400; */
/* Example of putting non-initialized variables into custom RAM locations. */
/* This assumes you have defined a region RAM2 above, and in the Rust
sources added the attribute `#[link_section = ".ram2bss"]` to the data
you want to place there. */
/* Note that the section will not be zero-initialized by the runtime! */
/* SECTIONS {
.ram2bss (NOLOAD) : ALIGN(4) {
*(.ram2bss);
. = ALIGN(4);
} > RAM2
} INSERT AFTER .bss;
*/
@@ -0,0 +1,8 @@
/venv
/.tmtc-history.txt
/log
/.idea/*
!/.idea/runConfigurations
/seqcnt.txt
/tmtc_conf.json
@@ -0,0 +1,4 @@
{
"com_if": "serial_cobs",
"serial_baudrate": 115200
}
+305
View File
@@ -0,0 +1,305 @@
#!/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()
@@ -0,0 +1,2 @@
tmtccmd == 8.0.1
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
@@ -1,56 +1,76 @@
#![no_main]
#![no_std] #![no_std]
#![no_main]
use satrs_stm32f3_disco_rtic as _;
use panic_probe as _; use stm32f3_discovery::leds::Leds;
use rtic::app; use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
#[app(device = embassy_stm32)] #[cortex_m_rt::entry]
mod app { fn main() -> ! {
use embassy_time::Timer; defmt::println!("STM32F3 Discovery Blinky");
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds}; let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let cp = cortex_m::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut delay = Delay::new(cp.SYST, clocks);
#[shared] let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
struct Shared {} 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);
#[local] //explicit on/off
struct Local { leds.ld4_nw.on().ok();
leds: Leds, delay.delay_ms(delay_ms);
current_dir: Direction, leds.ld4_nw.off().ok();
} delay.delay_ms(delay_ms);
#[init] leds.ld5_ne.on().ok();
fn init(_cx: init::Context) -> (Shared, Local) { delay.delay_ms(delay_ms);
let p = embassy_stm32::init(Default::default()); leds.ld5_ne.off().ok();
delay.delay_ms(delay_ms);
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2"); leds.ld6_w.on().ok();
delay.delay_ms(delay_ms);
leds.ld6_w.off().ok();
delay.delay_ms(delay_ms);
let led_pin_set = LedPinSet { leds.ld7_e.on().ok();
pin_n: p.PE8, delay.delay_ms(delay_ms);
pin_ne: p.PE9, leds.ld7_e.off().ok();
pin_e: p.PE10, delay.delay_ms(delay_ms);
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);
blinky::spawn().expect("failed to spawn blinky task"); leds.ld8_sw.on().ok();
( delay.delay_ms(delay_ms);
Shared {}, leds.ld8_sw.off().ok();
Local { delay.delay_ms(delay_ms);
leds,
current_dir: Direction::North,
},
)
}
#[task(local = [leds, current_dir])] leds.ld9_se.on().ok();
async fn blinky(cx: blinky::Context) { delay.delay_ms(delay_ms);
loop { leds.ld9_se.off().ok();
cx.local.leds.blink_next(cx.local.current_dir); delay.delay_ms(delay_ms);
Timer::after_millis(200).await;
} leds.ld10_s.on().ok();
delay.delay_ms(delay_ms);
leds.ld10_s.off().ok();
delay.delay_ms(delay_ms);
} }
} }
+35 -177
View File
@@ -1,193 +1,51 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use defmt_rtt as _; use cortex_m_semihosting::debug;
use defmt_brtt as _; // global logger
use stm32f3xx_hal as _; // memory layout
use panic_probe as _; use panic_probe as _;
use arbitrary_int::u11; // same panicking *behavior* as `panic-probe` but doesn't print a panic message
use core::time::Duration; // this prevents the panic message being printed *twice* when `defmt::panic` is invoked
use embassy_stm32::gpio::Output; #[defmt::panic_handler]
use spacepackets::{ fn panic() -> ! {
ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError, cortex_m::asm::udf()
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
};
pub const APID: u11 = u11::new(0x02);
#[derive(defmt::Format, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum Direction {
North,
NorthEast,
East,
SouthEast,
South,
SouthWest,
West,
NorthWest,
} }
impl Direction { /// Terminates the application and makes a semihosting-capable debug tool exit
pub fn switch_to_next(&mut self) -> (Self, Self) { /// with status code 0.
let curr = *self; pub fn exit() -> ! {
*self = match self { loop {
Direction::North => Direction::NorthEast, debug::exit(debug::EXIT_SUCCESS);
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)
} }
} }
#[derive(Copy, Clone, Debug, defmt::Format, serde::Serialize, serde::Deserialize)] /// Hardfault handler.
pub enum Request { ///
Ping, /// Terminates the application and makes a semihosting-capable debug tool exit
ChangeBlinkFrequency(Duration), /// with an error. This seems better than the default, which is to spin in a
} /// loop.
#[cortex_m_rt::exception]
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)] unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
pub struct TmHeader { loop {
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>, debug::exit(debug::EXIT_FAILURE);
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);
} }
} }
pub struct LedPinSet { // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
pub pin_n: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE8>, // once within a crate. the module can be in any file but there can only be at most
pub pin_ne: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE9>, // one `#[tests]` module in this library crate
pub pin_e: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE10>, #[cfg(test)]
pub pin_se: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE11>, #[defmt_test::tests]
pub pin_s: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE12>, mod unit_tests {
pub pin_sw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE13>, use defmt::assert;
pub pin_w: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE14>,
pub pin_nw: embassy_stm32::Peri<'static, embassy_stm32::peripherals::PE15>,
}
impl Leds { #[test]
pub fn new(pin_set: LedPinSet) -> Self { fn it_works() {
let led_n = Output::new( assert!(true)
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,
}
} }
} }
+588 -238
View File
@@ -1,334 +1,684 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use arbitrary_int::{u11, u14}; use satrs::pus::verification::{
use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS}; FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
use satrs_stm32f3_disco_rtic::{create_tm_packet, tm_size, Request, Response}; };
use spacepackets::{CcsdsPacketCreationError, CcsdsPacketIdAndPsc, SpHeader}; use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::ecss::EcssEnumU16;
use satrs::spacepackets::CcsdsPacket;
use satrs::spacepackets::{ByteConversionError, SpHeader};
// 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)]
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::ExtU32;
use satrs::seq_count::SequenceCountProviderCore;
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
use stm32f3xx_hal::dma::dma1;
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
use stm32f3xx_hal::pac::USART2;
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
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;
pub const PUS_APID: u11 = u11::new(0x02); type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
type InstantFugit = TimerInstantU32<1000>;
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
// 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_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN); const COBS_TC_OVERHEAD: usize = (MAX_TC_LEN + 254 - 1) / 254;
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;
const TC_DMA_BUF_LEN: usize = 512; // This is a static buffer which should ONLY (!) be used as the TX DMA
// transfer buffer.
static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
// This is a static buffer which should ONLY (!) be used as the RX DMA
// transfer buffer.
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
type TmPacket = heapless::Vec<u8, MAX_TM_LEN>; type TmPacket = Vec<u8, MAX_TM_LEN>;
type TcPacket = Vec<u8, MAX_TC_LEN>;
static TM_QUEUE: heapless::mpmc::Queue<TmPacket, 16> = heapless::mpmc::Queue::new(); static TM_REQUESTS: Q8<TmPacket> = Q8::new();
#[derive(Debug, defmt::Format, thiserror::Error)] use core::sync::atomic::{AtomicU16, Ordering};
pub enum TmSendError {
#[error("packet creation error: {0}")] pub struct SeqCountProviderAtomicRef {
PacketCreation(#[from] CcsdsPacketCreationError), atomic: AtomicU16,
#[error("queue error")] ordering: Ordering,
Queue, }
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)] #[derive(Debug, defmt::Format)]
pub struct RequestWithTcId { pub enum TmSendError {
pub request: Request, ByteConversion(ByteConversionError),
pub tc_id: CcsdsPacketIdAndPsc, Queue,
} }
#[app(device = embassy_stm32)] 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)]
pub enum Request {
Ping,
ChangeBlinkFrequency(u32),
}
#[derive(Debug, defmt::Format)]
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 core::time::Duration;
use super::*; use super::*;
use arbitrary_int::u14; use core::slice::Iter;
use embassy_time::Timer; use rtic_monotonics::systick::Systick;
use rtic::Mutex; use rtic_monotonics::Monotonic;
use rtic_sync::{ use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
channel::{Receiver, Sender}, use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
make_channel, #[allow(unused_imports)]
}; use stm32f3_discovery::leds::Direction;
use satrs_stm32f3_disco_rtic::{LedPinSet, Request, Response}; use stm32f3_discovery::leds::Leds;
use spacepackets::CcsdsPacketReader; use stm32f3xx_hal::prelude::*;
embassy_stm32::bind_interrupts!(struct Irqs { use stm32f3_discovery::switch_hal::OutputSwitch;
USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>; use stm32f3xx_hal::Switch;
DMA1_CHANNEL6 => embassy_stm32::dma::InterruptHandler<embassy_stm32::peripherals::DMA1_CH6>; #[allow(dead_code)]
DMA1_CHANNEL7 => embassy_stm32::dma::InterruptHandler<embassy_stm32::peripherals::DMA1_CH7>; type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
});
#[shared] #[shared]
struct Shared { struct Shared {
blink_freq: Duration, blink_freq: MillisDurationU32,
tx_shared: UartTxShared,
rx_transfer: Option<RxDmaTransferType>,
} }
#[local] #[local]
struct Local { struct Local {
leds: satrs_stm32f3_disco_rtic::Leds, verif_reporter: VerificationReportCreator,
current_dir: satrs_stm32f3_disco_rtic::Direction, leds: Leds,
seq_count: u14, last_dir: Direction,
tx: embassy_stm32::usart::UartTx<'static, embassy_stm32::mode::Async>, curr_dir: Iter<'static, Direction>,
rx: embassy_stm32::usart::RingBufferedUartRx<'static>,
} }
#[init] #[init]
fn init(_cx: init::Context) -> (Shared, Local) { fn init(cx: init::Context) -> (Shared, Local) {
static DMA_BUF: static_cell::ConstStaticCell<[u8; TC_DMA_BUF_LEN]> = let mut rcc = cx.device.RCC.constrain();
static_cell::ConstStaticCell::new([0; TC_DMA_BUF_LEN]);
let p = embassy_stm32::init(Default::default()); // Initialize the systick interrupt & obtain the token to prove that we did
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
let (req_sender, req_receiver) = make_channel!(RequestWithTcId, 16); let mut flash = cx.device.FLASH.constrain();
let clocks = rcc
.cfgr
.use_hse(8.MHz())
.sysclk(8.MHz())
.pclk1(8.MHz())
.freeze(&mut flash.acr);
defmt::info!("sat-rs demo application for the STM32F3-Discovery with RTICv2"); // Set up monotonic timer.
let led_pin_set = LedPinSet { //let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
pin_n: p.PE8,
pin_ne: p.PE9,
pin_e: p.PE10,
pin_se: p.PE11,
pin_s: p.PE12,
pin_sw: p.PE13,
pin_w: p.PE14,
pin_nw: p.PE15,
};
let leds = satrs_stm32f3_disco_rtic::Leds::new(led_pin_set);
let mut config = embassy_stm32::usart::Config::default(); defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
config.baudrate = UART_BAUD; let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
let uart = embassy_stm32::usart::Uart::new(
p.USART2, p.PA3, p.PA2, p.DMA1_CH7, p.DMA1_CH6, Irqs, config,
)
.unwrap();
let (tx, rx) = uart.split(); 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");
blinky::spawn().unwrap(); blink::spawn().unwrap();
serial_tx_handler::spawn().unwrap(); serial_tx_handler::spawn().unwrap();
serial_rx_handler::spawn(req_sender).unwrap();
req_handler::spawn(req_receiver).unwrap(); let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
( (
Shared { Shared {
blink_freq: Duration::from_millis(DEFAULT_BLINK_FREQ_MS as u64), blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
tx_shared: UartTxShared {
last_completed: None,
state: UartTxState::Idle(Some(TxIdle {
tx: tx_serial,
dma_channel: dma1.ch7,
})),
},
rx_transfer: Some(rx_transfer),
}, },
Local { Local {
verif_reporter,
leds, leds,
tx, last_dir: Direction::North,
seq_count: u14::new(0), curr_dir: Direction::iter(),
rx: rx.into_ring_buffered(DMA_BUF.take()),
current_dir: satrs_stm32f3_disco_rtic::Direction::North,
}, },
) )
} }
#[task(local = [leds, current_dir], shared=[blink_freq])] #[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
async fn blinky(mut cx: blinky::Context) { async fn blink(mut cx: blink::Context) {
let blink::LocalResources {
leds,
curr_dir,
last_dir,
..
} = cx.local;
let mut toggle_leds = |dir: &Direction| {
let last_led = leds.for_direction(*last_dir);
last_led.off().ok();
let led = leds.for_direction(*dir);
led.on().ok();
*last_dir = *dir;
};
loop { loop {
cx.local.leds.blink_next(cx.local.current_dir); match curr_dir.next() {
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);
Timer::after_millis(current_blink_freq.as_millis() as u64).await; Systick::delay(current_blink_freq).await;
} }
} }
#[task( #[task(
local = [ shared = [tx_shared],
tx,
encoded_buf: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN]
],
shared = [],
)] )]
async fn serial_tx_handler(cx: serial_tx_handler::Context) { async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
loop { loop {
while let Some(vec) = TM_QUEUE.dequeue() { let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
let encoded_len = if let UartTxState::Idle(_) = tx_shared.state {
cobs::encode_including_sentinels(&vec[0..vec.len()], cx.local.encoded_buf); return true;
defmt::debug!("sending {} bytes over UART", encoded_len); }
cx.local false
.tx });
.write(&cx.local.encoded_buf[0..encoded_len]) if is_idle {
.await let last_completed = cx.shared.tx_shared.lock(|shared| shared.last_completed);
.unwrap(); 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; continue;
} }
Timer::after_millis(TX_HANDLER_FREQ_MS as u64).await; 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 = [
rx, verif_reporter,
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(
cx: serial_rx_handler::Context, mut cx: serial_rx_handler::Context,
mut sender: Sender<'static, RequestWithTcId, 16>, received_packet: Vec<u8, MAX_TC_LEN>,
) { ) {
let mut decoder = cobs::CobsDecoder::new(cx.local.decode_buf); cx.local.timestamp[0] = P_FIELD_BASE;
loop { defmt::info!("Received packet with {} bytes", received_packet.len());
match cx.local.rx.read(cx.local.read_buf).await { let decode_buf = cx.local.decode_buf;
Ok(bytes) => { let packet = received_packet.as_slice();
defmt::debug!("received {} bytes over UART", bytes); let mut start_idx = None;
for byte in cx.local.read_buf[0..bytes].iter() { for (idx, byte) in packet.iter().enumerate() {
match decoder.feed(*byte) { if *byte != 0 {
Ok(None) => (), start_idx = Some(idx);
Ok(Some(packet_size)) => { break;
match CcsdsPacketReader::new_with_checksum( }
&decoder.dest()[0..packet_size], }
) { if start_idx.is_none() {
Ok(packet) => { defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
let tc_packet_id = return;
CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet); }
if let Ok(request) = let start_idx = start_idx.unwrap();
postcard::from_bytes::<Request>(packet.packet_data()) match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
{ Ok(len) => {
sender defmt::info!("Decoded packet length: {}", len);
.send(RequestWithTcId { let pus_tc = PusTcReader::new(decode_buf);
request, match pus_tc {
tc_id: tc_packet_id, Ok((tc, _tc_len)) => {
}) match convert_pus_tc_to_request(
.await &tc,
.unwrap(); cx.local.verif_reporter,
} cx.local.src_data_buf,
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);
} }
Err(e) => { Request::ChangeBlinkFrequency(new_freq_ms) => {
defmt::error!("error unpacking ccsds packet: {}", e); defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
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) => {
defmt::error!("cobs decoding error: {}", e); // TODO: Error handling: Send verification failure based on request error.
defmt::warn!("request error {}", e);
} }
} }
} }
} Err(e) => {
Err(e) => { defmt::warn!("Error unpacking PUS TC: {}", e);
defmt::error!("uart read error: {}", e);
}
}
}
}
#[task(shared = [blink_freq], local = [seq_count])]
async fn req_handler(
mut cx: req_handler::Context,
mut receiver: Receiver<'static, RequestWithTcId, 16>,
) {
loop {
match receiver.recv().await {
Ok(request_with_tc_id) => {
let tm_send_result = match request_with_tc_id.request {
Request::Ping => handle_ping_request(&mut cx, request_with_tc_id.tc_id),
Request::ChangeBlinkFrequency(duration) => {
handle_change_blink_frequency_request(
&mut cx,
request_with_tc_id.tc_id,
duration,
)
}
};
if let Err(e) = tm_send_result {
defmt::error!("error sending TM response: {}", e);
} }
} }
Err(_e) => defmt::error!("request receive error"), }
Err(_) => {
defmt::warn!("decoding error, can only process cobs encoded frames")
} }
} }
} }
fn handle_ping_request( fn handle_ping_request(timestamp: &[u8]) {
cx: &mut req_handler::Context, defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
tc_packet_id: CcsdsPacketIdAndPsc, let sp_header =
) -> Result<(), TmSendError> { SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
defmt::info!("Received PUS ping telecommand, sending ping reply"); let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?; let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1)); let mut tm_packet = TmPacket::new();
Ok(()) 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_change_blink_frequency_request( fn handle_start_verification(
cx: &mut req_handler::Context, accepted_token: VerificationToken<TcStateAccepted>,
tc_packet_id: CcsdsPacketIdAndPsc, verif_reporter: &mut VerificationReportCreator,
duration: Duration, src_data_buf: &mut [u8],
) -> Result<(), TmSendError> { timestamp: &[u8],
defmt::info!( ) -> VerificationToken<TcStateStarted> {
"Received ChangeBlinkFrequency request, new frequency: {} ms", let (tm_creator, started_token) = verif_reporter
duration.as_millis() .start_success(
src_data_buf,
accepted_token,
SEQ_COUNT_PROVIDER.get(),
0,
&timestamp,
)
.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(
verif_reporter
.completion_success(
src_data_buf,
started_token,
SEQ_COUNT_PROVIDER.get(),
0,
timestamp,
)
.unwrap(),
); );
if let Err(e) = result {
handle_tm_send_error(e);
}
}
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
let mut tc_packet = TcPacket::new();
cx.shared.rx_transfer.lock(|rx_transfer| {
let rx_ref = rx_transfer.as_ref().unwrap();
if rx_ref.is_complete() {
let uart_rx_owned = rx_transfer.take().unwrap();
let (buf, c, rx) = uart_rx_owned.stop();
// 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 serial_isr(mut cx: serial_isr::Context) {
cx.shared cx.shared
.blink_freq .tx_shared
.lock(|blink_freq| *blink_freq = duration); .lock(|tx_shared| match &mut tx_shared.state {
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?; UartTxState::Idle(_) => (),
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1)); UartTxState::Transmitting(transfer) => {
Ok(()) let transfer_ref = transfer.as_ref().unwrap();
} if transfer_ref.is_complete() {
} let transfer = transfer.take().unwrap();
let (_, dma_channel, mut tx) = transfer.stop();
fn send_tm( tx.clear_event(TxEvent::TransmissionComplete);
tc_packet_id: CcsdsPacketIdAndPsc, tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
response: Response, // We cache the last completed time to ensure that there is a minimum delay between consecutive
current_seq_count: u14, // transferred packets.
) -> Result<(), TmSendError> { tx_shared.last_completed = Some(Systick::now());
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), });
uptime_millis: embassy_time::Instant::now().as_millis() as u32, let mut tc_packet = TcPacket::new();
}; cx.shared.rx_transfer.lock(|rx_transfer| {
let mut tm_packet = TmPacket::new(); let rx_transfer_ref = rx_transfer.as_ref().unwrap();
let tm_size = tm_size(&tm_header, &response); // Received a partial packet.
tm_packet.resize(tm_size, 0).expect("vec resize failed"); if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
create_tm_packet(&mut tm_packet, sp_header, tm_header, response)?; let rx_transfer_owned = rx_transfer.take().unwrap();
if TM_QUEUE.enqueue(tm_packet).is_err() { let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
defmt::warn!("TC queue full"); // The received data is transferred to another task now to avoid any processing overhead
return Err(TmSendError::Queue); // during the interrupt. There are multiple ways to do this, we use a stack
} // allocated vector to do this.
Ok(()) tc_packet
} .resize(rx_len as usize, 0)
.expect("vec resize failed");
// same panicking *behavior* as `panic-probe` but doesn't print a panic message tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked rx.clear_event(RxEvent::Idle);
#[defmt::panic_handler] serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
fn panic() -> ! { *rx_transfer = Some(rx.read_exact(buf, ch));
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)
} }
} }
File diff suppressed because it is too large Load Diff
@@ -16,17 +16,16 @@ 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 = "1" defmt = "0.3"
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] } defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
panic-probe = { version = "1", features = ["print-defmt"] } panic-probe = { version = "0.3", 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.6" embedded-alloc = "0.5"
rtic-sync = { version = "1", features = ["defmt-03"] } rtic-sync = { version = "1", features = ["defmt-03"] }
[dependencies.smoltcp] [dependencies.smoltcp]
version = "0.12" version = "0.11.0"
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"]
@@ -35,16 +34,17 @@ version = "2"
features = ["thumbv7-backend"] features = ["thumbv7-backend"]
[dependencies.rtic-monotonics] [dependencies.rtic-monotonics]
version = "2" version = "1"
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.4" defmt-test = "0.3"
# cargo build/run # cargo build/run
[profile.dev] [profile.dev]
@@ -1,4 +1,4 @@
sat-rs example for the STM32H73ZI-Nucleo board sat-rs example for the STM32F3-Discovery 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,7 +3,8 @@
extern crate alloc; extern crate alloc;
use rtic::app; use rtic::app;
use rtic_monotonics::systick::prelude::*; use rtic_monotonics::systick::Systick;
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
@@ -12,7 +13,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::LlffHeap as Heap; use embedded_alloc::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;
@@ -31,8 +32,6 @@ 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;
@@ -73,7 +72,7 @@ impl Net {
let mut iface = Interface::new( let mut iface = Interface::new(
config, config,
&mut ethdev, &mut ethdev,
smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()), smoltcp::time::Instant::from_millis((Systick::now() - Systick::ZERO).to_millis()),
); );
// Create sockets // Create sockets
let dhcp_socket = dhcpv4::Socket::new(); let dhcp_socket = dhcpv4::Socket::new();
@@ -92,7 +91,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 = Mono::now().duration_since_epoch(); let uptime = Systick::now() - Systick::ZERO;
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)
@@ -204,7 +203,8 @@ mod app {
use core::ptr::addr_of_mut; use core::ptr::addr_of_mut;
use super::*; use super::*;
use rtic_monotonics::fugit::MillisDurationU32; use rtic_monotonics::systick::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,7 +256,12 @@ 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
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz()); let systick_mono_token = rtic_monotonics::create_systick_token!();
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.
@@ -372,24 +377,24 @@ mod app {
shared_pool shared_pool
.grow( .grow(
SUBPOOL_SMALL.get_mut().unwrap(), unsafe { SUBPOOL_SMALL.assume_init_mut() },
SUBPOOL_SMALL_SIZES.get_mut().unwrap(), unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
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(
SUBPOOL_MEDIUM.get_mut().unwrap(), unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(), unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
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(
SUBPOOL_LARGE.get_mut().unwrap(), unsafe { SUBPOOL_LARGE.assume_init_mut() },
SUBPOOL_LARGE_SIZES.get_mut().unwrap(), unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
SUBPOOL_LARGE_NUM_BLOCKS, SUBPOOL_LARGE_NUM_BLOCKS,
true, true,
) )
@@ -398,7 +403,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(&raw mut HEAP_MEM as usize, HEAP_SIZE) } unsafe { HEAP.init(HEAP_MEM.as_ptr() 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");
@@ -430,7 +435,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);
Mono::delay(current_blink_freq).await; Systick::delay(current_blink_freq).await;
} }
} }
@@ -452,7 +457,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");
} }
Mono::delay(100.millis()).await; Systick::delay(100.millis()).await;
} }
} }
@@ -478,7 +483,7 @@ mod app {
cx.local.udp.poll(sockets, pool); cx.local.udp.poll(sockets, pool);
}) })
}); });
Mono::delay(40.millis()).await; Systick::delay(40.millis()).await;
} }
} }
-8
View File
@@ -1,8 +0,0 @@
[package]
name = "satrs-gen"
version = "0.1.0"
edition = "2024"
[dependencies]
toml = "0.8"
heck = "0.5"
-34
View File
@@ -1,34 +0,0 @@
[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
View File
@@ -1,91 +0,0 @@
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
View File
@@ -1,260 +0,0 @@
<?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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

-29
View File
@@ -1,29 +0,0 @@
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
+4 -4
View File
@@ -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 important resources For these systems, the computation power and the available heap are the most important resources
which are also constrained. This might make completeley heap based memory management schemes which which are 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 commonly used in space systems is to limit heap allocations to program A useful pattern used commonly 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 candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all A huge 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
+4 -8
View File
@@ -5,8 +5,10 @@ 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
@@ -29,9 +31,7 @@ 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 [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim) the example application.
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,7 +43,3 @@ 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/).
-10
View File
@@ -7,13 +7,3 @@ 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
+19 -18
View File
@@ -1,41 +1,42 @@
[package] [package]
name = "satrs-example" name = "satrs-example"
version = "0.1.1" version = "0.1.1"
edition = "2024" edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
default-run = "satrs-example" default-run = "satrs-example"
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" 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.7" fern = "0.6"
chrono = "0.4" chrono = "0.4"
log = "0.4" log = "0.4"
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
delegate = "0.13" delegate = "0.10"
zerocopy = "0.8" zerocopy = "0.6"
csv = "1" csv = "1"
num_enum = "0.7" num_enum = "0.7"
thiserror = "2" thiserror = "1"
lazy_static = "1" lazy_static = "1"
strum = { version = "0.28", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
derive-new = "0.7" derive-new = "0.5"
cfg-if = "1"
arbitrary-int = "2"
bitbybit = "2"
postcard = "1"
ctrlc = "3"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
satrs = { path = "../satrs", features = ["test_util"] } [dependencies.satrs]
models = { path = "./models" } path = "../satrs"
satrs-minisim = { path = "./minisim" } features = ["test_util"]
satrs-mib = { path = "../satrs-mib" }
[dependencies.satrs-minisim]
path = "../satrs-minisim"
[dependencies.satrs-mib]
version = "0.1.1"
path = "../satrs-mib"
[features] [features]
# default = ["heap_tmtc"] dyn_tmtc = []
# heap_tmtc = [] default = ["dyn_tmtc"]
[dev-dependencies] [dev-dependencies]
env_logger = "0.11" env_logger = "0.11"
+1 -20
View File
@@ -14,7 +14,7 @@ You can run the application using `cargo run`.
# Features # Features
The example has the `heap_tmtc` feature which is enabled by default. With this feature enabled, The example has the `dyn_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.
@@ -73,22 +73,3 @@ 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`.
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4", features = ["derive"] }
log = "0.4"
fern = "0.7"
humantime = "2"
serde = { version = "1" }
satrs-example = { path = ".." }
models = { path = "../models" }
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
bitbybit = "2"
arbitrary-int = "2"
ctrlc = { version = "3.5" }
postcard = { version = "1" }
anyhow = "1"
-334
View File
@@ -1,334 +0,0 @@
use anyhow::bail;
use arbitrary_int::u11;
use clap::Parser as _;
use models::{Apid, MessageType, TcHeader, mgm::request::HkRequest};
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
use std::{
net::{IpAddr, SocketAddr, UdpSocket},
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::{Duration, SystemTime},
};
#[derive(clap::Parser)]
pub struct Cli {
#[arg(short, long)]
ping: bool,
#[arg(short, long)]
test_event: bool,
#[command(subcommand)]
commands: Option<Commands>,
}
#[derive(clap::Subcommand)]
enum Commands {
Mgm0(MgmArgs),
Mgm1(MgmArgs),
MgmAssy(MgmAssemblyArgs),
}
impl Commands {
#[inline]
pub fn target_id(&self) -> models::ComponentId {
match self {
Commands::Mgm0(_mgm_args) => models::ComponentId::AcsMgm0,
Commands::Mgm1(_mgm_args) => models::ComponentId::AcsMgm1,
Commands::MgmAssy(_mgm_assembly_args) => models::ComponentId::AcsMgmAssembly,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct MgmArgs {
#[arg(short, long)]
ping: bool,
#[arg(long)]
request_hk: bool,
#[arg(short, long)]
mode: Option<DeviceModeSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct MgmAssemblyArgs {
#[arg(short, long)]
ping: bool,
#[arg(short, long)]
mode: Option<AssemblyModeSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
pub enum DeviceModeSelect {
Off,
Normal,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
pub enum AssemblyModeSelect {
NoModeKeeping,
Off,
Normal,
}
fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {} {}] {}",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(),
record.target(),
message
))
})
.level(level)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}
fn main() -> anyhow::Result<()> {
setup_logger(log::LevelFilter::Debug).unwrap();
let kill_signal = Arc::new(AtomicBool::new(false));
let ctrl_kill_signal = kill_signal.clone();
ctrlc::set_handler(move || ctrl_kill_signal.store(true, Ordering::Relaxed)).unwrap();
let cli = Cli::parse();
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
client.set_nonblocking(true)?;
client.set_read_timeout(Some(Duration::from_millis(200)))?;
if cli.ping {
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(models::ComponentId::Controller, models::MessageType::Ping),
models::control::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!("sending ping request with TC ID {:#010x}", sent_tc_id.raw());
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if cli.test_event {
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(models::ComponentId::Controller, models::MessageType::Event),
models::control::request::Request::TestEvent,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending event request with TC ID {:#010x}",
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(cmd) = cli.commands {
let target_id = cmd.target_id();
match cmd {
Commands::Mgm0(args) | Commands::Mgm1(args) => {
if args.ping {
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
models::mgm::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} ping request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if args.request_hk {
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, models::MessageType::Hk),
models::mgm::request::Request::Hk(HkRequest {
id: models::mgm::request::HkId::Sensor,
req_type: models::HkRequestType::OneShot,
}),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(mode) = args.mode {
let dev_mode = match mode {
DeviceModeSelect::Off => models::DeviceMode::Off,
DeviceModeSelect::Normal => models::DeviceMode::Normal,
};
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, models::MessageType::Mode),
models::mgm::request::Request::Mode(
models::mgm::request::ModeRequest::SetMode(dev_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
}
Commands::MgmAssy(mgm_assembly_args) => {
if mgm_assembly_args.ping {
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
models::mgm::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} ping request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(mode) = mgm_assembly_args.mode {
let assembly_mode = match mode {
AssemblyModeSelect::NoModeKeeping => {
models::mgm_assembly::AssemblyMode::NoModeKeeping
}
AssemblyModeSelect::Off => {
models::mgm_assembly::AssemblyMode::Device(models::DeviceMode::Off)
}
AssemblyModeSelect::Normal => {
models::mgm_assembly::AssemblyMode::Device(models::DeviceMode::Normal)
}
};
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, models::MessageType::Mode),
models::mgm_assembly::request::Request::Mode(
models::mgm_assembly::request::ModeRequest::SetMode(assembly_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
}
}
}
let mut recv_buf: Box<[u8; 2048]> = Box::new([0; 2048]);
log::info!("entering listening loop");
loop {
if kill_signal.load(std::sync::atomic::Ordering::Relaxed) {
log::info!("received kill signal, exiting");
break;
}
match client.recv(recv_buf.as_mut_slice()) {
Ok(received_bytes) => handle_raw_tm_packet(&recv_buf.as_slice()[0..received_bytes])?,
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut
{
continue;
}
log::warn!("UDP reception error: {}", e)
}
}
}
Ok(())
}
fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
match spacepackets::CcsdsPacketReader::new_with_checksum(data) {
Ok(packet) => {
let tm_header_result =
postcard::take_from_bytes::<models::TmHeader>(packet.user_data());
if let Err(e) = tm_header_result {
bail!("Failed to deserialize TM header: {}", e);
}
let (tm_header, remainder) = tm_header_result.unwrap();
if let Some(tc_id) = tm_header.tc_id {
log::info!(
"Received TM with APID {} and from sender {:?} for TC ID {:#010x}",
packet.apid(),
tm_header.sender_id,
tc_id.raw()
);
} else {
log::info!(
"Received unsolicited TM with APID {} and from sender {:?}",
packet.apid(),
tm_header.sender_id,
);
}
if tm_header.message_type == MessageType::Event {
let response = postcard::from_bytes::<models::Event>(remainder);
log::info!(
"Received event from {:?}: {:?}",
tm_header.sender_id,
response.unwrap()
);
return Ok(());
}
match tm_header.sender_id {
models::ComponentId::EpsPcdu => {
let response =
postcard::from_bytes::<models::pcdu::response::Response>(remainder);
log::info!("Received response from PCDU: {:?}", response.unwrap());
}
models::ComponentId::Controller => {
let response =
postcard::from_bytes::<models::control::response::Response>(remainder);
log::info!("Received response from controller: {:?}", response.unwrap());
}
models::ComponentId::AcsSubsystem => todo!(),
models::ComponentId::AcsMgmAssembly => {
let response =
postcard::from_bytes::<models::mgm_assembly::response::Response>(remainder);
log::info!(
"Received response from MGM Assembly: {:?}",
response.unwrap()
);
}
models::ComponentId::AcsMgm0 => {
let response =
postcard::from_bytes::<models::mgm::response::Response>(remainder);
log::info!("Received response from MGM0: {:?}", response.unwrap());
}
models::ComponentId::AcsMgm1 => {
let response =
postcard::from_bytes::<models::mgm::response::Response>(remainder);
log::info!("Received response from MGM1: {:?}", response.unwrap());
}
models::ComponentId::EpsSubsystem => todo!(),
models::ComponentId::UdpServer => todo!(),
models::ComponentId::TcpServer => todo!(),
models::ComponentId::Ground => todo!(),
models::ComponentId::EventManager => {}
}
}
Err(_) => todo!(),
}
Ok(())
}
-32
View File
@@ -1,32 +0,0 @@
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"/>
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "models"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", features = ["derive"] }
spacepackets = { version = "0.17", git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false }
satrs = { path = "../../satrs" }
num_enum = { version = "0.7" }
strum = { version = "0.28", features = ["derive"] }
postcard = { version = "1" }
thiserror = { version = "2" }
bitbybit = "2"
arbitrary-int = "2"
-130
View File
@@ -1,130 +0,0 @@
use crate::TmHeader;
use serde::Serialize;
use spacepackets::{
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpHeader, SpacePacketHeader,
ccsds_packet_len_for_user_data_len_with_checksum,
};
use crate::TcHeader;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsdsTcPacketOwned {
pub sp_header: SpacePacketHeader,
pub tc_header: TcHeader,
pub payload: alloc::vec::Vec<u8>,
}
impl CcsdsTcPacketOwned {
pub fn new_with_request<R: serde::Serialize>(
sp_header: SpacePacketHeader,
tc_header: TcHeader,
request: R,
) -> Self {
let request_serialized = postcard::to_allocvec(&request).unwrap();
Self::new(sp_header, tc_header, request_serialized)
}
pub fn new(
sp_header: SpacePacketHeader,
tc_header: TcHeader,
payload: alloc::vec::Vec<u8>,
) -> Self {
Self {
sp_header,
tc_header,
payload,
}
}
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
let response_len =
postcard::experimental::serialized_size(&self.tc_header)? + self.payload.len();
let mut ccsds_tc = CcsdsPacketCreatorWithReservedData::new_tc_with_checksum(
self.sp_header,
response_len,
buf,
)?;
let user_data = ccsds_tc.packet_data_mut();
let ser_len = postcard::to_slice(&self.tc_header, user_data)?.len();
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
let ccsds_packet_len = ccsds_tc.finish();
Ok(ccsds_packet_len)
}
pub fn len_written(&self) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(&self.tc_header).unwrap() as usize
+ postcard::experimental::serialized_size(&self.payload).unwrap() as usize,
)
.unwrap()
}
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0u8; self.len_written()];
let len = self.write_to_bytes(&mut buf).unwrap();
buf.truncate(len);
buf
}
}
#[derive(Debug, thiserror::Error)]
pub enum CcsdsCreationError {
#[error("CCSDS packet creation error: {0}")]
CcsdsPacketCreation(#[from] CcsdsPacketCreationError),
#[error("postcard error: {0}")]
Postcard(#[from] postcard::Error),
#[error("timestamp generation error")]
Time,
}
/// Unserialized owned TM packet which can be cloned and sent around.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsdsTmPacketOwned {
pub sp_header: SpacePacketHeader,
pub tm_header: TmHeader,
pub payload: alloc::vec::Vec<u8>,
}
impl CcsdsTmPacketOwned {
pub fn new_with_serde_payload(
sp_header: SpHeader,
tm_header: &TmHeader,
payload: &impl Serialize,
) -> Result<Self, postcard::Error> {
Ok(CcsdsTmPacketOwned {
sp_header,
tm_header: *tm_header,
payload: postcard::to_allocvec(&payload)?,
})
}
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
let response_len =
postcard::experimental::serialized_size(&self.tm_header)? + self.payload.len();
let mut ccsds_tm = CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(
self.sp_header,
response_len,
buf,
)?;
let user_data = ccsds_tm.packet_data_mut();
let ser_len = postcard::to_slice(&self.tm_header, user_data)?.len();
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
let ccsds_packet_len = ccsds_tm.finish();
Ok(ccsds_packet_len)
}
pub fn len_written(&self) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(&self.tm_header).unwrap() as usize
+ postcard::experimental::serialized_size(&self.payload).unwrap() as usize,
)
.unwrap()
}
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0u8; self.len_written()];
let len = self.write_to_bytes(&mut buf).unwrap();
buf.truncate(len);
buf
}
}
-39
View File
@@ -1,39 +0,0 @@
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Event {
TestEvent,
}
impl Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
pub mod request {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
TestEvent,
}
}
pub mod response {
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Event(super::Event),
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Event(_event) => crate::MessageType::Event,
}
}
}
}
-196
View File
@@ -1,196 +0,0 @@
extern crate alloc;
use core::str::FromStr;
use spacepackets::{
CcsdsPacketIdAndPsc,
time::cds::{CdsTime, MIN_CDS_FIELD_LEN},
};
pub mod ccsds;
pub mod control;
pub mod mgm;
pub mod mgm_assembly;
pub mod pcdu;
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
)]
#[repr(u64)]
pub enum ComponentId {
Controller,
AcsSubsystem,
AcsMgmAssembly,
AcsMgm0,
AcsMgm1,
EpsSubsystem,
EpsPcdu,
UdpServer,
TcpServer,
EventManager,
Ground,
}
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
#[bitbybit::bitenum(u11)]
pub enum Apid {
Tmtc = 1,
Cfdp = 2,
Acs = 3,
Eps = 6,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum Event {
ControllerEvent(control::Event),
}
impl Message for Event {
fn message_type(&self) -> MessageType {
MessageType::Event
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct TmHeader {
pub sender_id: ComponentId,
pub target_id: ComponentId,
pub message_type: MessageType,
/// Telemetry can either be sent unsolicited, or as a response to telecommands.
pub tc_id: Option<CcsdsPacketIdAndPsc>,
/// Raw CDS short timestamp.
pub timestamp: Option<[u8; 7]>,
}
impl TmHeader {
pub fn new(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
tc_id: Option<CcsdsPacketIdAndPsc>,
cds_timestamp: &CdsTime,
) -> Self {
// Can not fail, CDS short always requires 7 bytes.
let mut stamp_buf: [u8; MIN_CDS_FIELD_LEN] = [0; MIN_CDS_FIELD_LEN];
cds_timestamp.write_to_bytes(&mut stamp_buf).unwrap();
Self {
sender_id,
target_id,
tc_id,
message_type,
timestamp: Some(stamp_buf),
}
}
pub fn new_for_unsolicited_tm(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
cds_timestamp: &CdsTime,
) -> Self {
Self::new(sender_id, target_id, message_type, None, cds_timestamp)
}
pub fn new_for_tc_response(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
tc_id: CcsdsPacketIdAndPsc,
cds_timestamp: &CdsTime,
) -> Self {
Self::new(
sender_id,
target_id,
message_type,
Some(tc_id),
cds_timestamp,
)
}
pub fn from_bytes_postcard(data: &[u8]) -> Result<(Self, &[u8]), postcard::Error> {
postcard::take_from_bytes::<TmHeader>(data)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct TcHeader {
pub target_id: ComponentId,
pub request_type: MessageType,
}
impl TcHeader {
pub fn new(target_id: ComponentId, request_type: MessageType) -> Self {
Self {
target_id,
request_type,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MessageType {
Ping,
Mode,
Hk,
Action,
Event,
Verification,
}
pub trait Message {
fn message_type(&self) -> MessageType;
}
/// Generic device mode which covers the requirements of most devices.
///
/// The states are related both to the physical and the logical state of the device. Some
/// device handlers control the power supply of their own device and an off state might also
/// mean that the device is physically off.
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
pub enum DeviceMode {
Off = 0,
On = 1,
/// Normal operation mode where periodic polling might be done as well.
Normal = 2,
}
impl FromStr for DeviceMode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"off" => Ok(DeviceMode::Off),
"on" => Ok(DeviceMode::On),
"normal" => Ok(DeviceMode::Normal),
_ => Err(()),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum HkRequestType {
OneShot,
/// Enable periodic HK generation with a specified frequency.
EnablePeriodic(core::time::Duration),
DisablePeriodic,
/// Modify periodic HK generation interval.
ModifyInterval(core::time::Duration),
}
#[cfg(test)]
mod tests {}
-90
View File
@@ -1,90 +0,0 @@
pub mod request {
use crate::{DeviceMode, HkRequestType, Message};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(DeviceMode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum HkId {
Sensor,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct HkRequest {
pub id: HkId,
pub req_type: HkRequestType,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Hk(HkRequest),
Mode(ModeRequest),
}
impl Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Hk(_hk_request) => crate::MessageType::Hk,
Request::Mode(_mode) => crate::MessageType::Mode,
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct MgmData {
pub valid: bool,
pub x: f32,
pub y: f32,
pub z: f32,
}
pub mod response {
use crate::{DeviceMode, Message, mgm::MgmData};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum HkResponse {
MgmData(MgmData),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
pub enum ModeResponse {
/// New mode has been set.
Mode(DeviceMode),
/// Setting a mode timed out.
SetModeTimeout,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Hk(HkResponse),
Mode(ModeResponse),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Hk(_hk_response) => crate::MessageType::Hk,
Response::Mode(_mode_failure) => crate::MessageType::Mode,
}
}
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
-109
View File
@@ -1,109 +0,0 @@
use core::str::FromStr;
use crate::DeviceMode;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssemblyMode {
/// The assembly mode ressembles the modes of the devices it controls. It also tries to keep
/// the children in the correct mode by re-commanding them into the correct mode.
Device(DeviceMode),
/// Mode keeping disabled.
NoModeKeeping,
}
impl FromStr for AssemblyMode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"off" => Ok(AssemblyMode::Device(DeviceMode::Off)),
"on" => Ok(AssemblyMode::Device(DeviceMode::On)),
"normal" => Ok(AssemblyMode::Device(DeviceMode::Normal)),
"no_mode_keeping" => Ok(AssemblyMode::NoModeKeeping),
_ => Err(()),
}
}
}
pub mod request {
use crate::{HkRequestType, Message, mgm_assembly::AssemblyMode};
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum HkId {
Sensor,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeRequest {
SetMode(AssemblyMode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct HkRequest {
pub id: HkId,
pub req_type: HkRequestType,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Mode(ModeRequest),
}
impl Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Mode(_mode) => crate::MessageType::Mode,
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
pub mod response {
use crate::{DeviceMode, Message};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeCommandFailure {
Timeout,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeReport {
/// Mode of the assembly.
Mode(super::AssemblyMode),
/// Timeout failure setting the children modes.
SetModeTimeout([Option<DeviceMode>; 2]),
/// Children are in wrong mode after commanding.
WrongMode([Option<DeviceMode>; 2]),
/// An assembly tried modekeeping but can not keep its mode.
CanNotKeepMode([Option<DeviceMode>; 2]),
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
Mode(ModeReport),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Mode(_mode_report) => crate::MessageType::Mode,
}
}
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
-147
View File
@@ -1,147 +0,0 @@
use std::collections::HashMap;
use strum::IntoEnumIterator as _;
#[bitbybit::bitfield(u16, debug, default = 0x0)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SwitchesBitfield {
#[bit(2, rw)]
magnetorquer: bool,
#[bit(1, rw)]
mgm1: bool,
#[bit(0, rw)]
mgm0: bool,
}
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
Hash,
strum::EnumIter,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
#[repr(u16)]
pub enum SwitchId {
Mgm0 = 0,
Mgm1 = 1,
Mgt = 2,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SwitchState {
Off = 0,
On = 1,
Unknown = 2,
Faulty = 3,
}
impl From<SwitchStateBinary> for SwitchState {
fn from(value: SwitchStateBinary) -> Self {
match value {
SwitchStateBinary::Off => SwitchState::Off,
SwitchStateBinary::On => SwitchState::On,
}
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SwitchStateBinary {
Off = 0,
On = 1,
}
pub type SwitchMapBinary = HashMap<SwitchId, SwitchStateBinary>;
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
impl Default for SwitchMapBinaryWrapper {
fn default() -> Self {
let mut switch_map = SwitchMapBinary::default();
for entry in SwitchId::iter() {
switch_map.insert(entry, SwitchStateBinary::Off);
}
Self(switch_map)
}
}
pub struct SwitchRequest {
pub switch_id: SwitchId,
pub target_state: SwitchStateBinary,
}
impl SwitchRequest {
pub fn new(switch_id: SwitchId, target_state: SwitchStateBinary) -> Self {
Self {
switch_id,
target_state,
}
}
pub fn switch_id(&self) -> SwitchId {
self.switch_id
}
pub fn target_state(&self) -> SwitchStateBinary {
self.target_state
}
}
pub mod request {
use crate::{DeviceMode, Message};
use super::*;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Mode(DeviceMode),
Ping,
GetSwitches,
EnableSwitches(SwitchesBitfield),
DisableSwitches(SwitchesBitfield),
}
impl Request {
pub fn message_type(&self) -> crate::MessageType {
match self {
Request::Mode(_mode) => crate::MessageType::Mode,
Request::Ping => crate::MessageType::Verification,
Request::GetSwitches => crate::MessageType::Action,
Request::EnableSwitches(_switches) | Request::DisableSwitches(_switches) => {
crate::MessageType::Action
}
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
pub mod response {
use super::*;
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Switches(SwitchesBitfield),
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Switches(_switches) => crate::MessageType::Action,
}
}
}
}
-1
View File
@@ -1,6 +1,5 @@
#!/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
+1 -1
View File
@@ -12,7 +12,7 @@ authors = [
{name = "Robin Mueller", email = "robin.mueller.m@gmail.com"}, {name = "Robin Mueller", email = "robin.mueller.m@gmail.com"},
] ]
dependencies = [ dependencies = [
"tmtccmd~=8.1", "tmtccmd~=8.0",
"pydantic~=2.7" "pydantic~=2.7"
] ]
@@ -1,11 +0,0 @@
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
+1 -4
View File
@@ -39,10 +39,7 @@ class EventU32:
class AcsId(enum.IntEnum): class AcsId(enum.IntEnum):
SUBSYSTEM = 1 MGM_0 = 0
MGM_ASSEMBLY = 2
MGM_0 = 3
MGM_1 = 4
class AcsHkIds(enum.IntEnum): class AcsHkIds(enum.IntEnum):
+1 -1
View File
@@ -12,7 +12,7 @@ from pytmtc.pus_tc import create_cmd_definition_tree
class SatrsConfigHook(HookBase): class SatrsConfigHook(HookBase):
def __init__(self, json_cfg_path: str): def __init__(self, json_cfg_path: str):
super().__init__(json_cfg_path) super().__init__(json_cfg_path=json_cfg_path)
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]: def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
from tmtccmd.config.com import ( from tmtccmd.config.com import (
+1 -1
View File
@@ -4,7 +4,7 @@ from spacepackets.ecss.pus_3_hk import Subservice
from spacepackets.ecss import PusTm from spacepackets.ecss import PusTm
from pytmtc.common import AcsId, Apid from pytmtc.common import AcsId, Apid
from pytmtc.acs.mgms import handle_mgm_hk_report from pytmtc.mgms import handle_mgm_hk_report
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
+11 -3
View File
@@ -17,9 +17,8 @@ from tmtccmd.tmtc import (
) )
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
from pytmtc.acs import create_acs_node
from pytmtc.common import Apid from pytmtc.common import Apid
from pytmtc.acs.mgms import create_mgm_cmds from pytmtc.mgms import create_mgm_cmds
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -68,6 +67,7 @@ class TcHandler(TcHandlerBase):
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)
@@ -101,7 +101,15 @@ 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
-1
View File
@@ -1 +0,0 @@
// TODO: Write dummy controller
File diff suppressed because it is too large Load Diff
-634
View File
@@ -1,634 +0,0 @@
use std::{sync::mpsc, time::Duration};
use models::{
ComponentId, DeviceMode,
mgm_assembly::{AssemblyMode, request, response},
};
use satrs::spacepackets::CcsdsPacketIdAndPsc;
use satrs_example::{ModeHelper, TmtcQueues};
use crate::ccsds::pack_ccsds_tm_packet_for_now;
pub struct ParentQueueHelper {
pub request_rx: mpsc::Receiver<request::ModeRequest>,
pub report_tx: mpsc::SyncSender<response::ModeReport>,
}
/// Helper component for communication with a parent component, which is usually as assembly.
pub struct ChildrenQueueHelper {
pub request_tx_queues: [mpsc::SyncSender<models::mgm::request::ModeRequest>; 2],
pub report_rx_queues: [mpsc::Receiver<models::mgm::response::ModeResponse>; 2],
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TransitionState {
#[default]
Idle,
AwaitingReplies,
}
#[derive(Debug, Default, Copy, Clone)]
pub struct MgmInfo {
reply_received: bool,
mode: Option<DeviceMode>,
}
/// MGM assembly component.
pub struct Assembly {
mode_helper: ModeHelper<AssemblyMode, TransitionState>,
/// This boolean is used for the distinction between transitions commanded by the parent
/// or by ground, and transitions which were commanded autonomously as part of children
/// mode keeping.
mode_keeping_transition: bool,
tmtc_queues: TmtcQueues,
mgm_modes: [MgmInfo; 2],
parent_queues: ParentQueueHelper,
pub(crate) children_queues: ChildrenQueueHelper,
}
impl Assembly {
pub const ID: ComponentId = ComponentId::AcsMgmAssembly;
pub fn new(
parent_queues: ParentQueueHelper,
children_queues: ChildrenQueueHelper,
tmtc_queues: TmtcQueues,
mode_timeout: Duration,
) -> Self {
Self {
mode_helper: ModeHelper::new(AssemblyMode::NoModeKeeping, mode_timeout),
mode_keeping_transition: false,
tmtc_queues,
mgm_modes: [MgmInfo::default(); 2],
parent_queues,
children_queues,
}
}
pub fn periodic_operation(&mut self) {
self.handle_telecommands();
self.handle_parent_mode_queue();
self.handle_children_mode_queues();
if self.mode_helper.transition_active() {
self.handle_mode_transition();
}
}
pub fn handle_telecommands(&mut self) {
loop {
match self.tmtc_queues.tc_rx.try_recv() {
Ok(packet) => {
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
match postcard::from_bytes::<models::mgm_assembly::request::Request>(
&packet.payload,
) {
Ok(request) => match request {
models::mgm_assembly::request::Request::Ping => {
self.send_telemetry(Some(tc_id), response::Response::Ok)
}
models::mgm_assembly::request::Request::Mode(request) => {
match request {
request::ModeRequest::SetMode(assembly_mode) => {
self.start_transition(false, assembly_mode, Some(tc_id))
}
request::ModeRequest::ReadMode => self.send_telemetry(
Some(tc_id),
response::Response::Mode(response::ModeReport::Mode(
self.mode(),
)),
),
}
}
},
Err(e) => {
log::warn!("failed to deserialize request: {}", e);
}
}
}
Err(e) => match e {
mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => log::warn!("packet sender disconnected"),
},
}
}
}
pub fn send_telemetry(
&self,
tc_id: Option<CcsdsPacketIdAndPsc>,
response: models::mgm_assembly::response::Response,
) {
match pack_ccsds_tm_packet_for_now(Self::ID, tc_id, &response) {
Ok(packet) => {
if let Err(e) = self.tmtc_queues.tm_tx.send(packet) {
log::warn!("failed to send TM packet: {}", e);
}
}
Err(e) => {
log::warn!("failed to pack TM packet: {}", e);
}
}
}
pub fn handle_parent_mode_queue(&mut self) {
loop {
match self.parent_queues.request_rx.try_recv() {
Ok(request) => match request {
request::ModeRequest::SetMode(assembly_mode) => match assembly_mode {
AssemblyMode::Device(_device_mode) => {
self.start_transition(false, assembly_mode, None);
}
AssemblyMode::NoModeKeeping => {
self.mode_helper.current = AssemblyMode::NoModeKeeping;
}
},
request::ModeRequest::ReadMode => self
.parent_queues
.report_tx
.send(response::ModeReport::Mode(self.mode_helper.current))
.unwrap(),
},
Err(e) => match e {
mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => {
log::warn!("packet sender disconnected")
}
},
}
}
}
pub fn handle_children_mode_queues(&mut self) {
let mut mode_report_received = false;
for (idx, rx) in self.children_queues.report_rx_queues.iter_mut().enumerate() {
loop {
match rx.try_recv() {
Ok(report) => match report {
models::mgm::response::ModeResponse::Mode(device_mode) => {
self.mgm_modes[idx].mode = Some(device_mode);
self.mgm_modes[idx].reply_received = true;
mode_report_received = true;
}
models::mgm::response::ModeResponse::SetModeTimeout => {
// Ignore, handle this with our own timeout.
log::warn!("MGM {} mode timeout", idx);
}
},
Err(e) => match e {
mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => {
log::warn!("packet sender disconnected")
}
},
}
}
}
if !mode_report_received {
return;
}
// Transition is active, check for completion.
if self.mode_helper.transition_active()
&& self.mgm_modes.iter().all(|i| i.reply_received)
&& let AssemblyMode::Device(device_mode) = self.mode_helper.target.unwrap()
{
// If at least one child reached the correct mode, we are done.
if self.mgm_modes.iter().any(|i| i.mode == Some(device_mode)) {
self.handle_mode_reached(true);
} else {
let report = if self.mode_keeping_transition {
response::ModeReport::CanNotKeepMode(self.mgm_modes.map(|info| info.mode))
} else {
response::ModeReport::WrongMode(self.mgm_modes.map(|info| info.mode))
};
self.handle_mode_transition_failure(report);
}
}
// Mode keeping active: Check children modes.
if let AssemblyMode::Device(device_mode) = self.mode_helper.current
&& self
.mgm_modes
.iter()
.all(|info| info.mode != Some(device_mode))
{
// Children lost mode. Try to command them back to the correct
// mode.
self.start_transition(true, self.mode_helper.current, None);
}
}
pub fn handle_mode_transition(&mut self) {
if self.mode_helper.target.is_none() {
self.handle_mode_reached(true);
return;
}
let target = self.mode_helper.target.unwrap();
let device_mode = match target {
AssemblyMode::Device(device_mode) => device_mode,
AssemblyMode::NoModeKeeping => {
self.handle_mode_reached(true);
return;
}
};
if self.mode_helper.transition_state == TransitionState::Idle {
self.command_children(device_mode);
self.mode_helper.transition_state = TransitionState::AwaitingReplies;
}
if self.mode_helper.transition_state == TransitionState::AwaitingReplies
&& self.mode_helper.timed_out()
{
let report = if self.mode_keeping_transition {
response::ModeReport::CanNotKeepMode(self.mgm_modes.map(|info| info.mode))
} else {
response::ModeReport::SetModeTimeout(self.mgm_modes.map(|info| info.mode))
};
self.handle_mode_transition_failure(report);
}
}
pub fn handle_mode_reached(&mut self, success: bool) {
let tc_commander = self.mode_helper.finish(success);
self.announce_mode();
if tc_commander.is_some() {
self.send_telemetry(tc_commander, response::Response::Ok);
}
self.parent_queues
.report_tx
.send(response::ModeReport::Mode(self.mode_helper.current))
.unwrap();
}
pub fn handle_mode_transition_failure(&mut self, report: response::ModeReport) {
if self.mode_helper.tc_commander.is_some() {
self.send_telemetry(
self.mode_helper.tc_commander,
response::Response::Mode(response::ModeReport::SetModeTimeout(
self.mgm_modes.map(|info| info.mode),
)),
);
}
self.parent_queues.report_tx.send(report).unwrap();
self.mode_helper.finish(false);
}
pub fn command_children(&self, mode: DeviceMode) {
for tx in &self.children_queues.request_tx_queues {
tx.send(models::mgm::request::ModeRequest::SetMode(mode))
.unwrap();
}
}
pub fn start_transition(
&mut self,
mode_keeping: bool,
target: AssemblyMode,
tc_id: Option<CcsdsPacketIdAndPsc>,
) {
self.mode_keeping_transition = mode_keeping;
self.mode_helper.tc_commander = tc_id;
self.mgm_modes
.iter_mut()
.for_each(|m| m.reply_received = false);
self.mode_helper.start(target);
}
fn announce_mode(&self) {
// TODO: Event?
log::info!(
"{:?} announcing mode: {:?}",
Self::ID,
self.mode_helper.current
);
}
#[inline]
pub fn mode(&self) -> AssemblyMode {
self.mode_helper.current
}
#[inline]
#[cfg(test)]
fn mode_transition_active(&self) -> bool {
self.mode_helper.transition_active()
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc::TryRecvError;
use arbitrary_int::u11;
use models::{
Apid, Message, MessageType, TcHeader,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
mgm_assembly,
};
use satrs::spacepackets::SpacePacketHeader;
use super::*;
pub struct Testbench {
subsystem_req_tx: mpsc::SyncSender<request::ModeRequest>,
subsystem_report_rx: mpsc::Receiver<response::ModeReport>,
mgm_request_rx: [mpsc::Receiver<models::mgm::request::ModeRequest>; 2],
mgm_report_tx: [mpsc::SyncSender<models::mgm::response::ModeResponse>; 2],
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
assembly: Assembly,
}
impl Testbench {
pub fn new() -> Self {
let (subsystem_req_tx, subsystem_req_rx) = mpsc::sync_channel(5);
let (subsystem_report_tx, subsystem_report_rx) = mpsc::sync_channel(5);
let (mgm_0_mode_request_tx, mgm_0_mode_request_rx) = mpsc::sync_channel(5);
let (mgm_1_mode_request_tx, mgm_1_mode_request_rx) = mpsc::sync_channel(5);
let (mgm_0_mode_report_tx, mgm_0_mode_report_rx) = mpsc::sync_channel(5);
let (mgm_1_mode_report_tx, mgm_1_mode_report_rx) = mpsc::sync_channel(5);
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
Self {
subsystem_req_tx,
subsystem_report_rx,
mgm_request_rx: [mgm_0_mode_request_rx, mgm_1_mode_request_rx],
mgm_report_tx: [mgm_0_mode_report_tx, mgm_1_mode_report_tx],
tc_tx,
tm_rx,
assembly: Assembly::new(
ParentQueueHelper {
request_rx: subsystem_req_rx,
report_tx: subsystem_report_tx,
},
ChildrenQueueHelper {
request_tx_queues: [mgm_0_mode_request_tx, mgm_1_mode_request_tx],
report_rx_queues: [mgm_0_mode_report_rx, mgm_1_mode_report_rx],
},
TmtcQueues { tc_rx, tm_tx },
Duration::from_millis(20),
),
}
}
pub fn assert_all_queues_empty(&self) {
assert!(
matches!(self.tm_rx.try_recv().unwrap_err(), TryRecvError::Empty),
"TM queue not empty"
);
assert!(
matches!(
self.subsystem_report_rx.try_recv().unwrap_err(),
TryRecvError::Empty
),
"subsystem report queue not empty"
);
for rx in self.mgm_request_rx.iter() {
assert!(
matches!(rx.try_recv().unwrap_err(), TryRecvError::Empty),
"mgm request queue not empty"
)
}
}
}
pub fn create_request_tc(
request: models::mgm_assembly::request::Request,
) -> models::ccsds::CcsdsTcPacketOwned {
models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(Assembly::ID, request.message_type()),
request,
)
}
#[test]
fn basic_test() {
let mut tb = Testbench::new();
tb.assert_all_queues_empty();
tb.assembly.periodic_operation();
tb.assert_all_queues_empty();
assert_eq!(tb.assembly.mode(), AssemblyMode::NoModeKeeping);
}
#[test]
fn test_tc_commanded_transition() {
let mut tb = Testbench::new();
tb.tc_tx
.send(create_request_tc(mgm_assembly::request::Request::Mode(
request::ModeRequest::SetMode(AssemblyMode::Device(DeviceMode::Normal)),
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
let response = tb.tm_rx.try_recv().unwrap();
assert_eq!(response.tm_header.sender_id, Assembly::ID);
assert_eq!(response.tm_header.message_type, MessageType::Verification);
let response: response::Response = postcard::from_bytes(&response.payload).unwrap();
assert_eq!(response, response::Response::Ok);
}
#[test]
fn test_parent_commanded_transition() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
}
#[test]
fn test_one_mgm_is_sufficient() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// One device is sufficient.
tb.mgm_report_tx[0]
.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
tb.mgm_report_tx[1]
.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
}
#[test]
fn test_mode_commanding_fails() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::NoModeKeeping);
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::WrongMode([Some(DeviceMode::Off), Some(DeviceMode::Off)])
);
}
#[test]
fn test_mode_keeping_fails() {
let mut tb = Testbench::new();
tb.subsystem_req_tx
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
DeviceMode::Normal,
)))
.unwrap();
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Confirm the mode is set.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(
DeviceMode::Normal,
))
.unwrap();
}
tb.assembly.periodic_operation();
assert!(!tb.assembly.mode_transition_active());
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
);
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
// This should start mode keeping.
tb.assembly.periodic_operation();
assert!(tb.assembly.mode_transition_active());
for rx in tb.mgm_request_rx.iter() {
let request = rx.try_recv().unwrap();
assert_eq!(
request,
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
);
}
// Let the mode keeping fail.
for tx in tb.mgm_report_tx.iter() {
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
.unwrap();
}
tb.assembly.periodic_operation();
let report = tb.subsystem_report_rx.try_recv().unwrap();
assert_eq!(
report,
response::ModeReport::CanNotKeepMode([Some(DeviceMode::Off), Some(DeviceMode::Off)])
);
}
}
-5
View File
@@ -1,6 +1 @@
pub mod ctrl;
pub mod mgm; pub mod mgm;
pub mod mgm_assembly;
pub mod subsystem;
-1
View File
@@ -1 +0,0 @@
// TODO: Write subsystem
+82
View File
@@ -0,0 +1,82 @@
use satrs::pus::verification::RequestId;
use satrs::spacepackets::ecss::tc::PusTcCreator;
use satrs::spacepackets::ecss::tm::PusTmReader;
use satrs::{
spacepackets::ecss::{PusPacket, WritablePusPacket},
spacepackets::SpHeader,
};
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::time::Duration;
fn main() {
let mut buf = [0; 32];
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
let tc_req_id = RequestId::new(&pus_tc);
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
let size = pus_tc
.write_to_bytes(&mut buf)
.expect("Creating PUS TC failed");
client
.send_to(&buf[0..size], addr)
.unwrap_or_else(|_| panic!("Sending to {addr:?} failed"));
client
.set_read_timeout(Some(Duration::from_secs(2)))
.expect("Setting read timeout failed");
loop {
let res = client.recv(&mut buf);
match res {
Ok(_len) => {
let (pus_tm, size) = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
if pus_tm.service() == 17 && pus_tm.subservice() == 2 {
println!("Received PUS Ping Reply TM[17,2]")
} else if pus_tm.service() == 1 {
if pus_tm.source_data().is_empty() {
println!("Invalid verification TM, no source data");
}
let src_data = pus_tm.source_data();
if src_data.len() < 4 {
println!("Invalid verification TM source data, less than 4 bytes")
}
let req_id = RequestId::from_bytes(src_data).unwrap();
if pus_tm.subservice() == 1 {
println!("Received TM[1,1] acceptance success for request ID {req_id}")
} else if pus_tm.subservice() == 2 {
println!("Received TM[1,2] acceptance failure for request ID {req_id}")
} else if pus_tm.subservice() == 3 {
println!("Received TM[1,3] start success for request ID {req_id}")
} else if pus_tm.subservice() == 4 {
println!("Received TM[1,2] start failure for request ID {req_id}")
} else if pus_tm.subservice() == 5 {
println!("Received TM[1,5] step success for request ID {req_id}")
} else if pus_tm.subservice() == 6 {
println!("Received TM[1,6] step failure for request ID {req_id}")
} else if pus_tm.subservice() == 7 {
println!("Received TM[1,7] completion success for request ID {req_id}")
} else if pus_tm.subservice() == 8 {
println!("Received TM[1,8] completion failure for request ID {req_id}");
}
} else {
println!(
"Received TM[{}, {}] with {} bytes",
pus_tm.service(),
pus_tm.subservice(),
size
);
}
}
Err(ref e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
println!("No reply received for 2 seconds");
break;
}
_ => {
println!("UDP receive error {:?}", res.unwrap_err());
}
}
}
}
+66
View File
@@ -0,0 +1,66 @@
#![allow(dead_code)]
use crossbeam_channel::{bounded, Receiver, Sender};
use std::sync::atomic::{AtomicU16, Ordering};
use std::thread;
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
trait FieldDataProvider: Send {
fn get_data(&self) -> &[u8];
}
struct FixedFieldDataWrapper {
data: [u8; 8],
}
impl FixedFieldDataWrapper {
pub fn from_two_u32(p0: u32, p1: u32) -> Self {
let mut data = [0; 8];
data[0..4].copy_from_slice(p0.to_be_bytes().as_slice());
data[4..8].copy_from_slice(p1.to_be_bytes().as_slice());
Self { data }
}
}
impl FieldDataProvider for FixedFieldDataWrapper {
fn get_data(&self) -> &[u8] {
self.data.as_slice()
}
}
type FieldDataTraitObj = Box<dyn FieldDataProvider>;
struct ExampleMgmSet {
mgm_vec: [f32; 3],
temperature: u16,
}
#[derive(FromBytes, AsBytes, Unaligned)]
#[repr(C)]
struct ExampleMgmSetZc {
mgm_vec: [u8; 12],
temperatur: U16<NetworkEndian>,
}
fn main() {
let (s0, r0): (Sender<FieldDataTraitObj>, Receiver<FieldDataTraitObj>) = bounded(5);
let data_wrapper = FixedFieldDataWrapper::from_two_u32(2, 3);
s0.send(Box::new(data_wrapper)).unwrap();
let jh0 = thread::spawn(move || {
let data = r0.recv().unwrap();
let raw = data.get_data();
println!("Received data {raw:?}");
});
let jh1 = thread::spawn(|| {});
jh0.join().unwrap();
jh1.join().unwrap();
//let mut max_val: u16 = u16::MAX;
//max_val += 1;
//println!("Max val: {}", max_val);
let atomic_u16: AtomicU16 = AtomicU16::new(u16::MAX);
atomic_u16.fetch_add(1, Ordering::SeqCst);
println!(
"atomic after overflow: {}",
atomic_u16.load(Ordering::SeqCst)
);
}
-34
View File
@@ -1,34 +0,0 @@
use arbitrary_int::u11;
use models::{Apid, ComponentId, Message, TmHeader, ccsds::CcsdsTmPacketOwned};
use satrs::spacepackets::{
CcsdsPacketIdAndPsc, SpHeader,
time::{StdTimestampError, cds::CdsTime},
};
use serde::Serialize;
#[derive(Debug, thiserror::Error)]
pub enum CcsdsTmCreationError {
#[error("postcard error: {0}")]
Postcard(#[from] postcard::Error),
#[error("timestamp error: {0}")]
Time(#[from] StdTimestampError),
}
pub fn pack_ccsds_tm_packet_for_now(
sender_id: ComponentId,
tc_id: Option<CcsdsPacketIdAndPsc>,
payload: &(impl Serialize + Message),
) -> Result<CcsdsTmPacketOwned, CcsdsTmCreationError> {
let now = CdsTime::now_with_u16_days()?;
let sp_header = SpHeader::new_from_apid(u11::new(Apid::Tmtc as u16));
let tm_header = TmHeader::new(
sender_id,
ComponentId::Ground,
payload.message_type(),
tc_id,
&now,
);
Ok(CcsdsTmPacketOwned::new_with_serde_payload(
sp_header, &tm_header, payload,
)?)
}
+73 -10
View File
@@ -1,4 +1,3 @@
use arbitrary_int::u11;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use satrs::{ use satrs::{
res_code::ResultU16, res_code::ResultU16,
@@ -7,10 +6,13 @@ use satrs::{
use satrs_mib::res_code::ResultU16Info; use satrs_mib::res_code::ResultU16Info;
use satrs_mib::resultcode; use satrs_mib::resultcode;
use std::{collections::HashSet, net::Ipv4Addr}; use std::{collections::HashSet, net::Ipv4Addr};
use strum::IntoEnumIterator as _; use strum::IntoEnumIterator;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::pool::{StaticMemoryPool, StaticPoolConfig}; use satrs::{
events::{EventU32TypedSev, SeverityInfo},
pool::{StaticMemoryPool, StaticPoolConfig},
};
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)] #[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)] #[repr(u8)]
@@ -36,17 +38,19 @@ pub enum GroupId {
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED; pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
pub const SERVER_PORT: u16 = 7301; pub const SERVER_PORT: u16 = 7301;
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(0, 0);
lazy_static! { lazy_static! {
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = { pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
let mut set = HashSet::new(); let mut set = HashSet::new();
for id in models::Apid::iter() { for id in components::Apid::iter() {
set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16))); set.insert(PacketId::new(PacketType::Tc, true, 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 models::Apid::iter() { for id in components::Apid::iter() {
set.insert(id as u16); set.insert(id as u16);
} }
set set
@@ -118,9 +122,68 @@ pub mod mode_err {
} }
pub mod components { pub mod components {
use satrs::ComponentId; use satrs::request::UniqueApidTargetId;
use strum::EnumIter;
pub const NO_SENDER: ComponentId = ComponentId::MAX; #[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
pub enum Apid {
Sched = 1,
GenericPus = 2,
Acs = 3,
Cfdp = 4,
Tmtc = 5,
Eps = 6,
}
// Component IDs for components with the PUS APID.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PusId {
PusEventManagement = 0,
PusRouting = 1,
PusTest = 2,
PusAction = 3,
PusMode = 4,
PusHk = 5,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum AcsId {
Mgm0 = 0,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum EpsId {
Pcdu = 0,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TmtcId {
UdpServer = 0,
TcpServer = 1,
}
pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
pub const PUS_TEST_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
pub const PUS_MODE_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
pub const PUS_HK_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Sched as u16, 0);
pub const MGM_HANDLER_0: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
pub const PCDU_HANDLER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32);
pub const UDP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
pub const TCP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
} }
pub mod pool { pub mod pool {
@@ -169,7 +232,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_AOCS: u64 = 200; pub const FREQ_MS_AOCS: u64 = 500;
pub const FREQ_MS_CONTROLLER: u64 = 200; pub const FREQ_MS_PUS_STACK: u64 = 200;
pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5; pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5;
} }
-84
View File
@@ -1,84 +0,0 @@
use models::{
ComponentId,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
control,
};
use satrs::spacepackets::CcsdsPacketIdAndPsc;
use crate::ccsds::pack_ccsds_tm_packet_for_now;
pub struct Controller {
pub tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
pub event_ctrl_tx: std::sync::mpsc::SyncSender<control::Event>,
}
impl Controller {
pub fn new(
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
event_ctrl_tx: std::sync::mpsc::SyncSender<control::Event>,
) -> Self {
Self {
tc_rx,
tm_tx,
event_ctrl_tx,
}
}
pub fn periodic_operation(&mut self) {
self.handle_telecommands();
}
pub fn handle_telecommands(&mut self) {
loop {
match self.tc_rx.try_recv() {
Ok(packet) => {
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
match postcard::from_bytes::<control::request::Request>(&packet.payload) {
Ok(request) => {
log::info!(
"received request {:?} with TC ID {:#010x}",
request,
tc_id.raw()
);
match request {
control::request::Request::Ping => self
.send_telemetry(Some(tc_id), control::response::Response::Ok),
control::request::Request::TestEvent => {
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap()
}
}
}
Err(e) => {
log::warn!("failed to deserialize request: {}", e);
}
}
}
Err(e) => match e {
std::sync::mpsc::TryRecvError::Empty => break,
std::sync::mpsc::TryRecvError::Disconnected => {
log::warn!("packet sender disconnected")
}
},
}
}
}
pub fn send_telemetry(
&self,
tc_id: Option<CcsdsPacketIdAndPsc>,
response: control::response::Response,
) {
match pack_ccsds_tm_packet_for_now(ComponentId::Controller, tc_id, &response) {
Ok(packet) => {
if let Err(e) = self.tm_tx.send(packet) {
log::warn!("failed to send TM packet: {}", e);
}
}
Err(e) => {
log::warn!("failed to pack TM packet: {}", e);
}
}
}
}
+29 -92
View File
@@ -1,15 +1,16 @@
use derive_new::new; use derive_new::new;
use models::pcdu::{SwitchId, SwitchRequest, SwitchState, SwitchStateBinary};
use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration}; use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration};
use satrs::{ use satrs::{
power::{
PowerSwitchInfo, PowerSwitcherCommandSender, SwitchRequest, SwitchState, SwitchStateBinary,
},
queue::GenericSendError, queue::GenericSendError,
request::{GenericMessage, MessageMetadata}, request::{GenericMessage, MessageMetadata},
}; };
use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper};
use thiserror::Error; use thiserror::Error;
use crate::eps::pcdu::SwitchMapWrapper;
use self::pcdu::SharedSwitchSet; use self::pcdu::SharedSwitchSet;
pub mod pcdu; pub mod pcdu;
@@ -21,7 +22,6 @@ pub struct PowerSwitchHelper {
} }
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum SwitchCommandingError { pub enum SwitchCommandingError {
#[error("send error: {0}")] #[error("send error: {0}")]
Send(#[from] GenericSendError), Send(#[from] GenericSendError),
@@ -31,72 +31,18 @@ pub enum SwitchCommandingError {
pub enum SwitchInfoError { pub enum SwitchInfoError {
/// This is a configuration error which should not occur. /// This is a configuration error which should not occur.
#[error("switch ID not in map")] #[error("switch ID not in map")]
SwitchIdNotInMap(SwitchId), SwitchIdNotInMap(PcduSwitch),
#[error("switch set invalid")] #[error("switch set invalid")]
SwitchSetInvalid, SwitchSetInvalid,
} }
impl PowerSwitchHelper { impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
pub fn send_switch_on_cmd(
&self,
requestor_info: satrs::request::MessageMetadata,
switch_id: SwitchId,
) -> Result<(), GenericSendError> {
self.switcher_tx.send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::On),
))?;
Ok(())
}
#[allow(dead_code)]
pub fn send_switch_off_cmd(
&self,
requestor_info: satrs::request::MessageMetadata,
switch_id: SwitchId,
) -> Result<(), GenericSendError> {
self.switcher_tx.send(GenericMessage::new(
requestor_info,
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
))?;
Ok(())
}
pub fn switch_state(&self, switch_id: SwitchId) -> Result<SwitchState, SwitchInfoError> {
let switch_set = self
.shared_switch_set
.lock()
.expect("failed to lock switch set");
if !switch_set.valid {
return Err(SwitchInfoError::SwitchSetInvalid);
}
if let Some(state) = switch_set.switch_map.get(&switch_id) {
return Ok(*state);
}
Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
}
#[allow(dead_code)]
fn switch_delay_ms(&self) -> Duration {
// Here, we could set device specific switch delays theoretically. Set it to this value
// for now.
Duration::from_millis(1000)
}
pub fn is_switch_on(&self, switch_id: SwitchId) -> bool {
if let Ok(state) = self.switch_state(switch_id) {
state == SwitchState::On
} else {
false
}
}
}
/*
impl PowerSwitchInfo<SwitchId> for PowerSwitchHelper {
type Error = SwitchInfoError; type Error = SwitchInfoError;
fn switch_state(&self, switch_id: SwitchId) -> Result<SwitchState, Self::Error> { fn switch_state(
&self,
switch_id: PcduSwitch,
) -> Result<satrs::power::SwitchState, Self::Error> {
let switch_set = self let switch_set = self
.shared_switch_set .shared_switch_set
.lock() .lock()
@@ -117,52 +63,42 @@ impl PowerSwitchInfo<SwitchId> for PowerSwitchHelper {
Duration::from_millis(1000) Duration::from_millis(1000)
} }
} }
*/
/* impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
impl PowerSwitcherCommandSender<SwitchId> for PowerSwitchHelper {
type Error = SwitchCommandingError; type Error = SwitchCommandingError;
fn send_switch_on_cmd( fn send_switch_on_cmd(
&self, &self,
requestor_info: satrs::request::MessageMetadata, requestor_info: satrs::request::MessageMetadata,
switch_id: SwitchId, switch_id: PcduSwitch,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.switcher_tx.send(GenericMessage::new( self.switcher_tx
requestor_info, .send_switch_on_cmd(requestor_info, switch_id)?;
SwitchRequest::new(switch_id, SwitchStateBinary::On),
));
Ok(()) Ok(())
} }
fn send_switch_off_cmd( fn send_switch_off_cmd(
&self, &self,
requestor_info: satrs::request::MessageMetadata, requestor_info: satrs::request::MessageMetadata,
switch_id: SwitchId, switch_id: PcduSwitch,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.switcher_tx.send(GenericMessage::new( self.switcher_tx
requestor_info, .send_switch_off_cmd(requestor_info, switch_id)?;
SwitchRequest::new(switch_id, SwitchStateBinary::Off),
));
Ok(()) Ok(())
} }
} }
*/
#[allow(dead_code)]
#[derive(new)] #[derive(new)]
pub struct SwitchRequestInfo { pub struct SwitchRequestInfo {
pub requestor_info: MessageMetadata, pub requestor_info: MessageMetadata,
pub switch_id: SwitchId, pub switch_id: PcduSwitch,
pub target_state: SwitchStateBinary, pub target_state: satrs::power::SwitchStateBinary,
} }
// Test switch helper which can be used for unittests. // Test switch helper which can be used for unittests.
#[allow(dead_code)]
pub struct TestSwitchHelper { pub struct TestSwitchHelper {
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>, pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
pub switch_info_requests: RefCell<VecDeque<SwitchId>>, pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
#[allow(dead_code)]
pub switch_delay_request_count: u32, pub switch_delay_request_count: u32,
pub next_switch_delay: Duration, pub next_switch_delay: Duration,
pub switch_map: RefCell<SwitchMapWrapper>, pub switch_map: RefCell<SwitchMapWrapper>,
@@ -182,11 +118,13 @@ impl Default for TestSwitchHelper {
} }
} }
/* impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
impl PowerSwitchInfo<SwitchId> for TestSwitchHelper {
type Error = SwitchInfoError; type Error = SwitchInfoError;
fn switch_state(&self, switch_id: SwitchId) -> Result<satrs::power::SwitchState, Self::Error> { 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(); let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut();
switch_info_requests_mut.push_back(switch_id); switch_info_requests_mut.push_back(switch_id);
if !self.switch_map_valid { if !self.switch_map_valid {
@@ -204,13 +142,13 @@ impl PowerSwitchInfo<SwitchId> for TestSwitchHelper {
} }
} }
impl PowerSwitcherCommandSender<SwitchId> for TestSwitchHelper { impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
type Error = SwitchCommandingError; type Error = SwitchCommandingError;
fn send_switch_on_cmd( fn send_switch_on_cmd(
&self, &self,
requestor_info: MessageMetadata, requestor_info: MessageMetadata,
switch_id: SwitchId, switch_id: PcduSwitch,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
let mut switch_requests_mut = self.switch_requests.borrow_mut(); let mut switch_requests_mut = self.switch_requests.borrow_mut();
switch_requests_mut.push_back(SwitchRequestInfo { switch_requests_mut.push_back(SwitchRequestInfo {
@@ -230,7 +168,7 @@ impl PowerSwitcherCommandSender<SwitchId> for TestSwitchHelper {
fn send_switch_off_cmd( fn send_switch_off_cmd(
&self, &self,
requestor_info: MessageMetadata, requestor_info: MessageMetadata,
switch_id: SwitchId, switch_id: PcduSwitch,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
let mut switch_requests_mut = self.switch_requests.borrow_mut(); let mut switch_requests_mut = self.switch_requests.borrow_mut();
switch_requests_mut.push_back(SwitchRequestInfo { switch_requests_mut.push_back(SwitchRequestInfo {
@@ -247,12 +185,11 @@ impl PowerSwitcherCommandSender<SwitchId> for TestSwitchHelper {
Ok(()) Ok(())
} }
} }
*/
#[allow(dead_code)] #[allow(dead_code)]
impl TestSwitchHelper { impl TestSwitchHelper {
// Helper function which can be used to force a switch to another state for test purposes. // Helper function which can be used to force a switch to another state for test purposes.
pub fn set_switch_state(&mut self, switch: SwitchId, state: SwitchState) { pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) {
self.switch_map.get_mut().0.insert(switch, state); self.switch_map.get_mut().0.insert(switch, state);
} }
} }
+223 -512
View File
@@ -1,109 +1,35 @@
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, VecDeque}, collections::VecDeque,
sync::{Arc, Mutex, mpsc}, sync::{mpsc, Arc, Mutex},
}; };
use derive_new::new; use derive_new::new;
use models::{
ComponentId, DeviceMode,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
pcdu::{
self, SwitchId, SwitchMapBinary, SwitchMapBinaryWrapper, SwitchRequest, SwitchState,
SwitchStateBinary, SwitchesBitfield,
},
};
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::{request::GenericMessage, spacepackets::CcsdsPacketIdAndPsc}; use satrs::{
use satrs_example::TimestampHelper; hk::{HkRequest, HkRequestVariant},
mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler},
power::SwitchRequest,
pus::{EcssTmSender, PusTmVariant},
queue::{GenericSendError, GenericTargetedMessagingError},
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
spacepackets::ByteConversionError,
};
use satrs_example::{config::components::PUS_MODE_SERVICE, DeviceMode, TimestampHelper};
use satrs_minisim::{ use satrs_minisim::{
eps::{
PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper,
},
SerializableSimMsgPayload, SimReply, SimRequest, SerializableSimMsgPayload, SimReply, SimRequest,
eps::{PcduReply, PcduRequest},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator as _;
use crate::ccsds::pack_ccsds_tm_packet_for_now; use crate::{
acs::mgm::MpscModeLeafInterface,
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] hk::PusHkHelper,
pub struct SwitchSet { pus::hk::{HkReply, HkReplyVariant},
pub valid: bool, requests::CompositeRequest,
pub switch_map: SwitchMap, };
}
impl SwitchSet {
pub fn new(switch_map: SwitchMap) -> Self {
Self {
valid: true,
switch_map,
}
}
pub fn new_with_init_switches_unknown() -> Self {
let wrapper = SwitchMapWrapper::default();
Self::new(wrapper.0)
}
pub fn as_bitfield(&self) -> Option<SwitchesBitfield> {
for entry in SwitchId::iter() {
if !self.switch_map.contains_key(&entry) {
return None;
}
}
Some(
SwitchesBitfield::builder()
.with_magnetorquer(*self.switch_map.get(&SwitchId::Mgt).unwrap() == SwitchState::On)
.with_mgm1(*self.switch_map.get(&SwitchId::Mgm1).unwrap() == SwitchState::On)
.with_mgm0(*self.switch_map.get(&SwitchId::Mgm0).unwrap() == SwitchState::On)
.build(),
)
}
#[allow(dead_code)]
pub fn set_switch_state(&mut self, switch_id: SwitchId, state: SwitchState) -> bool {
if !self.switch_map.contains_key(&switch_id) {
return false;
}
*self.switch_map.get_mut(&switch_id).unwrap() = state;
true
}
}
pub type SwitchMap = HashMap<SwitchId, SwitchState>;
pub struct SwitchMapWrapper(pub SwitchMap);
impl Default for SwitchMapWrapper {
fn default() -> Self {
let mut switch_map = SwitchMap::default();
for entry in SwitchId::iter() {
switch_map.insert(entry, SwitchState::Unknown);
}
Self(switch_map)
}
}
impl SwitchMapWrapper {
#[allow(dead_code)]
pub fn new_with_init_switches_off() -> Self {
let mut switch_map = SwitchMap::default();
for entry in SwitchId::iter() {
switch_map.insert(entry, SwitchState::Off);
}
Self(switch_map)
}
pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self {
Self(
switch_map
.iter()
.map(|(key, value)| (*key, SwitchState::from(*value)))
.collect(),
)
}
}
pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
pub trait SerialInterface { pub trait SerialInterface {
type Error: core::fmt::Debug; type Error: core::fmt::Debug;
@@ -134,9 +60,9 @@ impl SerialInterface for SerialInterfaceToSim {
type Error = (); type Error = ();
fn send(&self, data: &[u8]) -> Result<(), Self::Error> { fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
let request: PcduRequest = serde_json::from_slice(data).expect("expected a PCDU request"); let request: SimRequest = serde_json::from_slice(data).unwrap();
self.sim_request_tx self.sim_request_tx
.send(SimRequest::new_with_epoch_time(request)) .send(request)
.expect("failed to send request to simulation"); .expect("failed to send request to simulation");
Ok(()) Ok(())
} }
@@ -175,7 +101,9 @@ impl SerialInterface for SerialInterfaceDummy {
type Error = (); type Error = ();
fn send(&self, data: &[u8]) -> Result<(), Self::Error> { fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
let pcdu_req: PcduRequest = serde_json::from_slice(data).unwrap(); let sim_req: SimRequest = serde_json::from_slice(data).unwrap();
let pcdu_req =
PcduRequest::from_sim_message(&sim_req).expect("PCDU request creation failed");
let switch_map_mut = &mut self.switch_map.borrow_mut().0; let switch_map_mut = &mut self.switch_map.borrow_mut().0;
match pcdu_req { match pcdu_req {
PcduRequest::SwitchDevice { switch, state } => { PcduRequest::SwitchDevice { switch, state } => {
@@ -191,7 +119,7 @@ impl SerialInterface for SerialInterfaceDummy {
PcduRequest::RequestSwitchInfo => { PcduRequest::RequestSwitchInfo => {
let mut reply_deque_mut = self.reply_deque.borrow_mut(); let mut reply_deque_mut = self.reply_deque.borrow_mut();
reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo( reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo(
switch_map_mut.clone(), self.switch_map.borrow().0.clone(),
))); )));
} }
}; };
@@ -202,13 +130,15 @@ impl SerialInterface for SerialInterfaceDummy {
&self, &self,
mut f: ReplyHandler, mut f: ReplyHandler,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if self.reply_queue_empty() { if self.reply_deque.borrow().is_empty() {
return Ok(()); return Ok(());
} }
loop { loop {
let reply = self.get_next_reply_as_string(); let mut reply_deque_mut = self.reply_deque.borrow_mut();
let next_reply = reply_deque_mut.pop_front().unwrap();
let reply = serde_json::to_string(&next_reply).unwrap();
f(reply.as_bytes()); f(reply.as_bytes());
if self.reply_queue_empty() { if reply_deque_mut.is_empty() {
break; break;
} }
} }
@@ -216,18 +146,6 @@ impl SerialInterface for SerialInterfaceDummy {
} }
} }
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 { pub enum SerialSimInterfaceWrapper {
Dummy(SerialInterfaceDummy), Dummy(SerialInterfaceDummy),
Sim(SerialInterfaceToSim), Sim(SerialInterfaceToSim),
@@ -260,50 +178,49 @@ pub enum OpCode {
PollAndRecvReplies = 1, PollAndRecvReplies = 1,
} }
/// Example PCDU device handler. #[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[allow(clippy::too_many_arguments)] pub struct SwitchSet {
pub struct PcduHandler<ComInterface: SerialInterface> { pub valid: bool,
dev_str: &'static str, pub switch_map: SwitchMap,
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
pub com_interface: ComInterface,
shared_switch_map: Arc<Mutex<SwitchSet>>,
mode: DeviceMode,
stamp_helper: TimestampHelper,
} }
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> { pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
pub fn new(
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
com_interface: ComInterface,
shared_switch_map: Arc<Mutex<SwitchSet>>,
init_mode: DeviceMode,
) -> Self {
Self {
dev_str: "PCDU",
tc_rx,
switch_request_rx,
tm_tx,
com_interface,
shared_switch_map,
stamp_helper: TimestampHelper::default(),
// Start in normal mode by default. Assume that the PCDU itself is on by default.
mode: init_mode,
}
}
/// Example PCDU device handler.
#[derive(new)]
#[allow(clippy::too_many_arguments)]
pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
id: UniqueApidTargetId,
dev_str: &'static str,
mode_interface: MpscModeLeafInterface,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
tm_sender: TmSender,
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, TmSender: EcssTmSender> PcduHandler<ComInterface, TmSender> {
pub fn periodic_operation(&mut self, op_code: OpCode) { pub fn periodic_operation(&mut self, op_code: OpCode) {
match op_code { match op_code {
OpCode::RegularOp => { OpCode::RegularOp => {
self.stamp_helper.update_from_now(); self.stamp_helper.update_from_now();
// Handle requests. // Handle requests.
self.handle_telecommands(); self.handle_composite_requests();
self.handle_mode_requests();
self.handle_switch_requests(); self.handle_switch_requests();
// Poll the switch states and/or telemetry regularly here. // Poll the switch states and/or telemetry regularly here.
if self.mode() == DeviceMode::Normal || self.mode() == DeviceMode::On { if self.mode() == DeviceMode::Normal as u32 || self.mode() == DeviceMode::On as u32
{
self.handle_periodic_commands(); self.handle_periodic_commands();
} }
} }
@@ -313,122 +230,75 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
} }
} }
#[inline] pub fn handle_composite_requests(&mut self) {
pub fn mode(&self) -> DeviceMode {
self.mode
}
pub fn handle_telecommands(&mut self) {
loop { loop {
match self.tc_rx.try_recv() { match self.composite_request_rx.try_recv() {
Ok(packet) => { Ok(ref msg) => match &msg.message {
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header); CompositeRequest::Hk(hk_request) => {
match postcard::from_bytes::<pcdu::request::Request>(&packet.payload) { self.handle_hk_request(&msg.requestor_info, hk_request)
Ok(request) => {
log::info!(
"received request {:?} with TC ID {:#010x}",
request,
tc_id.raw()
);
match request {
pcdu::request::Request::Ping => {
self.send_tm(Some(tc_id), pcdu::response::Response::Ok)
}
pcdu::request::Request::GetSwitches => self.send_tm(
Some(tc_id),
pcdu::response::Response::Switches(
self.shared_switch_map
.lock()
.unwrap()
.as_bitfield()
.expect("could not build switches response"),
),
),
pcdu::request::Request::EnableSwitches(switches) => {
self.handle_switches_bitfield_request(
switches,
SwitchStateBinary::On,
);
}
pcdu::request::Request::DisableSwitches(switches) => {
self.handle_switches_bitfield_request(
switches,
SwitchStateBinary::Off,
);
}
pcdu::request::Request::Mode(device_mode) => {
self.switch_mode(tc_id, device_mode)
}
}
}
Err(e) => {
log::warn!("failed to deserialize request: {}", e);
}
}
}
Err(e) => match e {
std::sync::mpsc::TryRecvError::Empty => break,
std::sync::mpsc::TryRecvError::Disconnected => {
log::warn!("packet sender disconnected")
} }
// TODO: This object does not have actions (yet).. Still send back completion failure
// reply.
CompositeRequest::Action(_action_req) => {}
}, },
}
}
}
pub fn handle_switches_bitfield_request( Err(e) => {
&mut self, if e != mpsc::TryRecvError::Empty {
switches: SwitchesBitfield, log::warn!(
state: SwitchStateBinary, "{}: failed to receive composite request: {:?}",
) { self.dev_str,
if switches.mgm0() { e
self.handle_device_switching(SwitchId::Mgm0, state); );
} } else {
if switches.mgm1() { break;
self.handle_device_switching(SwitchId::Mgm1, state); }
}
if switches.magnetorquer() {
self.handle_device_switching(SwitchId::Mgt, state);
}
}
pub fn send_tm(&self, tc_id: Option<CcsdsPacketIdAndPsc>, response: pcdu::response::Response) {
match pack_ccsds_tm_packet_for_now(ComponentId::EpsPcdu, tc_id, &response) {
Ok(packet) => {
if let Err(e) = self.tm_tx.send(packet) {
log::warn!("failed to send TM packet: {}", e);
} }
} }
Err(e) => {
log::warn!("failed to pack TM packet: {}", e);
}
} }
} }
fn switch_mode(&mut self, requestor: CcsdsPacketIdAndPsc, mode: DeviceMode) { pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
log::info!("{}: transitioning to mode {:?}", self.dev_str, mode); match hk_request.variant {
self.mode = mode; HkRequestVariant::OneShot => {
if self.mode() == DeviceMode::Off { if hk_request.unique_id == SetId::SwitcherSet as u32 {
self.shared_switch_map.lock().unwrap().valid = false; if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
} self.stamp_helper.stamp(),
log::info!("{} announcing mode: {:?}", self.dev_str, self.mode); SetId::SwitcherSet as u32,
self.send_telemetry(Some(requestor), pcdu::response::Response::Ok); &mut |hk_buf| {
} // Send TM down as JSON.
let switch_map_snapshot = self
pub fn send_telemetry( .shared_switch_map
&self, .lock()
tc_id: Option<CcsdsPacketIdAndPsc>, .expect("failed to lock switch map")
response: pcdu::response::Response, .clone();
) { let switch_map_json = serde_json::to_string(&switch_map_snapshot)
match pack_ccsds_tm_packet_for_now(ComponentId::EpsPcdu, tc_id, &response) { .expect("failed to serialize switch map");
Ok(packet) => { if switch_map_json.len() > hk_buf.len() {
if let Err(e) = self.tm_tx.send(packet) { log::error!("switch map JSON too large for HK buffer");
log::warn!("failed to send TM packet: {}", e); return Err(ByteConversionError::ToSliceTooSmall {
found: hk_buf.len(),
expected: switch_map_json.len(),
});
}
Ok(switch_map_json.len())
},
&mut self.tm_buf,
) {
self.tm_sender
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
.expect("failed to send HK TM");
self.hk_reply_tx
.send(GenericMessage::new(
*requestor_info,
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
))
.expect("failed to send HK reply");
}
} }
} }
Err(e) => { HkRequestVariant::EnablePeriodic => todo!(),
log::warn!("failed to pack TM packet: {}", e); HkRequestVariant::DisablePeriodic => todo!(),
} HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
} }
} }
@@ -440,59 +310,48 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
} }
} }
/*
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_node.try_recv_mode_request() { match self.mode_interface.request_rx.try_recv() {
Ok(opt_msg) => { Ok(msg) => {
if let Some(msg) = opt_msg { let result = self.handle_mode_request(msg);
let result = self.handle_mode_request(msg); // TODO: Trigger event?
// TODO: Trigger event? if result.is_err() {
if result.is_err() { log::warn!(
log::warn!( "{}: mode request failed with error {:?}",
"{}: mode request failed with error {:?}", self.dev_str,
self.dev_str, result.err().unwrap()
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(_) => {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
}
},
} }
} }
} }
*/
pub fn handle_device_switching(&mut self, switch_id: SwitchId, state: SwitchStateBinary) {
let pcdu_req = PcduRequest::SwitchDevice {
switch: switch_id,
state,
};
let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
self.com_interface
.send(pcdu_req_ser.as_bytes())
.expect("failed to send switch request to PCDU");
}
pub fn handle_switch_requests(&mut self) { pub fn handle_switch_requests(&mut self) {
loop { loop {
match self.switch_request_rx.try_recv() { match self.switch_request_rx.try_recv() {
Ok(switch_req) => { Ok(switch_req) => match PcduSwitch::try_from(switch_req.message.switch_id()) {
self.handle_device_switching( Ok(pcdu_switch) => {
switch_req.message.switch_id(), let pcdu_req = PcduRequest::SwitchDevice {
switch_req.message.target_state(), 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 { Err(e) => match e {
mpsc::TryRecvError::Empty => break, mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => { mpsc::TryRecvError::Disconnected => {
@@ -512,245 +371,97 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
PcduReply::SwitchInfo(switch_info) => { PcduReply::SwitchInfo(switch_info) => {
let switch_map_wrapper = let switch_map_wrapper =
SwitchMapWrapper::from_binary_switch_map_ref(&switch_info); SwitchMapWrapper::from_binary_switch_map_ref(&switch_info);
let mut shared_switch_map = self self.shared_switch_map
.shared_switch_map
.lock() .lock()
.expect("failed to lock switch map"); .expect("failed to lock switch map")
shared_switch_map.switch_map = switch_map_wrapper.0; .switch_map = switch_map_wrapper.0;
shared_switch_map.valid = true;
} }
} }
}) { }) {
log::warn!("receiving PCDU replies failed: {e:?}"); log::warn!("receiving PCDU replies failed: {:?}", e);
} }
} }
} }
#[cfg(test)] impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeProvider
mod tests { for PcduHandler<ComInterface, TmSender>
use std::sync::mpsc; {
fn mode_and_submode(&self) -> ModeAndSubmode {
use arbitrary_int::u11; self.mode_and_submode
use models::{
Apid, TcHeader,
pcdu::{SwitchMapBinary, SwitchStateBinary},
};
use satrs::{
mode::{ModeReply, ModeRequest},
request::{GenericMessage, MessageMetadata},
spacepackets::SpacePacketHeader,
};
use super::*;
pub fn create_request_tc(
request: models::pcdu::request::Request,
) -> models::ccsds::CcsdsTcPacketOwned {
models::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Eps as u16)),
TcHeader::new(ComponentId::EpsPcdu, request.message_type()),
request,
)
} }
}
#[derive(Default)] impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
pub struct SerialInterfaceTest { for PcduHandler<ComInterface, TmSender>
pub inner: SerialInterfaceDummy, {
pub send_queue: RefCell<VecDeque<Vec<u8>>>, type Error = ModeError;
pub reply_queue: RefCell<VecDeque<String>>, fn start_transition(
} &mut self,
requestor: MessageMetadata,
impl SerialInterface for SerialInterfaceTest { mode_and_submode: ModeAndSubmode,
type Error = (); ) -> Result<(), satrs::mode::ModeError> {
log::info!(
fn send(&self, data: &[u8]) -> Result<(), Self::Error> { "{}: transitioning to mode {:?}",
let mut send_queue_mut = self.send_queue.borrow_mut(); self.dev_str,
send_queue_mut.push_back(data.to_vec()); mode_and_submode
self.inner.send(data) );
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 try_recv_replies<ReplyHandler: FnMut(&[u8])>( fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
&self, log::info!(
mut f: ReplyHandler, "{} announcing mode: {:?}",
) -> Result<(), Self::Error> { self.dev_str,
if self.inner.reply_queue_empty() { self.mode_and_submode
return Ok(()); );
}
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() != PUS_MODE_SERVICE.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()))?;
} }
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(())
} }
Ok(())
} }
#[allow(dead_code)] fn send_mode_reply(
pub struct PcduTestbench { &self,
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>, requestor: MessageMetadata,
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>, reply: ModeReply,
pub tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>, ) -> Result<(), Self::Error> {
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>, if requestor.sender_id() != PUS_MODE_SERVICE.id() {
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>, log::warn!(
pub handler: PcduHandler<SerialInterfaceTest>, "can not send back mode reply to sender {}",
} requestor.sender_id()
impl PcduTestbench {
pub fn new() -> Self {
let (mode_request_tx, _mode_request_rx) = mpsc::sync_channel(5);
let (_mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
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::new_with_init_switches_unknown()));
let handler = PcduHandler::new(
tc_rx,
tm_tx.clone(),
switch_reqest_rx,
SerialInterfaceTest::default(),
shared_switch_map,
DeviceMode::Off,
); );
Self {
mode_request_tx,
mode_reply_rx_to_parent,
tc_tx,
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: SwitchId,
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));
} }
self.mode_interface
.reply_to_pus_tx
.send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
Ok(())
} }
#[test] fn handle_mode_info(
fn test_basic_handler() { &mut self,
let mut testbench = PcduTestbench::new(); _requestor_info: MessageMetadata,
assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0); _info: ModeAndSubmode,
assert_eq!( ) -> Result<(), Self::Error> {
testbench.handler.com_interface.reply_queue.borrow().len(), Ok(())
0
);
assert_eq!(testbench.handler.mode(), DeviceMode::Off);
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(), DeviceMode::Off);
}
#[test]
fn test_normal_mode() {
let mut testbench = PcduTestbench::new();
testbench
.tc_tx
.send(create_request_tc(pcdu::request::Request::Mode(
DeviceMode::Normal,
)))
.unwrap();
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(), DeviceMode::Normal);
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
.tc_tx
.send(create_request_tc(pcdu::request::Request::Mode(
DeviceMode::Normal,
)))
.unwrap();
testbench
.switch_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, ComponentId::AcsMgm0 as u32),
SwitchRequest::new(SwitchId::Mgm0, 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, SwitchId::Mgm0, SwitchStateBinary::On);
testbench.verify_switch_info_req_was_sent(1);
let mut switch_map = SwitchMapBinaryWrapper::default().0;
*switch_map
.get_mut(&SwitchId::Mgm0)
.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);
} }
} }
-35
View File
@@ -1,35 +0,0 @@
use models::{ComponentId, Event, Message, ccsds::CcsdsTmPacketOwned, control};
use crate::ccsds::pack_ccsds_tm_packet_for_now;
// TODO: We should add the capability to enable/disable the TM generation of individual events and
// event groups as well.
pub struct EventManager {
pub ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
}
impl EventManager {
pub fn periodic_operation(&mut self) {
if let Ok(event) = self.ctrl_rx.try_recv() {
self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event));
}
}
pub fn event_to_tm(
&mut self,
sender_id: ComponentId,
event: &(impl serde::Serialize + Message),
) {
match pack_ccsds_tm_packet_for_now(sender_id, None, event) {
Ok(packet) => {
if let Err(e) = self.tm_tx.send(packet) {
log::warn!("error sending event TM packet: {:?}", e);
}
}
Err(e) => {
log::warn!("error packing event TM packet: {:?}", e);
}
}
}
}
@@ -1,12 +1,13 @@
use arbitrary_int::traits::Integer as _; use std::sync::mpsc::{self};
use arbitrary_int::u11;
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError}; use crate::pus::create_verification_reporter;
use satrs::pus::event::EventTmHook; use satrs::event_man::{EventMessageU32, EventRoutingError};
use satrs::pus::event::EventTmHookProvider;
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_legacy::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded}, event_man::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
pus::{ pus::{
event_man::{ event_man::{
DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken, DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
@@ -15,23 +16,22 @@ use satrs::{
}, },
spacepackets::time::cds::CdsTime, spacepackets::time::cds::CdsTime,
}; };
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT; use satrs_example::config::components::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: u11, pub next_apid: u16,
} }
impl EventTmHook for EventApidSetter { impl EventTmHookProvider 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,11 +59,12 @@ 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(),
u11::ZERO, 0,
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(
@@ -216,18 +217,20 @@ impl<TmSender: EcssTmSender> EventHandler<TmSender> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use arbitrary_int::u21;
use satrs::{ use satrs::{
events_legacy::EventU32, events::EventU32,
pus::verification::VerificationReporterConfig, pus::verification::VerificationReporterCfg,
spacepackets::ecss::{tm::PusTmReader, PusPacket}, spacepackets::{
ecss::{tm::PusTmReader, PusPacket},
CcsdsPacket,
},
tmtc::PacketAsVec, tmtc::PacketAsVec,
}; };
use super::*; use super::*;
const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(u11::new(1), u21::new(2)); const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(1, 2);
const TEST_EVENT: EventU32 = EventU32::new(satrs::events_legacy::Severity::Info, 1, 1); const TEST_EVENT: EventU32 = EventU32::new(satrs::events::Severity::Info, 1, 1);
pub struct EventManagementTestbench { pub struct EventManagementTestbench {
pub event_tx: mpsc::SyncSender<EventMessageU32>, pub event_tx: mpsc::SyncSender<EventMessageU32>,
@@ -241,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 = VerificationReporterConfig::new(u11::new(0x05), 2, 2, 128); let verif_reporter_cfg = VerificationReporterCfg::new(0x05, 2, 2, 128).unwrap();
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);
@@ -267,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_legacy::Severity::Info, 1, 1), EventU32::new(satrs::events::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();
@@ -278,7 +281,9 @@ 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).expect("failed to create TM reader"); let tm_reader = PusTmReader::new(&tm_packet.packet, 7)
.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());
@@ -290,4 +295,3 @@ mod tests {
// TODO: Add test. // TODO: Add test.
} }
} }
*/
@@ -1,9 +1,8 @@
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::ecss::hk;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::ecss::{hk, CreatorConfig, MessageTypeId};
use satrs::spacepackets::{ByteConversionError, SpHeader}; use satrs::spacepackets::{ByteConversionError, SpHeader};
#[derive(Debug, new, Copy, Clone)] #[derive(Debug, new, Copy, Clone)]
@@ -30,7 +29,7 @@ impl HkUniqueId {
expected: 8, expected: 8,
}); });
} }
buf[0..4].copy_from_slice(&self.target_id.unique_id.as_u32().to_be_bytes()); buf[0..4].copy_from_slice(&self.target_id.unique_id.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)
@@ -54,13 +53,9 @@ impl PusHkHelper {
hk_data_writer: &mut HkWriter, hk_data_writer: &mut HkWriter,
buf: &'b mut [u8], buf: &'b mut [u8],
) -> Result<PusTmCreator<'a, 'b>, ByteConversionError> { ) -> Result<PusTmCreator<'a, 'b>, ByteConversionError> {
let sec_header = PusTmSecondaryHeader::new( let sec_header =
MessageTypeId::new(3, hk::MessageSubtypeId::TmHkPacket as u8), PusTmSecondaryHeader::new(3, hk::Subservice::TmHkPacket as u8, 0, 0, timestamp);
0, buf[0..4].copy_from_slice(&self.component_id.unique_id.to_be_bytes());
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()); buf[4..8].copy_from_slice(&set_id.to_be_bytes());
let (_, second_half) = buf.split_at_mut(8); let (_, second_half) = buf.split_at_mut(8);
let hk_data_len = hk_data_writer(second_half)?; let hk_data_len = hk_data_writer(second_half)?;
@@ -68,7 +63,7 @@ impl PusHkHelper {
SpHeader::new_from_apid(self.component_id.apid), SpHeader::new_from_apid(self.component_id.apid),
sec_header, sec_header,
&buf[0..8 + hk_data_len], &buf[0..8 + hk_data_len],
CreatorConfig::default(), true,
)) ))
} }
} }
@@ -7,8 +7,8 @@ use std::{
use satrs::pus::HandlingStatus; use satrs::pus::HandlingStatus;
use satrs_minisim::{ use satrs_minisim::{
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimReply, SimRequest, udp::SIM_CTRL_PORT, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimReply,
udp::SIM_CTRL_PORT, SimRequest,
}; };
use satrs_minisim::{SimCtrlReply, SimCtrlRequest}; use satrs_minisim::{SimCtrlReply, SimCtrlRequest};
@@ -24,7 +24,7 @@ pub fn create_sim_client(sim_request_rx: mpsc::Receiver<SimRequest>) -> Option<S
return Some(sim_client); return Some(sim_client);
} }
Err(e) => { Err(e) => {
log::warn!("sim client creation error: {e}"); log::warn!("sim client creation error: {}", e);
} }
} }
None None
@@ -116,7 +116,7 @@ impl SimClientUdp {
.udp_client .udp_client
.send_to(request_json.as_bytes(), self.simulator_addr) .send_to(request_json.as_bytes(), self.simulator_addr)
{ {
log::error!("error sending data to UDP SIM server: {e}"); log::error!("error sending data to UDP SIM server: {}", e);
break; break;
} else { } else {
no_sim_requests_handled = false; no_sim_requests_handled = false;
@@ -151,7 +151,7 @@ impl SimClientUdp {
} }
} }
Err(e) => { Err(e) => {
log::warn!("failed to deserialize SIM reply: {e}"); log::warn!("failed to deserialize SIM reply: {}", e);
} }
} }
} }
@@ -161,7 +161,7 @@ impl SimClientUdp {
{ {
break; break;
} }
log::error!("error receiving data from UDP SIM server: {e}"); log::error!("error receiving data from UDP SIM server: {}", e);
break; break;
} }
} }
@@ -187,17 +187,16 @@ pub mod tests {
collections::HashMap, collections::HashMap,
net::{SocketAddr, UdpSocket}, net::{SocketAddr, UdpSocket},
sync::{ sync::{
Arc,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc, mpsc, Arc,
}, },
time::Duration, time::Duration,
}; };
use satrs_minisim::{ use satrs_minisim::{
eps::{PcduReply, PcduRequest},
SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider, SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider,
SimReply, SimRequest, SimReply, SimRequest,
eps::{PcduReply, PcduRequest},
}; };
use super::SimClientUdp; use super::SimClientUdp;
+35 -23
View File
@@ -1,20 +1,19 @@
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::PacketSource, tmtc::{PacketSenderRaw, PacketSource},
}; };
use crate::tmtc::sender::TmTcSender;
#[derive(Default)] #[derive(Default)]
pub struct ConnectionFinishedHandler {} pub struct ConnectionFinishedHandler {}
@@ -31,7 +30,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
@@ -103,39 +102,52 @@ impl PacketSource for SyncTcpTmSource {
} }
} }
pub type TcpServer<ReceivesTc> = TcpSpacepacketsServer< pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
SyncTcpTmSource, SyncTcpTmSource,
ReceivesTc, ReceivesTc,
SimplePacketValidator, SimplePacketValidator,
ConnectionFinishedHandler, ConnectionFinishedHandler,
(),
SendError,
>; >;
pub struct TcpTask(pub TcpServer<TmTcSender>); pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
pub TcpServer<TcSender, SendError>,
PhantomData<SendError>,
);
impl TcpTask { impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
TcpTask<TcSender, SendError>
{
pub fn new( pub fn new(
cfg: ServerConfig, cfg: ServerConfig,
tm_source: SyncTcpTmSource, tm_source: SyncTcpTmSource,
tc_sender: TmTcSender, tc_sender: TcSender,
valid_ids: HashSet<PacketId>, valid_ids: HashSet<PacketId>,
) -> Result<Self, std::io::Error> { ) -> Result<Self, std::io::Error> {
Ok(Self(TcpSpacepacketsServer::new( Ok(Self(
cfg, TcpSpacepacketsServer::new(
tm_source, cfg,
CcsdsPacketParser::new(cfg.id, 2048, tc_sender, SimplePacketValidator { valid_ids }), tm_source,
ConnectionFinishedHandler::default(), tc_sender,
None, SimplePacketValidator { valid_ids },
)?)) ConnectionFinishedHandler::default(),
None,
)?,
PhantomData,
))
} }
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {
let result = self loop {
.0 let result = self
.handle_all_connections(Some(Duration::from_millis(400))); .0
match result { .handle_all_connections(Some(Duration::from_millis(400)));
Ok(_conn_result) => (), match result {
Err(e) => { Ok(_conn_result) => (),
warn!("TCP server error: {e:?}"); Err(e) => {
warn!("TCP server error: {e:?}");
}
} }
} }
} }
+110 -99
View File
@@ -1,29 +1,41 @@
#![allow(dead_code)] use core::fmt::Debug;
use std::collections::VecDeque;
use std::net::{SocketAddr, UdpSocket}; use std::net::{SocketAddr, UdpSocket};
use std::sync::{Arc, Mutex, mpsc}; use std::sync::mpsc;
use log::warn; use log::{info, warn};
use models::ccsds::CcsdsTmPacketOwned;
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
use satrs::pus::HandlingStatus; use satrs::pus::HandlingStatus;
use satrs::queue::GenericSendError; use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
use satrs::{
hal::std::udp_server::{ReceiveResult, UdpTcServer},
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
};
use crate::tmtc::sender::TmTcSender; pub trait UdpTmHandler {
pub trait UdpTmHandlerProvider {
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr); fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
} }
pub struct UdpTmHandlerWithChannel { pub struct StaticUdpTmHandler {
pub tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>, pub tm_rx: mpsc::Receiver<PacketInPool>,
pub tm_store: SharedStaticMemoryPool,
} }
impl UdpTmHandlerProvider for UdpTmHandlerWithChannel { impl UdpTmHandler for StaticUdpTmHandler {
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(pus_tm_in_pool) = self.tm_rx.try_recv() {
log::debug!("sending TM from sender {:?}", tm.tm_header.sender_id); let store_lock = self.tm_store.write();
let result = socket.send_to(&tm.to_vec(), recv_addr); if store_lock.is_err() {
warn!("Locking TM store failed");
continue;
}
let mut store_lock = store_lock.unwrap();
let pg = store_lock.read_with_guard(pus_tm_in_pool.store_addr);
let read_res = pg.read_as_vec();
if read_res.is_err() {
warn!("Error reading TM pool data");
continue;
}
let buf = read_res.unwrap();
let result = socket.send_to(&buf, recv_addr);
if let Err(e) = result { if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}") warn!("Sending TM with UDP socket failed: {e}")
} }
@@ -31,49 +43,43 @@ impl UdpTmHandlerProvider for UdpTmHandlerWithChannel {
} }
} }
#[derive(Default, Debug, Clone)] pub struct DynamicUdpTmHandler {
pub struct TestTmHandler { pub tm_rx: mpsc::Receiver<PacketAsVec>,
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
} }
impl UdpTmHandlerProvider for TestTmHandler { impl UdpTmHandler for DynamicUdpTmHandler {
fn send_tm_to_udp_client(&mut self, _socket: &UdpSocket, recv_addr: &SocketAddr) {
self.addrs_to_send_to.lock().unwrap().push_back(*recv_addr);
}
}
pub enum UdpTmHandler {
Normal(UdpTmHandlerWithChannel),
Test(TestTmHandler),
}
impl From<UdpTmHandlerWithChannel> for UdpTmHandler {
fn from(handler: UdpTmHandlerWithChannel) -> Self {
UdpTmHandler::Normal(handler)
}
}
impl From<TestTmHandler> for UdpTmHandler {
fn from(handler: TestTmHandler) -> Self {
UdpTmHandler::Test(handler)
}
}
impl UdpTmHandlerProvider for UdpTmHandler {
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) { fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
match self { while let Ok(tm) = self.tm_rx.try_recv() {
UdpTmHandler::Normal(handler) => handler.send_tm_to_udp_client(socket, recv_addr), if tm.packet.len() > 9 {
UdpTmHandler::Test(handler) => handler.send_tm_to_udp_client(socket, recv_addr), let service = tm.packet[7];
let subservice = tm.packet[8];
info!("Sending PUS TM[{service},{subservice}]")
} else {
info!("Sending PUS TM");
}
let result = socket.send_to(&tm.packet, recv_addr);
if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}")
}
} }
} }
} }
pub struct UdpTmtcServer { pub struct UdpTmtcServer<
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>, TcSender: PacketSenderRaw<Error = SendError>,
pub tm_handler: UdpTmHandler, TmHandler: UdpTmHandler,
SendError,
> {
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
pub tm_handler: TmHandler,
} }
impl UdpTmtcServer { impl<
TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError: Debug + 'static,
> UdpTmtcServer<TcSender, TmHandler, SendError>
{
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {
loop { loop {
if self.poll_tc_server() == HandlingStatus::Empty { if self.poll_tc_server() == HandlingStatus::Empty {
@@ -107,48 +113,69 @@ impl UdpTmtcServer {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::net::IpAddr;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::{
use arbitrary_int::traits::Integer as _; cell::RefCell,
use arbitrary_int::u14; collections::VecDeque,
use models::Apid; net::IpAddr,
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId}; sync::{Arc, Mutex},
use satrs::{
ComponentId,
spacepackets::{
SpHeader,
ecss::{WritablePusPacket, tc::PusTcCreator},
},
}; };
use satrs_example::config::OBSW_SERVER_ADDR;
use crate::tmtc::sender::MockSender; use satrs::{
spacepackets::{
ecss::{tc::PusTcCreator, WritablePusPacket},
SpHeader,
},
tmtc::PacketSenderRaw,
ComponentId,
};
use satrs_example::config::{components, OBSW_SERVER_ADDR};
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 = TmTcSender::Mock(MockSender::default()); let test_receiver = TestSender::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.into(), tm_handler,
}; };
udp_dyn_server.periodic_operation(); udp_dyn_server.periodic_operation();
let queue = udp_dyn_server let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
.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());
} }
@@ -156,7 +183,8 @@ 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 = TmTcSender::Mock(MockSender::default()); let test_receiver = TestSender::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();
@@ -164,30 +192,19 @@ 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.into(), tm_handler,
}; };
let sph = SpHeader::new_for_unseg_tc(Apid::Tmtc.raw_value(), u14::ZERO, 0); let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
let ping_tc = PusTcCreator::new_simple( let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
sph, .to_vec()
MessageTypeId::new(17, 1), .unwrap();
&[],
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 let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut();
.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);
@@ -202,13 +219,7 @@ 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 let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
.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.
+6 -124
View File
@@ -1,28 +1,12 @@
extern crate alloc; use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
use std::{
sync::mpsc,
time::{Duration, Instant},
};
pub use models::ComponentId;
use models::ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned};
use satrs::spacepackets::{CcsdsPacketIdAndPsc, time::cds::CdsTime};
pub mod config; pub mod config;
/// Simple type modelling packet stored in the heap. This structure is intended to #[derive(Debug, PartialEq, Eq, Copy, Clone)]
/// be used when sending a packet via a message queue, so it also contains the sender ID. pub enum DeviceMode {
#[derive(Debug, PartialEq, Eq, Clone)] Off = 0,
pub struct PacketAsVec { On = 1,
pub sender_id: ComponentId, Normal = 2,
pub packet: Vec<u8>,
}
impl PacketAsVec {
pub fn new(sender_id: ComponentId, packet: Vec<u8>) -> Self {
Self { sender_id, packet }
}
} }
pub struct TimestampHelper { pub struct TimestampHelper {
@@ -53,105 +37,3 @@ impl Default for TimestampHelper {
} }
} }
} }
/// Helper structure for periodic HK generation of a single set.
#[derive(Debug)]
pub struct HkHelperSingleSet {
pub enabled: bool,
pub frequency: Duration,
pub last_generated: Option<Instant>,
}
impl HkHelperSingleSet {
#[inline]
pub const fn new(enabled: bool, init_frequency: Duration) -> Self {
Self {
enabled,
frequency: init_frequency,
last_generated: None,
}
}
#[inline]
pub const fn enabled(&self) -> bool {
self.enabled
}
/// Check whether a new HK packet needs to be generated.
pub fn needs_generation(&mut self) -> bool {
if !self.enabled {
return false;
}
if self.last_generated.is_none() {
self.last_generated = Some(Instant::now());
return true;
}
let last_generated = self.last_generated.unwrap();
if Instant::now() - last_generated >= self.frequency {
self.last_generated = Some(Instant::now());
return true;
}
false
}
}
pub struct TmtcQueues {
pub tc_rx: mpsc::Receiver<CcsdsTcPacketOwned>,
pub tm_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
}
#[derive(Debug)]
pub struct ModeHelper<Mode, TransitionState> {
pub current: Mode,
pub target: Option<Mode>,
pub tc_commander: Option<CcsdsPacketIdAndPsc>,
pub transition_start: Option<Instant>,
pub timeout: Duration,
pub transition_state: TransitionState,
}
impl<Mode: Copy + Clone, TransitionState: Default> ModeHelper<Mode, TransitionState> {
pub fn new(init_mode: Mode, timeout: Duration) -> Self {
Self {
current: init_mode,
target: Default::default(),
tc_commander: Default::default(),
transition_start: None,
timeout,
transition_state: Default::default(),
}
}
pub fn start(&mut self, target: Mode) {
self.target = Some(target);
self.transition_start = Some(Instant::now());
self.transition_state = TransitionState::default();
}
#[inline]
pub fn transition_active(&self) -> bool {
self.target.is_some()
}
pub fn timed_out(&self) -> bool {
if self.target.is_none() {
return false;
}
if let Some(transition_start) = self.transition_start {
return Instant::now() - transition_start >= self.timeout;
}
false
}
pub fn finish(&mut self, success: bool) -> Option<CcsdsPacketIdAndPsc> {
self.target?;
if success {
self.current = self.target.take().unwrap();
} else {
self.target = None;
}
self.transition_state = Default::default();
self.transition_start = None;
self.tc_commander.take()
}
}
+1 -10
View File
@@ -1,13 +1,4 @@
use std::str::FromStr as _;
pub fn setup_logger() -> Result<(), fern::InitError> { pub fn setup_logger() -> Result<(), fern::InitError> {
// Read the RUST_LOG environment variable and parse it.
// If the variable is not set or is invalid, default to Debug.
let mut log_level = log::LevelFilter::Info;
if let Ok(log_level_str) = std::env::var("RUST_LOG") {
log_level = log::LevelFilter::from_str(&log_level_str).unwrap_or(log::LevelFilter::Info);
}
fern::Dispatch::new() fern::Dispatch::new()
.format(|out, message, record| { .format(|out, message, record| {
out.finish(format_args!( out.finish(format_args!(
@@ -18,7 +9,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
message message
)) ))
}) })
.level(log_level) .level(log::LevelFilter::Debug)
.chain(std::io::stdout()) .chain(std::io::stdout())
.chain(fern::log_file("output.log")?) .chain(fern::log_file("output.log")?)
.apply()?; .apply()?;
+564 -257
View File
@@ -1,136 +1,205 @@
use std::{
net::{IpAddr, SocketAddr},
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
mpsc,
},
thread,
time::Duration,
};
use eps::{
PowerSwitchHelper,
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
};
use interface::{
sim_client_udp::create_sim_client,
tcp::{SyncTcpTmSource, TcpTask},
udp::UdpTmtcServer,
};
use log::info;
use logger::setup_logger;
use models::{ComponentId, DeviceMode};
use satrs::{
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
mode::{Mode, ModeAndSubmode, ModeRequest},
pus::HandlingStatus,
request::{GenericMessage, MessageMetadata},
spacepackets::time::cds::CdsTime,
};
use satrs_example::{
TmtcQueues,
config::{
OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
components::NO_SENDER,
tasks::{FREQ_MS_AOCS, FREQ_MS_CONTROLLER, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS},
},
};
use tmtc::sender::TmTcSender;
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
use crate::{
acs::{mgm, mgm_assembly},
control::Controller,
eps::pcdu::SwitchSet,
event_manager::EventManager,
interface::udp::UdpTmHandlerWithChannel,
tmtc::tc_source::CcsdsDistributor,
};
mod acs; mod acs;
mod ccsds;
mod control;
mod eps; mod eps;
mod event_manager; mod events;
mod hk;
mod interface; mod interface;
mod logger; mod logger;
mod pus;
mod requests;
mod tmtc; mod tmtc;
fn main() {
static KILL_SIGNAL: AtomicBool = AtomicBool::new(false);
setup_logger().expect("setting up logging with fern failed"); use crate::eps::pcdu::{
println!("Runng OBSW example"); PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper,
ctrlc::set_handler(move || { };
log::info!("Received Ctrl-C, shutting down"); use crate::eps::PowerSwitchHelper;
KILL_SIGNAL.store(true, Ordering::Relaxed); use crate::events::EventHandler;
}) use crate::interface::udp::DynamicUdpTmHandler;
.expect("Error setting Ctrl-C handler"); use crate::pus::stack::PusStack;
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic};
use log::info;
use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer;
use satrs::pus::HandlingStatus;
use satrs::request::GenericMessage;
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{
FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS,
};
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
use crate::acs::mgm::{
MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface, SpiSimInterface,
SpiSimInterfaceWrapper,
};
use crate::interface::sim_client_udp::create_sim_client;
use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
use crate::logger::setup_logger;
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static};
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
use crate::pus::test::create_test_service_static;
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
use crate::requests::{CompositeRequest, GenericRequestRouter};
use satrs::mode::ModeRequest;
use satrs::pus::event_man::EventRequestWithToken;
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
use satrs_example::config::components::{MGM_HANDLER_0, PCDU_HANDLER, TCP_SERVER, UDP_SERVER};
use std::net::{IpAddr, SocketAddr};
use std::sync::{mpsc, Mutex};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
#[allow(dead_code)]
fn static_tmtc_pool_main() {
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 =
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
let (sim_request_tx, sim_request_rx) = mpsc::channel(); let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel(); let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel(); let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx); let mut opt_sim_client = create_sim_client(sim_request_rx);
let (mgm_0_handler_tc_tx, mgm_0_handler_tc_rx) = mpsc::sync_channel(10); let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
let (mgm_1_handler_tc_tx, mgm_1_handler_tc_rx) = mpsc::sync_channel(10); mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
let (mgm_assembly_tc_tx, mgm_assembly_tc_rx) = mpsc::sync_channel(10); let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
let (pcdu_handler_tc_tx, pcdu_handler_tc_rx) = mpsc::sync_channel(30); mpsc::sync_channel::<GenericMessage<CompositeRequest>>(30);
let (controller_tc_tx, controller_tc_rx) = mpsc::sync_channel(10);
// These message handles need to go into the MGM assembly and ACS subsystem. let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
let (_mgm_assembly_request_tx, mgm_assembly_request_rx) = mpsc::sync_channel(5); mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (mgm_assembly_report_tx, _mgm_assembly_report_rx) = mpsc::sync_channel(5); let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
// These message handles need to go into the MGM assembly and MGM devices. // Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let (mgm_0_mode_request_tx, mgm_0_mode_request_rx) = mpsc::sync_channel(5); let mut request_map = GenericRequestRouter::default();
let (mgm_1_mode_request_tx, mgm_1_mode_request_rx) = mpsc::sync_channel(5); request_map
let (mgm_0_mode_report_tx, mgm_0_mode_report_rx) = mpsc::sync_channel(5); .composite_router_map
let (mgm_1_mode_report_tx, mgm_1_mode_report_rx) = mpsc::sync_channel(5); .insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
request_map
.composite_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
request_map
.mode_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx);
let (pcdu_handler_mode_tx, _pcdu_handler_mode_rx) = mpsc::sync_channel(5); // This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source.
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
let (event_ctrl_tx, event_ctrl_rx) = mpsc::sync_channel(10); // Create event handling components
let mut event_manager = EventManager { // These sender handles are used to send event requests, for example to enable or disable
ctrl_rx: event_ctrl_rx, // certain events.
tm_tx: tm_sink_tx.clone(), let (event_tx, event_rx) = mpsc::sync_channel(100);
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
sched_tc_sender: pus_sched_tx,
hk_tc_sender: pus_hk_tx,
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
}; };
let pus_test_service = create_test_service_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 controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx); let mut tmtc_task = TcSourceTaskStatic::new(
shared_tc_pool_wrapper.clone(),
let ccsds_distributor = CcsdsDistributor::default(); tc_source_rx,
let mut tc_source = TcSourceTask::new(tc_source_rx, ccsds_distributor); PusTcDistributor::new(tm_sink_tx_sender, pus_router),
tc_source.add_target(ComponentId::EpsPcdu, pcdu_handler_tc_tx); );
tc_source.add_target(ComponentId::Controller, controller_tc_tx);
tc_source.add_target(ComponentId::AcsMgm0, mgm_0_handler_tc_tx);
tc_source.add_target(ComponentId::AcsMgm1, mgm_1_handler_tc_tx);
tc_source.add_target(ComponentId::AcsMgmAssembly, mgm_assembly_tc_tx);
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( let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
ComponentId::UdpServer as u32, .expect("creating UDP TMTC server failed");
sock_addr,
2048,
tc_sender.clone(),
)
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer { let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server, udp_tc_server,
tm_handler: udp_tm_handler.into(), tm_handler: StaticUdpTmHandler {
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone(),
},
}; };
let tcp_server_cfg = ServerConfig::new( let tcp_server_cfg = ServerConfig::new(
ComponentId::TcpServer as u32, TCP_SERVER.id(),
sock_addr, sock_addr,
Duration::from_millis(400), Duration::from_millis(400),
4096, 4096,
@@ -140,87 +209,60 @@ fn 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_sender, tc_source.clone(),
PACKET_ID_VALIDATOR.clone(), PACKET_ID_VALIDATOR.clone(),
) )
.expect("tcp server creation failed"); .expect("tcp server creation failed");
let mut tm_sink = TmSink::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx); let mut tm_sink = TmSinkStatic::new(
shared_tm_pool_wrapper,
sync_tm_tcp_source,
tm_sink_rx,
tm_server_tx,
);
let shared_switch_set = Arc::new(Mutex::new(SwitchSet::new_with_init_switches_unknown())); let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(5);
let shared_switch_set = Arc::new(Mutex::default());
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20); let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone()); let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
let shared_mgm_0_set = Arc::default(); let shared_mgm_set = Arc::default();
let shared_mgm_1_set = Arc::default(); let mgm_mode_leaf_interface = MpscModeLeafInterface {
let (mgm_0_spi_interface, mgm_1_spi_interface) = request_rx: mgm_handler_mode_rx,
if let Some(sim_client) = opt_sim_client.as_mut() { reply_to_pus_tx: pus_mode_reply_tx.clone(),
sim_client reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
.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); let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
( sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
mgm::SpiCommunication::Sim(mgm::SpiSimInterface { SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(), sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_0_sim_reply_rx, sim_reply_rx: mgm_sim_reply_rx,
}), })
mgm::SpiCommunication::Sim(mgm::SpiSimInterface { } else {
sim_request_tx: sim_request_tx.clone(), SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
sim_reply_rx: mgm_1_sim_reply_rx, };
}), let mut mgm_handler = MgmHandlerLis3Mdl::new(
) MGM_HANDLER_0,
} else { "MGM_0",
( mgm_mode_leaf_interface,
mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), mgm_handler_composite_rx,
mgm::SpiCommunication::Dummy(mgm::SpiDummyInterface::default()), pus_hk_reply_tx.clone(),
)
};
let mut mgm_0_handler = mgm::MgmHandlerLis3Mdl::new(
mgm::MgmId::_0,
TmtcQueues {
tc_rx: mgm_0_handler_tc_rx,
tm_tx: tm_sink_tx.clone(),
},
switch_helper.clone(), switch_helper.clone(),
mgm_0_spi_interface, tm_sink_tx.clone(),
shared_mgm_0_set, mgm_spi_interface,
mgm::ModeLeafHelper { shared_mgm_set,
request_rx: mgm_0_mode_request_rx,
report_tx: mgm_0_mode_report_tx,
},
Duration::from_millis(1000)
);
let mut mgm_1_handler = mgm::MgmHandlerLis3Mdl::new(
mgm::MgmId::_1,
TmtcQueues {
tc_rx: mgm_1_handler_tc_rx,
tm_tx: tm_sink_tx.clone(),
},
switch_helper.clone(),
mgm_1_spi_interface,
shared_mgm_1_set,
mgm::ModeLeafHelper {
request_rx: mgm_1_mode_request_rx,
report_tx: mgm_1_mode_report_tx,
},
Duration::from_millis(1000)
);
let mut mgm_assembly = mgm_assembly::Assembly::new(
mgm_assembly::ParentQueueHelper {
request_rx: mgm_assembly_request_rx,
report_tx: mgm_assembly_report_tx,
},
mgm_assembly::ChildrenQueueHelper {
request_tx_queues: [mgm_0_mode_request_tx, mgm_1_mode_request_tx],
report_rx_queues: [mgm_0_mode_report_rx, mgm_1_mode_report_rx],
},
TmtcQueues {
tc_rx: mgm_assembly_tc_rx,
tm_tx: tm_sink_tx.clone(),
},
Duration::from_millis(2000),
); );
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(10);
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
request_rx: pcdu_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx,
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
};
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() { let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx); sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new( SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
@@ -231,36 +273,25 @@ fn main() {
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
}; };
let mut pcdu_handler = PcduHandler::new( let mut pcdu_handler = PcduHandler::new(
pcdu_handler_tc_rx, PCDU_HANDLER,
tm_sink_tx.clone(), "PCDU",
pcdu_mode_leaf_interface,
pcdu_handler_composite_rx,
pus_hk_reply_tx,
switch_request_rx, switch_request_rx,
tm_sink_tx,
pcdu_serial_interface, pcdu_serial_interface,
shared_switch_set, shared_switch_set,
DeviceMode::Normal,
); );
// 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()
.name("TMTC & UDP".to_string()) .name("SATRS tmtc-udp".to_string())
.spawn(move || { .spawn(move || {
info!("Running UDP server on port {SERVER_PORT}"); info!("Running UDP server on port {SERVER_PORT}");
loop { loop {
if KILL_SIGNAL.load(Ordering::Relaxed) {
break;
}
udp_tmtc_server.periodic_operation(); udp_tmtc_server.periodic_operation();
tc_source.periodic_operation(); tmtc_task.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC)); thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
} }
}) })
@@ -268,13 +299,10 @@ fn main() {
info!("Starting TCP task"); info!("Starting TCP task");
let jh_tcp = thread::Builder::new() let jh_tcp = thread::Builder::new()
.name("TCP".to_string()) .name("sat-rs tcp".to_string())
.spawn(move || { .spawn(move || {
info!("Running TCP server on port {SERVER_PORT}"); info!("Running TCP server on port {SERVER_PORT}");
loop { loop {
if KILL_SIGNAL.load(Ordering::Relaxed) {
break;
}
tcp_server.periodic_operation(); tcp_server.periodic_operation();
} }
}) })
@@ -282,14 +310,9 @@ fn main() {
info!("Starting TM funnel task"); info!("Starting TM funnel task");
let jh_tm_funnel = thread::Builder::new() let jh_tm_funnel = thread::Builder::new()
.name("TM SINK".to_string()) .name("tm sink".to_string())
.spawn(move || { .spawn(move || loop {
loop { tm_sink.operation();
if KILL_SIGNAL.load(Ordering::Relaxed) {
break;
}
tm_sink.operation();
}
}) })
.unwrap(); .unwrap();
@@ -298,15 +321,10 @@ fn main() {
info!("Starting UDP sim client task"); info!("Starting UDP sim client task");
opt_jh_sim_client = Some( opt_jh_sim_client = Some(
thread::Builder::new() thread::Builder::new()
.name("SIM ADAPTER".to_string()) .name("sat-rs sim adapter".to_string())
.spawn(move || { .spawn(move || loop {
loop { if sim_client.operation() == HandlingStatus::Empty {
if KILL_SIGNAL.load(Ordering::Relaxed) { std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
break;
}
if sim_client.operation() == HandlingStatus::Empty {
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
}
} }
}) })
.unwrap(), .unwrap(),
@@ -315,55 +333,35 @@ fn main() {
info!("Starting AOCS thread"); info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new() let jh_aocs = thread::Builder::new()
.name("AOCS".to_string()) .name("sat-rs aocs".to_string())
.spawn(move || { .spawn(move || loop {
loop { mgm_handler.periodic_operation();
if KILL_SIGNAL.load(Ordering::Relaxed) { thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
break;
}
mgm_0_handler.periodic_operation();
mgm_1_handler.periodic_operation();
mgm_assembly.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
}
}) })
.unwrap(); .unwrap();
info!("Starting EPS thread"); info!("Starting EPS thread");
let jh_eps = thread::Builder::new() let jh_eps = thread::Builder::new()
.name("EPS".to_string()) .name("sat-rs eps".to_string())
.spawn(move || { .spawn(move || loop {
loop { // TODO: We should introduce something like a fixed timeslot helper to allow a more
if KILL_SIGNAL.load(Ordering::Relaxed) { // declarative API. It would also be very useful for the AOCS task.
break; pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
} thread::sleep(Duration::from_millis(50));
// TODO: We should introduce something like a fixed timeslot helper to allow a more pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
// declarative API. It would also be very useful for the AOCS task. thread::sleep(Duration::from_millis(50));
// pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
// TODO: The fixed timeslot handler exists.. use it. thread::sleep(Duration::from_millis(300));
// TODO: Why not just use sync code in the PCDU handler, and fully delay there?
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(); .unwrap();
info!("Starting controller thread"); info!("Starting PUS handler thread");
let jh_controller_thread = thread::Builder::new() let jh_pus_handler = thread::Builder::new()
.name("CTRL".to_string()) .name("sat-rs pus".to_string())
.spawn(move || { .spawn(move || loop {
loop { event_handler.periodic_operation();
if KILL_SIGNAL.load(Ordering::Relaxed) { pus_stack.periodic_operation();
break; thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
}
controller.periodic_operation();
event_manager.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_CONTROLLER));
}
}) })
.unwrap(); .unwrap();
@@ -383,11 +381,320 @@ fn main() {
} }
jh_aocs.join().expect("Joining AOCS thread failed"); jh_aocs.join().expect("Joining AOCS thread failed");
jh_eps.join().expect("Joining EPS thread failed"); jh_eps.join().expect("Joining EPS thread failed");
jh_controller_thread 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_sink_tx, tm_sink_rx) = mpsc::channel();
let (tm_server_tx, tm_server_rx) = mpsc::channel();
let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(5);
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(10);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map
.composite_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
request_map
.composite_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
request_map
.mode_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx);
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
// certain events.
let (event_tx, event_rx) = mpsc::sync_channel(100);
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
sched_tc_sender: pus_sched_tx,
hk_tc_sender: pus_hk_tx,
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
};
let pus_test_service =
create_test_service_dynamic(tm_sink_tx.clone(), event_tx.clone(), pus_test_rx);
let pus_scheduler_service = create_scheduler_service_dynamic(
tm_sink_tx.clone(),
tc_source_tx.clone(),
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service =
create_event_service_dynamic(tm_sink_tx.clone(), pus_event_rx, event_request_tx);
let pus_action_service = create_action_service_dynamic(
tm_sink_tx.clone(),
pus_action_rx,
request_map.clone(),
pus_action_reply_rx,
);
let pus_hk_service = create_hk_service_dynamic(
tm_sink_tx.clone(),
pus_hk_rx,
request_map.clone(),
pus_hk_reply_rx,
);
let pus_mode_service = create_mode_service_dynamic(
tm_sink_tx.clone(),
pus_mode_rx,
request_map,
pus_mode_reply_rx,
);
let mut pus_stack = PusStack::new(
pus_test_service,
pus_hk_service,
pus_event_service,
pus_action_service,
pus_scheduler_service,
pus_mode_service,
);
let mut tmtc_task = TcSourceTaskDynamic::new(
tc_source_rx,
PusTcDistributor::new(tm_sink_tx.clone(), pus_router),
);
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source_tx.clone())
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
tm_handler: DynamicUdpTmHandler {
tm_rx: tm_server_rx,
},
};
let tcp_server_cfg = ServerConfig::new(
TCP_SERVER.id(),
sock_addr,
Duration::from_millis(400),
4096,
8192,
);
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
let mut tcp_server = TcpTask::new(
tcp_server_cfg,
sync_tm_tcp_source.clone(),
tc_source_tx.clone(),
PACKET_ID_VALIDATOR.clone(),
)
.expect("tcp server creation failed");
let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
let shared_switch_set = Arc::new(Mutex::default());
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(5);
let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx.clone(),
reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
};
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_sim_reply_rx,
})
} else {
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
};
let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0,
"MGM_0",
mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sink_tx.clone(),
mgm_spi_interface,
shared_mgm_set,
);
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(10);
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
request_rx: pcdu_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx,
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
};
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
sim_request_tx.clone(),
pcdu_sim_reply_rx,
))
} else {
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
};
let mut pcdu_handler = PcduHandler::new(
PCDU_HANDLER,
"PCDU",
pcdu_mode_leaf_interface,
pcdu_handler_composite_rx,
pus_hk_reply_tx,
switch_request_rx,
tm_sink_tx,
pcdu_serial_interface,
shared_switch_set,
);
info!("Starting TMTC and UDP task");
let jh_udp_tmtc = thread::Builder::new()
.name("sat-rs tmtc-udp".to_string())
.spawn(move || {
info!("Running UDP server on port {SERVER_PORT}");
loop {
udp_tmtc_server.periodic_operation();
tmtc_task.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
}
})
.unwrap();
info!("Starting TCP task");
let jh_tcp = thread::Builder::new()
.name("sat-rs tcp".to_string())
.spawn(move || {
info!("Running TCP server on port {SERVER_PORT}");
loop {
tcp_server.periodic_operation();
}
})
.unwrap();
info!("Starting TM funnel task");
let jh_tm_funnel = thread::Builder::new()
.name("sat-rs tm-sink".to_string())
.spawn(move || loop {
tm_funnel.operation();
})
.unwrap();
let mut opt_jh_sim_client = None;
if let Some(mut sim_client) = opt_sim_client {
info!("Starting UDP sim client task");
opt_jh_sim_client = Some(
thread::Builder::new()
.name("sat-rs sim adapter".to_string())
.spawn(move || loop {
if sim_client.operation() == HandlingStatus::Empty {
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
}
})
.unwrap(),
);
}
info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new()
.name("sat-rs aocs".to_string())
.spawn(move || loop {
mgm_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
})
.unwrap();
info!("Starting EPS thread");
let jh_eps = thread::Builder::new()
.name("sat-rs eps".to_string())
.spawn(move || loop {
// TODO: We should introduce something like a fixed timeslot helper to allow a more
// declarative API. It would also be very useful for the AOCS task.
pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(300));
})
.unwrap();
info!("Starting PUS handler thread");
let jh_pus_handler = thread::Builder::new()
.name("sat-rs pus".to_string())
.spawn(move || loop {
pus_stack.periodic_operation();
event_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
})
.unwrap();
jh_udp_tmtc
.join()
.expect("Joining UDP TMTC server thread failed");
jh_tcp
.join()
.expect("Joining TCP TMTC server thread failed");
jh_tm_funnel
.join()
.expect("Joining TM Funnel thread failed");
if let Some(jh_sim_client) = opt_jh_sim_client {
jh_sim_client
.join()
.expect("Joining SIM client thread failed");
}
jh_aocs.join().expect("Joining AOCS thread failed");
jh_eps.join().expect("Joining EPS thread failed");
jh_pus_handler
.join()
.expect("Joining PUS handler thread failed");
}
fn main() {
setup_logger().expect("setting up logging with fern failed");
println!("Running OBSW example");
#[cfg(not(feature = "dyn_tmtc"))]
static_tmtc_pool_main();
#[cfg(feature = "dyn_tmtc")]
dyn_tmtc_pool_main();
}
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) { pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
time_provider time_provider
.update_from_now() .update_from_now()
@@ -1,5 +1,6 @@
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,
}; };
@@ -9,21 +10,21 @@ use satrs::pus::verification::{
VerificationReportingProvider, VerificationToken, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
ActiveRequest, EcssTcAndToken, EcssTcCacher, EcssTmSender, EcssTmtcError, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler, EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
PusServiceHelper, PusTcToRequestConverter, MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper,
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,
@@ -161,7 +162,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.message_subtype_id(); let subservice = tc.subservice();
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
@@ -206,20 +207,20 @@ impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for Actio
} }
} }
pub fn create_action_service( pub fn create_action_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tc_in_mem_converter: EcssTcCacher, tc_pool: SharedStaticMemoryPool,
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 { ) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let action_request_handler = PusTargetedRequestService::new( let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new( PusServiceHelper::new(
ids::generic_pus::PUS_ACTION.id(), PUS_ACTION_SERVICE.id(),
pus_action_rx, pus_action_rx,
tm_sender, tm_sender,
create_verification_reporter(PUS_ACTION.id(), PUS_ACTION.apid), create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
tc_in_mem_converter, EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
), ),
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
@@ -234,9 +235,36 @@ pub fn create_action_service(
} }
} }
pub struct ActionServiceWrapper { pub fn create_action_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
action_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) -> ActionServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_ACTION_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(),
ActionReplyHandler::default(),
action_router,
reply_receiver,
);
ActionServiceWrapper {
service: action_request_handler,
}
}
pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService< pub(crate) service: PusTargetedRequestService<
MpscTcReceiver, MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter, VerificationReporter,
ActionRequestConverter, ActionRequestConverter,
ActionReplyHandler, ActionReplyHandler,
@@ -247,7 +275,9 @@ pub struct ActionServiceWrapper {
>, >,
} }
impl TargetedPusService for ActionServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
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";
@@ -270,15 +300,12 @@ impl TargetedPusService for ActionServiceWrapper {
#[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,
@@ -311,7 +338,7 @@ 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::sync_channel(5); let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
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::sync_channel(10); let (action_req_tx, action_req_rx) = mpsc::sync_channel(10);
@@ -325,9 +352,9 @@ mod tests {
PusServiceHelper::new( PusServiceHelper::new(
owner_id, owner_id,
pus_action_rx, pus_action_rx,
TmTcSender::Heap(tm_funnel_tx.clone()), tm_funnel_tx.clone(),
verif_reporter, verif_reporter,
EcssTcCacher::Heap(EcssTcVecCacher::default()), EcssTcInVecConverter::default(),
), ),
ActionRequestConverter::default(), ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(), DefaultActiveActionRequestMap::default(),
@@ -370,7 +397,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(); let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap().0;
panic!("unexpected TM packet {unexpected_tm:?}"); panic!("unexpected TM packet {unexpected_tm:?}");
} }
} }
@@ -413,11 +440,7 @@ 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 let token = self.service.service_helper.verif_reporter_mut().add_tc(tc);
.service
.service_helper
.verif_reporter_mut()
.start_verification(tc);
let accepted_token = self let accepted_token = self
.service .service
.service_helper .service_helper
@@ -450,13 +473,12 @@ 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(MessageTypeId::new(8, 128)); let sec_header = PusTcSecondaryHeader::new_simple(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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.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 = let pus8_packet = PusTcCreator::new(sp_header, sec_header, &app_data, true);
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);
@@ -492,7 +514,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(MessageTypeId::new(8, 128)); let sec_header = PusTcSecondaryHeader::new_simple(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.
@@ -502,7 +524,7 @@ mod tests {
SpHeader::new_from_apid(TEST_APID), SpHeader::new_from_apid(TEST_APID),
sec_header, sec_header,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
testbench.add_tc(&pus8_packet); testbench.add_tc(&pus8_packet);
let time_stamp: [u8; 7] = [0; 7]; let time_stamp: [u8; 7] = [0; 7];
@@ -518,17 +540,17 @@ mod tests {
TEST_COMPONENT_ID_0.raw(), TEST_COMPONENT_ID_0.raw(),
ActionRequestConverter::default(), ActionRequestConverter::default(),
); );
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new(8, 128)); let sec_header = PusTcSecondaryHeader::new_simple(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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.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,
CreatorConfig::default(), true,
); );
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);
@@ -554,11 +576,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(MessageTypeId::new(8, 128)); let sec_header = PusTcSecondaryHeader::new_simple(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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.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;
@@ -567,7 +589,7 @@ mod tests {
SpHeader::new_from_apid(TEST_APID), SpHeader::new_from_apid(TEST_APID),
sec_header, sec_header,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
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);
@@ -697,7 +719,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_u32), action_reply); GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), 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,32 +1,34 @@
use std::sync::mpsc; use std::sync::mpsc;
use crate::pus::create_verification_reporter; use crate::pus::create_verification_reporter;
use crate::tmtc::sender::TmTcSender; use satrs::pool::SharedStaticMemoryPool;
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, EcssTcCacher, MpscTcReceiver, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
PartialPusHandlingError, PusServiceHelper, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
}; };
use satrs::spacepackets::ecss::PusServiceId; use satrs::spacepackets::ecss::PusServiceId;
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
use super::{DirectPusService, HandlingStatus}; use super::{DirectPusService, HandlingStatus};
pub fn create_event_service( pub fn create_event_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tm_in_pool_converter: EcssTcCacher, tc_pool: SharedStaticMemoryPool,
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 { ) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
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),
tm_in_pool_converter, EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
), ),
event_request_tx, event_request_tx,
); );
@@ -35,12 +37,34 @@ pub fn create_event_service(
} }
} }
pub struct EventServiceWrapper { pub fn create_event_service_dynamic(
pub handler: tm_funnel_tx: mpsc::Sender<PacketAsVec>,
PusEventServiceHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>, 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,
}
} }
impl DirectPusService for EventServiceWrapper { pub struct EventServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub handler:
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
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,26 +1,28 @@
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, ActiveRequest, DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher, ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, PusPacketHandlingError, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
PusReplyHandler, PusServiceHelper, PusTcToRequestConverter, EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
}; };
use satrs::request::{GenericMessage, UniqueApidTargetId}; use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::res_code::ResultU16; 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};
@@ -31,7 +33,6 @@ pub struct HkReply {
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
#[allow(dead_code)]
pub enum HkReplyVariant { pub enum HkReplyVariant {
Ack, Ack,
Failed(ResultU16), Failed(ResultU16),
@@ -164,11 +165,11 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
found: 4, found: 4,
}); });
} }
let subservice = tc.message_subtype_id(); let subservice = tc.subservice();
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::MessageSubtypeId::try_from(subservice); let standard_subservice = hk::Subservice::try_from(subservice);
if standard_subservice.is_err() { if standard_subservice.is_err() {
verif_reporter verif_reporter
.start_failure( .start_failure(
@@ -180,22 +181,19 @@ 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::MessageSubtypeId::TcEnableHkGeneration hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
| hk::MessageSubtypeId::TcEnableDiagGeneration => {
HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic) HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic)
} }
hk::MessageSubtypeId::TcDisableHkGeneration hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
| hk::MessageSubtypeId::TcDisableDiagGeneration => {
HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic) HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic)
} }
hk::MessageSubtypeId::TcReportHkReportStructures => todo!(), hk::Subservice::TcReportHkReportStructures => todo!(),
hk::MessageSubtypeId::TmHkPacket => todo!(), hk::Subservice::TmHkPacket => todo!(),
hk::MessageSubtypeId::TcGenerateOneShotHk hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
| hk::MessageSubtypeId::TcGenerateOneShotDiag => {
HkRequest::new(unique_id, HkRequestVariant::OneShot) HkRequest::new(unique_id, HkRequestVariant::OneShot)
} }
hk::MessageSubtypeId::TcModifyDiagCollectionInterval hk::Subservice::TcModifyDiagCollectionInterval
| hk::MessageSubtypeId::TcModifyHkCollectionInterval => { | hk::Subservice::TcModifyHkCollectionInterval => {
if user_data.len() < 12 { if user_data.len() < 12 {
verif_reporter verif_reporter
.start_failure( .start_failure(
@@ -243,20 +241,20 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
} }
} }
pub fn create_hk_service( pub fn create_hk_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tc_in_mem_converter: EcssTcCacher, tc_pool: SharedStaticMemoryPool,
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 { ) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let pus_3_handler = PusTargetedRequestService::new( let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new( PusServiceHelper::new(
PUS_HK.id(), PUS_HK_SERVICE.id(),
pus_hk_rx, pus_hk_rx,
tm_sender, tm_sender,
create_verification_reporter(PUS_HK.id(), PUS_HK.apid), create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
tc_in_mem_converter, EcssTcInSharedStoreConverter::new(tc_pool, 2048),
), ),
HkRequestConverter::default(), HkRequestConverter::default(),
DefaultActiveRequestMap::default(), DefaultActiveRequestMap::default(),
@@ -269,9 +267,36 @@ pub fn create_hk_service(
} }
} }
pub struct HkServiceWrapper { pub fn create_hk_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
) -> HkServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_HK_SERVICE.id(),
pus_hk_rx,
tm_funnel_tx,
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
EcssTcInVecConverter::default(),
),
HkRequestConverter::default(),
DefaultActiveRequestMap::default(),
HkReplyHandler::default(),
request_router,
reply_receiver,
);
HkServiceWrapper {
service: pus_3_handler,
}
}
pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService< pub(crate) service: PusTargetedRequestService<
MpscTcReceiver, MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter, VerificationReporter,
HkRequestConverter, HkRequestConverter,
HkReplyHandler, HkReplyHandler,
@@ -282,7 +307,9 @@ pub struct HkServiceWrapper {
>, >,
} }
impl TargetedPusService for HkServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
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";
@@ -305,19 +332,16 @@ impl TargetedPusService for HkServiceWrapper {
#[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::MessageSubtypeId, tc::PusTcCreator}, ecss::{hk::Subservice, tc::PusTcCreator},
SpHeader, SpHeader,
}, },
}; };
@@ -334,18 +358,19 @@ 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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&target_id.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,
MessageTypeId::new(3, MessageSubtypeId::TcGenerateOneShotHk as u8), 3,
Subservice::TcGenerateOneShotHk as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
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
@@ -363,11 +388,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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&target_id.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);
@@ -382,16 +407,18 @@ mod tests {
}; };
let tc0 = PusTcCreator::new_simple( let tc0 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcEnableHkGeneration as u8), 3,
Subservice::TcEnableHkGeneration as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc0); generic_check(&tc0);
let tc1 = PusTcCreator::new_simple( let tc1 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcEnableDiagGeneration as u8), 3,
Subservice::TcEnableDiagGeneration as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc1); generic_check(&tc1);
} }
@@ -400,11 +427,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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&target_id.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);
@@ -419,16 +446,18 @@ mod tests {
}; };
let tc0 = PusTcCreator::new_simple( let tc0 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcDisableHkGeneration as u8), 3,
Subservice::TcDisableHkGeneration as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc0); generic_check(&tc0);
let tc1 = PusTcCreator::new_simple( let tc1 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcDisableDiagGeneration as u8), 3,
Subservice::TcDisableDiagGeneration as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc1); generic_check(&tc1);
} }
@@ -437,12 +466,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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&target_id.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());
@@ -460,16 +489,18 @@ mod tests {
}; };
let tc0 = PusTcCreator::new_simple( let tc0 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcModifyHkCollectionInterval as u8), 3,
Subservice::TcModifyHkCollectionInterval as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc0); generic_check(&tc0);
let tc1 = PusTcCreator::new_simple( let tc1 = PusTcCreator::new_simple(
sp_header, sp_header,
MessageTypeId::new(3, MessageSubtypeId::TcModifyDiagCollectionInterval as u8), 3,
Subservice::TcModifyDiagCollectionInterval as u8,
&app_data, &app_data,
CreatorConfig::default(), true,
); );
generic_check(&tc1); generic_check(&tc1);
} }
@@ -478,8 +509,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_u32; let sender_id = 2_u64;
let apid_target_id = u21::new(3); let apid_target_id = 3_u32;
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(
@@ -500,7 +531,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_u32), action_reply); GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), 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,13 +1,12 @@
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,
VerificationReporterConfig, VerificationReportingProvider, VerificationToken, VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
ActiveRequest, ActiveRequestStore, CacheAndReadRawEcssTc, EcssTcAndToken, EcssTcCacher, ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError, EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper, HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
PusTcToRequestConverter, TcInMemory, PusTcToRequestConverter, TcInMemory,
@@ -18,11 +17,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::ids::generic_pus::PUS_ROUTING;
use satrs_example::TimestampHelper; use satrs_example::TimestampHelper;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::mpsc; use std::sync::mpsc::{self, Sender};
pub mod action; pub mod action;
pub mod event; pub mod event;
@@ -33,39 +32,39 @@ 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 = VerificationReporterConfig::new(apid, 1, 2, 8); let verif_cfg = VerificationReporterCfg::new(apid, 1, 2, 8).unwrap();
// 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: mpsc::SyncSender<EcssTcAndToken>, pub test_tc_sender: Sender<EcssTcAndToken>,
pub event_tc_sender: mpsc::SyncSender<EcssTcAndToken>, pub event_tc_sender: Sender<EcssTcAndToken>,
pub sched_tc_sender: mpsc::SyncSender<EcssTcAndToken>, pub sched_tc_sender: Sender<EcssTcAndToken>,
pub hk_tc_sender: mpsc::SyncSender<EcssTcAndToken>, pub hk_tc_sender: Sender<EcssTcAndToken>,
#[allow(dead_code)] pub action_tc_sender: Sender<EcssTcAndToken>,
pub action_tc_sender: mpsc::SyncSender<EcssTcAndToken>, pub mode_tc_sender: Sender<EcssTcAndToken>,
pub mode_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
} }
pub struct PusTcDistributor { pub struct PusTcDistributor<TmSender: EcssTmSender> {
#[allow(dead_code)]
pub id: ComponentId, pub id: ComponentId,
pub tm_sender: TmTcSender, pub tm_sender: TmSender,
pub verif_reporter: VerificationReporter, pub verif_reporter: VerificationReporter,
pub pus_router: PusTcMpscRouter, pub pus_router: PusTcMpscRouter,
stamp_helper: TimestampHelper, stamp_helper: TimestampHelper,
} }
impl PusTcDistributor { impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
pub fn new(tm_sender: TmTcSender, pus_router: PusTcMpscRouter) -> Self { pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
Self { Self {
id: PUS_ROUTING.raw(), id: PUS_ROUTING_SERVICE.raw(),
tm_sender, tm_sender,
verif_reporter: create_verification_reporter(PUS_ROUTING.id(), PUS_ROUTING.apid), verif_reporter: create_verification_reporter(
PUS_ROUTING_SERVICE.id(),
PUS_ROUTING_SERVICE.apid,
),
pus_router, pus_router,
stamp_helper: TimestampHelper::default(), stamp_helper: TimestampHelper::default(),
} }
@@ -103,18 +102,18 @@ impl PusTcDistributor {
sender_id, sender_id,
pus_tc_result.unwrap_err() pus_tc_result.unwrap_err()
); );
log::warn!("raw data: {raw_tc:x?}"); log::warn!("raw data: {:x?}", raw_tc);
// 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(); let pus_tc = pus_tc_result.unwrap().0;
let init_token = self.verif_reporter.start_verification(&pus_tc); let init_token = self.verif_reporter.add_tc(&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_type_id()); let service = PusServiceId::try_from(pus_tc.service());
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 {
@@ -188,7 +187,6 @@ impl PusTcDistributor {
Ok(HandlingStatus::HandledOne) Ok(HandlingStatus::HandledOne)
} }
} }
*/
pub trait TargetedPusService { pub trait TargetedPusService {
const SERVICE_ID: u8; const SERVICE_ID: u8;
@@ -268,19 +266,21 @@ 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>,
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>, ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
RequestType, RequestType,
ReplyType, ReplyType,
> { > {
pub service_helper: pub service_helper:
PusServiceHelper<TcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>, PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub request_router: GenericRequestRouter, pub request_router: GenericRequestRouter,
pub request_converter: RequestConverter, pub request_converter: RequestConverter,
pub active_request_map: ActiveRequestMapInstance, pub active_request_map: ActiveRequestMap,
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,20 +288,24 @@ 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>,
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>, ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
RequestType, RequestType,
ReplyType, ReplyType,
> >
PusTargetedRequestService< PusTargetedRequestService<
TcReceiver, TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter, VerificationReporter,
RequestConverter, RequestConverter,
ReplyHandler, ReplyHandler,
ActiveRequestMapInstance, ActiveRequestMap,
ActiveRequestInfo, ActiveRequestInfo,
RequestType, RequestType,
ReplyType, ReplyType,
@@ -312,12 +316,12 @@ where
pub fn new( pub fn new(
service_helper: PusServiceHelper< service_helper: PusServiceHelper<
TcReceiver, TcReceiver,
TmTcSender, TmSender,
EcssTcCacher, TcInMemConverter,
VerificationReporter, VerificationReporter,
>, >,
request_converter: RequestConverter, request_converter: RequestConverter,
active_request_map: ActiveRequestMapInstance, active_request_map: ActiveRequestMap,
reply_hook: ReplyHandler, reply_hook: ReplyHandler,
request_router: GenericRequestRouter, request_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>, reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
@@ -511,7 +515,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 ActiveRequest + Debug), active_request: &(impl ActiveRequestProvider + Debug),
verification_handler: &impl VerificationReportingProvider, verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8], time_stamp: &[u8],
service_str: &'static str, service_str: &'static str,
@@ -533,15 +537,13 @@ 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,
ActiveRequestStore, MpscTcReceiver, ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
}, },
request::UniqueApidTargetId, request::UniqueApidTargetId,
spacepackets::{ spacepackets::{
@@ -560,7 +562,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: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
Reply, Reply,
> { > {
pub id: ComponentId, pub id: ComponentId,
@@ -574,7 +576,7 @@ pub(crate) mod tests {
impl< impl<
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>, ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
ActiveRequestInfo: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
Reply, Reply,
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply> > ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
{ {
@@ -594,17 +596,17 @@ pub(crate) mod tests {
pub fn add_tc( pub fn add_tc(
&mut self, &mut self,
apid: u11, apid: u16,
apid_target: u21, apid_target: u32,
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(MessageTypeId::new(0, 0)); let sec_header_dummy = PusTcSecondaryHeader::new_simple(0, 0);
let init = self.verif_reporter.start_verification(&PusTcCreator::new( let init = self.verif_reporter.add_tc(&PusTcCreator::new(
sp_header, sp_header,
sec_header_dummy, sec_header_dummy,
&[], &[],
CreatorConfig::default(), true,
)); ));
let accepted = self let accepted = self
.verif_reporter .verif_reporter
@@ -675,7 +677,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: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
Request, Request,
> { > {
pub id: ComponentId, pub id: ComponentId,
@@ -689,7 +691,7 @@ pub(crate) mod tests {
impl< impl<
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>, Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
ActiveRequestInfo: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
Request, Request,
> PusConverterTestbench<Converter, ActiveRequestInfo, Request> > PusConverterTestbench<Converter, ActiveRequestInfo, Request>
{ {
@@ -707,7 +709,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.start_verification(tc); let token = self.verif_reporter.add_tc(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
@@ -723,8 +725,8 @@ pub(crate) mod tests {
&mut self, &mut self,
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
time_stamp: &[u8], time_stamp: &[u8],
expected_apid: u11, expected_apid: u16,
expected_apid_target: u21, expected_apid_target: u32,
) -> 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(
@@ -735,7 +737,7 @@ pub(crate) mod tests {
let tc_reader = PusTcReader::new(&current_packet).unwrap(); let tc_reader = PusTcReader::new(&current_packet).unwrap();
let (active_info, request) = self.converter.convert( let (active_info, request) = self.converter.convert(
token, token,
&tc_reader, &tc_reader.0,
&self.dummy_sender, &self.dummy_sender,
&self.verif_reporter, &self.verif_reporter,
time_stamp, time_stamp,
@@ -755,17 +757,19 @@ 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>,
ActiveRequestMapInstance: ActiveRequestStore<ActiveRequestInfo>, ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequest, ActiveRequestInfo: ActiveRequestProvider,
RequestType, RequestType,
ReplyType, ReplyType,
> { > {
pub service: PusTargetedRequestService< pub service: PusTargetedRequestService<
MpscTcReceiver, MpscTcReceiver,
MpscTmAsVecSender,
EcssTcInVecConverter,
TestVerificationReporter, TestVerificationReporter,
RequestConverter, RequestConverter,
ReplyHandler, ReplyHandler,
ActiveRequestMapInstance, ActiveRequestMap,
ActiveRequestInfo, ActiveRequestInfo,
RequestType, RequestType,
ReplyType, ReplyType,
@@ -1,17 +1,14 @@
use arbitrary_int::traits::Integer as _;
use arbitrary_int::u14;
use derive_new::new; use derive_new::new;
use satrs::mode_tree::{ModeNode, ModeParent}; use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
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 crate::tmtc::sender::TmTcSender; use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::verification::VerificationReporter; use satrs::pus::verification::VerificationReporter;
use satrs::pus::{ use satrs::pus::{
DefaultActiveRequestMap, EcssTcAndToken, EcssTcCacher, MpscTcReceiver, PusPacketHandlingError, DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError,
PusServiceHelper, PusServiceHelper,
}; };
use satrs::request::GenericMessage; use satrs::request::GenericMessage;
@@ -23,8 +20,8 @@ use satrs::{
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider, self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
VerificationToken, VerificationToken,
}, },
ActivePusRequestStd, ActiveRequest, EcssTmSender, EcssTmtcError, GenericConversionError, ActivePusRequestStd, ActiveRequestProvider, EcssTmSender, EcssTmtcError,
PusReplyHandler, PusTcToRequestConverter, PusTmVariant, GenericConversionError, PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
}, },
request::UniqueApidTargetId, request::UniqueApidTargetId,
spacepackets::{ spacepackets::{
@@ -37,6 +34,7 @@ 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::{
@@ -80,19 +78,10 @@ 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(), u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tm(req_id.packet_id().apid(), 0, 0);
let sec_header = PusTmSecondaryHeader::new( let sec_header =
MessageTypeId::new(200, Subservice::TmModeReply as u8), PusTmSecondaryHeader::new(200, Subservice::TmModeReply as u8, 0, 0, time_stamp);
0, let pus_tm = PusTmCreator::new(sp_header, sec_header, &source_data, true);
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)?;
} }
@@ -121,7 +110,6 @@ impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
), ),
)?; )?;
} }
ModeReply::ModeInfo(_mode_and_submode) => (),
}; };
Ok(true) Ok(true)
} }
@@ -158,7 +146,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.message_subtype_id(); let subservice = tc.subservice();
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
@@ -202,13 +190,7 @@ 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(( Ok((active_request, ModeRequest::SetMode(mode_and_submode)))
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)),
@@ -220,27 +202,24 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
} }
} }
pub fn create_mode_service( pub fn create_mode_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tc_in_mem_converter: EcssTcCacher, tc_pool: SharedStaticMemoryPool,
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 { ) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let mode_request_handler = PusTargetedRequestService::new( let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new( PusServiceHelper::new(
ids::generic_pus::PUS_MODE.id(), PUS_MODE_SERVICE.id(),
pus_action_rx, pus_action_rx,
tm_sender, tm_sender,
create_verification_reporter( create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
ids::generic_pus::PUS_MODE.id(), EcssTcInSharedStoreConverter::new(tc_pool, 2048),
ids::generic_pus::PUS_MODE.apid,
),
tc_in_mem_converter,
), ),
ModeRequestConverter::default(), ModeRequestConverter::default(),
DefaultActiveRequestMap::default(), DefaultActiveRequestMap::default(),
ModeReplyHandler::new(ids::generic_pus::PUS_MODE.id()), ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
mode_router, mode_router,
reply_receiver, reply_receiver,
); );
@@ -249,9 +228,36 @@ pub fn create_mode_service(
} }
} }
pub struct ModeServiceWrapper { pub fn create_mode_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
mode_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
) -> ModeServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_MODE_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ModeRequestConverter::default(),
DefaultActiveRequestMap::default(),
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
mode_router,
reply_receiver,
);
ModeServiceWrapper {
service: mode_request_handler,
}
}
pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService< pub(crate) service: PusTargetedRequestService<
MpscTcReceiver, MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter, VerificationReporter,
ModeRequestConverter, ModeRequestConverter,
ModeReplyHandler, ModeReplyHandler,
@@ -262,24 +268,9 @@ pub struct ModeServiceWrapper {
>, >,
} }
impl ModeNode for ModeServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
fn id(&self) -> ComponentId { for ModeServiceWrapper<TmSender, TcInMemConverter>
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";
@@ -302,11 +293,8 @@ impl TargetedPusService for ModeServiceWrapper {
#[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,
@@ -329,12 +317,11 @@ 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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcReadMode as u8);
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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default()); let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
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)
@@ -346,41 +333,31 @@ 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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcSetMode as u8);
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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.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, CreatorConfig::default()); let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
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!( assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new( let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceMode as u8);
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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default()); let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
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)
@@ -392,14 +369,12 @@ 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, u14::ZERO, 0); let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(MessageTypeId::new( let sec_header =
200, PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceModeRecursive as u8);
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.as_u32().to_be_bytes()); app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, CreatorConfig::default()); let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
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)
@@ -415,7 +390,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_u32), mode_reply); GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), 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::{PusSchedulerAlloc, TcInfo}; use satrs::pus::scheduler::{PusScheduler, 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, EcssTcCacher, MpscTcReceiver, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
PartialPusHandlingError, PusServiceHelper, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
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::ids::sched::PUS_SCHED; use satrs_example::config::components::PUS_SCHED_SERVICE;
use super::{DirectPusService, HandlingStatus}; use super::{DirectPusService, HandlingStatus};
pub trait TcReleaseProvider { pub trait TcReleaser {
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 TcReleaseProvider for PacketSenderWithSharedPool { impl TcReleaser for PacketSenderWithSharedPool {
fn release( fn release(
&mut self, &mut self,
sender_id: ComponentId, sender_id: ComponentId,
@@ -48,7 +48,7 @@ impl TcReleaseProvider for PacketSenderWithSharedPool {
} }
} }
impl TcReleaseProvider for mpsc::SyncSender<PacketAsVec> { impl TcReleaser for mpsc::Sender<PacketAsVec> {
fn release( fn release(
&mut self, &mut self,
sender_id: ComponentId, sender_id: ComponentId,
@@ -65,35 +65,23 @@ impl TcReleaseProvider for mpsc::SyncSender<PacketAsVec> {
} }
} }
#[allow(dead_code)] pub struct SchedulingServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
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,
TmTcSender, TmSender,
EcssTcCacher, TcInMemConverter,
VerificationReporter, VerificationReporter,
PusSchedulerAlloc, PusScheduler,
>, >,
pub sched_tc_pool: StaticMemoryPool, pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096], pub releaser_buf: [u8; 4096],
pub tc_releaser: TcReleaser, pub tc_releaser: Box<dyn TcReleaser + Send>,
} }
impl DirectPusService for SchedulingServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
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";
@@ -146,7 +134,9 @@ impl DirectPusService for SchedulingServiceWrapper {
} }
} }
impl SchedulingServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
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 {
@@ -172,22 +162,21 @@ impl SchedulingServiceWrapper {
} }
} }
pub fn create_scheduler_service( pub fn create_scheduler_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tc_in_mem_converter: EcssTcCacher, tc_releaser: PacketSenderWithSharedPool,
tc_releaser: TcReleaser,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>, pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool, sched_tc_pool: StaticMemoryPool,
) -> SchedulingServiceWrapper { ) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let scheduler = PusSchedulerAlloc::new_with_current_init_time(Duration::from_secs(5)) let scheduler = PusScheduler::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.id(), PUS_SCHED_SERVICE.id(),
pus_sched_rx, pus_sched_rx,
tm_sender, tm_sender,
create_verification_reporter(PUS_SCHED.id(), PUS_SCHED.apid), create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
tc_in_mem_converter, EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
), ),
scheduler, scheduler,
); );
@@ -195,6 +184,34 @@ pub fn create_scheduler_service(
pus_11_handler, pus_11_handler,
sched_tc_pool, sched_tc_pool,
releaser_buf: [0; 4096], releaser_buf: [0; 4096],
tc_releaser, tc_releaser: Box::new(tc_releaser),
}
}
pub fn create_scheduler_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
tc_source_sender: mpsc::Sender<PacketAsVec>,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> SchedulingServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
//let sched_srv_receiver =
//MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx);
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
.expect("Creating PUS Scheduler failed");
let pus_11_handler = PusSchedServiceHandler::new(
PusServiceHelper::new(
PUS_SCHED_SERVICE.id(),
pus_sched_rx,
tm_funnel_tx,
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
EcssTcInVecConverter::default(),
),
scheduler,
);
SchedulingServiceWrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
tc_releaser: Box::new(tc_source_sender),
} }
} }
@@ -1,6 +1,9 @@
use crate::pus::mode::ModeServiceWrapper; use crate::pus::mode::ModeServiceWrapper;
use derive_new::new; use derive_new::new;
use satrs::spacepackets::time::{cds, TimeWriter}; use satrs::{
pus::{EcssTcInMemConverter, EcssTmSender},
spacepackets::time::{cds, TimeWriter},
};
use super::{ use super::{
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper, action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
@@ -8,17 +11,21 @@ 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 { pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub test_srv: TestCustomServiceWrapper, test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
pub hk_srv_wrapper: HkServiceWrapper, hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
pub event_srv: EventServiceWrapper, event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
pub action_srv_wrapper: ActionServiceWrapper, action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
pub schedule_srv: SchedulingServiceWrapper, schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
pub mode_srv: ModeServiceWrapper, mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
} }
impl PusStack { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
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,34 +1,35 @@
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_legacy::{EventMessage, EventMessageU32}; use satrs::event_man::{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::{
CacheAndReadRawEcssTc, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcCacher, DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter,
MpscTcReceiver, PusServiceHelper, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, 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( pub fn create_test_service_static(
tm_sender: TmTcSender, tm_sender: PacketSenderWithSharedPool,
tc_in_mem_converter: EcssTcCacher, tc_pool: SharedStaticMemoryPool,
event_sender: mpsc::SyncSender<EventMessageU32>, event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>, pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> TestCustomServiceWrapper { ) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new( let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
PUS_TEST.id(), PUS_TEST_SERVICE.id(),
pus_test_rx, pus_test_rx,
tm_sender, tm_sender,
create_verification_reporter(PUS_TEST.id(), PUS_TEST.apid), create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
tc_in_mem_converter, EcssTcInSharedStoreConverter::new(tc_pool, 2048),
)); ));
TestCustomServiceWrapper { TestCustomServiceWrapper {
handler: pus17_handler, handler: pus17_handler,
@@ -36,13 +37,34 @@ pub fn create_test_service(
} }
} }
pub struct TestCustomServiceWrapper { pub fn create_test_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
PUS_TEST_SERVICE.id(),
pus_test_rx,
tm_funnel_tx,
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
EcssTcInVecConverter::default(),
));
TestCustomServiceWrapper {
handler: pus17_handler,
event_tx: event_sender,
}
}
pub struct TestCustomServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
{
pub handler: pub handler:
PusService17TestHandler<MpscTcReceiver, TmTcSender, EcssTcCacher, VerificationReporter>, PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub event_tx: mpsc::SyncSender<EventMessageU32>, pub event_tx: mpsc::SyncSender<EventMessageU32>,
} }
impl DirectPusService for TestCustomServiceWrapper { impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
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";
@@ -86,7 +108,7 @@ impl DirectPusService for TestCustomServiceWrapper {
); );
} }
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
@@ -96,7 +118,7 @@ impl DirectPusService for TestCustomServiceWrapper {
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.id(), TEST_EVENT.into())) .send(EventMessage::new(PUS_TEST_SERVICE.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(),
@@ -122,7 +144,7 @@ impl DirectPusService for TestCustomServiceWrapper {
} }
} }
} else { } else {
let fail_data = [tc.message_subtype_id()]; let fail_data = [tc.subservice()];
self.handler self.handler
.service_helper .service_helper
.verif_reporter() .verif_reporter()
@@ -8,15 +8,15 @@ use satrs::mode::ModeRequest;
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken, FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{ActiveRequest, EcssTmSender, GenericRoutingError, PusRequestRouter}; use satrs::pus::{ActiveRequestProvider, 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;
/*
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum CompositeRequest { pub enum CompositeRequest {
@@ -26,7 +26,6 @@ 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: pub composite_router_map:
@@ -37,7 +36,7 @@ pub struct GenericRequestRouter {
impl Default for GenericRequestRouter { impl Default for GenericRequestRouter {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: ids::generic_pus::PUS_ROUTING.raw(), id: PUS_ROUTING_SERVICE.raw(),
composite_router_map: Default::default(), composite_router_map: Default::default(),
mode_router_map: Default::default(), mode_router_map: Default::default(),
} }
@@ -46,7 +45,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 ActiveRequest, active_request: &impl ActiveRequestProvider,
tc: &PusTcReader, tc: &PusTcReader,
error: GenericRoutingError, error: GenericRoutingError,
tm_sender: &(impl EcssTmSender + ?Sized), tm_sender: &(impl EcssTmSender + ?Sized),
@@ -55,7 +54,7 @@ impl GenericRequestRouter {
) { ) {
warn!( warn!(
"Routing request for service {} failed: {error:?}", "Routing request for service {} failed: {error:?}",
tc.service_type_id() tc.service()
); );
let accepted_token: VerificationToken<TcStateAccepted> = active_request let accepted_token: VerificationToken<TcStateAccepted> = active_request
.token() .token()
@@ -66,8 +65,7 @@ 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; core::mem::size_of::<ComponentId>()] = let mut fail_data: [u8; 8] = [0; 8];
[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(
@@ -153,4 +151,3 @@ impl PusRequestRouter<ModeRequest> for GenericRequestRouter {
Err(GenericRoutingError::UnknownTargetId(target_id)) Err(GenericRoutingError::UnknownTargetId(target_id))
} }
} }
*/
-1
View File
@@ -1,3 +1,2 @@
pub mod sender;
pub mod tc_source; pub mod tc_source;
pub mod tm_sink; pub mod tm_sink;
-55
View File
@@ -1,55 +0,0 @@
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
use satrs::{
ComponentId,
queue::GenericSendError,
tmtc::{PacketAsVec, PacketHandler},
};
#[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 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(())
}
}
+76 -66
View File
@@ -1,88 +1,98 @@
use models::{ComponentId, TcHeader, ccsds::CcsdsTcPacketOwned};
use satrs::{ use satrs::{
pool::PoolProvider,
pus::HandlingStatus, pus::HandlingStatus,
spacepackets::{CcsdsPacketReader, ChecksumType}, tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
tmtc::PacketAsVec,
};
use std::{
collections::HashMap,
sync::mpsc::{self, TryRecvError},
}; };
use std::sync::mpsc::{self, TryRecvError};
pub type CcsdsDistributor = HashMap<ComponentId, std::sync::mpsc::SyncSender<CcsdsTcPacketOwned>>; use satrs::pus::MpscTmAsVecSender;
// TC source components where the heap is the backing memory of the received telecommands. use crate::pus::PusTcDistributor;
pub struct TcSourceTask {
pub tc_receiver: mpsc::Receiver<PacketAsVec>, // TC source components where static pools are the backing memory of the received telecommands.
ccsds_distributor: CcsdsDistributor, pub struct TcSourceTaskStatic {
shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>,
tc_buf: [u8; 4096],
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
} }
impl TcSourceTask { impl TcSourceTaskStatic {
pub fn new( pub fn new(
tc_receiver: mpsc::Receiver<PacketAsVec>, shared_tc_pool: SharedPacketPool,
ccsds_distributor: CcsdsDistributor, tc_receiver: mpsc::Receiver<PacketInPool>,
pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
) -> Self { ) -> Self {
Self { Self {
shared_tc_pool,
tc_receiver, tc_receiver,
ccsds_distributor, tc_buf: [0; 4096],
pus_distributor: pus_receiver,
} }
} }
pub fn add_target(
&mut self,
target_id: ComponentId,
sender: mpsc::SyncSender<CcsdsTcPacketOwned>,
) {
self.ccsds_distributor.insert(target_id, sender);
}
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {
loop { self.poll_tc();
if self.poll_tc() == HandlingStatus::Empty {
break;
}
}
} }
pub fn poll_tc(&mut self) -> HandlingStatus { pub fn poll_tc(&mut self) -> HandlingStatus {
// Right now, we only expect ECSS PUS packets.
// If packets like CFDP are expected, we might have to check the APID first.
match self.tc_receiver.try_recv() { match self.tc_receiver.try_recv() {
Ok(packet) => { Ok(packet_in_pool) => {
log::debug!("received raw packet: {:?}", packet); let pool = self
let ccsds_tc_reader_result = .shared_tc_pool
CcsdsPacketReader::new(&packet.packet, Some(ChecksumType::WithCrc16)); .0
if ccsds_tc_reader_result.is_err() { .read()
log::warn!( .expect("locking tc pool failed");
"received invalid CCSDS TC packet: {:?}", pool.read(&packet_in_pool.store_addr, &mut self.tc_buf)
ccsds_tc_reader_result.err() .expect("reading pool failed");
); drop(pool);
// TODO: Send a dedicated TM packet. self.pus_distributor
return HandlingStatus::HandledOne; .handle_tc_packet_in_store(packet_in_pool, &self.tc_buf)
} .ok();
let ccsds_tc_reader = ccsds_tc_reader_result.unwrap(); HandlingStatus::HandledOne
let tc_header_result = }
postcard::take_from_bytes::<TcHeader>(ccsds_tc_reader.user_data()); Err(e) => match e {
if tc_header_result.is_err() { TryRecvError::Empty => HandlingStatus::Empty,
log::warn!( TryRecvError::Disconnected => {
"received CCSDS TC packet with invalid TC header: {:?}", log::warn!("tmtc thread: sender disconnected");
tc_header_result.err() HandlingStatus::Empty
); }
// TODO: Send a dedicated TM packet. },
return HandlingStatus::HandledOne; }
} }
let (tc_header, payload) = tc_header_result.unwrap(); }
if let Some(sender) = self.ccsds_distributor.get(&tc_header.target_id) {
log::debug!("sending TC packet to target ID: {:?}", tc_header.target_id); // TC source components where the heap is the backing memory of the received telecommands.
sender pub struct TcSourceTaskDynamic {
.send(CcsdsTcPacketOwned { pub tc_receiver: mpsc::Receiver<PacketAsVec>,
sp_header: *ccsds_tc_reader.sp_header(), pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
tc_header, }
payload: payload.to_vec(),
}) impl TcSourceTaskDynamic {
.ok(); pub fn new(
} else { tc_receiver: mpsc::Receiver<PacketAsVec>,
log::warn!("no TC handler for target ID {:?}", tc_header.target_id); pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
// TODO: Send a dedicated TM packet. ) -> Self {
} Self {
tc_receiver,
pus_distributor: pus_receiver,
}
}
pub fn periodic_operation(&mut self) {
self.poll_tc();
}
pub fn poll_tc(&mut self) -> HandlingStatus {
// Right now, we only expect ECSS PUS packets.
// If packets like CFDP are expected, we might have to check the APID first.
match self.tc_receiver.try_recv() {
Ok(packet_as_vec) => {
self.pus_distributor
.handle_tc_packet_vec(packet_as_vec)
.ok();
HandlingStatus::HandledOne HandlingStatus::HandledOne
} }
Err(e) => match e { Err(e) => match e {
+127 -24
View File
@@ -3,53 +3,156 @@ use std::{
sync::mpsc::{self}, sync::mpsc::{self},
}; };
use arbitrary_int::{u11, u14}; use log::info;
use models::ccsds::CcsdsTmPacketOwned; use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
use satrs::spacepackets::seq_count::{SequenceCounter, SequenceCounterCcsdsSimple}; use satrs::{
pool::PoolProvider,
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
spacepackets::{
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
time::cds::MIN_CDS_FIELD_LEN,
CcsdsPacket,
},
};
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<u11, SequenceCounterCcsdsSimple>, apid_seq_counter_map: HashMap<u16, CcsdsSimpleSeqCountProvider>,
} }
impl CcsdsSeqCounterMap { impl CcsdsSeqCounterMap {
pub fn get_and_increment(&mut self, apid: u11) -> u14 { pub fn get_and_increment(&mut self, apid: u16) -> u16 {
u14::new( self.apid_seq_counter_map
self.apid_seq_counter_map .entry(apid)
.entry(apid) .or_default()
.or_default() .get_and_increment()
.get_and_increment(),
)
} }
} }
pub struct TmSink { pub struct TmFunnelCommon {
seq_counter_map: CcsdsSeqCounterMap, seq_counter_map: CcsdsSeqCounterMap,
msg_counter_map: HashMap<u8, u16>,
sync_tm_tcp_source: SyncTcpTmSource, sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
} }
impl TmSink { impl TmFunnelCommon {
pub fn new( pub fn new(sync_tm_tcp_source: SyncTcpTmSource) -> Self {
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
tm_server_tx: mpsc::SyncSender<CcsdsTmPacketOwned>,
) -> Self {
Self { Self {
seq_counter_map: Default::default(), seq_counter_map: Default::default(),
msg_counter_map: Default::default(),
sync_tm_tcp_source, sync_tm_tcp_source,
}
}
// Applies common packet processing operations for PUS TM packets. This includes setting
// a sequence counter
fn apply_packet_processing(&mut self, mut zero_copy_writer: PusTmZeroCopyWriter) {
// zero_copy_writer.set_apid(PUS_APID);
zero_copy_writer.set_seq_count(
self.seq_counter_map
.get_and_increment(zero_copy_writer.apid()),
);
let entry = self
.msg_counter_map
.entry(zero_copy_writer.service())
.or_insert(0);
zero_copy_writer.set_msg_count(*entry);
if *entry == u16::MAX {
*entry = 0;
} else {
*entry += 1;
}
Self::packet_printout(&zero_copy_writer);
// This operation has to come last!
zero_copy_writer.finish();
}
fn packet_printout(tm: &PusTmZeroCopyWriter) {
info!(
"Sending PUS TM[{},{}] with APID {}",
tm.service(),
tm.subservice(),
tm.apid()
);
}
}
pub struct TmSinkStatic {
common: TmFunnelCommon,
shared_tm_store: SharedPacketPool,
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
tm_server_tx: mpsc::SyncSender<PacketInPool>,
}
impl TmSinkStatic {
pub fn new(
shared_tm_store: SharedPacketPool,
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
tm_server_tx: mpsc::SyncSender<PacketInPool>,
) -> Self {
Self {
common: TmFunnelCommon::new(sync_tm_tcp_source),
shared_tm_store,
tm_funnel_rx, tm_funnel_rx,
tm_server_tx, tm_server_tx,
} }
} }
pub fn operation(&mut self) { pub fn operation(&mut self) {
if let Ok(mut tm) = self.tm_funnel_rx.try_recv() { if let Ok(pus_tm_in_pool) = self.tm_funnel_rx.recv() {
tm.sp_header // Read the TM, set sequence counter and message counter, and finally update
.set_seq_count(self.seq_counter_map.get_and_increment(tm.sp_header.apid())); // the CRC.
self.sync_tm_tcp_source.add_tm(&tm.to_vec()); let shared_pool = self.shared_tm_store.0.clone();
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
let mut tm_copy = Vec::new();
pool_guard
.modify(&pus_tm_in_pool.store_addr, |buf| {
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
tm_copy = buf.to_vec()
})
.expect("Reading TM from pool failed");
self.tm_server_tx
.send(pus_tm_in_pool)
.expect("Sending TM to server failed");
// We could also do this step in the update closure, but I'd rather avoid this, could
// lead to nested locking.
self.common.sync_tm_tcp_source.add_tm(&tm_copy);
}
}
}
pub struct TmSinkDynamic {
common: TmFunnelCommon,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
}
impl TmSinkDynamic {
pub fn new(
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
) -> Self {
Self {
common: TmFunnelCommon::new(sync_tm_tcp_source),
tm_funnel_rx,
tm_server_tx,
}
}
pub fn operation(&mut self) {
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
// Read the TM, set sequence counter and message counter, and finally update
// the CRC.
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
self.tm_server_tx self.tm_server_tx
.send(tm) .send(tm)
.expect("Sending TM to server failed"); .expect("Sending TM to server failed");
-7
View File
@@ -8,10 +8,6 @@ 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`.
@@ -23,6 +19,3 @@ 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
+2 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "satrs-mib" name = "satrs-mib"
version = "0.1.3" version = "0.1.2"
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,8 +23,7 @@ version = "1"
optional = true optional = true
[dependencies.satrs-shared] [dependencies.satrs-shared]
version = "0.2" version = ">=0.1.3, <0.2"
path = "../satrs-shared"
features = ["serde"] features = ["serde"]
[dependencies.satrs-mib-codegen] [dependencies.satrs-mib-codegen]
+1 -2
View File
@@ -28,8 +28,7 @@ features = ["full"]
trybuild = { version = "1", features = ["diff"] } trybuild = { version = "1", features = ["diff"] }
[dev-dependencies.satrs-shared] [dev-dependencies.satrs-shared]
version = "0.2" version = ">=0.1.3, <0.2"
path = "../../satrs-shared"
[dev-dependencies.satrs-mib] [dev-dependencies.satrs-mib]
path = ".." path = ".."
+2
View File
@@ -1,4 +1,6 @@
#![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,16 +9,20 @@ 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 = "2" thiserror = "1"
fern = "0.7" fern = "0.5"
strum = { version = "0.28", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
num_enum = "0.7" num_enum = "0.7"
humantime = "2" humantime = "2"
tai-time = { version = "0.3", features = ["serde"] }
nexosim = { version = "0.3.1" }
satrs = { path = "../../satrs" } [dependencies.asynchronix]
models = { path = "../models" } version = "0.2.1"
git = "https://github.com/asynchronics/asynchronix.git"
branch = "main"
features = ["serde"]
[dependencies.satrs]
path = "../satrs"
[dev-dependencies] [dev-dependencies]
delegate = "0.13" delegate = "0.12"
@@ -1,10 +1,10 @@
use std::{f32::consts::PI, sync::mpsc, time::Duration}; use std::{f32::consts::PI, sync::mpsc, time::Duration};
use models::pcdu::SwitchStateBinary; use asynchronix::{
use nexosim::{ model::{Model, Output},
model::{Context, Model}, time::Scheduler,
ports::Output,
}; };
use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
acs::{ acs::{
lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla, lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla,
@@ -31,7 +31,6 @@ const PHASE_Z: f32 = 0.2;
/// might still be possible and is probably sufficient for many OBSW needs. /// might still be possible and is probably sufficient for many OBSW needs.
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> { pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
pub switch_state: SwitchStateBinary, pub switch_state: SwitchStateBinary,
#[allow(dead_code)]
pub periodicity: Duration, pub periodicity: Duration,
pub external_mag_field: Option<MgmSensorValuesMicroTesla>, pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
pub reply_sender: mpsc::Sender<SimReply>, pub reply_sender: mpsc::Sender<SimReply>,
@@ -55,7 +54,7 @@ impl<ReplyProvider: MgmReplyProvider> MagnetometerModel<ReplyProvider> {
self.switch_state = switch_state; self.switch_state = switch_state;
} }
pub async fn send_sensor_values(&mut self, _: (), scheduler: &mut Context<Self>) { pub async fn send_sensor_values(&mut self, _: (), scheduler: &Scheduler<Self>) {
self.reply_sender self.reply_sender
.send(ReplyProvider::create_mgm_reply(MgmReplyCommon { .send(ReplyProvider::create_mgm_reply(MgmReplyCommon {
switch_state: self.switch_state, switch_state: self.switch_state,
@@ -114,11 +113,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),
cx: &mut Context<Self>, scheduler: &Scheduler<Self>,
) { ) {
self.torque_dipole = duration_and_dipole.1; self.torque_dipole = duration_and_dipole.1;
self.torquing = true; self.torquing = true;
if cx if scheduler
.schedule_event(duration_and_dipole.0, Self::clear_torque, ()) .schedule_event(duration_and_dipole.0, Self::clear_torque, ())
.is_err() .is_err()
{ {
@@ -138,11 +137,12 @@ impl MagnetorquerModel {
self.generate_magnetic_field(()).await; self.generate_magnetic_field(()).await;
} }
pub async fn request_housekeeping_data(&mut self, _: (), cx: &mut Context<Self>) { pub async fn request_housekeeping_data(&mut self, _: (), scheduler: &Scheduler<Self>) {
if self.switch_state != SwitchStateBinary::On { if self.switch_state != SwitchStateBinary::On {
return; return;
} }
cx.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ()) scheduler
.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
.expect("requesting housekeeping data failed") .expect("requesting housekeeping data failed")
} }
@@ -179,12 +179,13 @@ impl Model for MagnetorquerModel {}
pub mod tests { pub mod tests {
use std::time::Duration; use std::time::Duration;
use models::pcdu::{SwitchId, SwitchStateBinary}; use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
acs::{ acs::{
lis3mdl::{self, MgmLis3MdlReply}, lis3mdl::{self, MgmLis3MdlReply},
MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest, MgmRequestLis3Mdl, MgtDipole, MgtHkSet, MgtReply, MgtRequest,
}, },
eps::PcduSwitch,
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
}; };
@@ -198,11 +199,11 @@ 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().unwrap(); sim_testbench.step();
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.component(), SimComponent::Mgm0Lis3Mdl); assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
let reply = MgmLis3MdlReply::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.common.switch_state, SwitchStateBinary::Off); assert_eq!(reply.common.switch_state, SwitchStateBinary::Off);
@@ -214,28 +215,28 @@ pub mod tests {
#[test] #[test]
fn test_basic_mgm_request_switched_on() { fn test_basic_mgm_request_switched_on() {
let mut sim_testbench = SimTestbench::new(); let mut sim_testbench = SimTestbench::new();
switch_device_on(&mut sim_testbench, SwitchId::Mgm0); switch_device_on(&mut sim_testbench, PcduSwitch::Mgm);
let mut request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::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().unwrap(); sim_testbench.step();
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.component(), SimComponent::Mgm0Lis3Mdl); assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
let first_reply = MgmLis3MdlReply::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_until(Duration::from_millis(50)).unwrap(); sim_testbench.step_by(Duration::from_millis(50));
request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::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().unwrap(); sim_testbench.step();
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();
@@ -270,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().unwrap(); sim_testbench.step();
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());
} }
@@ -278,14 +279,14 @@ pub mod tests {
#[test] #[test]
fn test_basic_mgt_request_is_on() { fn test_basic_mgt_request_is_on() {
let mut sim_testbench = SimTestbench::new(); let mut sim_testbench = SimTestbench::new();
switch_device_on(&mut sim_testbench, SwitchId::Mgt); switch_device_on(&mut sim_testbench, PcduSwitch::Mgt);
let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk); let request = SimRequest::new_with_epoch_time(MgtRequest::RequestHk);
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().unwrap(); sim_testbench.step();
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();
@@ -306,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().unwrap(); sim_testbench.step();
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();
@@ -323,7 +324,7 @@ pub mod tests {
#[test] #[test]
fn test_basic_mgt_request_is_on_and_torquing() { fn test_basic_mgt_request_is_on_and_torquing() {
let mut sim_testbench = SimTestbench::new(); let mut sim_testbench = SimTestbench::new();
switch_device_on(&mut sim_testbench, SwitchId::Mgt); switch_device_on(&mut sim_testbench, PcduSwitch::Mgt);
let commanded_dipole = MgtDipole { let commanded_dipole = MgtDipole {
x: -200, x: -200,
y: 200, y: 200,
@@ -337,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_until(Duration::from_millis(5)).unwrap(); sim_testbench.step_by(Duration::from_millis(5));
check_mgt_hk( check_mgt_hk(
&mut sim_testbench, &mut sim_testbench,
@@ -346,9 +347,7 @@ pub mod tests {
torquing: true, torquing: true,
}, },
); );
sim_testbench sim_testbench.step_by(Duration::from_millis(100));
.step_until(Duration::from_millis(100))
.unwrap();
check_mgt_hk( check_mgt_hk(
&mut sim_testbench, &mut sim_testbench,
MgtHkSet { MgtHkSet {
@@ -1,7 +1,7 @@
use std::{sync::mpsc, time::Duration}; use std::{sync::mpsc, time::Duration};
use nexosim::{ use asynchronix::{
simulation::{Address, Scheduler, Simulation}, simulation::{Address, Simulation},
time::{Clock, MonotonicTime, SystemClock}, time::{Clock, MonotonicTime, SystemClock},
}; };
use satrs_minisim::{ use satrs_minisim::{
@@ -16,62 +16,40 @@ use crate::{
eps::PcduModel, eps::PcduModel,
}; };
const WARNING_FOR_STALE_DATA: bool = false; const SIM_CTRL_REQ_WIRETAPPING: bool = true;
const MGM_REQ_WIRETAPPING: bool = true;
const SIM_CTRL_REQ_WIRETAPPING: bool = false; const PCDU_REQ_WIRETAPPING: bool = true;
const MGM_REQ_WIRETAPPING: bool = false; const MGT_REQ_WIRETAPPING: bool = true;
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 scheduler: Scheduler, pub mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pub addr_wrapper: ModelAddrWrapper, pub pcdu_addr: Address<PcduModel>,
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,
scheduler: Scheduler, mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
addr_wrapper: ModelAddrWrapper, pcdu_addr: Address<PcduModel>,
mgt_addr: Address<MagnetorquerModel>,
) -> Self { ) -> Self {
Self { Self {
sys_clock, sys_clock,
request_receiver, request_receiver,
reply_sender, reply_sender,
simulation, simulation,
scheduler, mgm_addr,
addr_wrapper, pcdu_addr,
mgt_addr,
} }
} }
@@ -82,7 +60,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);
let _synch_status = self.sys_clock.synchronize(t); 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)
@@ -94,13 +72,12 @@ 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 && WARNING_FOR_STALE_DATA { if request.timestamp < old_timestamp {
log::warn!("stale data with timestamp {:?} received", request.timestamp); log::warn!("stale data with timestamp {:?} received", request.timestamp);
} }
if let Err(e) = match request.component() { if let Err(e) = match request.component() {
SimComponent::SimCtrl => self.handle_ctrl_request(&request), SimComponent::SimCtrl => self.handle_ctrl_request(&request),
SimComponent::Mgm0Lis3Mdl => self.handle_mgm_request(0, &request), SimComponent::MgmLis3Mdl => self.handle_mgm_request(&request),
SimComponent::Mgm1Lis3Mdl => self.handle_mgm_request(1, &request),
SimComponent::Mgt => self.handle_mgt_request(&request), SimComponent::Mgt => self.handle_mgt_request(&request),
SimComponent::Pcdu => self.handle_pcdu_request(&request), SimComponent::Pcdu => self.handle_pcdu_request(&request),
} { } {
@@ -120,7 +97,7 @@ 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 { if SIM_CTRL_REQ_WIRETAPPING {
log::info!("received sim ctrl request: {sim_ctrl_request:?}"); log::info!("received sim ctrl request: {:?}", sim_ctrl_request);
} }
match sim_ctrl_request { match sim_ctrl_request {
SimCtrlRequest::Ping => { SimCtrlRequest::Ping => {
@@ -132,26 +109,18 @@ impl SimController {
Ok(()) Ok(())
} }
fn handle_mgm_request( fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
&mut self,
mgm_idx: usize,
request: &SimRequest,
) -> Result<(), SimRequestError> {
let mgm_request = MgmRequestLis3Mdl::from_sim_message(request)?; let mgm_request = MgmRequestLis3Mdl::from_sim_message(request)?;
if MGM_REQ_WIRETAPPING { if MGM_REQ_WIRETAPPING {
log::info!("received MGM request: {mgm_request:?}"); log::info!("received MGM request: {:?}", mgm_request);
} }
match mgm_request { match mgm_request {
MgmRequestLis3Mdl::RequestSensorData => { MgmRequestLis3Mdl::RequestSensorData => {
let addr = match mgm_idx { self.simulation.send_event(
0 => &self.addr_wrapper.mgm_0_addr, MagnetometerModel::send_sensor_values,
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(())
@@ -160,26 +129,19 @@ 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 { if PCDU_REQ_WIRETAPPING {
log::info!("received PCDU request: {pcdu_request:?}"); log::info!("received PCDU request: {:?}", pcdu_request);
} }
match pcdu_request { match pcdu_request {
PcduRequest::RequestSwitchInfo => { PcduRequest::RequestSwitchInfo => {
self.simulation self.simulation
.process_event( .send_event(PcduModel::request_switch_info, (), &self.pcdu_addr);
PcduModel::request_switch_info,
(),
&self.addr_wrapper.pcdu_addr,
)
.unwrap();
} }
PcduRequest::SwitchDevice { switch, state } => { PcduRequest::SwitchDevice { switch, state } => {
self.simulation self.simulation.send_event(
.process_event( PcduModel::switch_device,
PcduModel::switch_device, (switch, state),
(switch, state), &self.pcdu_addr,
&self.addr_wrapper.pcdu_addr, );
)
.unwrap();
} }
} }
Ok(()) Ok(())
@@ -188,26 +150,20 @@ 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)?;
if MGT_REQ_WIRETAPPING { if MGT_REQ_WIRETAPPING {
log::info!("received MGT request: {mgt_request:?}"); log::info!("received MGT request: {:?}", mgt_request);
} }
match mgt_request { match mgt_request {
MgtRequest::ApplyTorque { duration, dipole } => self MgtRequest::ApplyTorque { duration, dipole } => self.simulation.send_event(
.simulation MagnetorquerModel::apply_torque,
.process_event( (duration, dipole),
MagnetorquerModel::apply_torque, &self.mgt_addr,
(duration, dipole), ),
&self.addr_wrapper.mgt_addr, MgtRequest::RequestHk => self.simulation.send_event(
) MagnetorquerModel::request_housekeeping_data,
.unwrap(), (),
MgtRequest::RequestHk => self &self.mgt_addr,
.simulation ),
.process_event( }
MagnetorquerModel::request_housekeeping_data,
(),
&self.addr_wrapper.mgt_addr,
)
.unwrap(),
};
Ok(()) Ok(())
} }
@@ -241,7 +197,7 @@ 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().unwrap(); sim_testbench.step();
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();
@@ -1,18 +1,20 @@
use std::{sync::mpsc, time::Duration}; use std::{sync::mpsc, time::Duration};
use models::pcdu::{SwitchId, SwitchMapBinaryWrapper, SwitchStateBinary}; use asynchronix::{
use nexosim::{ model::{Model, Output},
model::{Context, Model}, time::Scheduler,
ports::Output, };
use satrs::power::SwitchStateBinary;
use satrs_minisim::{
eps::{PcduReply, PcduSwitch, SwitchMapBinaryWrapper},
SimReply,
}; };
use satrs_minisim::{eps::PcduReply, 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: SwitchMapBinaryWrapper, pub switcher_map: SwitchMapBinaryWrapper,
pub mgm_0_switch: Output<SwitchStateBinary>, pub mgm_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>,
} }
@@ -21,20 +23,20 @@ impl PcduModel {
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self { pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
Self { Self {
switcher_map: Default::default(), switcher_map: Default::default(),
mgm_0_switch: Output::new(), mgm_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, _: (), cx: &mut Context<Self>) { pub async fn request_switch_info(&mut self, _: (), scheduler: &Scheduler<Self>) {
cx.schedule_event( scheduler
Duration::from_millis(SWITCH_INFO_DELAY_MS), .schedule_event(
Self::send_switch_info, Duration::from_millis(SWITCH_INFO_DELAY_MS),
(), 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) {
@@ -42,12 +44,10 @@ impl PcduModel {
self.reply_sender.send(reply).unwrap(); self.reply_sender.send(reply).unwrap();
} }
pub async fn switch_device(&mut self, switch_and_target_state: (SwitchId, SwitchStateBinary)) { pub async fn switch_device(
log::info!( &mut self,
"switching {:?} to {:?}", switch_and_target_state: (PcduSwitch, SwitchStateBinary),
switch_and_target_state.0, ) {
switch_and_target_state.1
);
let val = self let val = self
.switcher_map .switcher_map
.0 .0
@@ -55,13 +55,12 @@ impl PcduModel {
.unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0)); .unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0));
*val = switch_and_target_state.1; *val = switch_and_target_state.1;
match switch_and_target_state.0 { match switch_and_target_state.0 {
SwitchId::Mgm0 => { PcduSwitch::Mgm => {
self.mgm_0_switch.send(switch_and_target_state.1).await; self.mgm_switch.send(switch_and_target_state.1).await;
} }
SwitchId::Mgt => { PcduSwitch::Mgt => {
self.mgt_switch.send(switch_and_target_state.1).await; self.mgt_switch.send(switch_and_target_state.1).await;
} }
SwitchId::Mgm1 => todo!(),
} }
} }
} }
@@ -73,16 +72,16 @@ pub(crate) mod tests {
use super::*; use super::*;
use std::time::Duration; use std::time::Duration;
use models::pcdu::SwitchMapBinary;
use satrs_minisim::{ use satrs_minisim::{
eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, eps::{PcduRequest, SwitchMapBinary},
SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
}; };
use crate::test_helpers::SimTestbench; use crate::test_helpers::SimTestbench;
fn switch_device( fn switch_device(
sim_testbench: &mut SimTestbench, sim_testbench: &mut SimTestbench,
switch: SwitchId, switch: PcduSwitch,
target: SwitchStateBinary, target: SwitchStateBinary,
) { ) {
let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice { let request = SimRequest::new_with_epoch_time(PcduRequest::SwitchDevice {
@@ -93,14 +92,14 @@ 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().unwrap(); sim_testbench.step();
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: SwitchId) { pub(crate) fn switch_device_off(sim_testbench: &mut SimTestbench, switch: PcduSwitch) {
switch_device(sim_testbench, switch, SwitchStateBinary::Off); switch_device(sim_testbench, switch, SwitchStateBinary::Off);
} }
pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: SwitchId) { pub(crate) fn switch_device_on(sim_testbench: &mut SimTestbench, switch: PcduSwitch) {
switch_device(sim_testbench, switch, SwitchStateBinary::On); switch_device(sim_testbench, switch, SwitchStateBinary::On);
} }
@@ -114,7 +113,7 @@ 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().unwrap(); sim_testbench.step();
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();
@@ -128,7 +127,7 @@ pub(crate) mod tests {
} }
} }
fn test_pcdu_switching_single_switch(switch: SwitchId, target: SwitchStateBinary) { fn test_pcdu_switching_single_switch(switch: PcduSwitch, target: SwitchStateBinary) {
let mut sim_testbench = SimTestbench::new(); let mut sim_testbench = SimTestbench::new();
switch_device(&mut sim_testbench, switch, target); switch_device(&mut sim_testbench, switch, target);
let mut switcher_map = get_all_off_switch_map(); let mut switcher_map = get_all_off_switch_map();
@@ -144,12 +143,12 @@ 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_until(Duration::from_millis(1)).unwrap(); sim_testbench.step_by(Duration::from_millis(1));
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_until(Duration::from_millis(25)).unwrap(); sim_testbench.step_by(Duration::from_millis(25));
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();
@@ -165,17 +164,17 @@ pub(crate) mod tests {
#[test] #[test]
fn test_pcdu_switching_mgm_on() { fn test_pcdu_switching_mgm_on() {
test_pcdu_switching_single_switch(SwitchId::Mgm0, SwitchStateBinary::On); test_pcdu_switching_single_switch(PcduSwitch::Mgm, SwitchStateBinary::On);
} }
#[test] #[test]
fn test_pcdu_switching_mgt_on() { fn test_pcdu_switching_mgt_on() {
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On); test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On);
} }
#[test] #[test]
fn test_pcdu_switching_mgt_off() { fn test_pcdu_switching_mgt_off() {
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::On); test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::On);
test_pcdu_switching_single_switch(SwitchId::Mgt, SwitchStateBinary::Off); test_pcdu_switching_single_switch(PcduSwitch::Mgt, SwitchStateBinary::Off);
} }
} }
@@ -1,11 +1,11 @@
use nexosim::time::MonotonicTime; use asynchronix::time::MonotonicTime;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum SimComponent { pub enum SimComponent {
SimCtrl, SimCtrl,
Mgm0Lis3Mdl, MgmLis3Mdl,
Mgm1Lis3Mdl,
Mgt, Mgt,
Pcdu, Pcdu,
} }
@@ -161,7 +161,73 @@ impl From<SimRequestError> for SimCtrlReply {
pub mod eps { pub mod eps {
use super::*; use super::*;
use models::pcdu::{SwitchId, SwitchMapBinary, SwitchStateBinary}; use satrs::power::{SwitchState, SwitchStateBinary};
use std::collections::HashMap;
use strum::{EnumIter, IntoEnumIterator};
pub type SwitchMap = HashMap<PcduSwitch, SwitchState>;
pub type SwitchMapBinary = HashMap<PcduSwitch, SwitchStateBinary>;
pub struct SwitchMapWrapper(pub SwitchMap);
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
Hash,
EnumIter,
IntoPrimitive,
TryFromPrimitive,
)]
#[repr(u16)]
pub enum PcduSwitch {
Mgm = 0,
Mgt = 1,
}
impl Default for SwitchMapBinaryWrapper {
fn default() -> Self {
let mut switch_map = SwitchMapBinary::default();
for entry in PcduSwitch::iter() {
switch_map.insert(entry, SwitchStateBinary::Off);
}
Self(switch_map)
}
}
impl Default for SwitchMapWrapper {
fn default() -> Self {
let mut switch_map = SwitchMap::default();
for entry in PcduSwitch::iter() {
switch_map.insert(entry, SwitchState::Unknown);
}
Self(switch_map)
}
}
impl SwitchMapWrapper {
pub fn new_with_init_switches_off() -> Self {
let mut switch_map = SwitchMap::default();
for entry in PcduSwitch::iter() {
switch_map.insert(entry, SwitchState::Off);
}
Self(switch_map)
}
pub fn from_binary_switch_map_ref(switch_map: &SwitchMapBinary) -> Self {
Self(
switch_map
.iter()
.map(|(key, value)| (*key, SwitchState::from(*value)))
.collect(),
)
}
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[repr(u8)] #[repr(u8)]
@@ -170,10 +236,10 @@ pub mod eps {
RequestSwitchInfo = 1, RequestSwitchInfo = 1,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum PcduRequest { pub enum PcduRequest {
SwitchDevice { SwitchDevice {
switch: SwitchId, switch: PcduSwitch,
state: SwitchStateBinary, state: SwitchStateBinary,
}, },
RequestSwitchInfo, RequestSwitchInfo,
@@ -197,7 +263,7 @@ pub mod eps {
pub mod acs { pub mod acs {
use std::time::Duration; use std::time::Duration;
use models::pcdu::SwitchStateBinary; use satrs::power::SwitchStateBinary;
use super::*; use super::*;
@@ -211,7 +277,7 @@ pub mod acs {
} }
impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl { impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl {
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl; const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
} }
// 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
@@ -302,7 +368,7 @@ pub mod acs {
} }
impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply { impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply {
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl; const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
} }
impl MgmReplyProvider for MgmLis3MdlReply { impl MgmReplyProvider for MgmLis3MdlReply {
@@ -352,7 +418,7 @@ pub mod acs {
} }
impl SerializableSimMsgPayload<SimReply> for MgtReply { impl SerializableSimMsgPayload<SimReply> for MgtReply {
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl; const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
} }
} }

Some files were not shown because too many files have changed in this diff Show More