Compare commits

..

30 Commits

Author SHA1 Message Date
2eaa78dfbc this actually works!
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 13:46:14 +02:00
a6d9bee5df Merge branch 'sim-mgm-update' into serialization-prototyping
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 13:09:25 +02:00
a77bbfa953 Merge remote-tracking branch 'origin/main' into serialization-prototyping 2024-05-25 13:08:54 +02:00
4c67bcdde1 clean up serializatio ntest code 2024-05-25 13:08:32 +02:00
a710b30013 Merge remote-tracking branch 'origin/main' into serialization-prototyping
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-05-25 12:31:51 +02:00
29783b2b07 introduce new HK helper
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-25 12:29:44 +02:00
2a2a3a3eab PCDU switch set TM handling
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-22 18:48:46 +02:00
2507469e68 continue PCDU integration
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-22 18:34:37 +02:00
b4febefa33 introduce switch handling for MGM
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-22 16:48:51 +02:00
fe60cb9ccf continue integrating power subsystem
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-19 17:33:37 +02:00
27e88ed7f7 fix tests
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-18 18:45:42 +02:00
295fed9a72 continue PCDU handler
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 18:39:25 +02:00
8e89c8dd66 compiles again
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 17:58:54 +02:00
cb0a65c4d4 continue PCDU
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 14:08:42 +02:00
3db54da3df Merge remote-tracking branch 'origin/main' into sim-mgm-update
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-05-18 12:49:20 +02:00
15fcb17363 continue PCDU handler
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-16 16:28:22 +02:00
8728c7ebea continued sample PCDU handler
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-12 14:23:42 +02:00
7606767f63 the PCDU handler is already required
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-11 19:11:41 +02:00
37b32a9008 try to make MGM set HK data work
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-10 17:55:11 +02:00
9e096193dd clean up python commander a bit 2024-05-10 17:21:59 +02:00
43bd77eef0 check that MGM data conversion works
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-10 15:33:43 +02:00
a4888bce01 add first MGM device unittests 2024-05-09 21:38:56 +02:00
6e5b70af34 basic tests for SIM client
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 13:23:40 +02:00
d1476eb770 added basic tests for pytmtc app
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 11:41:11 +02:00
783388aa6f pytmtc as regular package now
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-09 11:07:08 +02:00
4a8db6b26a fix tests
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-08 21:08:41 +02:00
b86c2eb1d1 added some test stubs
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-05-08 21:02:16 +02:00
fe4126f7e2 first connection success
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-05-08 20:55:56 +02:00
c20163b10a start integrating sim in example
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-05-08 20:38:45 +02:00
b970154488 add serialization prototyping
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-26 10:01:29 +02:00
161 changed files with 49875 additions and 13851 deletions

View File

@@ -11,9 +11,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- run: cargo check - run: cargo check --release
# Check example with static pool configuration
- run: cargo check -p satrs-example --no-default-features
test: test:
name: Run Tests name: Run Tests
@@ -39,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
@@ -47,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:
@@ -57,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 - run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
clippy: clippy:
name: Clippy name: Clippy
@@ -65,6 +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: cargo clippy -- -D warnings - run: cargo clippy -- -D warnings

View File

@@ -6,10 +6,11 @@ members = [
"satrs-example", "satrs-example",
"satrs-minisim", "satrs-minisim",
"satrs-shared", "satrs-shared",
"embedded-examples/embedded-client",
] ]
exclude = [ exclude = [
"embedded-examples/stm32f3-disco-rtic", "embedded-examples/stm32f3-disco-rtic",
"embedded-examples/stm32h7-nucleo-rtic", "embedded-examples/stm32h7-rtic",
"serialization-prototyping",
] ]

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

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

View File

@@ -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"

View File

@@ -1,2 +0,0 @@
[interface]
serial_port = "/dev/ttyUSB0"

View File

@@ -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()
}

View File

@@ -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

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.4", features = ["defmt", "stm32f303vc", "unstable-pac"] }
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"
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] } 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.4" defmt-test = "0.3"
# cargo test # cargo test
[profile.test] [profile.test]

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -0,0 +1,8 @@
/venv
/.tmtc-history.txt
/log
/.idea/*
!/.idea/runConfigurations
/seqcnt.txt
/tmtc_conf.json

View File

@@ -0,0 +1,4 @@
{
"com_if": "serial_cobs",
"serial_baudrate": 115200
}

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()

View File

@@ -0,0 +1,2 @@
tmtccmd == 8.0.1
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd

View File

@@ -1,61 +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 rtic_monotonics::fugit::ExtU32; defmt::println!("STM32F3 Discovery Blinky");
use rtic_monotonics::Monotonic as _; let dp = pac::Peripherals::take().unwrap();
use satrs_stm32f3_disco_rtic::{Direction, LedPinSet, Leds}; 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);
rtic_monotonics::systick_monotonic!(Mono, 1000); let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
let mut leds = Leds::new(
#[shared] gpioe.pe8,
struct Shared {} gpioe.pe9,
gpioe.pe10,
#[local] gpioe.pe11,
struct Local { gpioe.pe12,
leds: Leds, gpioe.pe13,
current_dir: Direction, gpioe.pe14,
} gpioe.pe15,
&mut gpioe.moder,
#[init] &mut gpioe.otyper,
fn init(cx: init::Context) -> (Shared, Local) { );
let p = embassy_stm32::init(Default::default()); let delay_ms = 200u16;
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery using RTICv2");
let led_pin_set = LedPinSet {
pin_n: p.PE8,
pin_ne: p.PE9,
pin_e: p.PE10,
pin_se: p.PE11,
pin_s: p.PE12,
pin_sw: p.PE13,
pin_w: p.PE14,
pin_nw: p.PE15,
};
let leds = Leds::new(led_pin_set);
// Initialize the systick interrupt & obtain the token to prove that we did
Mono::start(cx.core.SYST, 8_000_000);
blinky::spawn().expect("failed to spawn blinky task");
(
Shared {},
Local {
leds,
current_dir: Direction::North,
},
)
}
#[task(local = [leds, current_dir])]
async fn blinky(cx: blinky::Context) {
loop { loop {
cx.local.leds.blink_next(cx.local.current_dir); leds.ld3_n.toggle().ok();
Mono::delay(200.millis()).await; delay.delay_ms(delay_ms);
} leds.ld3_n.toggle().ok();
delay.delay_ms(delay_ms);
//explicit on/off
leds.ld4_nw.on().ok();
delay.delay_ms(delay_ms);
leds.ld4_nw.off().ok();
delay.delay_ms(delay_ms);
leds.ld5_ne.on().ok();
delay.delay_ms(delay_ms);
leds.ld5_ne.off().ok();
delay.delay_ms(delay_ms);
leds.ld6_w.on().ok();
delay.delay_ms(delay_ms);
leds.ld6_w.off().ok();
delay.delay_ms(delay_ms);
leds.ld7_e.on().ok();
delay.delay_ms(delay_ms);
leds.ld7_e.off().ok();
delay.delay_ms(delay_ms);
leds.ld8_sw.on().ok();
delay.delay_ms(delay_ms);
leds.ld8_sw.off().ok();
delay.delay_ms(delay_ms);
leds.ld9_se.on().ok();
delay.delay_ms(delay_ms);
leds.ld9_se.off().ok();
delay.delay_ms(delay_ms);
leds.ld10_s.on().ok();
delay.delay_ms(delay_ms);
leds.ld10_s.off().ok();
delay.delay_ms(delay_ms);
} }
} }

View File

@@ -1,190 +1,51 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use arbitrary_int::u11; use cortex_m_semihosting::debug;
use core::time::Duration;
use embassy_stm32::gpio::Output;
use spacepackets::{
ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError,
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
};
pub const APID: u11 = u11::new(0x02); use defmt_brtt as _; // global logger
#[derive(defmt::Format, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Copy)] use stm32f3xx_hal as _; // memory layout
pub enum Direction {
North, use panic_probe as _;
NorthEast,
East, // same panicking *behavior* as `panic-probe` but doesn't print a panic message
SouthEast, // this prevents the panic message being printed *twice* when `defmt::panic` is invoked
South, #[defmt::panic_handler]
SouthWest, fn panic() -> ! {
West, cortex_m::asm::udf()
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,
}
} }
} }

View File

@@ -1,349 +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, CcsdsPacketId, Request, Response}; };
use spacepackets::{CcsdsPacketCreationError, SpHeader}; use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use defmt_rtt as _; // global logger use satrs::spacepackets::ecss::EcssEnumU16;
use satrs::spacepackets::CcsdsPacket;
use panic_probe as _; 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)] #[allow(unused_imports)]
use rtic_monotonics::fugit::{MillisDurationU32, TimerInstantU32}; use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::prelude::*; use rtic_monotonics::systick::ExtU32;
use satrs::seq_count::SequenceCountProviderCore;
use crate::app::Mono; 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: CcsdsPacketId, Queue,
} }
#[app(device = embassy_stm32)] impl From<ByteConversionError> for TmSendError {
mod app { fn from(value: ByteConversionError) -> Self {
use core::time::Duration; Self::ByteConversion(value)
}
}
use super::*; fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
use arbitrary_int::u14; if tm_creator.len_written() > MAX_TM_LEN {
use rtic::Mutex; return Err(ByteConversionError::ToSliceTooSmall {
use rtic_sync::{ expected: tm_creator.len_written(),
channel::{Receiver, Sender}, found: MAX_TM_LEN,
make_channel, }
}; .into());
use satrs_stm32f3_disco_rtic::{CcsdsPacketId, LedPinSet, Request, Response}; }
use spacepackets::CcsdsPacketReader; 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(())
}
systick_monotonic!(Mono, 1000); fn handle_tm_send_error(error: TmSendError) {
defmt::warn!("sending tm failed with error {}", error);
}
embassy_stm32::bind_interrupts!(struct Irqs { pub enum UartTxState {
USART2 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART2>; // 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 {
use super::*;
use core::slice::Iter;
use rtic_monotonics::systick::Systick;
use rtic_monotonics::Monotonic;
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
#[allow(unused_imports)]
use stm32f3_discovery::leds::Direction;
use stm32f3_discovery::leds::Leds;
use stm32f3xx_hal::prelude::*;
use stm32f3_discovery::switch_hal::OutputSwitch;
use stm32f3xx_hal::Switch;
#[allow(dead_code)]
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());
let (req_sender, req_receiver) = make_channel!(RequestWithTcId, 16);
// Initialize the systick interrupt & obtain the token to prove that we did // Initialize the systick interrupt & obtain the token to prove that we did
Mono::start(cx.core.SYST, 8_000_000); let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
defmt::info!("sat-rs demo application for the STM32F3-Discovery with RTICv2"); let mut flash = cx.device.FLASH.constrain();
let led_pin_set = LedPinSet { let clocks = rcc
pin_n: p.PE8, .cfgr
pin_ne: p.PE9, .use_hse(8.MHz())
pin_e: p.PE10, .sysclk(8.MHz())
pin_se: p.PE11, .pclk1(8.MHz())
pin_s: p.PE12, .freeze(&mut flash.acr);
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(); // Set up monotonic timer.
config.baudrate = UART_BAUD; //let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
let uart = embassy_stm32::usart::Uart::new(
p.USART2, p.PA3, p.PA2, Irqs, p.DMA1_CH7, p.DMA1_CH6, config,
)
.unwrap();
let (tx, rx) = uart.split(); defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
let leds = Leds::new(
gpioe.pe8,
gpioe.pe9,
gpioe.pe10,
gpioe.pe11,
gpioe.pe12,
gpioe.pe13,
gpioe.pe14,
gpioe.pe15,
&mut gpioe.moder,
&mut gpioe.otyper,
);
let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
// 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);
Mono::delay(MillisDurationU32::from_ticks( Systick::delay(current_blink_freq).await;
current_blink_freq.as_millis() as u32, }
)) }
#[task(
shared = [tx_shared],
)]
async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
loop {
let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
if let UartTxState::Idle(_) = tx_shared.state {
return true;
}
false
});
if is_idle {
let last_completed = cx.shared.tx_shared.lock(|shared| shared.last_completed);
if let Some(last_completed) = last_completed {
let elapsed_ms = (Systick::now() - last_completed).to_millis();
if elapsed_ms < MIN_DELAY_BETWEEN_TX_PACKETS_MS {
Systick::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis())
.await; .await;
} }
} }
} else {
#[task( // Check for completion after 1 ms
local = [ Systick::delay(1.millis()).await;
tx,
encoded_buf: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN]
],
shared = [],
)]
async fn serial_tx_handler(cx: serial_tx_handler::Context) {
loop {
while let Some(vec) = TM_QUEUE.dequeue() {
let encoded_len =
cobs::encode_including_sentinels(&vec[0..vec.len()], cx.local.encoded_buf);
defmt::debug!("sending {} bytes over UART", encoded_len);
cx.local
.tx
.write(&cx.local.encoded_buf[0..encoded_len])
.await
.unwrap();
continue; continue;
} }
Mono::delay(TX_HANDLER_FREQ_MS.millis()).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() {
defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
return;
}
let start_idx = start_idx.unwrap();
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
Ok(len) => {
defmt::info!("Decoded packet length: {}", len);
let pus_tc = PusTcReader::new(decode_buf);
match pus_tc {
Ok((tc, _tc_len)) => {
match convert_pus_tc_to_request(
&tc,
cx.local.verif_reporter,
cx.local.src_data_buf,
cx.local.timestamp,
) { ) {
Ok(packet) => { Ok(request_with_token) => {
let packet_id = packet.packet_id(); let started_token = handle_start_verification(
let psc = packet.psc(); request_with_token.token,
let tc_packet_id = CcsdsPacketId { packet_id, psc }; cx.local.verif_reporter,
if let Ok(request) = cx.local.src_data_buf,
postcard::from_bytes::<Request>(packet.packet_data()) cx.local.timestamp,
{
sender
.send(RequestWithTcId {
request,
tc_id: tc_packet_id,
})
.await
.unwrap();
}
}
Err(e) => {
defmt::error!("error unpacking ccsds packet: {}", e);
}
}
}
Err(e) => {
defmt::error!("cobs decoding error: {}", e);
}
}
}
}
Err(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"),
}
}
}
fn handle_ping_request(
cx: &mut req_handler::Context,
tc_packet_id: CcsdsPacketId,
) -> Result<(), TmSendError> {
defmt::info!("Received PUS ping telecommand, sending ping reply");
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
Ok(())
}
fn handle_change_blink_frequency_request(
cx: &mut req_handler::Context,
tc_packet_id: CcsdsPacketId,
duration: Duration,
) -> Result<(), TmSendError> {
defmt::info!(
"Received ChangeBlinkFrequency request, new frequency: {} ms",
duration.as_millis()
); );
cx.shared
.blink_freq
.lock(|blink_freq| *blink_freq = duration);
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
Ok(())
}
}
fn send_tm( match request_with_token.request {
tc_packet_id: CcsdsPacketId, Request::Ping => {
response: Response, handle_ping_request(cx.local.timestamp);
current_seq_count: u14, }
) -> Result<(), TmSendError> { Request::ChangeBlinkFrequency(new_freq_ms) => {
let sp_header = SpHeader::new_for_unseg_tc(PUS_APID, current_seq_count, 0); defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
let tm_header = satrs_stm32f3_disco_rtic::TmHeader { cx.shared.blink_freq.lock(|blink_freq| {
tc_packet_id: Some(tc_packet_id), *blink_freq =
uptime_millis: Mono::now().duration_since_epoch().to_millis(), 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) => {
// TODO: Error handling: Send verification failure based on request error.
defmt::warn!("request error {}", e);
}
}
}
Err(e) => {
defmt::warn!("Error unpacking PUS TC: {}", e);
}
}
}
Err(_) => {
defmt::warn!("decoding error, can only process cobs encoded frames")
}
}
}
fn handle_ping_request(timestamp: &[u8]) {
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
let sp_header =
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
let mut tm_packet = TmPacket::new(); let mut tm_packet = TmPacket::new();
let tm_size = tm_size(&tm_header, &response); tm_packet
tm_packet.resize(tm_size, 0).expect("vec resize failed"); .resize(ping_reply.len_written(), 0)
create_tm_packet(&mut tm_packet, sp_header, tm_header, response)?; .expect("vec resize failed");
if TM_QUEUE.enqueue(tm_packet).is_err() { ping_reply.write_to_bytes(&mut tm_packet).unwrap();
if TM_REQUESTS.enqueue(tm_packet).is_err() {
defmt::warn!("TC queue full"); defmt::warn!("TC queue full");
return Err(TmSendError::Queue); return;
}
} }
Ok(())
}
// same panicking *behavior* as `panic-probe` but doesn't print a panic message fn handle_start_verification(
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked accepted_token: VerificationToken<TcStateAccepted>,
#[defmt::panic_handler] verif_reporter: &mut VerificationReportCreator,
fn panic() -> ! { src_data_buf: &mut [u8],
cortex_m::asm::udf() timestamp: &[u8],
} ) -> VerificationToken<TcStateStarted> {
let (tm_creator, started_token) = verif_reporter
.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
}
/// Terminates the application and makes a semihosting-capable debug tool exit fn handle_completion_verification(
/// with status code 0. started_token: VerificationToken<TcStateStarted>,
pub fn exit() -> ! { verif_reporter: &mut VerificationReportCreator,
loop { src_data_buf: &mut [u8],
debug::exit(EXIT_SUCCESS); timestamp: &[u8],
} ) {
} let result = send_tm(
verif_reporter
/// Hardfault handler. .completion_success(
/// src_data_buf,
/// Terminates the application and makes a semihosting-capable debug tool exit started_token,
/// with an error. This seems better than the default, which is to spin in a SEQ_COUNT_PROVIDER.get(),
/// loop. 0,
#[cortex_m_rt::exception] timestamp,
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! { )
loop { .unwrap(),
debug::exit(EXIT_FAILURE); );
} if let Err(e) = result {
} handle_tm_send_error(e);
}
// 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 #[task(binds = DMA1_CH6, shared = [rx_transfer])]
#[cfg(test)] fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
#[defmt_test::tests] let mut tc_packet = TcPacket::new();
mod unit_tests { cx.shared.rx_transfer.lock(|rx_transfer| {
use defmt::assert; let rx_ref = rx_transfer.as_ref().unwrap();
if rx_ref.is_complete() {
#[test] let uart_rx_owned = rx_transfer.take().unwrap();
fn it_works() { let (buf, c, rx) = uart_rx_owned.stop();
assert!(true) // 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
.tx_shared
.lock(|tx_shared| match &mut tx_shared.state {
UartTxState::Idle(_) => (),
UartTxState::Transmitting(transfer) => {
let transfer_ref = transfer.as_ref().unwrap();
if transfer_ref.is_complete() {
let transfer = transfer.take().unwrap();
let (_, dma_channel, mut tx) = transfer.stop();
tx.clear_event(TxEvent::TransmissionComplete);
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
// We cache the last completed time to ensure that there is a minimum delay between consecutive
// transferred packets.
tx_shared.last_completed = Some(Systick::now());
}
}
});
let mut tc_packet = TcPacket::new();
cx.shared.rx_transfer.lock(|rx_transfer| {
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
// Received a partial packet.
if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
let rx_transfer_owned = rx_transfer.take().unwrap();
let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
// 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
// allocated vector to do this.
tc_packet
.resize(rx_len as usize, 0)
.expect("vec resize failed");
tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
rx.clear_event(RxEvent::Idle);
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
*rx_transfer = Some(rx.read_exact(buf, ch));
}
});
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -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]

View File

@@ -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)

View File

@@ -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;
} }
} }

View File

@@ -1,8 +0,0 @@
[package]
name = "satrs-gen"
version = "0.1.0"
edition = "2024"
[dependencies]
toml = "0.8"
heck = "0.5"

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

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();
}
}
}

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

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
fmt:
cargo fmt --all
check-fmt:
cargo fmt --all -- --check
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

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

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/).

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

View File

@@ -8,31 +8,35 @@ 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.27", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
derive-new = "0.7" derive-new = "0.5"
cfg-if = "1"
arbitrary-int = "2"
bitbybit = "1.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
satrs = { path = "../satrs", features = ["test_util"] } [dependencies.satrs]
satrs-minisim = { path = "../satrs-minisim" } path = "../satrs"
satrs-mib = { path = "../satrs-mib" } features = ["test_util"]
[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"

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`.

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

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"
] ]

View File

@@ -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

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):

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 (

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__)

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

View File

@@ -1 +0,0 @@
// TODO: Write the assembly

View File

@@ -1 +0,0 @@
// TODO: Write dummy controller

View File

@@ -1,8 +1,7 @@
use derive_new::new; use derive_new::new;
use satrs::hk::{HkRequest, HkRequestVariant}; use satrs::hk::{HkRequest, HkRequestVariant};
use satrs::mode_tree::{ModeChild, ModeNode};
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender}; use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
use satrs_example::ids::generic_pus::PUS_MODE; use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs_example::{DeviceMode, TimestampHelper}; use satrs_example::{DeviceMode, TimestampHelper};
use satrs_minisim::acs::lis3mdl::{ use satrs_minisim::acs::lis3mdl::{
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
@@ -16,18 +15,15 @@ use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use satrs::mode::{ use satrs::mode::{
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
ModeRequestHandlerMpscBounded,
}; };
use satrs::pus::{EcssTmSender, PusTmVariant}; use satrs::pus::{EcssTmSender, PusTmVariant};
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId}; use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs_example::config::components::NO_SENDER; use satrs_example::config::components::PUS_MODE_SERVICE;
use crate::hk::PusHkHelper; use crate::hk::PusHkHelper;
use crate::pus::hk::{HkReply, HkReplyVariant}; use crate::pus::hk::{HkReply, HkReplyVariant};
use crate::requests::CompositeRequest; use crate::requests::CompositeRequest;
use crate::spi::SpiInterface;
use crate::tmtc::sender::TmTcSender;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -52,6 +48,11 @@ pub enum TransitionState {
Done, Done,
} }
pub trait SpiInterface {
type Error: Debug;
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
}
#[derive(Default)] #[derive(Default)]
pub struct SpiDummyInterface { pub struct SpiDummyInterface {
pub dummy_values: MgmLis3RawValues, pub dummy_values: MgmLis3RawValues,
@@ -83,7 +84,7 @@ impl SpiInterface for SpiSimInterface {
.sim_request_tx .sim_request_tx
.send(SimRequest::new_with_epoch_time(mgm_sensor_request)) .send(SimRequest::new_with_epoch_time(mgm_sensor_request))
{ {
log::error!("failed to send MGM LIS3 request: {e}"); log::error!("failed to send MGM LIS3 request: {}", e);
} }
match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) { match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) {
Ok(sim_reply) => { Ok(sim_reply) => {
@@ -97,7 +98,7 @@ impl SpiInterface for SpiSimInterface {
.copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes()); .copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes());
} }
Err(e) => { Err(e) => {
log::warn!("MGM LIS3 SIM reply timeout: {e}"); log::warn!("MGM LIS3 SIM reply timeout: {}", e);
} }
} }
Ok(()) Ok(())
@@ -128,6 +129,12 @@ pub struct MgmData {
pub z: f32, pub z: f32,
} }
pub struct MpscModeLeafInterface {
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
pub reply_to_pus_tx: mpsc::Sender<GenericMessage<ModeReply>>,
pub reply_to_parent_tx: mpsc::SyncSender<GenericMessage<ModeReply>>,
}
#[derive(Default)] #[derive(Default)]
pub struct BufWrapper { pub struct BufWrapper {
tx_buf: [u8; 32], tx_buf: [u8; 32],
@@ -158,15 +165,16 @@ impl Default for ModeHelpers {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub struct MgmHandlerLis3Mdl< pub struct MgmHandlerLis3Mdl<
ComInterface: SpiInterface, ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>, SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> { > {
id: UniqueApidTargetId, id: UniqueApidTargetId,
dev_str: &'static str, dev_str: &'static str,
mode_node: ModeRequestHandlerMpscBounded, mode_interface: MpscModeLeafInterface,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>, composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>, hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
switch_helper: SwitchHelper, switch_helper: SwitchHelper,
tm_sender: TmTcSender, tm_sender: TmSender,
pub com_interface: ComInterface, pub com_interface: ComInterface,
shared_mgm_set: Arc<Mutex<MgmData>>, shared_mgm_set: Arc<Mutex<MgmData>>,
#[new(value = "PusHkHelper::new(id)")] #[new(value = "PusHkHelper::new(id)")]
@@ -181,8 +189,9 @@ pub struct MgmHandlerLis3Mdl<
impl< impl<
ComInterface: SpiInterface, ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>, SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> MgmHandlerLis3Mdl<ComInterface, SwitchHelper> > MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{ {
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now(); self.stamp_helper.update_from_now();
@@ -265,9 +274,8 @@ impl<
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() {
@@ -277,16 +285,14 @@ impl<
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(e) => {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
}
},
} }
} }
} }
@@ -358,8 +364,9 @@ impl<
impl< impl<
ComInterface: SpiInterface, ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>, SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper> > ModeProvider for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{ {
fn mode_and_submode(&self) -> ModeAndSubmode { fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_helpers.current self.mode_helpers.current
@@ -368,8 +375,9 @@ impl<
impl< impl<
ComInterface: SpiInterface, ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>, SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper> > ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
{ {
type Error = ModeError; type Error = ModeError;
@@ -377,7 +385,6 @@ impl<
&mut self, &mut self,
requestor: MessageMetadata, requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode, mode_and_submode: ModeAndSubmode,
_forced: bool,
) -> Result<(), satrs::mode::ModeError> { ) -> Result<(), satrs::mode::ModeError> {
log::info!( log::info!(
"{}: transitioning to mode {:?}", "{}: transitioning to mode {:?}",
@@ -414,12 +421,9 @@ impl<
self.mode_helpers.target = None; self.mode_helpers.target = None;
self.announce_mode(requestor, false); self.announce_mode(requestor, false);
if let Some(requestor) = requestor { if let Some(requestor) = requestor {
if requestor.sender_id() == NO_SENDER { if requestor.sender_id() != PUS_MODE_SERVICE.id() {
return Ok(());
}
if requestor.sender_id() != PUS_MODE.id() {
log::warn!( log::warn!(
"can not send back mode reply to sender {:x}", "can not send back mode reply to sender {}",
requestor.sender_id() requestor.sender_id()
); );
} else { } else {
@@ -434,15 +438,16 @@ impl<
requestor: MessageMetadata, requestor: MessageMetadata,
reply: ModeReply, reply: ModeReply,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if requestor.sender_id() != PUS_MODE.id() { if requestor.sender_id() != PUS_MODE_SERVICE.id() {
log::warn!( log::warn!(
"can not send back mode reply to sender {}", "can not send back mode reply to sender {}",
requestor.sender_id() requestor.sender_id()
); );
} }
self.mode_node self.mode_interface
.send_mode_reply(requestor, reply) .reply_to_pus_tx
.map_err(ModeError::Send)?; .send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
Ok(()) Ok(())
} }
@@ -455,45 +460,17 @@ impl<
} }
} }
impl<
ComInterface: SpiInterface,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeNode for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
fn id(&self) -> satrs::ComponentId {
self.id.into()
}
}
impl<
ComInterface: SpiInterface,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeChild for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
self.mode_node.add_message_target(id, reply_sender);
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{ use std::sync::{mpsc, Arc};
collections::HashMap,
sync::{mpsc, Arc},
};
use arbitrary_int::u21;
use satrs::{ use satrs::{
mode::{ModeReply, ModeRequest}, mode::{ModeReply, ModeRequest},
mode_tree::ModeParent,
power::SwitchStateBinary, power::SwitchStateBinary,
request::{GenericMessage, UniqueApidTargetId}, request::{GenericMessage, UniqueApidTargetId},
tmtc::PacketAsVec, tmtc::PacketAsVec,
ComponentId,
}; };
use satrs_example::ids::{acs::ASSEMBLY, Apid}; use satrs_example::config::components::Apid;
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues; use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest}; use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
@@ -521,88 +498,49 @@ mod tests {
} }
} }
#[allow(dead_code)]
pub struct MgmTestbench { pub struct MgmTestbench {
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>, pub mode_request_tx: mpsc::Sender<GenericMessage<ModeRequest>>,
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>, pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>, pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>, pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>, pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>, pub tm_rx: mpsc::Receiver<PacketAsVec>,
pub handler: MgmHandlerLis3Mdl<TestSpiInterface, TestSwitchHelper>, pub handler:
} MgmHandlerLis3Mdl<TestSpiInterface, mpsc::Sender<PacketAsVec>, TestSwitchHelper>,
#[derive(Default)]
pub struct MgmAssemblyMock(
pub HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
);
impl ModeNode for MgmAssemblyMock {
fn id(&self) -> satrs::ComponentId {
PUS_MODE.into()
}
}
impl ModeParent for MgmAssemblyMock {
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
self.0.insert(id, request_sender);
}
}
#[derive(Default)]
pub struct PusMock {
pub request_sender_map: HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
}
impl ModeNode for PusMock {
fn id(&self) -> satrs::ComponentId {
PUS_MODE.into()
}
}
impl ModeParent for PusMock {
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
self.request_sender_map.insert(id, request_sender);
}
} }
impl MgmTestbench { impl MgmTestbench {
pub fn new() -> Self { pub fn new() -> Self {
let (request_tx, request_rx) = mpsc::sync_channel(5); let (request_tx, request_rx) = mpsc::channel();
let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5); let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::channel();
let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5); let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
let id = UniqueApidTargetId::new(Apid::Acs.raw_value(), u21::new(1)); let mode_interface = MpscModeLeafInterface {
let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx); request_rx,
reply_to_pus_tx: reply_tx_to_pus,
reply_to_parent_tx: reply_tx_to_parent,
};
let (composite_request_tx, composite_request_rx) = mpsc::channel(); let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10); let (hk_reply_tx, hk_reply_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::sync_channel(10); let (tm_tx, tm_rx) = mpsc::channel::<PacketAsVec>();
let tm_sender = TmTcSender::Heap(tm_tx);
let shared_mgm_set = Arc::default(); let shared_mgm_set = Arc::default();
let mut handler = MgmHandlerLis3Mdl::new(
id,
"TEST_MGM",
mode_node,
composite_request_rx,
hk_reply_tx,
TestSwitchHelper::default(),
tm_sender,
TestSpiInterface::default(),
shared_mgm_set,
);
handler.add_mode_parent(PUS_MODE.into(), reply_tx_to_pus);
handler.add_mode_parent(ASSEMBLY.into(), reply_tx_to_parent);
Self { Self {
mode_request_tx: request_tx, mode_request_tx: request_tx,
mode_reply_rx_to_pus: reply_rx_to_pus, mode_reply_rx_to_pus: reply_rx_to_pus,
mode_reply_rx_to_parent: reply_rx_to_parent, mode_reply_rx_to_parent: reply_rx_to_parent,
composite_request_tx, composite_request_tx,
handler,
tm_rx, tm_rx,
hk_reply_rx, hk_reply_rx,
handler: MgmHandlerLis3Mdl::new(
UniqueApidTargetId::new(Apid::Acs as u16, 1),
"test-mgm",
mode_interface,
composite_request_rx,
hk_reply_tx,
TestSwitchHelper::default(),
tm_tx,
TestSpiInterface::default(),
shared_mgm_set,
),
} }
} }
} }
@@ -632,11 +570,8 @@ mod tests {
testbench testbench
.mode_request_tx .mode_request_tx
.send(GenericMessage::new( .send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE.id()), MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode { ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
)) ))
.expect("failed to send mode request"); .expect("failed to send mode request");
testbench.handler.periodic_operation(); testbench.handler.periodic_operation();
@@ -693,11 +628,8 @@ mod tests {
testbench testbench
.mode_request_tx .mode_request_tx
.send(GenericMessage::new( .send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE.id()), MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode { ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
)) ))
.expect("failed to send mode request"); .expect("failed to send mode request");
testbench.handler.periodic_operation(); testbench.handler.periodic_operation();

View File

@@ -1,4 +1 @@
pub mod assembly;
pub mod ctrl;
pub mod mgm; pub mod mgm;
pub mod subsystem;

View File

@@ -1 +0,0 @@
// TODO: Write subsystem

View File

@@ -1,9 +1,10 @@
use arbitrary_int::u11;
use satrs::pus::verification::RequestId; use satrs::pus::verification::RequestId;
use satrs::spacepackets::ecss::tc::PusTcCreator; use satrs::spacepackets::ecss::tc::PusTcCreator;
use satrs::spacepackets::ecss::tm::PusTmReader; use satrs::spacepackets::ecss::tm::PusTmReader;
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId}; use satrs::{
use satrs::spacepackets::SpHeader; spacepackets::ecss::{PusPacket, WritablePusPacket},
spacepackets::SpHeader,
};
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT}; use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use std::net::{IpAddr, SocketAddr, UdpSocket}; use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::time::Duration; use std::time::Duration;
@@ -11,12 +12,7 @@ use std::time::Duration;
fn main() { fn main() {
let mut buf = [0; 32]; let mut buf = [0; 32];
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let pus_tc = PusTcCreator::new_simple( let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
SpHeader::new_from_apid(u11::new(0x02)),
MessageTypeId::new(17, 1),
&[],
CreatorConfig::default(),
);
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed"); let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
let tc_req_id = RequestId::new(&pus_tc); let tc_req_id = RequestId::new(&pus_tc);
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}"); println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
@@ -33,10 +29,10 @@ fn main() {
let res = client.recv(&mut buf); let res = client.recv(&mut buf);
match res { match res {
Ok(_len) => { Ok(_len) => {
let pus_tm = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed"); let (pus_tm, size) = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
if pus_tm.service_type_id() == 17 && pus_tm.message_subtype_id() == 2 { if pus_tm.service() == 17 && pus_tm.subservice() == 2 {
println!("Received PUS Ping Reply TM[17,2]") println!("Received PUS Ping Reply TM[17,2]")
} else if pus_tm.service_type_id() == 1 { } else if pus_tm.service() == 1 {
if pus_tm.source_data().is_empty() { if pus_tm.source_data().is_empty() {
println!("Invalid verification TM, no source data"); println!("Invalid verification TM, no source data");
} }
@@ -45,29 +41,28 @@ fn main() {
println!("Invalid verification TM source data, less than 4 bytes") println!("Invalid verification TM source data, less than 4 bytes")
} }
let req_id = RequestId::from_bytes(src_data).unwrap(); let req_id = RequestId::from_bytes(src_data).unwrap();
let subtype_id = pus_tm.message_subtype_id(); if pus_tm.subservice() == 1 {
if subtype_id == 1 {
println!("Received TM[1,1] acceptance success for request ID {req_id}") println!("Received TM[1,1] acceptance success for request ID {req_id}")
} else if subtype_id == 2 { } else if pus_tm.subservice() == 2 {
println!("Received TM[1,2] acceptance failure for request ID {req_id}") println!("Received TM[1,2] acceptance failure for request ID {req_id}")
} else if subtype_id == 3 { } else if pus_tm.subservice() == 3 {
println!("Received TM[1,3] start success for request ID {req_id}") println!("Received TM[1,3] start success for request ID {req_id}")
} else if subtype_id == 4 { } else if pus_tm.subservice() == 4 {
println!("Received TM[1,2] start failure for request ID {req_id}") println!("Received TM[1,2] start failure for request ID {req_id}")
} else if subtype_id == 5 { } else if pus_tm.subservice() == 5 {
println!("Received TM[1,5] step success for request ID {req_id}") println!("Received TM[1,5] step success for request ID {req_id}")
} else if subtype_id == 6 { } else if pus_tm.subservice() == 6 {
println!("Received TM[1,6] step failure for request ID {req_id}") println!("Received TM[1,6] step failure for request ID {req_id}")
} else if subtype_id == 7 { } else if pus_tm.subservice() == 7 {
println!("Received TM[1,7] completion success for request ID {req_id}") println!("Received TM[1,7] completion success for request ID {req_id}")
} else if subtype_id == 8 { } else if pus_tm.subservice() == 8 {
println!("Received TM[1,8] completion failure for request ID {req_id}"); println!("Received TM[1,8] completion failure for request ID {req_id}");
} }
} else { } else {
println!( println!(
"Received TM[{}, {}] with {} bytes", "Received TM[{}, {}] with {} bytes",
pus_tm.service_type_id(), pus_tm.service(),
pus_tm.message_subtype_id(), pus_tm.subservice(),
size size
); );
} }

View File

@@ -3,7 +3,7 @@
use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_channel::{bounded, Receiver, Sender};
use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::atomic::{AtomicU16, Ordering};
use std::thread; use std::thread;
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16}; use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
trait FieldDataProvider: Send { trait FieldDataProvider: Send {
fn get_data(&self) -> &[u8]; fn get_data(&self) -> &[u8];
@@ -35,7 +35,7 @@ struct ExampleMgmSet {
temperature: u16, temperature: u16,
} }
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)] #[derive(FromBytes, AsBytes, Unaligned)]
#[repr(C)] #[repr(C)]
struct ExampleMgmSetZc { struct ExampleMgmSetZc {
mgm_vec: [u8; 12], mgm_vec: [u8; 12],

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,
@@ -11,7 +10,7 @@ use strum::IntoEnumIterator;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::{ use satrs::{
events_legacy::{EventU32TypedSev, SeverityInfo}, events::{EventU32TypedSev, SeverityInfo},
pool::{StaticMemoryPool, StaticPoolConfig}, pool::{StaticMemoryPool, StaticPoolConfig},
}; };
@@ -44,14 +43,14 @@ pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<Severi
lazy_static! { lazy_static! {
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = { pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
let mut set = HashSet::new(); let mut set = HashSet::new();
for id in crate::ids::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 crate::ids::Apid::iter() { for id in components::Apid::iter() {
set.insert(id as u16); set.insert(id as u16);
} }
set set
@@ -123,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 {

View File

@@ -88,7 +88,6 @@ impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
} }
} }
#[allow(dead_code)]
#[derive(new)] #[derive(new)]
pub struct SwitchRequestInfo { pub struct SwitchRequestInfo {
pub requestor_info: MessageMetadata, pub requestor_info: MessageMetadata,
@@ -100,7 +99,6 @@ pub struct SwitchRequestInfo {
pub struct TestSwitchHelper { pub struct TestSwitchHelper {
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>, pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>, pub switch_info_requests: RefCell<VecDeque<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>,

View File

@@ -8,22 +8,14 @@ use derive_new::new;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::{ use satrs::{
hk::{HkRequest, HkRequestVariant}, hk::{HkRequest, HkRequestVariant},
mode::{ mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler},
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
ModeRequestHandlerMpscBounded,
},
mode_tree::{ModeChild, ModeNode},
power::SwitchRequest, power::SwitchRequest,
pus::{EcssTmSender, PusTmVariant}, pus::{EcssTmSender, PusTmVariant},
queue::GenericSendError, queue::{GenericSendError, GenericTargetedMessagingError},
request::{GenericMessage, MessageMetadata, UniqueApidTargetId}, request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
spacepackets::ByteConversionError, spacepackets::ByteConversionError,
}; };
use satrs_example::{ use satrs_example::{config::components::PUS_MODE_SERVICE, DeviceMode, TimestampHelper};
config::components::NO_SENDER,
ids::{eps::PCDU, generic_pus::PUS_MODE},
DeviceMode, TimestampHelper,
};
use satrs_minisim::{ use satrs_minisim::{
eps::{ eps::{
PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper, PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper,
@@ -33,10 +25,10 @@ use satrs_minisim::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
acs::mgm::MpscModeLeafInterface,
hk::PusHkHelper, hk::PusHkHelper,
pus::hk::{HkReply, HkReplyVariant}, pus::hk::{HkReply, HkReplyVariant},
requests::CompositeRequest, requests::CompositeRequest,
tmtc::sender::TmTcSender,
}; };
pub trait SerialInterface { pub trait SerialInterface {
@@ -68,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(())
} }
@@ -109,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 } => {
@@ -125,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(),
))); )));
} }
}; };
@@ -136,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;
} }
} }
@@ -150,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),
@@ -205,14 +189,14 @@ pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
/// Example PCDU device handler. /// Example PCDU device handler.
#[derive(new)] #[derive(new)]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub struct PcduHandler<ComInterface: SerialInterface> { pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
id: UniqueApidTargetId, id: UniqueApidTargetId,
dev_str: &'static str, dev_str: &'static str,
mode_node: ModeRequestHandlerMpscBounded, mode_interface: MpscModeLeafInterface,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>, composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>, hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>, switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
tm_sender: TmTcSender, tm_sender: TmSender,
pub com_interface: ComInterface, pub com_interface: ComInterface,
shared_switch_map: Arc<Mutex<SwitchSet>>, shared_switch_map: Arc<Mutex<SwitchSet>>,
#[new(value = "PusHkHelper::new(id)")] #[new(value = "PusHkHelper::new(id)")]
@@ -225,7 +209,7 @@ pub struct PcduHandler<ComInterface: SerialInterface> {
tm_buf: [u8; 256], tm_buf: [u8; 256],
} }
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> { 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 => {
@@ -329,9 +313,8 @@ 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() {
@@ -341,18 +324,14 @@ impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
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);
}
},
} }
} }
} }
@@ -392,33 +371,34 @@ 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);
} }
} }
} }
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> { impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeProvider
for PcduHandler<ComInterface, TmSender>
{
fn mode_and_submode(&self) -> ModeAndSubmode { fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_and_submode self.mode_and_submode
} }
} }
impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterface> { impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
for PcduHandler<ComInterface, TmSender>
{
type Error = ModeError; type Error = ModeError;
fn start_transition( fn start_transition(
&mut self, &mut self,
requestor: MessageMetadata, requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode, mode_and_submode: ModeAndSubmode,
_forced: bool,
) -> Result<(), satrs::mode::ModeError> { ) -> Result<(), satrs::mode::ModeError> {
log::info!( log::info!(
"{}: transitioning to mode {:?}", "{}: transitioning to mode {:?}",
@@ -447,10 +427,7 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.announce_mode(requestor, false); self.announce_mode(requestor, false);
if let Some(requestor) = requestor { if let Some(requestor) = requestor {
if requestor.sender_id() == NO_SENDER { if requestor.sender_id() != PUS_MODE_SERVICE.id() {
return Ok(());
}
if requestor.sender_id() != PUS_MODE.id() {
log::warn!( log::warn!(
"can not send back mode reply to sender {}", "can not send back mode reply to sender {}",
requestor.sender_id() requestor.sender_id()
@@ -467,15 +444,16 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
requestor: MessageMetadata, requestor: MessageMetadata,
reply: ModeReply, reply: ModeReply,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if requestor.sender_id() != PUS_MODE.id() { if requestor.sender_id() != PUS_MODE_SERVICE.id() {
log::warn!( log::warn!(
"can not send back mode reply to sender {}", "can not send back mode reply to sender {}",
requestor.sender_id() requestor.sender_id()
); );
} }
self.mode_node self.mode_interface
.send_mode_reply(requestor, reply) .reply_to_pus_tx
.map_err(|_| GenericSendError::RxDisconnected)?; .send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
Ok(()) Ok(())
} }
@@ -487,262 +465,3 @@ impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterf
Ok(()) Ok(())
} }
} }
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
fn id(&self) -> satrs::ComponentId {
PCDU.into()
}
}
impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
self.mode_node.add_message_target(id, reply_sender);
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use arbitrary_int::u21;
use satrs::{
mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
};
use satrs_example::ids::{self, Apid};
use satrs_minisim::eps::SwitchMapBinary;
use super::*;
#[derive(Default)]
pub struct SerialInterfaceTest {
pub inner: SerialInterfaceDummy,
pub send_queue: RefCell<VecDeque<Vec<u8>>>,
pub reply_queue: RefCell<VecDeque<String>>,
}
impl SerialInterface for SerialInterfaceTest {
type Error = ();
fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
let mut send_queue_mut = self.send_queue.borrow_mut();
send_queue_mut.push_back(data.to_vec());
self.inner.send(data)
}
fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
&self,
mut f: ReplyHandler,
) -> Result<(), Self::Error> {
if self.inner.reply_queue_empty() {
return Ok(());
}
loop {
let reply = self.inner.get_next_reply_as_string();
self.reply_queue.borrow_mut().push_back(reply.clone());
f(reply.as_bytes());
if self.inner.reply_queue_empty() {
break;
}
}
Ok(())
}
}
pub struct PcduTestbench {
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>,
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
pub handler: PcduHandler<SerialInterfaceTest>,
}
impl PcduTestbench {
pub fn new() -> Self {
let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5);
let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5);
let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
let mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx);
let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
let mut handler = PcduHandler::new(
UniqueApidTargetId::new(Apid::Eps.raw_value(), u21::new(0)),
"TEST_PCDU",
mode_node,
composite_request_rx,
hk_reply_tx,
switch_reqest_rx,
TmTcSender::Heap(tm_tx.clone()),
SerialInterfaceTest::default(),
shared_switch_map,
);
handler.add_mode_parent(ids::eps::SUBSYSTEM.into(), mode_reply_tx_to_parent);
handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus);
Self {
mode_request_tx,
mode_reply_rx_to_pus,
mode_reply_rx_to_parent,
composite_request_tx,
hk_reply_rx,
tm_rx,
switch_request_tx,
handler,
}
}
pub fn verify_switch_info_req_was_sent(&self, expected_queue_len: usize) {
// Check that there is now communication happening.
let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
assert_eq!(send_queue_mut.len(), expected_queue_len);
let packet_sent = send_queue_mut.pop_front().unwrap();
drop(send_queue_mut);
let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
assert_eq!(pcdu_req, PcduRequest::RequestSwitchInfo);
}
pub fn verify_switch_req_was_sent(
&self,
expected_queue_len: usize,
switch_id: PcduSwitch,
target_state: SwitchStateBinary,
) {
// Check that there is now communication happening.
let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
assert_eq!(send_queue_mut.len(), expected_queue_len);
let packet_sent = send_queue_mut.pop_front().unwrap();
drop(send_queue_mut);
let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
assert_eq!(
pcdu_req,
PcduRequest::SwitchDevice {
switch: switch_id,
state: target_state
}
)
}
pub fn verify_switch_reply_received(
&self,
expected_queue_len: usize,
expected_map: SwitchMapBinary,
) {
// Check that a switch reply was read back.
let mut reply_received_mut = self.handler.com_interface.reply_queue.borrow_mut();
assert_eq!(reply_received_mut.len(), expected_queue_len);
let reply_received = reply_received_mut.pop_front().unwrap();
let sim_reply: SimReply = serde_json::from_str(&reply_received).unwrap();
let pcdu_reply = PcduReply::from_sim_message(&sim_reply).unwrap();
assert_eq!(pcdu_reply, PcduReply::SwitchInfo(expected_map));
}
}
#[test]
fn test_basic_handler() {
let mut testbench = PcduTestbench::new();
assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
assert_eq!(
testbench.handler.com_interface.reply_queue.borrow().len(),
0
);
assert_eq!(
testbench.handler.mode_and_submode().mode(),
DeviceMode::Off as u32
);
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
testbench.handler.periodic_operation(OpCode::RegularOp);
testbench
.handler
.periodic_operation(OpCode::PollAndRecvReplies);
// Handler is OFF, no changes expected.
assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
assert_eq!(
testbench.handler.com_interface.reply_queue.borrow().len(),
0
);
assert_eq!(
testbench.handler.mode_and_submode().mode(),
DeviceMode::Off as u32
);
assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
}
#[test]
fn test_normal_mode() {
let mut testbench = PcduTestbench::new();
testbench
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE.id()),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
))
.expect("failed to send mode request");
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
assert!(!switch_map_shared.valid);
drop(switch_map_shared);
testbench.handler.periodic_operation(OpCode::RegularOp);
testbench
.handler
.periodic_operation(OpCode::PollAndRecvReplies);
// Check correctness of mode.
assert_eq!(
testbench.handler.mode_and_submode().mode(),
DeviceMode::Normal as u32
);
assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
testbench.verify_switch_info_req_was_sent(1);
testbench.verify_switch_reply_received(1, SwitchMapBinaryWrapper::default().0);
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
assert!(switch_map_shared.valid);
drop(switch_map_shared);
}
#[test]
fn test_switch_request_handling() {
let mut testbench = PcduTestbench::new();
testbench
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE.id()),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
))
.expect("failed to send mode request");
testbench
.switch_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, ids::acs::MGM0.id()),
SwitchRequest::new(0, SwitchStateBinary::On),
))
.expect("failed to send switch request");
testbench.handler.periodic_operation(OpCode::RegularOp);
testbench
.handler
.periodic_operation(OpCode::PollAndRecvReplies);
testbench.verify_switch_req_was_sent(2, PcduSwitch::Mgm, SwitchStateBinary::On);
testbench.verify_switch_info_req_was_sent(1);
let mut switch_map = SwitchMapBinaryWrapper::default().0;
*switch_map
.get_mut(&PcduSwitch::Mgm)
.expect("switch state setting failed") = SwitchStateBinary::On;
testbench.verify_switch_reply_received(1, switch_map);
let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
assert!(switch_map_shared.valid);
drop(switch_map_shared);
}
}

View File

@@ -1,15 +1,13 @@
use std::sync::mpsc::{self}; use std::sync::mpsc::{self};
use crate::pus::create_verification_reporter; use crate::pus::create_verification_reporter;
use arbitrary_int::traits::Integer as _; use satrs::event_man::{EventMessageU32, EventRoutingError};
use arbitrary_int::u11; use satrs::pus::event::EventTmHookProvider;
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
use satrs::pus::event::EventTmHook;
use satrs::pus::verification::VerificationReporter; use satrs::pus::verification::VerificationReporter;
use satrs::pus::EcssTmSender; use satrs::pus::EcssTmSender;
use satrs::request::UniqueApidTargetId; use satrs::request::UniqueApidTargetId;
use satrs::{ use satrs::{
event_man_legacy::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded}, event_man::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
pus::{ pus::{
event_man::{ event_man::{
DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken, DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
@@ -18,17 +16,17 @@ 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);
} }
@@ -61,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(
@@ -218,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>,
@@ -243,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);
@@ -269,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();
@@ -280,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());

View File

@@ -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,
)) ))
} }
} }

View File

@@ -1,109 +0,0 @@
//! This is an auto-generated configuration module.
use satrs::request::UniqueApidTargetId;
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
#[bitbybit::bitenum(u11)]
pub enum Apid {
Sched = 1,
GenericPus = 2,
Acs = 3,
Cfdp = 4,
Tmtc = 5,
Eps = 6,
}
pub mod acs {
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u21, exhaustive = false)]
pub enum Id {
Subsystem = 1,
Assembly = 2,
Mgm0 = 3,
Mgm1 = 4,
}
pub const SUBSYSTEM: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Subsystem.raw_value());
pub const ASSEMBLY: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Assembly.raw_value());
pub const MGM0: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm0.raw_value());
pub const MGM1: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Acs.raw_value(), Id::Mgm1.raw_value());
}
pub mod eps {
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u21, exhaustive = false)]
pub enum Id {
Pcdu = 0,
Subsystem = 1,
}
pub const PCDU: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Pcdu.raw_value());
pub const SUBSYSTEM: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Eps.raw_value(), Id::Subsystem.raw_value());
}
pub mod generic_pus {
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u21, exhaustive = false)]
pub enum Id {
PusEventManagement = 0,
PusRouting = 1,
PusTest = 2,
PusAction = 3,
PusMode = 4,
PusHk = 5,
}
pub const PUS_EVENT_MANAGEMENT: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
super::Apid::GenericPus.raw_value(),
Id::PusEventManagement.raw_value(),
);
pub const PUS_ROUTING: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
super::Apid::GenericPus.raw_value(),
Id::PusRouting.raw_value(),
);
pub const PUS_TEST: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
super::Apid::GenericPus.raw_value(),
Id::PusTest.raw_value(),
);
pub const PUS_ACTION: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
super::Apid::GenericPus.raw_value(),
Id::PusAction.raw_value(),
);
pub const PUS_MODE: super::UniqueApidTargetId = super::UniqueApidTargetId::new(
super::Apid::GenericPus.raw_value(),
Id::PusMode.raw_value(),
);
pub const PUS_HK: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::GenericPus.raw_value(), Id::PusHk.raw_value());
}
pub mod sched {
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u21, exhaustive = false)]
pub enum Id {
PusSched = 0,
}
pub const PUS_SCHED: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Sched.raw_value(), Id::PusSched.raw_value());
}
pub mod tmtc {
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u21, exhaustive = false)]
pub enum Id {
UdpServer = 0,
TcpServer = 1,
}
pub const UDP_SERVER: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::UdpServer.raw_value());
pub const TCP_SERVER: super::UniqueApidTargetId =
super::UniqueApidTargetId::new(super::Apid::Tmtc.raw_value(), Id::TcpServer.raw_value());
}

View File

@@ -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;
} }
} }

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,29 +102,40 @@ 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(
TcpSpacepacketsServer::new(
cfg, cfg,
tm_source, tm_source,
CcsdsPacketParser::new(cfg.id, 2048, tc_sender, SimplePacketValidator { valid_ids }), tc_sender,
SimplePacketValidator { valid_ids },
ConnectionFinishedHandler::default(), ConnectionFinishedHandler::default(),
None, None,
)?)) )?,
PhantomData,
))
} }
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {

View File

@@ -1,17 +1,14 @@
#![allow(dead_code)] use core::fmt::Debug;
use std::net::{SocketAddr, UdpSocket}; use std::net::{SocketAddr, UdpSocket};
use std::sync::mpsc; use std::sync::mpsc;
use log::{info, warn}; use log::{info, warn};
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
use satrs::pus::HandlingStatus; use satrs::pus::HandlingStatus;
use satrs::queue::GenericSendError; use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
use satrs::tmtc::PacketAsVec; use satrs::{
hal::std::udp_server::{ReceiveResult, UdpTcServer},
use satrs::pool::{PoolProviderWithGuards, SharedStaticMemoryPool}; pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
use satrs::tmtc::PacketInPool; };
use crate::tmtc::sender::TmTcSender;
pub trait UdpTmHandler { pub trait 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);
@@ -68,12 +65,21 @@ impl UdpTmHandler for DynamicUdpTmHandler {
} }
} }
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> { pub struct UdpTmtcServer<
pub udp_tc_server: UdpTcServer<TmTcSender, GenericSendError>, TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError,
> {
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
pub tm_handler: TmHandler, pub tm_handler: TmHandler,
} }
impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> { 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 {
@@ -109,30 +115,41 @@ impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
mod tests { mod tests {
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::{ use std::{
cell::RefCell,
collections::VecDeque, collections::VecDeque,
net::IpAddr, net::IpAddr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use arbitrary_int::traits::Integer as _;
use arbitrary_int::u14;
use satrs::spacepackets::ecss::{CreatorConfig, MessageTypeId};
use satrs::{ use satrs::{
spacepackets::{ spacepackets::{
ecss::{tc::PusTcCreator, WritablePusPacket}, ecss::{tc::PusTcCreator, WritablePusPacket},
SpHeader, SpHeader,
}, },
tmtc::PacketSenderRaw,
ComponentId, ComponentId,
}; };
use satrs_example::config::OBSW_SERVER_ADDR; use satrs_example::config::{components, OBSW_SERVER_ADDR};
use satrs_example::ids;
use crate::tmtc::sender::{MockSender, TmTcSender};
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)] #[derive(Default, Debug, Clone)]
pub struct TestTmHandler { pub struct TestTmHandler {
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>, addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
@@ -147,7 +164,8 @@ mod tests {
#[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();
@@ -157,13 +175,7 @@ mod tests {
tm_handler, 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());
} }
@@ -171,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();
@@ -181,13 +194,8 @@ mod tests {
udp_tc_server, udp_tc_server,
tm_handler, tm_handler,
}; };
let sph = SpHeader::new_for_unseg_tc(ids::Apid::GenericPus.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,
MessageTypeId::new(17, 1),
&[],
CreatorConfig::default(),
)
.to_vec() .to_vec()
.unwrap(); .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");
@@ -196,13 +204,7 @@ mod tests {
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);
@@ -217,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.

View File

@@ -1,7 +1,6 @@
use satrs::spacepackets::time::cds::CdsTime; use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
pub mod config; pub mod config;
pub mod ids;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DeviceMode { pub enum DeviceMode {

View File

@@ -1,155 +1,108 @@
use std::{
net::{IpAddr, SocketAddr},
sync::{mpsc, Arc, Mutex},
thread,
time::Duration,
};
use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper};
use eps::{
pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper},
PowerSwitchHelper,
};
use events::EventHandler;
use interface::{
sim_client_udp::create_sim_client,
tcp::{SyncTcpTmSource, TcpTask},
udp::UdpTmtcServer,
};
use log::info;
use logger::setup_logger;
/*
use pus::{
action::create_action_service,
event::create_event_service,
hk::create_hk_service,
mode::create_mode_service,
scheduler::{create_scheduler_service, TcReleaser},
stack::PusStack,
test::create_test_service,
PusTcDistributor, PusTcMpscRouter,
};
*/
use requests::GenericRequestRouter;
use satrs::{
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded},
mode_tree::connect_mode_nodes,
pus::{event_man::EventRequestWithToken, EcssTcCacher, HandlingStatus},
request::{GenericMessage, MessageMetadata},
spacepackets::time::cds::CdsTime,
};
use satrs_example::{
config::{
components::NO_SENDER,
pool::create_sched_tc_pool,
tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS},
OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
},
ids::{
acs::*,
eps::*,
tmtc::{TCP_SERVER, UDP_SERVER},
},
DeviceMode,
};
use tmtc::sender::TmTcSender;
use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink};
cfg_if::cfg_if! {
if #[cfg(feature = "heap_tmtc")] {
use interface::udp::DynamicUdpTmHandler;
use satrs::pus::EcssTcVecCacher;
use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic};
} else {
use std::sync::RwLock;
use interface::udp::StaticUdpTmHandler;
use satrs::pus::EcssTcInSharedPoolCacher;
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::create_static_pools;
use tmtc::{
tc_source::TcSourceTaskStatic,
tm_sink::TmSinkStatic,
};
}
}
mod acs; mod acs;
mod eps; mod eps;
mod events; mod events;
mod hk; mod hk;
mod interface; mod interface;
mod logger; mod logger;
//mod pus; mod pus;
mod requests; mod requests;
mod spi;
mod tmtc; mod tmtc;
fn main() { use crate::eps::pcdu::{
setup_logger().expect("setting up logging with fern failed"); PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper,
println!("Running OBSW example"); };
use crate::eps::PowerSwitchHelper;
use crate::events::EventHandler;
use crate::interface::udp::DynamicUdpTmHandler;
use crate::pus::stack::PusStack;
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic};
use log::info;
use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer;
use satrs::pus::HandlingStatus;
use satrs::request::GenericMessage;
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};
cfg_if::cfg_if! { use crate::acs::mgm::{
if #[cfg(not(feature = "heap_tmtc"))] { 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 (tm_pool, tc_pool) = create_static_pools();
let shared_tm_pool = Arc::new(RwLock::new(tm_pool)); let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
let shared_tc_pool = Arc::new(RwLock::new(tc_pool)); let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool); let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
}
}
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50); let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50); let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50); let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
cfg_if::cfg_if! { let tm_sink_tx_sender =
if #[cfg(not(feature = "heap_tmtc"))] { PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
let tm_sender = TmTcSender::Static(
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone())
);
} else if #[cfg(feature = "heap_tmtc")] {
let tm_sender = TmTcSender::Heap(tm_sink_tx.clone());
}
}
let (sim_request_tx, sim_request_rx) = mpsc::channel(); let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel(); let (mgm_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_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10); let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10); mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30); let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
let (mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5); mpsc::sync_channel::<GenericMessage<CompositeRequest>>(30);
let (mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5); let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID. // Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default(); let mut request_map = GenericRequestRouter::default();
request_map request_map
.composite_router_map .composite_router_map
.insert(MGM0.id(), mgm_0_handler_composite_tx); .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 request_map
.composite_router_map .composite_router_map
.insert(MGM1.id(), mgm_1_handler_composite_tx); .insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
request_map request_map
.composite_router_map .mode_router_map
.insert(PCDU.id(), pcdu_handler_composite_tx); .insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx);
// This helper structure is used by all telecommand providers which need to send telecommands // This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source. // to the TC source.
cfg_if::cfg_if! { let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
if #[cfg(not(feature = "heap_tmtc"))] {
let tc_sender_with_shared_pool =
PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
let tc_in_mem_converter =
EcssTcCacher::Static(EcssTcInSharedPoolCacher::new(shared_tc_pool, 4096));
} else if #[cfg(feature = "heap_tmtc")] {
let tc_in_mem_converter = EcssTcCacher::Heap(EcssTcVecCacher::default());
}
}
// Create event handling components // Create event handling components
// These sender handles are used to send event requests, for example to enable or disable // These sender handles are used to send event requests, for example to enable or disable
@@ -161,25 +114,17 @@ fn main() {
// in the sat-rs documentation. // in the sat-rs documentation.
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx); let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20); let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10); let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50); let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50); let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50); let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50); let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel(); let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50); let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30); let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let tc_releaser = TcReleaser::Static(tc_sender_with_shared_pool.clone());
} else if #[cfg(feature = "heap_tmtc")] {
let tc_releaser = TcReleaser::Heap(tc_source_tx.clone());
}
}
/*
let pus_router = PusTcMpscRouter { let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx, test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx, event_tc_sender: pus_event_tx,
@@ -188,42 +133,41 @@ fn main() {
action_tc_sender: pus_action_tx, action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx, mode_tc_sender: pus_mode_tx,
}; };
let pus_test_service = create_test_service( let pus_test_service = create_test_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), shared_tc_pool.clone(),
event_tx.clone(), event_tx.clone(),
pus_test_rx, pus_test_rx,
); );
let pus_scheduler_service = create_scheduler_service( let pus_scheduler_service = create_scheduler_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), tc_source.clone(),
tc_releaser,
pus_sched_rx, pus_sched_rx,
create_sched_tc_pool(), create_sched_tc_pool(),
); );
let pus_event_service = create_event_service( let pus_event_service = create_event_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), shared_tc_pool.clone(),
pus_event_rx, pus_event_rx,
event_request_tx, event_request_tx,
); );
let pus_action_service = create_action_service( let pus_action_service = create_action_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), shared_tc_pool.clone(),
pus_action_rx, pus_action_rx,
request_map.clone(), request_map.clone(),
pus_action_reply_rx, pus_action_reply_rx,
); );
let pus_hk_service = create_hk_service( let pus_hk_service = create_hk_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), shared_tc_pool.clone(),
pus_hk_rx, pus_hk_rx,
request_map.clone(), request_map.clone(),
pus_hk_reply_rx, pus_hk_reply_rx,
); );
let pus_mode_service = create_mode_service( let pus_mode_service = create_mode_service_static(
tm_sender.clone(), tm_sink_tx_sender.clone(),
tc_in_mem_converter.clone(), shared_tc_pool.clone(),
pus_mode_rx, pus_mode_rx,
request_map, request_map,
pus_mode_reply_rx, pus_mode_reply_rx,
@@ -236,38 +180,22 @@ fn main() {
pus_scheduler_service, pus_scheduler_service,
pus_mode_service, pus_mode_service,
); );
*/
cfg_if::cfg_if! { let mut tmtc_task = TcSourceTaskStatic::new(
if #[cfg(not(feature = "heap_tmtc"))] {
let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new(
shared_tc_pool_wrapper.clone(), shared_tc_pool_wrapper.clone(),
tc_source_rx, tc_source_rx,
PusTcDistributor::new(tm_sender.clone(), pus_router), PusTcDistributor::new(tm_sink_tx_sender, pus_router),
)); );
let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool);
let udp_tm_handler = StaticUdpTmHandler {
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone(),
};
} else if #[cfg(feature = "heap_tmtc")] {
let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new(
tc_source_rx,
PusTcDistributor::new(tm_sender.clone(), pus_router),
));
let tc_sender = TmTcSender::Heap(tc_source_tx.clone());
let udp_tm_handler = DynamicUdpTmHandler {
tm_rx: tm_server_rx,
};
}
}
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone()) let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
.expect("creating UDP TMTC server failed"); .expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer { let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server, udp_tc_server,
tm_handler: udp_tm_handler, 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(
@@ -281,94 +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");
cfg_if::cfg_if! { let mut tm_sink = TmSinkStatic::new(
if #[cfg(not(feature = "heap_tmtc"))] {
let mut tm_sink = TmSink::Static(TmSinkStatic::new(
shared_tm_pool_wrapper, shared_tm_pool_wrapper,
sync_tm_tcp_source, sync_tm_tcp_source,
tm_sink_rx, tm_sink_rx,
tm_server_tx, tm_server_tx,
)); );
} else if #[cfg(feature = "heap_tmtc")] {
let mut tm_sink = TmSink::Heap(TmSinkDynamic::new( let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
sync_tm_tcp_source, mpsc::sync_channel(5);
tm_sink_rx,
tm_server_tx,
));
}
}
let shared_switch_set = Arc::new(Mutex::default()); 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_mode_node = ModeRequestHandlerMpscBounded::new(MGM0.into(), mgm_0_handler_mode_rx); request_rx: mgm_handler_mode_rx,
let mgm_1_mode_node = ModeRequestHandlerMpscBounded::new(MGM1.into(), mgm_1_handler_mode_rx); reply_to_pus_tx: pus_mode_reply_tx.clone(),
let (mgm_0_spi_interface, mgm_1_spi_interface) = reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
sim_client
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
(
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_0_sim_reply_rx,
}),
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_1_sim_reply_rx,
}),
)
} else {
(
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()),
)
}; };
let mut mgm_0_handler = MgmHandlerLis3Mdl::new(
MGM0, 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", "MGM_0",
mgm_0_mode_node, mgm_mode_leaf_interface,
mgm_0_handler_composite_rx, mgm_handler_composite_rx,
pus_hk_reply_tx.clone(), pus_hk_reply_tx.clone(),
switch_helper.clone(), switch_helper.clone(),
tm_sender.clone(), tm_sink_tx.clone(),
mgm_0_spi_interface, mgm_spi_interface,
shared_mgm_0_set, shared_mgm_set,
);
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
MGM1,
"MGM_1",
mgm_1_mode_node,
mgm_1_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sender.clone(),
mgm_1_spi_interface,
shared_mgm_1_set,
);
// Connect PUS service to device handlers.
connect_mode_nodes(
&mut pus_stack.mode_srv,
mgm_0_handler_mode_tx,
&mut mgm_0_handler,
pus_mode_reply_tx.clone(),
);
connect_mode_nodes(
&mut pus_stack.mode_srv,
mgm_1_handler_mode_tx,
&mut mgm_1_handler,
pus_mode_reply_tx.clone(),
); );
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(10);
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
request_rx: pcdu_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx,
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
};
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() { 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(
@@ -378,35 +272,17 @@ fn main() {
} else { } else {
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
}; };
let pcdu_mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), pcdu_handler_mode_rx);
let mut pcdu_handler = PcduHandler::new( let mut pcdu_handler = PcduHandler::new(
PCDU, PCDU_HANDLER,
"PCDU", "PCDU",
pcdu_mode_node, pcdu_mode_leaf_interface,
pcdu_handler_composite_rx, pcdu_handler_composite_rx,
pus_hk_reply_tx, pus_hk_reply_tx,
switch_request_rx, switch_request_rx,
tm_sender.clone(), tm_sink_tx,
pcdu_serial_interface, pcdu_serial_interface,
shared_switch_set, shared_switch_set,
); );
connect_mode_nodes(
&mut pus_stack.mode_srv,
pcdu_handler_mode_tx.clone(),
&mut pcdu_handler,
pus_mode_reply_tx,
);
// The PCDU is a critical component which should be in normal mode immediately.
pcdu_handler_mode_tx
.send(GenericMessage::new(
MessageMetadata::new(0, NO_SENDER),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as Mode, 0),
forced: false,
},
))
.expect("sending initial mode request failed");
info!("Starting TMTC and UDP task"); info!("Starting TMTC and UDP task");
let jh_udp_tmtc = thread::Builder::new() let jh_udp_tmtc = thread::Builder::new()
@@ -459,8 +335,7 @@ fn main() {
let jh_aocs = thread::Builder::new() let jh_aocs = thread::Builder::new()
.name("sat-rs aocs".to_string()) .name("sat-rs aocs".to_string())
.spawn(move || loop { .spawn(move || loop {
mgm_0_handler.periodic_operation(); mgm_handler.periodic_operation();
mgm_1_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS)); thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
}) })
.unwrap(); .unwrap();
@@ -471,13 +346,11 @@ fn main() {
.spawn(move || loop { .spawn(move || loop {
// TODO: We should introduce something like a fixed timeslot helper to allow a more // TODO: We should introduce something like a fixed timeslot helper to allow a more
// declarative API. It would also be very useful for the AOCS task. // declarative API. It would also be very useful for the AOCS task.
// pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
// TODO: The fixed timeslot handler exists.. use it.
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp);
thread::sleep(Duration::from_millis(50)); thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies); pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(50)); thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies); pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(300)); thread::sleep(Duration::from_millis(300));
}) })
.unwrap(); .unwrap();
@@ -513,6 +386,315 @@ fn main() {
.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()

View File

@@ -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);

View File

@@ -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";

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -2,28 +2,28 @@ use std::sync::mpsc;
use std::time::Duration; use std::time::Duration;
use crate::pus::create_verification_reporter; use crate::pus::create_verification_reporter;
use crate::tmtc::sender::TmTcSender;
use log::info; use log::info;
use satrs::pool::{PoolProvider, StaticMemoryPool}; use satrs::pool::{PoolProvider, StaticMemoryPool};
use satrs::pus::scheduler::{PusScheduler, TcInfo}; use satrs::pus::scheduler::{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,
PusScheduler, 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 = PusScheduler::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),
} }
} }

View File

@@ -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.

View File

@@ -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()

View File

@@ -8,14 +8,14 @@ use satrs::mode::ModeRequest;
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken, FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{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;
use satrs_example::ids;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[non_exhaustive] #[non_exhaustive]
@@ -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(

View File

@@ -1,6 +0,0 @@
use core::fmt::Debug;
pub trait SpiInterface {
type Error: Debug;
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
}

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;

View File

@@ -1,82 +0,0 @@
use std::{cell::RefCell, collections::VecDeque, sync::mpsc};
use satrs::{
pus::EcssTmSender,
queue::GenericSendError,
spacepackets::ecss::WritablePusPacket,
tmtc::{PacketAsVec, PacketHandler, PacketSenderWithSharedPool},
ComponentId,
};
#[derive(Default, Debug, Clone)]
pub struct MockSender(pub RefCell<VecDeque<PacketAsVec>>);
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum TmTcSender {
Static(PacketSenderWithSharedPool),
Heap(mpsc::SyncSender<PacketAsVec>),
Mock(MockSender),
}
impl TmTcSender {
#[allow(dead_code)]
pub fn get_mock_sender(&mut self) -> Option<&mut MockSender> {
match self {
TmTcSender::Mock(sender) => Some(sender),
_ => None,
}
}
}
impl EcssTmSender for TmTcSender {
fn send_tm(
&self,
sender_id: satrs::ComponentId,
tm: satrs::pus::PusTmVariant,
) -> Result<(), satrs::pus::EcssTmtcError> {
match self {
TmTcSender::Static(sync_sender) => sync_sender.send_tm(sender_id, tm),
TmTcSender::Heap(sync_sender) => match tm {
satrs::pus::PusTmVariant::InStore(_) => panic!("can not send TM in store"),
satrs::pus::PusTmVariant::Direct(pus_tm_creator) => sync_sender
.send(PacketAsVec::new(sender_id, pus_tm_creator.to_vec()?))
.map_err(|_| GenericSendError::RxDisconnected.into()),
},
TmTcSender::Mock(_) => Ok(()),
}
}
}
impl PacketHandler for TmTcSender {
type Error = GenericSendError;
fn handle_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
match self {
TmTcSender::Static(packet_sender_with_shared_pool) => {
if let Err(e) = packet_sender_with_shared_pool.handle_packet(sender_id, packet) {
log::error!("Error sending packet via Static TM/TC sender: {:?}", e);
}
}
TmTcSender::Heap(sync_sender) => {
if let Err(e) = sync_sender.handle_packet(sender_id, packet) {
log::error!("Error sending packet via Heap TM/TC sender: {:?}", e);
}
}
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(())
}
}

View File

@@ -1,13 +1,11 @@
use satrs::{ use satrs::{
pool::PoolProvider, pool::PoolProvider,
pus::HandlingStatus, pus::HandlingStatus,
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}, tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
ComponentId,
};
use std::{
collections::HashMap,
sync::mpsc::{self, TryRecvError},
}; };
use std::sync::mpsc::{self, TryRecvError};
use satrs::pus::MpscTmAsVecSender;
use crate::pus::PusTcDistributor; use crate::pus::PusTcDistributor;
@@ -15,23 +13,20 @@ use crate::pus::PusTcDistributor;
pub struct TcSourceTaskStatic { pub struct TcSourceTaskStatic {
shared_tc_pool: SharedPacketPool, shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>, tc_receiver: mpsc::Receiver<PacketInPool>,
/// We allocate this buffer from the heap to avoid a clippy warning on large enum variant tc_buf: [u8; 4096],
/// differences. pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
tc_buf: Box<[u8; 4096]>,
pus_distributor: PusTcDistributor,
} }
#[allow(dead_code)]
impl TcSourceTaskStatic { impl TcSourceTaskStatic {
pub fn new( pub fn new(
shared_tc_pool: SharedPacketPool, shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>, tc_receiver: mpsc::Receiver<PacketInPool>,
pus_receiver: PusTcDistributor, pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
) -> Self { ) -> Self {
Self { Self {
shared_tc_pool, shared_tc_pool,
tc_receiver, tc_receiver,
tc_buf: Box::new([0; 4096]), tc_buf: [0; 4096],
pus_distributor: pus_receiver, pus_distributor: pus_receiver,
} }
} }
@@ -50,11 +45,11 @@ impl TcSourceTaskStatic {
.0 .0
.read() .read()
.expect("locking tc pool failed"); .expect("locking tc pool failed");
pool.read(&packet_in_pool.store_addr, self.tc_buf.as_mut_slice()) pool.read(&packet_in_pool.store_addr, &mut self.tc_buf)
.expect("reading pool failed"); .expect("reading pool failed");
drop(pool); drop(pool);
self.pus_distributor self.pus_distributor
.handle_tc_packet_in_store(packet_in_pool, self.tc_buf.as_slice()) .handle_tc_packet_in_store(packet_in_pool, &self.tc_buf)
.ok(); .ok();
HandlingStatus::HandledOne HandlingStatus::HandledOne
} }
@@ -69,24 +64,20 @@ impl TcSourceTaskStatic {
} }
} }
pub type CcsdsDistributorDyn = HashMap<ComponentId, std::sync::mpsc::SyncSender<PacketAsVec>>;
pub type CcsdsDistributorStatic = HashMap<ComponentId, std::sync::mpsc::SyncSender<PacketInPool>>;
// TC source components where the heap is the backing memory of the received telecommands. // TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic { pub struct TcSourceTaskDynamic {
pub tc_receiver: mpsc::Receiver<PacketAsVec>, pub tc_receiver: mpsc::Receiver<PacketAsVec>,
ccsds_distributor: CcsdsDistributorDyn, pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
} }
#[allow(dead_code)]
impl TcSourceTaskDynamic { impl TcSourceTaskDynamic {
pub fn new( pub fn new(
tc_receiver: mpsc::Receiver<PacketAsVec>, tc_receiver: mpsc::Receiver<PacketAsVec>,
ccsds_distributor: CcsdsDistributorDyn, pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
) -> Self { ) -> Self {
Self { Self {
tc_receiver, tc_receiver,
ccsds_distributor, pus_distributor: pus_receiver,
} }
} }
@@ -114,18 +105,3 @@ impl TcSourceTaskDynamic {
} }
} }
} }
#[allow(dead_code)]
pub enum TcSourceTask {
Static(TcSourceTaskStatic),
Heap(TcSourceTaskDynamic),
}
impl TcSourceTask {
pub fn periodic_operation(&mut self) {
match self {
TcSourceTask::Static(task) => task.periodic_operation(),
TcSourceTask::Heap(task) => task.periodic_operation(),
}
}
}

View File

@@ -3,34 +3,30 @@ use std::{
sync::mpsc::{self}, sync::mpsc::{self},
}; };
use arbitrary_int::{u11, u14};
use log::info; use log::info;
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
use satrs::{ use satrs::{
pool::PoolProvider, pool::PoolProvider,
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
spacepackets::{ spacepackets::{
ecss::{tm::PusTmZeroCopyWriter, PusPacket}, ecss::{tm::PusTmZeroCopyWriter, PusPacket},
seq_count::SequenceCounter,
seq_count::SequenceCounterCcsdsSimple,
time::cds::MIN_CDS_FIELD_LEN, time::cds::MIN_CDS_FIELD_LEN,
CcsdsPacket, CcsdsPacket,
}, },
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
}; };
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()
)
} }
} }
@@ -59,7 +55,7 @@ impl TmFunnelCommon {
); );
let entry = self let entry = self
.msg_counter_map .msg_counter_map
.entry(zero_copy_writer.service_type_id()) .entry(zero_copy_writer.service())
.or_insert(0); .or_insert(0);
zero_copy_writer.set_msg_count(*entry); zero_copy_writer.set_msg_count(*entry);
if *entry == u16::MAX { if *entry == u16::MAX {
@@ -76,8 +72,8 @@ impl TmFunnelCommon {
fn packet_printout(tm: &PusTmZeroCopyWriter) { fn packet_printout(tm: &PusTmZeroCopyWriter) {
info!( info!(
"Sending PUS TM[{},{}] with APID {}", "Sending PUS TM[{},{}] with APID {}",
tm.service_type_id(), tm.service(),
tm.message_subtype_id(), tm.subservice(),
tm.apid() tm.apid()
); );
} }
@@ -90,7 +86,6 @@ pub struct TmSinkStatic {
tm_server_tx: mpsc::SyncSender<PacketInPool>, tm_server_tx: mpsc::SyncSender<PacketInPool>,
} }
#[allow(dead_code)]
impl TmSinkStatic { impl TmSinkStatic {
pub fn new( pub fn new(
shared_tm_store: SharedPacketPool, shared_tm_store: SharedPacketPool,
@@ -115,7 +110,7 @@ impl TmSinkStatic {
let mut tm_copy = Vec::new(); let mut tm_copy = Vec::new();
pool_guard pool_guard
.modify(&pus_tm_in_pool.store_addr, |buf| { .modify(&pus_tm_in_pool.store_addr, |buf| {
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN, true) let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed"); .expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer); self.common.apply_packet_processing(zero_copy_writer);
tm_copy = buf.to_vec() tm_copy = buf.to_vec()
@@ -134,15 +129,14 @@ impl TmSinkStatic {
pub struct TmSinkDynamic { pub struct TmSinkDynamic {
common: TmFunnelCommon, common: TmFunnelCommon,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>, tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::SyncSender<PacketAsVec>, tm_server_tx: mpsc::Sender<PacketAsVec>,
} }
#[allow(dead_code)]
impl TmSinkDynamic { impl TmSinkDynamic {
pub fn new( pub fn new(
sync_tm_tcp_source: SyncTcpTmSource, sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>, tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::SyncSender<PacketAsVec>, tm_server_tx: mpsc::Sender<PacketAsVec>,
) -> Self { ) -> Self {
Self { Self {
common: TmFunnelCommon::new(sync_tm_tcp_source), common: TmFunnelCommon::new(sync_tm_tcp_source),
@@ -155,8 +149,7 @@ impl TmSinkDynamic {
if let Ok(mut tm) = self.tm_funnel_rx.recv() { if let Ok(mut tm) = self.tm_funnel_rx.recv() {
// Read the TM, set sequence counter and message counter, and finally update // Read the TM, set sequence counter and message counter, and finally update
// the CRC. // the CRC.
let zero_copy_writer = let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN, true)
.expect("Creating TM zero copy writer failed"); .expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer); self.common.apply_packet_processing(zero_copy_writer);
self.common.sync_tm_tcp_source.add_tm(&tm.packet); self.common.sync_tm_tcp_source.add_tm(&tm.packet);
@@ -166,18 +159,3 @@ impl TmSinkDynamic {
} }
} }
} }
#[allow(dead_code)]
pub enum TmSink {
Static(TmSinkStatic),
Heap(TmSinkDynamic),
}
impl TmSink {
pub fn operation(&mut self) {
match self {
TmSink::Static(static_sink) => static_sink.operation(),
TmSink::Heap(dynamic_sink) => dynamic_sink.operation(),
}
}
}

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

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]

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 = ".."

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;

View File

@@ -9,18 +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.27", 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"] }
[dependencies.nexosim] [dependencies.asynchronix]
version = "0.3.1" version = "0.2.1"
git = "https://github.com/asynchronics/asynchronix.git"
branch = "main"
features = ["serde"]
[dependencies.satrs] [dependencies.satrs]
path = "../satrs" path = "../satrs"
[dev-dependencies] [dev-dependencies]
delegate = "0.13" delegate = "0.12"

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"/>

View File

@@ -1,8 +1,8 @@
use std::{f32::consts::PI, sync::mpsc, time::Duration}; use std::{f32::consts::PI, sync::mpsc, time::Duration};
use nexosim::{ use asynchronix::{
model::{Context, Model}, model::{Model, Output},
ports::Output, time::Scheduler,
}; };
use satrs::power::SwitchStateBinary; use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
@@ -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")
} }
@@ -199,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);
@@ -222,21 +222,21 @@ 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 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();
@@ -271,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());
} }
@@ -286,7 +286,7 @@ pub mod tests {
.send_request(request) .send_request(request)
.expect("sending MGM request failed"); .expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step().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();
@@ -307,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();
@@ -338,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,
@@ -347,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 {

View File

@@ -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.addr_wrapper.pcdu_addr, &self.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
.process_event(
MagnetorquerModel::apply_torque, MagnetorquerModel::apply_torque,
(duration, dipole), (duration, dipole),
&self.addr_wrapper.mgt_addr, &self.mgt_addr,
) ),
.unwrap(), MgtRequest::RequestHk => self.simulation.send_event(
MgtRequest::RequestHk => self
.simulation
.process_event(
MagnetorquerModel::request_housekeeping_data, MagnetorquerModel::request_housekeeping_data,
(), (),
&self.addr_wrapper.mgt_addr, &self.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();

View File

@@ -1,8 +1,8 @@
use std::{sync::mpsc, time::Duration}; use std::{sync::mpsc, time::Duration};
use nexosim::{ use asynchronix::{
model::{Context, Model}, model::{Model, Output},
ports::Output, time::Scheduler,
}; };
use satrs::power::SwitchStateBinary; use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
@@ -14,8 +14,7 @@ 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>,
} }
@@ -24,15 +23,15 @@ 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
.schedule_event(
Duration::from_millis(SWITCH_INFO_DELAY_MS), Duration::from_millis(SWITCH_INFO_DELAY_MS),
Self::send_switch_info, Self::send_switch_info,
(), (),
@@ -57,7 +56,7 @@ impl PcduModel {
*val = switch_and_target_state.1; *val = switch_and_target_state.1;
match switch_and_target_state.0 { match switch_and_target_state.0 {
PcduSwitch::Mgm => { PcduSwitch::Mgm => {
self.mgm_0_switch.send(switch_and_target_state.1).await; self.mgm_switch.send(switch_and_target_state.1).await;
} }
PcduSwitch::Mgt => { PcduSwitch::Mgt => {
self.mgt_switch.send(switch_and_target_state.1).await; self.mgt_switch.send(switch_and_target_state.1).await;
@@ -93,7 +92,7 @@ pub(crate) mod tests {
.send_request(request) .send_request(request)
.expect("sending MGM switch request failed"); .expect("sending MGM switch request failed");
sim_testbench.handle_sim_requests_time_agnostic(); sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step().unwrap(); sim_testbench.step();
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -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();
@@ -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();

View File

@@ -1,12 +1,11 @@
use nexosim::time::MonotonicTime; use asynchronix::time::MonotonicTime;
use num_enum::{IntoPrimitive, TryFromPrimitive}; 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,
} }
@@ -237,7 +236,7 @@ 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: PcduSwitch, switch: PcduSwitch,
@@ -278,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
@@ -369,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 {
@@ -419,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;
} }
} }

View File

@@ -1,8 +1,8 @@
use acs::{MagnetometerModel, MagnetorquerModel}; use acs::{MagnetometerModel, MagnetorquerModel};
use controller::{ModelAddrWrapper, SimController}; use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{MonotonicTime, SystemClock};
use controller::SimController;
use eps::PcduModel; use eps::PcduModel;
use nexosim::simulation::{Mailbox, SimInit};
use nexosim::time::{MonotonicTime, SystemClock};
use satrs_minisim::udp::SIM_CTRL_PORT; use satrs_minisim::udp::SIM_CTRL_PORT;
use satrs_minisim::{SimReply, SimRequest}; use satrs_minisim::{SimReply, SimRequest};
use std::sync::mpsc; use std::sync::mpsc;
@@ -31,15 +31,11 @@ fn create_sim_controller(
request_receiver: mpsc::Receiver<SimRequest>, request_receiver: mpsc::Receiver<SimRequest>,
) -> SimController { ) -> SimController {
// Instantiate models and their mailboxes. // Instantiate models and their mailboxes.
let mgm_0_model = let mgm_model =
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
let mgm_1_model =
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone()); MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
let mgm_0_mailbox = Mailbox::new(); let mgm_mailbox = Mailbox::new();
let mgm_0_addr = mgm_0_mailbox.address(); let mgm_addr = mgm_mailbox.address();
let mgm_1_mailbox = Mailbox::new();
let mgm_1_addr = mgm_1_mailbox.address();
let pcdu_mailbox = Mailbox::new(); let pcdu_mailbox = Mailbox::new();
let pcdu_addr = pcdu_mailbox.address(); let pcdu_addr = pcdu_mailbox.address();
let mgt_mailbox = Mailbox::new(); let mgt_mailbox = Mailbox::new();
@@ -47,11 +43,8 @@ fn create_sim_controller(
let mut pcdu_model = PcduModel::new(reply_sender.clone()); let mut pcdu_model = PcduModel::new(reply_sender.clone());
pcdu_model pcdu_model
.mgm_0_switch .mgm_switch
.connect(MagnetometerModel::switch_device, &mgm_0_addr); .connect(MagnetometerModel::switch_device, &mgm_addr);
pcdu_model
.mgm_1_switch
.connect(MagnetometerModel::switch_device, &mgm_1_addr);
let mut mgt_model = MagnetorquerModel::new(reply_sender.clone()); let mut mgt_model = MagnetorquerModel::new(reply_sender.clone());
// Input connections. // Input connections.
@@ -59,14 +52,9 @@ fn create_sim_controller(
.mgt_switch .mgt_switch
.connect(MagnetorquerModel::switch_device, &mgt_addr); .connect(MagnetorquerModel::switch_device, &mgt_addr);
// Output connections. // Output connections.
mgt_model.gen_magnetic_field.connect( mgt_model
MagnetometerModel::apply_external_magnetic_field, .gen_magnetic_field
&mgm_0_addr, .connect(MagnetometerModel::apply_external_magnetic_field, &mgm_addr);
);
mgt_model.gen_magnetic_field.connect(
MagnetometerModel::apply_external_magnetic_field,
&mgm_1_addr,
);
// Instantiate the simulator // Instantiate the simulator
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now()); let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
@@ -75,21 +63,19 @@ fn create_sim_controller(
} else { } else {
SimInit::new() SimInit::new()
}; };
let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr); let simulation = sim_init
let (simulation, scheduler) = sim_init .add_model(mgm_model, mgm_mailbox)
.add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model") .add_model(pcdu_model, pcdu_mailbox)
.add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model") .add_model(mgt_model, mgt_mailbox)
.add_model(pcdu_model, pcdu_mailbox, "PCDU model") .init(start_time);
.add_model(mgt_model, mgt_mailbox, "MGT model")
.init(start_time)
.unwrap();
SimController::new( SimController::new(
sys_clock, sys_clock,
request_receiver, request_receiver,
reply_sender, reply_sender,
simulation, simulation,
scheduler, mgm_addr,
addrs, pcdu_addr,
mgt_addr,
) )
} }
@@ -130,7 +116,7 @@ fn main() {
let mut udp_server = let mut udp_server =
SimUdpServer::new(SIM_CTRL_PORT, request_sender, reply_receiver, 200, None) SimUdpServer::new(SIM_CTRL_PORT, request_sender, reply_receiver, 200, None)
.expect("could not create UDP request server"); .expect("could not create UDP request server");
log::info!("starting UDP server on port {SIM_CTRL_PORT}"); log::info!("starting UDP server on port {}", SIM_CTRL_PORT);
// This thread manages the simulator UDP server. // This thread manages the simulator UDP server.
let udp_tc_thread = thread::spawn(move || { let udp_tc_thread = thread::spawn(move || {
udp_server.run(); udp_server.run();

View File

@@ -1,10 +1,7 @@
use delegate::delegate; use delegate::delegate;
use std::sync::mpsc; use std::{sync::mpsc, time::Duration};
use nexosim::{ use asynchronix::time::MonotonicTime;
simulation::ExecutionError,
time::{Deadline, MonotonicTime},
};
use satrs_minisim::{SimReply, SimRequest}; use satrs_minisim::{SimReply, SimRequest};
use crate::{controller::SimController, create_sim_controller, ThreadingModel}; use crate::{controller::SimController, create_sim_controller, ThreadingModel};
@@ -38,8 +35,8 @@ impl SimTestbench {
pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime); pub fn handle_sim_requests(&mut self, old_timestamp: MonotonicTime);
} }
to self.sim_controller.simulation { to self.sim_controller.simulation {
pub fn step(&mut self) -> Result<(), ExecutionError>; pub fn step(&mut self);
pub fn step_until(&mut self, duration: impl Deadline) -> Result<(), ExecutionError>; pub fn step_by(&mut self, duration: Duration);
} }
} }

View File

@@ -1,4 +1,4 @@
use nexosim::time::MonotonicTime; use asynchronix::time::MonotonicTime;
pub fn current_millis(time: MonotonicTime) -> u64 { pub fn current_millis(time: MonotonicTime) -> u64 {
(time.as_secs() as u64 * 1000) + (time.subsec_nanos() as u64 / 1_000_000) (time.as_secs() as u64 * 1000) + (time.subsec_nanos() as u64 / 1_000_000)

View File

@@ -8,26 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.2.4] 2025-11-06
`spacepackets` v0.17.0
# [v0.2.3] 2025-07-22
`spacepackets` range v0.14 to v0.15
# [v0.2.2] 2025-05-10
- Bump to `spacepackests` v0.14
# [v0.2.1] 2024-11-15
Increased allowed spacepackets to v0.13
# [v0.2.0] 2024-11-04
Semver bump, due to added features in v0.1.4
# [v0.1.4] 2024-04-24 # [v0.1.4] 2024-04-24
## Added ## Added
@@ -53,8 +33,3 @@ Allow `spacepackets` range starting with v0.10 and v0.11.
# [v0.1.0] 2024-02-12 # [v0.1.0] 2024-02-12
Initial release. Initial release.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.4...HEAD
[v0.2.3]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.3...satrs-shared-v0.2.4
[v0.2.3]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.1...satrs-shared-v0.2.3
[v0.2.2]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-shared-v0.2.1...satrs-shared-v0.2.2

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "satrs-shared" name = "satrs-shared"
description = "Components shared by multiple sat-rs crates" description = "Components shared by multiple sat-rs crates"
version = "0.2.4" version = "0.1.4"
edition = "2021" edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@@ -11,14 +11,23 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
spacepackets = { version = "0.17", default-features = false }
serde = { version = "1", default-features = false, optional = true } [dependencies.serde]
defmt = {version = "1", optional = true } version = "1"
default-features = false
optional = true
[dependencies.defmt]
version = "0.3"
optional = true
[dependencies.spacepackets]
version = ">0.9, <=0.11"
default-features = false
[features] [features]
serde = ["dep:serde", "spacepackets/serde"] serde = ["dep:serde", "spacepackets/serde"]
defmt = ["dep:defmt", "spacepackets/defmt"] spacepackets = ["dep:defmt", "spacepackets/defmt"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
rustdoc-args = ["--generate-link-to-definition"]

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

View File

@@ -1,23 +0,0 @@
Checklist for new releases
=======
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs by running
`docs.sh`.
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.
4. Run `cargo test --all-features` or `cargo nextest r --all-features` and `cargo test --doc`.
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
targets.
# Release
1. `cargo publish`
# Post-Release
1. Create a new release on `EGit` with the name `satrs-<version>`.

View File

@@ -1,4 +1,4 @@
//! This crates contains modules shared among other sat-rs framework crates. //! This crates contains modules shared among other sat-rs framework crates.
#![no_std] #![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docs_rs, feature(doc_auto_cfg))]
pub mod res_code; pub mod res_code;

View File

@@ -14,7 +14,6 @@ pub struct ResultU16 {
} }
impl ResultU16 { impl ResultU16 {
#[inline]
pub const fn new(group_id: u8, unique_id: u8) -> Self { pub const fn new(group_id: u8, unique_id: u8) -> Self {
Self { Self {
group_id, group_id,
@@ -22,22 +21,18 @@ impl ResultU16 {
} }
} }
#[inline] pub fn raw(&self) -> u16 {
pub const fn raw(&self) -> u16 {
((self.group_id as u16) << 8) | self.unique_id as u16 ((self.group_id as u16) << 8) | self.unique_id as u16
} }
#[inline] pub fn group_id(&self) -> u8 {
pub const fn group_id(&self) -> u8 {
self.group_id self.group_id
} }
#[inline] pub fn unique_id(&self) -> u8 {
pub const fn unique_id(&self) -> u8 {
self.unique_id self.unique_id
} }
#[inline]
pub fn from_be_bytes(bytes: [u8; 2]) -> Self { pub fn from_be_bytes(bytes: [u8; 2]) -> Self {
Self::from(u16::from_be_bytes(bytes)) Self::from(u16::from_be_bytes(bytes))
} }
@@ -56,7 +51,6 @@ impl From<ResultU16> for EcssEnumU16 {
} }
impl UnsignedEnum for ResultU16 { impl UnsignedEnum for ResultU16 {
#[inline]
fn size(&self) -> usize { fn size(&self) -> usize {
core::mem::size_of::<u16>() core::mem::size_of::<u16>()
} }
@@ -73,14 +67,12 @@ impl UnsignedEnum for ResultU16 {
Ok(self.size()) Ok(self.size())
} }
#[inline] fn value(&self) -> u64 {
fn value_raw(&self) -> u64 {
self.raw() as u64 self.raw() as u64
} }
} }
impl EcssEnumeration for ResultU16 { impl EcssEnumeration for ResultU16 {
#[inline]
fn pfc(&self) -> u8 { fn pfc(&self) -> u8 {
16 16
} }

View File

@@ -8,58 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.3.0-alpha.3] 2025-11-06
- Bump `sat-rs` edition to 2024.
- Bumped `spacepackets` to v0.17
- `ComponentId` is u32 now
- Simplified TCP servers
## Changed
Some trait renaming to be more in-line with Rust naming conventions.
- `EventTmHookProvider` -> `EventTmHook`
- `ActiveRequestProvider` -> `ActiveRequest`
- `EcssTcInMemConversionProvider` -> `CacheAndReadRawEcssTc`
- `ActiveRequestMapProvider` -> `ActiveRequestStore`
- `CountdownProvider` -> `Countdown`
# [v0.3.0-alpha.2] 2025-07-22
`satrs-shared` update
# [v0.3.0-alpha.1] 2025-07-22
`spacepackets` range v0.14 to v0.15
# [v0.3.0-alpha.0] 2025-02-18
`spacepackets` v0.13
## Changed ## Changed
- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new - Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new
`new` implementation expects a type struct instead of tuples. `new` implementation expects a type struct instead of tuples.
- Moved `cfdp` module to [dedicated crate](https://egit.irs.uni-stuttgart.de/rust/cfdp)
- Moved `seq_count` module to [spacepackets](https://egit.irs.uni-stuttgart.de/rust/spacepackets)
crate
## Added ## Added
- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers. - `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers.
- Scheduling table for systems with a standard runtime
- Mode Tree Feature which allows building a network of mode components which can send mode
messages to each other.
- Added first helper features like the `SubsystemExecutionHelper` and the
`SubsystemCommandingHelper` which allows to build subsystem components. Subsystem components
are able to execute mode sequences and perform target keeping based on a declarative table
format.
- Added `DevManagerCommandingHelper` which performs some of the boilerplate logik required
by Assembly and Device Management components. This includes forwarding mode requests and
handling mode replies.
- First basic health module with `HealthState`s and the `HealthTableProvider` trait. These
components are important for any FDIR components which get added in the future.
# [v0.2.1] 2024-05-19 # [v0.2.1] 2024-05-19
@@ -223,9 +179,3 @@ docs-rs hotfix
# [v0.1.0] 2024-02-12 # [v0.1.0] 2024-02-12
Initial release. Initial release.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.3...HEAD
[v0.3.0-alpha.3]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.2...satrs-v0.3.0-alpha.3
[v0.3.0-alpha.2]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.1...satrs-v0.3.0-alpha.2
[v0.3.0-alpha.1]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.3.0-alpha.0...satrs-v0.3.0-alpha.1
[v0.3.0-alpha.0]: https://egit.irs.uni-stuttgart.de/rust/sat-rs/compare/satrs-v0.2.1...satrs-v0.3.0-alpha.0

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "satrs" name = "satrs"
version = "0.3.0-alpha.3" version = "0.2.1"
edition = "2024" edition = "2021"
rust-version = "1.85.0" rust-version = "1.71.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "A library collection to build software for remote systems" description = "A framework to build software for remote systems"
homepage = "https://github.com/us-irs/sat-rs" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["no-std", "space", "aerospace"] keywords = ["no-std", "space", "aerospace"]
@@ -13,58 +13,110 @@ keywords = ["no-std", "space", "aerospace"]
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"] categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
[dependencies] [dependencies]
satrs-shared = { version = "0.2", path = "../satrs-shared" } delegate = ">0.7, <=0.10"
spacepackets = { version = "0.17", default-features = false }
delegate = "0.13"
paste = "1" paste = "1"
derive-new = "0.7" derive-new = "0.6"
num_enum = { version = "0.7", default-features = false } smallvec = "1"
cobs = { version = "0.5", default-features = false } crc = "3"
thiserror = { version = "2", default-features = false }
hashbrown = { version = "0.16", optional = true } [dependencies.satrs-shared]
static_cell = { version = "2" } version = ">=0.1.3, <0.2"
heapless = { version = "0.9", optional = true }
dyn-clone = { version = "1", optional = true } [dependencies.num_enum]
downcast-rs = { version = "2", default-features = false, optional = true } version = ">0.5, <=0.7"
bus = { version = "2.2", optional = true } default-features = false
crossbeam-channel = { version = "0.5", default-features = false, optional = true }
postcard = { version = "1", features = ["alloc"] } [dependencies.spacepackets]
serde = { version = "1", default-features = false, optional = true } version = "0.11"
socket2 = { version = "0.6", features = ["all"], optional = true } default-features = false
arbitrary-int = "2"
mio = { version = "1", features = ["os-poll", "net"], optional = true } [dependencies.cobs]
defmt = { version = "1", optional = true } git = "https://github.com/robamu/cobs.rs.git"
version = "0.2.3"
branch = "all_features"
default-features = false
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.dyn-clone]
version = "1"
optional = true
[dependencies.hashbrown]
version = "0.14"
optional = true
[dependencies.heapless]
version = "0.7"
optional = true
[dependencies.downcast-rs]
version = "1.2"
default-features = false
optional = true
[dependencies.bus]
version = "2.2"
optional = true
[dependencies.crossbeam-channel]
version= "0.5"
default-features = false
optional = true
[dependencies.thiserror]
version = "1"
optional = true
[dependencies.serde]
version = "1"
default-features = false
optional = true
[dependencies.socket2]
version = "0.5.4"
features = ["all"]
optional = true
[dependencies.mio]
version = "0.8"
features = ["os-poll", "net"]
optional = true
[dependencies.defmt]
version = "0.3"
optional = true
[dev-dependencies] [dev-dependencies]
serde = "1" serde = "1"
zerocopy = "0.8" zerocopy = "0.7"
once_cell = "1" once_cell = "1"
serde_json = "1" serde_json = "1"
rand = "0.9" rand = "0.8"
tempfile = "3" tempfile = "3"
[dev-dependencies.postcard] [dev-dependencies.postcard]
version = "1" version = "1"
[features] [features]
default = ["std", "heapless"] default = ["std"]
std = [ std = [
"downcast-rs/std", "downcast-rs/std",
"alloc", "alloc",
"bus", "bus",
"postcard/use-std",
"crossbeam-channel/std", "crossbeam-channel/std",
"serde/std", "serde/std",
"spacepackets/std", "spacepackets/std",
"num_enum/std", "num_enum/std",
"thiserror/std", "thiserror",
"socket2", "socket2",
"mio" "mio"
] ]
alloc = [ alloc = [
"serde/alloc", "serde/alloc",
"cobs/alloc",
"spacepackets/alloc", "spacepackets/alloc",
"hashbrown", "hashbrown",
"dyn-clone", "dyn-clone",
@@ -72,14 +124,11 @@ alloc = [
] ]
serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"] serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"]
crossbeam = ["crossbeam-channel"] crossbeam = ["crossbeam-channel"]
heapless = ["dep:heapless"]
defmt = ["dep:defmt", "spacepackets/defmt"] defmt = ["dep:defmt", "spacepackets/defmt"]
test_util = [] test_util = []
doc-images = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--generate-link-to-definition"] rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
[[test]]
name = "event_test"
path = "tests/pus_events.rs"
required-features = ["test_util"]

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