Compare commits

...

40 Commits

Author SHA1 Message Date
f3e0609910 Merge pull request 'Mode Sequences and Tables' (#211) from mode-tables-sequence-tables into main
Reviewed-on: #211
2025-02-13 12:44:28 +01:00
05106354e3 Mode Tree Feature Update 2025-02-13 12:18:05 +01:00
719b70d834 Merge pull request 'use static cell instead of custom struct' (#220) from use-static-cell into main
Reviewed-on: #220
2025-02-07 16:21:23 +01:00
d4082fa098 use static cell instead of custom struct 2025-02-07 16:15:23 +01:00
c9c9e4f735 Merge pull request 'update STM32H7 project' (#221) from update-stm32h7-project into main
Reviewed-on: #221
2025-02-07 16:15:08 +01:00
2c92a95184
update STM32H7 project 2025-02-07 15:43:03 +01:00
1a1d330814 Merge pull request 'update some outdated dependencies' (#219) from update-outdated-deps into main
Reviewed-on: #219
2025-01-31 18:37:49 +01:00
ef7b5d66fe
update some outdated dependencies 2025-01-31 18:37:12 +01:00
b3497e0592 Merge pull request 'update embedded examples' (#218) from update-embedded-examples into main
Reviewed-on: #218
2025-01-31 18:32:41 +01:00
73286e36c2
update embedded examples 2025-01-31 18:20:06 +01:00
ed266a11f6 Merge pull request 'Avoid static muts for static pool buffer declaration' (#215) from avoid-static-muts-static-pools into main
Reviewed-on: #215
2025-01-31 12:12:57 +01:00
af972e174f
macro fix 2025-01-31 12:04:40 +01:00
3d12083c16
use full path to helper type 2025-01-31 11:56:38 +01:00
e8d2c020fa Merge pull request 'use released nexosim version' (#217) from use-released-nexosim into main
Reviewed-on: #217
2025-01-31 11:33:56 +01:00
69e172b633 avoid static muts for static pools 2025-01-31 11:27:09 +01:00
e1dda751bc
use released nexosim version 2025-01-31 11:24:56 +01:00
b01628d8ef Merge pull request 'fix minisim tests' (#216) from update-minisim into main
Reviewed-on: #216
2025-01-31 11:18:03 +01:00
31844e4fe2 fix tests 2025-01-31 11:17:39 +01:00
738872f421 Merge pull request 'update mini simulator' (#214) from update-minisim into main
Reviewed-on: #214
2025-01-30 18:57:27 +01:00
309e39999f
small tweak 2025-01-30 18:56:44 +01:00
1c43c3adf9 update mini simulator 2025-01-30 18:55:47 +01:00
d9e0abffa7 Merge pull request 'clippy and test fix' (#213) from clippy-fixes into main
Reviewed-on: #213
2025-01-23 17:22:46 +01:00
abec9dd448
clippy and test fix 2025-01-23 17:19:26 +01:00
c18fbb59ad Merge pull request 're-run formatter' (#212) from run-afmt into main
Reviewed-on: #212
2025-01-23 17:04:58 +01:00
c91ddcd658
re-run formatter 2025-01-23 17:02:16 +01:00
c5fa1955d7 Merge pull request 'Scheduling Table for std runtimes' (#210) from scheduling-table into main
Reviewed-on: #210
2024-11-20 17:22:08 +01:00
c9708810e6
changelog 2024-11-20 17:21:54 +01:00
79d0c2e222 Scheduling Table for std runtimes 2024-11-20 17:20:42 +01:00
8e87875c0e Merge pull request 'use released satrs-shared' (#209) from use-released-satrs-shared into main
Reviewed-on: #209
2024-11-15 12:15:06 +01:00
1ac6c02c06
use released satrs-shared 2024-11-15 12:14:33 +01:00
6e7907522e Merge pull request 'changelog satrs-shared' (#208) from satrs-shared-changelog into main
Reviewed-on: #208
2024-11-15 12:08:27 +01:00
f747a5efdc
changelog satrs-shared 2024-11-15 12:07:32 +01:00
6ffd55cec2 Merge pull request 'bump satrs-shared' (#207) from satrs-shared-v0.2.1 into main
Reviewed-on: #207
2024-11-15 11:59:00 +01:00
5e51b3de42
bump satrs-shared 2024-11-15 11:52:48 +01:00
bd059a2541 Merge pull request 'Bump dependecies' (#206) from bump-dependencies into main
Reviewed-on: #206
2024-11-15 11:49:19 +01:00
1a4d764f25 bump dependencies 2024-11-15 11:37:14 +01:00
b38c617fae Merge pull request 'clippy fixes' (#205) from small-clippy-fix into main
Reviewed-on: #205
2024-11-04 13:55:27 +01:00
edcd5491f1 clippy fixes 2024-11-04 13:51:36 +01:00
b4cb034b73 Merge pull request 'Extract CFDP' (#202) from extract-cfdp into main
Reviewed-on: #202
2024-11-04 12:19:32 +01:00
47c86aea5c Extracted CFDP components 2024-11-04 12:18:41 +01:00
93 changed files with 6897 additions and 5701 deletions

View File

@ -11,7 +11,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check --release
- run: cargo check
# Check example with static pool configuration
- run: cargo check -p satrs-example --no-default-features
test:
name: Run Tests
@ -37,7 +39,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
- run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
- run: cargo check -p satrs --target=${{matrix.target}} --no-default-features
fmt:
name: Check formatting

View File

@ -10,6 +10,5 @@ members = [
exclude = [
"embedded-examples/stm32f3-disco-rtic",
"embedded-examples/stm32h7-rtic",
"embedded-examples/stm32h7-nucleo-rtic",
]

3
docs.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open

View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "accelerometer"
@ -11,20 +11,11 @@ dependencies = [
"micromath",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bare-metal"
@ -77,7 +68,7 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
dependencies = [
"rustc_version 0.4.0",
"rustc_version 0.4.1",
]
[[package]]
@ -88,9 +79,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"num-traits",
]
@ -103,8 +94,12 @@ checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
[[package]]
name = "cobs"
version = "0.2.3"
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "cortex-m"
@ -121,22 +116,22 @@ dependencies = [
[[package]]
name = "cortex-m-rt"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2722f5b7d6ea8583cffa4d247044e280ccbb9fe501bed56552e2ba48b02d5f3d"
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.0"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.96",
]
[[package]]
@ -165,15 +160,15 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "critical-section"
version = "1.1.2"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "darling"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
@ -181,33 +176,33 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
name = "darling_macro"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
name = "defmt"
version = "0.3.8"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0"
checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130"
dependencies = [
"bitflags",
"defmt-macros",
@ -225,22 +220,22 @@ dependencies = [
[[package]]
name = "defmt-macros"
version = "0.3.9"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb"
checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6"
dependencies = [
"defmt-parser",
"proc-macro-error",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
name = "defmt-parser"
version = "0.3.4"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f"
checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3"
dependencies = [
"thiserror",
]
@ -265,7 +260,7 @@ checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
@ -287,7 +282,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
@ -315,6 +310,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-async"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
dependencies = [
"embedded-hal 1.0.0",
]
[[package]]
name = "embedded-time"
version = "0.12.1"
@ -326,23 +330,23 @@ dependencies = [
[[package]]
name = "enumset"
version = "1.1.3"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d"
checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293"
dependencies = [
"enumset_derive",
]
[[package]]
name = "enumset_derive"
version = "0.8.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af"
checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
@ -368,21 +372,21 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-task",
@ -426,9 +430,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.5"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heapless"
@ -448,9 +452,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
@ -555,22 +559,22 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
@ -591,9 +595,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@ -602,43 +606,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
name = "portable-atomic"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"version_check",
"syn 2.0.96",
]
[[package]]
name = "proc-macro2"
version = "1.0.83"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@ -654,14 +662,14 @@ dependencies = [
[[package]]
name = "rtic"
version = "2.1.1"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c443db16326376bdd64377da268f6616d5f804aba8ce799bac7d1f7f244e9d51"
checksum = "401961431a1e491124cdd216a313fada2d395aa2b5bee2867c872fc8af7c1bc1"
dependencies = [
"atomic-polyfill",
"bare-metal 1.0.0",
"cortex-m",
"critical-section",
"portable-atomic",
"rtic-core",
"rtic-macros",
]
@ -683,38 +691,40 @@ checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
[[package]]
name = "rtic-macros"
version = "2.1.0"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a"
checksum = "ac22ab522d80079b48f46ac66ded4d349e1adf81b52430d6a74faa3a7790ed80"
dependencies = [
"indexmap",
"proc-macro-error",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
name = "rtic-monotonics"
version = "1.5.0"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7"
checksum = "f1cb90bcfdbbacf3ca37340cdab52ec2de5611c744095ef7889e9c50c233b748"
dependencies = [
"atomic-polyfill",
"cfg-if",
"cortex-m",
"embedded-hal 1.0.0",
"fugit",
"portable-atomic",
"rtic-time",
]
[[package]]
name = "rtic-time"
version = "1.3.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19"
checksum = "a7b1d853fa50dc125695414ce4510567a0d420221e455b1568cfa8c9aece9614"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
"embedded-hal-async",
"fugit",
"futures-util",
"rtic-common",
]
@ -730,11 +740,11 @@ dependencies = [
[[package]]
name = "rustc_version"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.23",
"semver 1.0.25",
]
[[package]]
@ -743,7 +753,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866fcae3b683ccc37b5ad77982483a0ee01d5dc408dea5aad2117ad404b60fe1"
dependencies = [
"cobs 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cobs 0.2.3",
"crc",
"defmt",
"delegate",
@ -769,7 +779,7 @@ dependencies = [
name = "satrs-stm32f3-disco-rtic"
version = "0.1.0"
dependencies = [
"cobs 0.2.3 (git+https://github.com/robamu/cobs.rs.git?branch=all_features)",
"cobs 0.3.0",
"cortex-m",
"cortex-m-rt",
"cortex-m-semihosting",
@ -798,9 +808,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.23"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]]
name = "semver-parser"
@ -922,9 +932,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.65"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
@ -933,22 +943,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.61"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]
[[package]]
@ -959,9 +969,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "usb-device"
@ -977,9 +987,9 @@ checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "void"
@ -998,9 +1008,9 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
@ -1008,11 +1018,11 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.65",
"syn 2.0.96",
]

View File

@ -22,12 +22,11 @@ version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "1"
version = "2"
features = ["cortex-m-systick"]
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
branch = "all_features"
version = "0.3"
default-features = false
[dependencies.stm32f3xx-hal]

View File

@ -15,8 +15,8 @@ use rtic::app;
use heapless::{mpmc::Q8, Vec};
#[allow(unused_imports)]
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::ExtU32;
use rtic_monotonics::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::prelude::*;
use satrs::seq_count::SequenceCountProviderCore;
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
use stm32f3xx_hal::dma::dma1;
@ -245,8 +245,6 @@ pub fn convert_pus_tc_to_request(
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)]
@ -259,6 +257,8 @@ mod app {
#[allow(dead_code)]
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
systick_monotonic!(Mono, 1000);
#[shared]
struct Shared {
blink_freq: MillisDurationU32,
@ -279,8 +279,7 @@ mod app {
let mut rcc = cx.device.RCC.constrain();
// Initialize the systick interrupt & obtain the token to prove that we did
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
Mono::start(cx.core.SYST, 8_000_000);
let mut flash = cx.device.FLASH.constrain();
let clocks = rcc
@ -394,7 +393,7 @@ mod app {
}
}
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
Systick::delay(current_blink_freq).await;
Mono::delay(current_blink_freq).await;
}
}
@ -412,15 +411,14 @@ mod app {
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();
let elapsed_ms = (Mono::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;
Mono::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis()).await;
}
}
} else {
// Check for completion after 1 ms
Systick::delay(1.millis()).await;
Mono::delay(1.millis()).await;
continue;
}
if let Some(vec) = TM_REQUESTS.dequeue() {
@ -459,11 +457,11 @@ mod app {
UartTxState::Transmitting(_) => (),
});
// Check for completion after 1 ms
Systick::delay(1.millis()).await;
Mono::delay(1.millis()).await;
continue;
}
// Nothing to do, and we are idle.
Systick::delay(TX_HANDLER_FREQ_MS.millis()).await;
Mono::delay(TX_HANDLER_FREQ_MS.millis()).await;
}
}
@ -657,7 +655,7 @@ mod app {
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
// We cache the last completed time to ensure that there is a minimum delay between consecutive
// transferred packets.
tx_shared.last_completed = Some(Systick::now());
tx_shared.last_completed = Some(Mono::now());
}
}
});

View File

@ -1,21 +1,12 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
version = 4
[[package]]
name = "autocfg"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bare-metal"
@ -23,7 +14,7 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version 0.2.3",
"rustc_version",
]
[[package]]
@ -32,6 +23,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bitfield"
version = "0.13.2"
@ -64,8 +61,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cobs"
version = "0.2.3"
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "const-default"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa"
[[package]]
name = "cortex-m"
@ -82,22 +89,22 @@ dependencies = [
[[package]]
name = "cortex-m-rt"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2722f5b7d6ea8583cffa4d247044e280ccbb9fe501bed56552e2ba48b02d5f3d"
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.0"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.96",
]
[[package]]
@ -126,15 +133,15 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "critical-section"
version = "1.1.2"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "defmt"
version = "0.3.8"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0"
checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130"
dependencies = [
"bitflags",
"defmt-macros",
@ -152,22 +159,22 @@ dependencies = [
[[package]]
name = "defmt-macros"
version = "0.3.9"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb"
checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6"
dependencies = [
"defmt-parser",
"proc-macro-error",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
name = "defmt-parser"
version = "0.3.4"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f"
checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3"
dependencies = [
"thiserror",
]
@ -192,39 +199,41 @@ checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
name = "delegate"
version = "0.10.0"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee5df75c70b95bd3aacc8e2fd098797692fb1d54121019c4de481e42f04c8a1"
checksum = "297806318ef30ad066b15792a8372858020ae3ca2e414ee6c2133b1eb9e9e945"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.96",
]
[[package]]
name = "derive-new"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
name = "embedded-alloc"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815"
checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd"
dependencies = [
"const-default",
"critical-section",
"linked_list_allocator",
"rlsf",
]
[[package]]
@ -300,21 +309,21 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-task",
@ -328,15 +337,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hash32"
version = "0.3.1"
@ -348,22 +348,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.5"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32 0.2.1",
"rustc_version 0.4.0",
"spin",
"stable_deref_trait",
]
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heapless"
@ -372,36 +359,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"defmt",
"hash32 0.3.1",
"hash32",
"stable_deref_trait",
]
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "managed"
version = "0.8.0"
@ -434,22 +417,22 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
@ -470,9 +453,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@ -482,62 +465,72 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.6.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"version_check",
"syn 2.0.96",
]
[[package]]
name = "proc-macro2"
version = "1.0.82"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rtic"
version = "2.1.1"
name = "rlsf"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c443db16326376bdd64377da268f6616d5f804aba8ce799bac7d1f7f244e9d51"
checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf"
dependencies = [
"cfg-if",
"const-default",
"libc",
"svgbobdoc",
]
[[package]]
name = "rtic"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "401961431a1e491124cdd216a313fada2d395aa2b5bee2867c872fc8af7c1bc1"
dependencies = [
"atomic-polyfill",
"bare-metal 1.0.0",
"cortex-m",
"critical-section",
"portable-atomic",
"rtic-core",
"rtic-macros",
]
@ -559,28 +552,27 @@ checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
[[package]]
name = "rtic-macros"
version = "2.1.0"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a"
checksum = "ac22ab522d80079b48f46ac66ded4d349e1adf81b52430d6a74faa3a7790ed80"
dependencies = [
"indexmap",
"proc-macro-error",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
name = "rtic-monotonics"
version = "1.5.0"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7"
checksum = "f1cb90bcfdbbacf3ca37340cdab52ec2de5611c744095ef7889e9c50c233b748"
dependencies = [
"atomic-polyfill",
"cfg-if",
"cortex-m",
"embedded-hal 1.0.0",
"fugit",
"portable-atomic",
"rtic-time",
]
@ -595,18 +587,21 @@ dependencies = [
"embedded-hal 1.0.0",
"embedded-hal-async",
"embedded-hal-bus",
"heapless 0.8.0",
"heapless",
"portable-atomic",
"rtic-common",
]
[[package]]
name = "rtic-time"
version = "1.3.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19"
checksum = "a7b1d853fa50dc125695414ce4510567a0d420221e455b1568cfa8c9aece9614"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
"embedded-hal-async",
"fugit",
"futures-util",
"rtic-common",
]
@ -617,16 +612,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.23",
"semver",
]
[[package]]
@ -638,20 +624,22 @@ dependencies = [
"defmt",
"delegate",
"derive-new",
"heapless 0.7.17",
"heapless",
"num-traits",
"num_enum",
"paste",
"satrs-shared",
"smallvec",
"spacepackets",
"static_cell",
"thiserror",
]
[[package]]
name = "satrs-shared"
version = "0.1.4"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6042477018c2d43fffccaaa5099bc299a58485139b4d31c5b276889311e474f1"
checksum = "f10b962c131d0bcb0de06fefa5d4716cbd7f836167f90229f7f38405dc573bd3"
dependencies = [
"spacepackets",
]
@ -676,12 +664,6 @@ dependencies = [
"stm32h7xx-hal",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "0.9.0"
@ -691,12 +673,6 @@ dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "semver-parser"
version = "0.7.0"
@ -719,39 +695,41 @@ dependencies = [
"byteorder",
"cfg-if",
"defmt",
"heapless 0.8.0",
"heapless",
"managed",
]
[[package]]
name = "spacepackets"
version = "0.11.2"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e85574d113a06312010c0ba51aadccd4ba2806231ebe9a49fc6473d0534d8696"
checksum = "591d42bfa6af2ab308e5efd8f0870e2d0eb5dd19a141bdcb7da731f5ff9cc11c"
dependencies = [
"crc",
"defmt",
"delegate",
"num-traits",
"num_enum",
"paste",
"thiserror",
"zerocopy",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_cell"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e"
dependencies = [
"portable-atomic",
]
[[package]]
name = "stm32h7"
version = "0.15.1"
@ -784,6 +762,19 @@ dependencies = [
"void",
]
[[package]]
name = "svgbobdoc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50"
dependencies = [
"base64",
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-width",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -797,9 +788,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.64"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
@ -808,29 +799,35 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.61"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "vcell"
@ -838,12 +835,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "void"
version = "1.0.2"
@ -861,21 +852,20 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.34"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
"syn 2.0.96",
]

View File

@ -21,11 +21,11 @@ defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
panic-probe = { version = "0.3", features = ["print-defmt"] }
cortex-m-semihosting = "0.5.0"
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
embedded-alloc = "0.5"
embedded-alloc = "0.6"
rtic-sync = { version = "1", features = ["defmt-03"] }
[dependencies.smoltcp]
version = "0.11.0"
version = "0.11"
default-features = false
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
@ -34,7 +34,7 @@ version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "1"
version = "2"
features = ["cortex-m-systick"]
[dependencies.satrs]

View File

@ -3,8 +3,7 @@
extern crate alloc;
use rtic::app;
use rtic_monotonics::systick::Systick;
use rtic_monotonics::Monotonic;
use rtic_monotonics::systick::prelude::*;
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
use satrs::static_subpool;
// global logger + panicking-behavior + memory layout
@ -13,7 +12,7 @@ use smoltcp::socket::udp::UdpMetadata;
use smoltcp::socket::{dhcpv4, udp};
use core::mem::MaybeUninit;
use embedded_alloc::Heap;
use embedded_alloc::LlffHeap as Heap;
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
use stm32h7xx_hal::ethernet;
@ -32,6 +31,8 @@ pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_
#[global_allocator]
static HEAP: Heap = Heap::empty();
systick_monotonic!(Mono, 1000);
// We place the memory pool buffers inside the larger AXISRAM.
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
@ -72,7 +73,7 @@ impl Net {
let mut iface = Interface::new(
config,
&mut ethdev,
smoltcp::time::Instant::from_millis((Systick::now() - Systick::ZERO).to_millis()),
smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()),
);
// Create sockets
let dhcp_socket = dhcpv4::Socket::new();
@ -91,7 +92,7 @@ impl Net {
/// Polls on the ethernet interface. You should refer to the smoltcp
/// documentation for poll() to understand how to call poll efficiently
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
let uptime = Systick::now() - Systick::ZERO;
let uptime = Mono::now().duration_since_epoch();
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
self.iface.poll(timestamp, &mut self.ethdev, sockets)
@ -203,8 +204,7 @@ mod app {
use core::ptr::addr_of_mut;
use super::*;
use rtic_monotonics::systick::fugit::MillisDurationU32;
use rtic_monotonics::systick::Systick;
use rtic_monotonics::fugit::MillisDurationU32;
use satrs::spacepackets::ecss::tc::PusTcReader;
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
use stm32h7xx_hal::gpio::{Output, Pin};
@ -256,12 +256,7 @@ mod app {
.freeze(pwrcfg, &cx.device.SYSCFG);
// Initialize the systick interrupt & obtain the token to prove that we did
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(
cx.core.SYST,
ccdr.clocks.sys_ck().to_Hz(),
systick_mono_token,
);
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz());
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
// good for.
@ -377,24 +372,24 @@ mod app {
shared_pool
.grow(
unsafe { SUBPOOL_SMALL.assume_init_mut() },
unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
SUBPOOL_SMALL.get_mut().unwrap(),
SUBPOOL_SMALL_SIZES.get_mut().unwrap(),
SUBPOOL_SMALL_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
shared_pool
.grow(
unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
SUBPOOL_MEDIUM.get_mut().unwrap(),
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(),
SUBPOOL_MEDIUM_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
shared_pool
.grow(
unsafe { SUBPOOL_LARGE.assume_init_mut() },
unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
SUBPOOL_LARGE.get_mut().unwrap(),
SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
SUBPOOL_LARGE_NUM_BLOCKS,
true,
)
@ -403,7 +398,7 @@ mod app {
// Set up global allocator. Use AXISRAM for the heap.
#[link_section = ".axisram"]
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
eth_link_check::spawn().expect("eth link check failed");
blinky::spawn().expect("spawning blink task failed");
@ -435,7 +430,7 @@ mod app {
leds.led1.toggle();
leds.led2.toggle();
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
Systick::delay(current_blink_freq).await;
Mono::delay(current_blink_freq).await;
}
}
@ -457,7 +452,7 @@ mod app {
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
defmt::info!("Ethernet link down");
}
Systick::delay(100.millis()).await;
Mono::delay(100.millis()).await;
}
}
@ -483,7 +478,7 @@ mod app {
cx.local.udp.poll(sockets, pool);
})
});
Systick::delay(40.millis()).await;
Mono::delay(40.millis()).await;
}
}

View File

@ -8,18 +8,19 @@ homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
[dependencies]
fern = "0.6"
fern = "0.7"
chrono = "0.4"
log = "0.4"
crossbeam-channel = "0.5"
delegate = "0.10"
zerocopy = "0.6"
delegate = "0.13"
zerocopy = "0.8"
csv = "1"
num_enum = "0.7"
thiserror = "1"
thiserror = "2"
lazy_static = "1"
strum = { version = "0.26", features = ["derive"] }
derive-new = "0.5"
derive-new = "0.7"
cfg-if = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@ -35,8 +36,8 @@ version = "0.1.1"
path = "../satrs-mib"
[features]
dyn_tmtc = []
default = ["dyn_tmtc"]
heap_tmtc = []
default = ["heap_tmtc"]
[dev-dependencies]
env_logger = "0.11"

View File

@ -14,7 +14,7 @@ You can run the application using `cargo run`.
# Features
The example has the `dyn_tmtc` feature which is enabled by default. With this feature enabled,
The example has the `heap_tmtc` feature which is enabled by default. With this feature enabled,
TMTC packets are exchanged using the heap as the backing memory instead of pre-allocated static
stores.

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
"""Example client for the sat-rs example application"""
import logging
import sys
import time

View File

@ -12,7 +12,7 @@ authors = [
{name = "Robin Mueller", email = "robin.mueller.m@gmail.com"},
]
dependencies = [
"tmtccmd~=8.0",
"tmtccmd~=8.1",
"pydantic~=2.7"
]

View File

@ -0,0 +1,11 @@
from tmtccmd.config import CmdTreeNode
def create_acs_node(mode_node: CmdTreeNode, hk_node: CmdTreeNode) -> CmdTreeNode:
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
mgm_node = CmdTreeNode("mgms", "MGM devices node")
mgm_node.add_child(mode_node)
mgm_node.add_child(hk_node)
acs_node.add_child(mgm_node)
return acs_node

View File

@ -39,7 +39,10 @@ class EventU32:
class AcsId(enum.IntEnum):
MGM_0 = 0
SUBSYSTEM = 1
MGM_ASSEMBLY = 2
MGM_0 = 3
MGM_1 = 4
class AcsHkIds(enum.IntEnum):

View File

@ -12,7 +12,7 @@ from pytmtc.pus_tc import create_cmd_definition_tree
class SatrsConfigHook(HookBase):
def __init__(self, json_cfg_path: str):
super().__init__(json_cfg_path=json_cfg_path)
super().__init__(json_cfg_path)
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
from tmtccmd.config.com import (

View File

View File

@ -4,7 +4,7 @@ from spacepackets.ecss.pus_3_hk import Subservice
from spacepackets.ecss import PusTm
from pytmtc.common import AcsId, Apid
from pytmtc.mgms import handle_mgm_hk_report
from pytmtc.acs.mgms import handle_mgm_hk_report
_LOGGER = logging.getLogger(__name__)

View File

@ -17,8 +17,9 @@ from tmtccmd.tmtc import (
)
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
from pytmtc.acs import create_acs_node
from pytmtc.common import Apid
from pytmtc.mgms import create_mgm_cmds
from pytmtc.acs.mgms import create_mgm_cmds
_LOGGER = logging.getLogger(__name__)
@ -67,7 +68,6 @@ class TcHandler(TcHandlerBase):
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
@ -101,15 +101,7 @@ def create_cmd_definition_tree() -> CmdTreeNode:
)
)
root_node.add_child(scheduler_node)
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
mgm_node = CmdTreeNode("mgms", "MGM devices node")
mgm_node.add_child(mode_node)
mgm_node.add_child(hk_node)
acs_node.add_child(mgm_node)
root_node.add_child(acs_node)
root_node.add_child(create_acs_node(mode_node, hk_node))
return root_node

View File

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

View File

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

View File

@ -1,7 +1,8 @@
use derive_new::new;
use satrs::hk::{HkRequest, HkRequestVariant};
use satrs::mode_tree::{ModeChild, ModeNode};
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs_example::config::pus::PUS_MODE_SERVICE;
use satrs_example::{DeviceMode, TimestampHelper};
use satrs_minisim::acs::lis3mdl::{
MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
@ -15,15 +16,18 @@ use std::sync::{Arc, Mutex};
use std::time::Duration;
use satrs::mode::{
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
ModeRequestHandlerMpscBounded,
};
use satrs::pus::{EcssTmSender, PusTmVariant};
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs_example::config::components::{NO_SENDER, PUS_MODE_SERVICE};
use satrs_example::config::components::NO_SENDER;
use crate::hk::PusHkHelper;
use crate::pus::hk::{HkReply, HkReplyVariant};
use crate::requests::CompositeRequest;
use crate::spi::SpiInterface;
use crate::tmtc::sender::TmTcSender;
use serde::{Deserialize, Serialize};
@ -48,11 +52,6 @@ pub enum TransitionState {
Done,
}
pub trait SpiInterface {
type Error: Debug;
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
}
#[derive(Default)]
pub struct SpiDummyInterface {
pub dummy_values: MgmLis3RawValues,
@ -129,12 +128,6 @@ pub struct MgmData {
pub z: f32,
}
pub struct MpscModeLeafInterface {
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
pub reply_to_pus_tx: mpsc::Sender<GenericMessage<ModeReply>>,
pub reply_to_parent_tx: mpsc::SyncSender<GenericMessage<ModeReply>>,
}
#[derive(Default)]
pub struct BufWrapper {
tx_buf: [u8; 32],
@ -165,16 +158,15 @@ impl Default for ModeHelpers {
#[allow(clippy::too_many_arguments)]
pub struct MgmHandlerLis3Mdl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> {
id: UniqueApidTargetId,
dev_str: &'static str,
mode_interface: MpscModeLeafInterface,
mode_node: ModeRequestHandlerMpscBounded,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
switch_helper: SwitchHelper,
tm_sender: TmSender,
tm_sender: TmTcSender,
pub com_interface: ComInterface,
shared_mgm_set: Arc<Mutex<MgmData>>,
#[new(value = "PusHkHelper::new(id)")]
@ -189,9 +181,8 @@ pub struct MgmHandlerLis3Mdl<
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
> MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now();
@ -274,25 +265,28 @@ impl<
pub fn handle_mode_requests(&mut self) {
loop {
// TODO: Only allow one set mode request per cycle?
match self.mode_interface.request_rx.try_recv() {
Ok(msg) => {
let result = self.handle_mode_request(msg);
// TODO: Trigger event?
if result.is_err() {
log::warn!(
"{}: mode request failed with error {:?}",
self.dev_str,
result.err().unwrap()
);
}
}
Err(e) => {
if e != mpsc::TryRecvError::Empty {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
match self.mode_node.try_recv_mode_request() {
Ok(opt_msg) => {
if let Some(msg) = opt_msg {
let result = self.handle_mode_request(msg);
// TODO: Trigger event?
if result.is_err() {
log::warn!(
"{}: mode request failed with error {:?}",
self.dev_str,
result.err().unwrap()
);
}
} else {
break;
}
}
Err(e) => match e {
satrs::queue::GenericReceiveError::Empty => break,
satrs::queue::GenericReceiveError::TxDisconnected(e) => {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
}
},
}
}
}
@ -364,9 +358,8 @@ impl<
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
> ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_helpers.current
@ -375,9 +368,8 @@ impl<
impl<
ComInterface: SpiInterface,
TmSender: EcssTmSender,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, TmSender, SwitchHelper>
> ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
type Error = ModeError;
@ -385,6 +377,7 @@ impl<
&mut self,
requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode,
_forced: bool,
) -> Result<(), satrs::mode::ModeError> {
log::info!(
"{}: transitioning to mode {:?}",
@ -447,10 +440,9 @@ impl<
requestor.sender_id()
);
}
self.mode_interface
.reply_to_pus_tx
.send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
self.mode_node
.send_mode_reply(requestor, reply)
.map_err(ModeError::Send)?;
Ok(())
}
@ -463,17 +455,44 @@ impl<
}
}
impl<
ComInterface: SpiInterface,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeNode for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
fn id(&self) -> satrs::ComponentId {
self.id.into()
}
}
impl<
ComInterface: SpiInterface,
SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
> ModeChild for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
{
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
self.mode_node.add_message_target(id, reply_sender);
}
}
#[cfg(test)]
mod tests {
use std::sync::{mpsc, Arc};
use std::{
collections::HashMap,
sync::{mpsc, Arc},
};
use satrs::{
mode::{ModeReply, ModeRequest},
mode_tree::ModeParent,
power::SwitchStateBinary,
request::{GenericMessage, UniqueApidTargetId},
tmtc::PacketAsVec,
ComponentId,
};
use satrs_example::config::components::Apid;
use satrs_example::config::{acs::MGM_ASSEMBLY, components::Apid};
use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
@ -501,49 +520,88 @@ mod tests {
}
}
#[allow(dead_code)]
pub struct MgmTestbench {
pub mode_request_tx: mpsc::Sender<GenericMessage<ModeRequest>>,
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>,
pub handler:
MgmHandlerLis3Mdl<TestSpiInterface, mpsc::Sender<PacketAsVec>, TestSwitchHelper>,
pub handler: MgmHandlerLis3Mdl<TestSpiInterface, TestSwitchHelper>,
}
#[derive(Default)]
pub struct MgmAssemblyMock(
pub HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
);
impl ModeNode for MgmAssemblyMock {
fn id(&self) -> satrs::ComponentId {
PUS_MODE_SERVICE.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_SERVICE.into()
}
}
impl ModeParent for PusMock {
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
self.request_sender_map.insert(id, request_sender);
}
}
impl MgmTestbench {
pub fn new() -> Self {
let (request_tx, request_rx) = mpsc::channel();
let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::channel();
let (request_tx, request_rx) = mpsc::sync_channel(5);
let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5);
let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
let mode_interface = MpscModeLeafInterface {
request_rx,
reply_to_pus_tx: reply_tx_to_pus,
reply_to_parent_tx: reply_tx_to_parent,
};
let id = UniqueApidTargetId::new(Apid::Acs as u16, 1);
let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx);
let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (hk_reply_tx, hk_reply_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel::<PacketAsVec>();
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
let (tm_tx, tm_rx) = mpsc::sync_channel(10);
let tm_sender = TmTcSender::Heap(tm_tx);
let shared_mgm_set = Arc::default();
let mut handler = MgmHandlerLis3Mdl::new(
id,
"TEST_MGM",
mode_node,
composite_request_rx,
hk_reply_tx,
TestSwitchHelper::default(),
tm_sender,
TestSpiInterface::default(),
shared_mgm_set,
);
handler.add_mode_parent(PUS_MODE_SERVICE.into(), reply_tx_to_pus);
handler.add_mode_parent(MGM_ASSEMBLY.into(), reply_tx_to_parent);
Self {
mode_request_tx: request_tx,
mode_reply_rx_to_pus: reply_rx_to_pus,
mode_reply_rx_to_parent: reply_rx_to_parent,
composite_request_tx,
handler,
tm_rx,
hk_reply_rx,
handler: MgmHandlerLis3Mdl::new(
UniqueApidTargetId::new(Apid::Acs as u16, 1),
"TEST_MGM",
mode_interface,
composite_request_rx,
hk_reply_tx,
TestSwitchHelper::default(),
tm_tx,
TestSpiInterface::default(),
shared_mgm_set,
),
}
}
}
@ -574,7 +632,10 @@ mod tests {
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
))
.expect("failed to send mode request");
testbench.handler.periodic_operation();
@ -632,7 +693,10 @@ mod tests {
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
))
.expect("failed to send mode request");
testbench.handler.periodic_operation();

View File

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

View File

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

View File

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

View File

@ -135,6 +135,33 @@ pub mod components {
Eps = 6,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum EpsId {
Pcdu = 0,
Subsystem = 1,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TmtcId {
UdpServer = 0,
TcpServer = 1,
}
pub const EPS_SUBSYSTEM: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Subsystem as u32);
pub const PCDU_HANDLER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32);
pub const UDP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
pub const TCP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
pub const NO_SENDER: ComponentId = ComponentId::MAX;
}
pub mod pus {
use super::components::Apid;
use satrs::request::UniqueApidTargetId;
// Component IDs for components with the PUS APID.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PusId {
@ -145,23 +172,6 @@ pub mod components {
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 =
@ -176,15 +186,26 @@ pub mod components {
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Sched as u16, 0);
}
pub mod acs {
use super::components::Apid;
use satrs::request::UniqueApidTargetId;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum AcsId {
Subsystem = 1,
Assembly = 2,
Mgm0 = 3,
Mgm1 = 4,
}
pub const MGM_ASSEMBLY: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Assembly as u32);
pub const MGM_HANDLER_0: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
pub const PCDU_HANDLER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32);
pub const UDP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
pub const TCP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
pub const NO_SENDER: ComponentId = ComponentId::MAX;
pub const MGM_HANDLER_1: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
}
pub mod pool {

View File

@ -88,6 +88,7 @@ impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
}
}
#[allow(dead_code)]
#[derive(new)]
pub struct SwitchRequestInfo {
pub requestor_info: MessageMetadata,
@ -99,6 +100,7 @@ pub struct SwitchRequestInfo {
pub struct TestSwitchHelper {
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
#[allow(dead_code)]
pub switch_delay_request_count: u32,
pub next_switch_delay: Duration,
pub switch_map: RefCell<SwitchMapWrapper>,

View File

@ -8,15 +8,22 @@ use derive_new::new;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::{
hk::{HkRequest, HkRequestVariant},
mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler},
mode::{
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
ModeRequestHandlerMpscBounded,
},
mode_tree::{ModeChild, ModeNode},
power::SwitchRequest,
pus::{EcssTmSender, PusTmVariant},
queue::{GenericSendError, GenericTargetedMessagingError},
queue::GenericSendError,
request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
spacepackets::ByteConversionError,
};
use satrs_example::{
config::components::{NO_SENDER, PUS_MODE_SERVICE},
config::{
components::{NO_SENDER, PCDU_HANDLER},
pus::PUS_MODE_SERVICE,
},
DeviceMode, TimestampHelper,
};
use satrs_minisim::{
@ -28,10 +35,10 @@ use satrs_minisim::{
use serde::{Deserialize, Serialize};
use crate::{
acs::mgm::MpscModeLeafInterface,
hk::PusHkHelper,
pus::hk::{HkReply, HkReplyVariant},
requests::CompositeRequest,
tmtc::sender::TmTcSender,
};
pub trait SerialInterface {
@ -200,14 +207,14 @@ pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
/// Example PCDU device handler.
#[derive(new)]
#[allow(clippy::too_many_arguments)]
pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
pub struct PcduHandler<ComInterface: SerialInterface> {
id: UniqueApidTargetId,
dev_str: &'static str,
mode_interface: MpscModeLeafInterface,
mode_node: ModeRequestHandlerMpscBounded,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_tx: mpsc::Sender<GenericMessage<HkReply>>,
hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
tm_sender: TmSender,
tm_sender: TmTcSender,
pub com_interface: ComInterface,
shared_switch_map: Arc<Mutex<SwitchSet>>,
#[new(value = "PusHkHelper::new(id)")]
@ -220,7 +227,7 @@ pub struct PcduHandler<ComInterface: SerialInterface, TmSender: EcssTmSender> {
tm_buf: [u8; 256],
}
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInterface, TmSender> {
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
pub fn periodic_operation(&mut self, op_code: OpCode) {
match op_code {
OpCode::RegularOp => {
@ -324,25 +331,30 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInter
pub fn handle_mode_requests(&mut self) {
loop {
// TODO: Only allow one set mode request per cycle?
match self.mode_interface.request_rx.try_recv() {
Ok(msg) => {
let result = self.handle_mode_request(msg);
// TODO: Trigger event?
if result.is_err() {
log::warn!(
"{}: mode request failed with error {:?}",
self.dev_str,
result.err().unwrap()
);
}
}
Err(e) => {
if e != mpsc::TryRecvError::Empty {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
match self.mode_node.try_recv_mode_request() {
Ok(opt_msg) => {
if let Some(msg) = opt_msg {
let result = self.handle_mode_request(msg);
// TODO: Trigger event?
if result.is_err() {
log::warn!(
"{}: mode request failed with error {:?}",
self.dev_str,
result.err().unwrap()
);
}
} else {
break;
}
}
Err(e) => match e {
satrs::queue::GenericReceiveError::Empty => {
break;
}
satrs::queue::GenericReceiveError::TxDisconnected(_) => {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
}
},
}
}
}
@ -396,22 +408,19 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> PcduHandler<ComInter
}
}
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeProvider
for PcduHandler<ComInterface, TmSender>
{
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> {
fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_and_submode
}
}
impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
for PcduHandler<ComInterface, TmSender>
{
impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterface> {
type Error = ModeError;
fn start_transition(
&mut self,
requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode,
_forced: bool,
) -> Result<(), satrs::mode::ModeError> {
log::info!(
"{}: transitioning to mode {:?}",
@ -466,10 +475,9 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
requestor.sender_id()
);
}
self.mode_interface
.reply_to_pus_tx
.send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
self.mode_node
.send_mode_reply(requestor, reply)
.map_err(|_| GenericSendError::RxDisconnected)?;
Ok(())
}
@ -482,6 +490,20 @@ impl<ComInterface: SerialInterface, TmSender: EcssTmSender> ModeRequestHandler
}
}
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
fn id(&self) -> satrs::ComponentId {
PCDU_HANDLER.into()
}
}
impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
self.mode_node.add_message_target(id, reply_sender);
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
@ -489,7 +511,10 @@ mod tests {
use satrs::{
mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
};
use satrs_example::config::components::{Apid, MGM_HANDLER_0};
use satrs_example::config::{
acs::MGM_HANDLER_0,
components::{Apid, EPS_SUBSYSTEM, PCDU_HANDLER},
};
use satrs_minisim::eps::SwitchMapBinary;
use super::*;
@ -530,31 +555,41 @@ mod tests {
}
pub struct PcduTestbench {
pub mode_request_tx: mpsc::Sender<GenericMessage<ModeRequest>>,
pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>,
pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
pub handler: PcduHandler<SerialInterfaceTest, mpsc::Sender<PacketAsVec>>,
pub handler: PcduHandler<SerialInterfaceTest>,
}
impl PcduTestbench {
pub fn new() -> Self {
let (mode_request_tx, mode_request_rx) = mpsc::channel();
let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::channel();
let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5);
let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5);
let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
let mode_interface = MpscModeLeafInterface {
request_rx: mode_request_rx,
reply_to_pus_tx: mode_reply_tx_to_pus,
reply_to_parent_tx: mode_reply_tx_to_parent,
};
let mode_node =
ModeRequestHandlerMpscBounded::new(PCDU_HANDLER.into(), mode_request_rx);
let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (hk_reply_tx, hk_reply_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel::<PacketAsVec>();
let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
let mut handler = PcduHandler::new(
UniqueApidTargetId::new(Apid::Eps as u16, 0),
"TEST_PCDU",
mode_node,
composite_request_rx,
hk_reply_tx,
switch_reqest_rx,
TmTcSender::Heap(tm_tx.clone()),
SerialInterfaceTest::default(),
shared_switch_map,
);
handler.add_mode_parent(EPS_SUBSYSTEM.into(), mode_reply_tx_to_parent);
handler.add_mode_parent(PUS_MODE_SERVICE.into(), mode_reply_tx_to_pus);
Self {
mode_request_tx,
mode_reply_rx_to_pus,
@ -563,17 +598,7 @@ mod tests {
hk_reply_rx,
tm_rx,
switch_request_tx,
handler: PcduHandler::new(
UniqueApidTargetId::new(Apid::Eps as u16, 0),
"TEST_PCDU",
mode_interface,
composite_request_rx,
hk_reply_tx,
switch_reqest_rx,
tm_tx,
SerialInterfaceTest::default(),
shared_switch_map,
),
handler,
}
}
@ -660,7 +685,10 @@ mod tests {
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
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();
@ -692,7 +720,10 @@ mod tests {
.mode_request_tx
.send(GenericMessage::new(
MessageMetadata::new(0, PUS_MODE_SERVICE.id()),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
forced: false,
},
))
.expect("failed to send mode request");
testbench

View File

@ -16,7 +16,7 @@ use satrs::{
},
spacepackets::time::cds::CdsTime,
};
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
use satrs_example::config::pus::PUS_EVENT_MANAGEMENT;
use crate::update_time;

View File

@ -1,19 +1,20 @@
use std::time::Duration;
use std::{
collections::{HashSet, VecDeque},
fmt::Debug,
marker::PhantomData,
sync::{Arc, Mutex},
};
use log::{info, warn};
use satrs::tmtc::StoreAndSendError;
use satrs::{
encoding::ccsds::{SpValidity, SpacePacketValidator},
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
spacepackets::{CcsdsPacket, PacketId},
tmtc::{PacketSenderRaw, PacketSource},
tmtc::PacketSource,
};
use crate::tmtc::sender::TmTcSender;
#[derive(Default)]
pub struct ConnectionFinishedHandler {}
@ -111,31 +112,23 @@ pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
SendError,
>;
pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
pub TcpServer<TcSender, SendError>,
PhantomData<SendError>,
);
pub struct TcpTask(pub TcpServer<TmTcSender, StoreAndSendError>);
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
TcpTask<TcSender, SendError>
{
impl TcpTask {
pub fn new(
cfg: ServerConfig,
tm_source: SyncTcpTmSource,
tc_sender: TcSender,
tc_sender: TmTcSender,
valid_ids: HashSet<PacketId>,
) -> Result<Self, std::io::Error> {
Ok(Self(
TcpSpacepacketsServer::new(
cfg,
tm_source,
tc_sender,
SimplePacketValidator { valid_ids },
ConnectionFinishedHandler::default(),
None,
)?,
PhantomData,
))
Ok(Self(TcpSpacepacketsServer::new(
cfg,
tm_source,
tc_sender,
SimplePacketValidator { valid_ids },
ConnectionFinishedHandler::default(),
None,
)?))
}
pub fn periodic_operation(&mut self) {

View File

@ -1,15 +1,16 @@
use core::fmt::Debug;
use std::net::{SocketAddr, UdpSocket};
use std::sync::mpsc;
use log::{info, warn};
use satrs::pus::HandlingStatus;
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
use satrs::tmtc::{PacketAsVec, PacketInPool, StoreAndSendError};
use satrs::{
hal::std::udp_server::{ReceiveResult, UdpTcServer},
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
};
use crate::tmtc::sender::TmTcSender;
pub trait UdpTmHandler {
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
}
@ -65,21 +66,12 @@ impl UdpTmHandler for DynamicUdpTmHandler {
}
}
pub struct UdpTmtcServer<
TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError,
> {
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> {
pub udp_tc_server: UdpTcServer<TmTcSender, StoreAndSendError>,
pub tm_handler: TmHandler,
}
impl<
TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError: Debug + 'static,
> UdpTmtcServer<TcSender, TmHandler, SendError>
{
impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
pub fn periodic_operation(&mut self) {
loop {
if self.poll_tc_server() == HandlingStatus::Empty {
@ -115,7 +107,6 @@ impl<
mod tests {
use std::net::Ipv4Addr;
use std::{
cell::RefCell,
collections::VecDeque,
net::IpAddr,
sync::{Arc, Mutex},
@ -126,30 +117,16 @@ mod tests {
ecss::{tc::PusTcCreator, WritablePusPacket},
SpHeader,
},
tmtc::PacketSenderRaw,
ComponentId,
};
use satrs_example::config::{components, OBSW_SERVER_ADDR};
use crate::tmtc::sender::{MockSender, TmTcSender};
use super::*;
const UDP_SERVER_ID: ComponentId = 0x05;
#[derive(Default, Debug)]
pub struct TestSender {
tc_vec: RefCell<VecDeque<PacketAsVec>>,
}
impl PacketSenderRaw for TestSender {
type Error = ();
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
let mut mut_queue = self.tc_vec.borrow_mut();
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
Ok(())
}
}
#[derive(Default, Debug, Clone)]
pub struct TestTmHandler {
addrs_to_send_to: Arc<Mutex<VecDeque<SocketAddr>>>,
@ -164,8 +141,7 @@ mod tests {
#[test]
fn test_basic() {
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
let test_receiver = TestSender::default();
// let tc_queue = test_receiver.tc_vec.clone();
let test_receiver = TmTcSender::Mock(MockSender::default());
let udp_tc_server =
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
let tm_handler = TestTmHandler::default();
@ -175,7 +151,13 @@ mod tests {
tm_handler,
};
udp_dyn_server.periodic_operation();
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
let queue = udp_dyn_server
.udp_tc_server
.tc_sender
.get_mock_sender()
.unwrap()
.0
.borrow();
assert!(queue.is_empty());
assert!(tm_handler_calls.lock().unwrap().is_empty());
}
@ -183,8 +165,7 @@ mod tests {
#[test]
fn test_transactions() {
let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
let test_receiver = TestSender::default();
// let tc_queue = test_receiver.tc_vec.clone();
let test_receiver = TmTcSender::Mock(MockSender::default());
let udp_tc_server =
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
let server_addr = udp_tc_server.socket.local_addr().unwrap();
@ -204,7 +185,13 @@ mod tests {
client.send_to(&ping_tc, server_addr).unwrap();
udp_dyn_server.periodic_operation();
{
let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut();
let mut queue = udp_dyn_server
.udp_tc_server
.tc_sender
.get_mock_sender()
.unwrap()
.0
.borrow_mut();
assert!(!queue.is_empty());
let packet_with_sender = queue.pop_front().unwrap();
assert_eq!(packet_with_sender.packet, ping_tc);
@ -219,7 +206,13 @@ mod tests {
assert_eq!(received_addr, client_addr);
}
udp_dyn_server.periodic_operation();
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
let queue = udp_dyn_server
.udp_tc_server
.tc_sender
.get_mock_sender()
.unwrap()
.0
.borrow();
assert!(queue.is_empty());
drop(queue);
// Still tries to send to the same client.

View File

@ -1,3 +1,73 @@
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, EcssTcInMemConverter, HandlingStatus},
request::{GenericMessage, MessageMetadata},
spacepackets::time::{cds::CdsTime, TimeWriter},
};
use satrs_example::{
config::{
acs::{MGM_HANDLER_0, MGM_HANDLER_1},
components::{NO_SENDER, PCDU_HANDLER, TCP_SERVER, UDP_SERVER},
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,
},
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::EcssTcInVecConverter;
use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic};
} else {
use std::sync::RwLock;
use interface::udp::StaticUdpTmHandler;
use satrs::pus::EcssTcInSharedPoolConverter;
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::create_static_pools;
use tmtc::{
tc_source::TcSourceTaskStatic,
tm_sink::TmSinkStatic,
};
}
}
mod acs;
mod eps;
mod events;
@ -6,106 +76,74 @@ mod interface;
mod logger;
mod pus;
mod requests;
mod spi;
mod tmtc;
use crate::eps::pcdu::{
PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper,
};
use crate::eps::PowerSwitchHelper;
use crate::events::EventHandler;
use crate::interface::udp::DynamicUdpTmHandler;
use crate::pus::stack::PusStack;
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic};
use log::info;
use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer;
use satrs::pus::HandlingStatus;
use satrs::request::{GenericMessage, MessageMetadata};
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{
FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS,
};
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
use satrs_example::DeviceMode;
fn main() {
setup_logger().expect("setting up logging with fern failed");
println!("Running OBSW example");
use crate::acs::mgm::{
MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface, SpiSimInterface,
SpiSimInterfaceWrapper,
};
use crate::interface::sim_client_udp::create_sim_client;
use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
use crate::logger::setup_logger;
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static};
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
use crate::pus::test::create_test_service_static;
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
use crate::requests::{CompositeRequest, GenericRequestRouter};
use satrs::mode::{Mode, ModeAndSubmode, ModeRequest};
use satrs::pus::event_man::EventRequestWithToken;
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
use satrs_example::config::components::{
MGM_HANDLER_0, NO_SENDER, PCDU_HANDLER, TCP_SERVER, UDP_SERVER,
};
use std::net::{IpAddr, SocketAddr};
use std::sync::{mpsc, Mutex};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let (tm_pool, tc_pool) = create_static_pools();
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
}
}
#[allow(dead_code)]
fn static_tmtc_pool_main() {
let (tm_pool, tc_pool) = create_static_pools();
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
let tm_sink_tx_sender =
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let tm_sender = TmTcSender::Static(
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone())
);
} else if #[cfg(feature = "heap_tmtc")] {
let tm_sender = TmTcSender::Heap(tm_sink_tx.clone());
}
}
let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let (mgm_0_sim_reply_tx, mgm_0_sim_reply_rx) = mpsc::channel();
let (mgm_1_sim_reply_tx, mgm_1_sim_reply_rx) = mpsc::channel();
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx);
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(30);
let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (mgm_0_handler_composite_tx, mgm_0_handler_composite_rx) = mpsc::sync_channel(10);
let (mgm_1_handler_composite_tx, mgm_1_handler_composite_rx) = mpsc::sync_channel(10);
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30);
let (mgm_0_handler_mode_tx, mgm_0_handler_mode_rx) = mpsc::sync_channel(5);
let (mgm_1_handler_mode_tx, mgm_1_handler_mode_rx) = mpsc::sync_channel(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map
.composite_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
.insert(MGM_HANDLER_0.id(), mgm_0_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
.composite_router_map
.insert(MGM_HANDLER_0.id(), mgm_1_handler_composite_tx);
request_map
.composite_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
request_map
.mode_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone());
// This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source.
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let tc_sender_with_shared_pool =
PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
let tc_in_mem_converter =
EcssTcInMemConverter::Static(EcssTcInSharedPoolConverter::new(shared_tc_pool, 4096));
} else if #[cfg(feature = "heap_tmtc")] {
let tc_in_mem_converter = EcssTcInMemConverter::Heap(EcssTcInVecConverter::default());
}
}
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
@ -117,17 +155,24 @@ fn static_tmtc_pool_main() {
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20);
let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10);
let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50);
let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50);
let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50);
let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50);
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50);
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30);
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let tc_releaser = TcReleaser::Static(tc_sender_with_shared_pool.clone());
} else if #[cfg(feature = "heap_tmtc")] {
let tc_releaser = TcReleaser::Heap(tc_source_tx.clone());
}
}
let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
@ -136,41 +181,42 @@ fn static_tmtc_pool_main() {
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
};
let pus_test_service = create_test_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
let pus_test_service = create_test_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
event_tx.clone(),
pus_test_rx,
);
let pus_scheduler_service = create_scheduler_service_static(
tm_sink_tx_sender.clone(),
tc_source.clone(),
let pus_scheduler_service = create_scheduler_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
tc_releaser,
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service = create_event_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
let pus_event_service = create_event_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
pus_event_rx,
event_request_tx,
);
let pus_action_service = create_action_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
let pus_action_service = create_action_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
pus_action_rx,
request_map.clone(),
pus_action_reply_rx,
);
let pus_hk_service = create_hk_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
let pus_hk_service = create_hk_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
pus_hk_rx,
request_map.clone(),
pus_hk_reply_rx,
);
let pus_mode_service = create_mode_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
let pus_mode_service = create_mode_service(
tm_sender.clone(),
tc_in_mem_converter.clone(),
pus_mode_rx,
request_map,
pus_mode_reply_rx,
@ -184,21 +230,36 @@ fn static_tmtc_pool_main() {
pus_mode_service,
);
let mut tmtc_task = TcSourceTaskStatic::new(
shared_tc_pool_wrapper.clone(),
tc_source_rx,
PusTcDistributor::new(tm_sink_tx_sender, pus_router),
);
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new(
shared_tc_pool_wrapper.clone(),
tc_source_rx,
PusTcDistributor::new(tm_sender.clone(), pus_router),
));
let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool);
let udp_tm_handler = StaticUdpTmHandler {
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone(),
};
} else if #[cfg(feature = "heap_tmtc")] {
let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new(
tc_source_rx,
PusTcDistributor::new(tm_sender.clone(), pus_router),
));
let tc_sender = TmTcSender::Heap(tc_source_tx.clone());
let udp_tm_handler = DynamicUdpTmHandler {
tm_rx: tm_server_rx,
};
}
}
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone())
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
tm_handler: StaticUdpTmHandler {
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone(),
},
tm_handler: udp_tm_handler,
};
let tcp_server_cfg = ServerConfig::new(
@ -212,60 +273,96 @@ fn static_tmtc_pool_main() {
let mut tcp_server = TcpTask::new(
tcp_server_cfg,
sync_tm_tcp_source.clone(),
tc_source.clone(),
tc_sender,
PACKET_ID_VALIDATOR.clone(),
)
.expect("tcp server creation failed");
let mut tm_sink = TmSinkStatic::new(
shared_tm_pool_wrapper,
sync_tm_tcp_source,
tm_sink_rx,
tm_server_tx,
);
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(5);
cfg_if::cfg_if! {
if #[cfg(not(feature = "heap_tmtc"))] {
let mut tm_sink = TmSink::Static(TmSinkStatic::new(
shared_tm_pool_wrapper,
sync_tm_tcp_source,
tm_sink_rx,
tm_server_tx,
));
} else if #[cfg(feature = "heap_tmtc")] {
let mut tm_sink = TmSink::Heap(TmSinkDynamic::new(
sync_tm_tcp_source,
tm_sink_rx,
tm_server_tx,
));
}
}
let shared_switch_set = Arc::new(Mutex::default());
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
let shared_mgm_set = Arc::default();
let mgm_mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx.clone(),
reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
};
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
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(
let shared_mgm_0_set = Arc::default();
let shared_mgm_1_set = Arc::default();
let mgm_0_mode_node =
ModeRequestHandlerMpscBounded::new(MGM_HANDLER_0.into(), mgm_0_handler_mode_rx);
let mgm_1_mode_node =
ModeRequestHandlerMpscBounded::new(MGM_HANDLER_1.into(), mgm_1_handler_mode_rx);
let (mgm_0_spi_interface, mgm_1_spi_interface) =
if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client
.add_reply_recipient(satrs_minisim::SimComponent::Mgm0Lis3Mdl, mgm_0_sim_reply_tx);
sim_client
.add_reply_recipient(satrs_minisim::SimComponent::Mgm1Lis3Mdl, mgm_1_sim_reply_tx);
(
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_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(
MGM_HANDLER_0,
"MGM_0",
mgm_mode_leaf_interface,
mgm_handler_composite_rx,
mgm_0_mode_node,
mgm_0_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sink_tx.clone(),
mgm_spi_interface,
shared_mgm_set,
tm_sender.clone(),
mgm_0_spi_interface,
shared_mgm_0_set,
);
let mut mgm_1_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_1,
"MGM_1",
mgm_1_mode_node,
mgm_1_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sender.clone(),
mgm_1_spi_interface,
shared_mgm_1_set,
);
// Connect PUS service to device handlers.
connect_mode_nodes(
&mut pus_stack.mode_srv,
mgm_0_handler_mode_tx,
&mut mgm_0_handler,
pus_mode_reply_tx.clone(),
);
connect_mode_nodes(
&mut pus_stack.mode_srv,
mgm_1_handler_mode_tx,
&mut mgm_1_handler,
pus_mode_reply_tx.clone(),
);
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(10);
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
request_rx: pcdu_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx,
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
};
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
@ -275,23 +372,34 @@ fn static_tmtc_pool_main() {
} else {
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
};
let pcdu_mode_node =
ModeRequestHandlerMpscBounded::new(PCDU_HANDLER.into(), pcdu_handler_mode_rx);
let mut pcdu_handler = PcduHandler::new(
PCDU_HANDLER,
"PCDU",
pcdu_mode_leaf_interface,
pcdu_mode_node,
pcdu_handler_composite_rx,
pus_hk_reply_tx,
switch_request_rx,
tm_sink_tx,
tm_sender.clone(),
pcdu_serial_interface,
shared_switch_set,
);
connect_mode_nodes(
&mut pus_stack.mode_srv,
pcdu_handler_mode_tx.clone(),
&mut pcdu_handler,
pus_mode_reply_tx,
);
// The PCDU is a critical component which should be in normal mode immediately.
pcdu_handler_mode_tx
.send(GenericMessage::new(
MessageMetadata::new(0, NO_SENDER),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)),
ModeRequest::SetMode {
mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as Mode, 0),
forced: false,
},
))
.expect("sending initial mode request failed");
@ -346,7 +454,8 @@ fn static_tmtc_pool_main() {
let jh_aocs = thread::Builder::new()
.name("sat-rs aocs".to_string())
.spawn(move || loop {
mgm_handler.periodic_operation();
mgm_0_handler.periodic_operation();
mgm_1_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
})
.unwrap();
@ -357,11 +466,13 @@ fn static_tmtc_pool_main() {
.spawn(move || loop {
// TODO: We should introduce something like a fixed timeslot helper to allow a more
// declarative API. It would also be very useful for the AOCS task.
pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
//
// TODO: The fixed timeslot handler exists.. use it.
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(300));
})
.unwrap();
@ -397,322 +508,6 @@ fn static_tmtc_pool_main() {
.expect("Joining PUS handler thread failed");
}
#[allow(dead_code)]
fn dyn_tmtc_pool_main() {
let (tc_source_tx, tc_source_rx) = mpsc::channel();
let (tm_sink_tx, tm_sink_rx) = mpsc::channel();
let (tm_server_tx, tm_server_rx) = mpsc::channel();
let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(5);
let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) =
mpsc::sync_channel::<GenericMessage<CompositeRequest>>(10);
let (mgm_handler_mode_tx, mgm_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(5);
let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) =
mpsc::sync_channel::<GenericMessage<ModeRequest>>(10);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map
.composite_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
request_map
.composite_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx);
request_map
.mode_router_map
.insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone());
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
// certain events.
let (event_tx, event_rx) = mpsc::sync_channel(100);
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
sched_tc_sender: pus_sched_tx,
hk_tc_sender: pus_hk_tx,
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
};
let pus_test_service =
create_test_service_dynamic(tm_sink_tx.clone(), event_tx.clone(), pus_test_rx);
let pus_scheduler_service = create_scheduler_service_dynamic(
tm_sink_tx.clone(),
tc_source_tx.clone(),
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service =
create_event_service_dynamic(tm_sink_tx.clone(), pus_event_rx, event_request_tx);
let pus_action_service = create_action_service_dynamic(
tm_sink_tx.clone(),
pus_action_rx,
request_map.clone(),
pus_action_reply_rx,
);
let pus_hk_service = create_hk_service_dynamic(
tm_sink_tx.clone(),
pus_hk_rx,
request_map.clone(),
pus_hk_reply_rx,
);
let pus_mode_service = create_mode_service_dynamic(
tm_sink_tx.clone(),
pus_mode_rx,
request_map,
pus_mode_reply_rx,
);
let mut pus_stack = PusStack::new(
pus_test_service,
pus_hk_service,
pus_event_service,
pus_action_service,
pus_scheduler_service,
pus_mode_service,
);
let mut tmtc_task = TcSourceTaskDynamic::new(
tc_source_rx,
PusTcDistributor::new(tm_sink_tx.clone(), pus_router),
);
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source_tx.clone())
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
tm_handler: DynamicUdpTmHandler {
tm_rx: tm_server_rx,
},
};
let tcp_server_cfg = ServerConfig::new(
TCP_SERVER.id(),
sock_addr,
Duration::from_millis(400),
4096,
8192,
);
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
let mut tcp_server = TcpTask::new(
tcp_server_cfg,
sync_tm_tcp_source.clone(),
tc_source_tx.clone(),
PACKET_ID_VALIDATOR.clone(),
)
.expect("tcp server creation failed");
let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx);
let shared_switch_set = Arc::new(Mutex::default());
let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20);
let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone());
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(5);
let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx.clone(),
reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx,
};
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_sim_reply_rx,
})
} else {
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
};
let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0,
"MGM_0",
mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx.clone(),
switch_helper.clone(),
tm_sink_tx.clone(),
mgm_spi_interface,
shared_mgm_set,
);
let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) =
mpsc::sync_channel(10);
let pcdu_mode_leaf_interface = MpscModeLeafInterface {
request_rx: pcdu_handler_mode_rx,
reply_to_pus_tx: pus_mode_reply_tx,
reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx,
};
let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx);
SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new(
sim_request_tx.clone(),
pcdu_sim_reply_rx,
))
} else {
SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default())
};
let mut pcdu_handler = PcduHandler::new(
PCDU_HANDLER,
"PCDU",
pcdu_mode_leaf_interface,
pcdu_handler_composite_rx,
pus_hk_reply_tx,
switch_request_rx,
tm_sink_tx,
pcdu_serial_interface,
shared_switch_set,
);
// The PCDU is a critical component which should be in normal mode immediately.
pcdu_handler_mode_tx
.send(GenericMessage::new(
MessageMetadata::new(0, NO_SENDER),
ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)),
))
.expect("sending initial mode request failed");
info!("Starting TMTC and UDP task");
let jh_udp_tmtc = thread::Builder::new()
.name("sat-rs tmtc-udp".to_string())
.spawn(move || {
info!("Running UDP server on port {SERVER_PORT}");
loop {
udp_tmtc_server.periodic_operation();
tmtc_task.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
}
})
.unwrap();
info!("Starting TCP task");
let jh_tcp = thread::Builder::new()
.name("sat-rs tcp".to_string())
.spawn(move || {
info!("Running TCP server on port {SERVER_PORT}");
loop {
tcp_server.periodic_operation();
}
})
.unwrap();
info!("Starting TM funnel task");
let jh_tm_funnel = thread::Builder::new()
.name("sat-rs tm-sink".to_string())
.spawn(move || loop {
tm_funnel.operation();
})
.unwrap();
let mut opt_jh_sim_client = None;
if let Some(mut sim_client) = opt_sim_client {
info!("Starting UDP sim client task");
opt_jh_sim_client = Some(
thread::Builder::new()
.name("sat-rs sim adapter".to_string())
.spawn(move || loop {
if sim_client.operation() == HandlingStatus::Empty {
std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS));
}
})
.unwrap(),
);
}
info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new()
.name("sat-rs aocs".to_string())
.spawn(move || loop {
mgm_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
})
.unwrap();
info!("Starting EPS thread");
let jh_eps = thread::Builder::new()
.name("sat-rs eps".to_string())
.spawn(move || loop {
// TODO: We should introduce something like a fixed timeslot helper to allow a more
// declarative API. It would also be very useful for the AOCS task.
pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(50));
pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies);
thread::sleep(Duration::from_millis(300));
})
.unwrap();
info!("Starting PUS handler thread");
let jh_pus_handler = thread::Builder::new()
.name("sat-rs pus".to_string())
.spawn(move || loop {
pus_stack.periodic_operation();
event_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
})
.unwrap();
jh_udp_tmtc
.join()
.expect("Joining UDP TMTC server thread failed");
jh_tcp
.join()
.expect("Joining TCP TMTC server thread failed");
jh_tm_funnel
.join()
.expect("Joining TM Funnel thread failed");
if let Some(jh_sim_client) = opt_jh_sim_client {
jh_sim_client
.join()
.expect("Joining SIM client thread failed");
}
jh_aocs.join().expect("Joining AOCS thread failed");
jh_eps.join().expect("Joining EPS thread failed");
jh_pus_handler
.join()
.expect("Joining PUS handler thread failed");
}
fn main() {
setup_logger().expect("setting up logging with fern failed");
println!("Running OBSW example");
#[cfg(not(feature = "dyn_tmtc"))]
static_tmtc_pool_main();
#[cfg(feature = "dyn_tmtc")]
dyn_tmtc_pool_main();
}
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
time_provider
.update_from_now()

View File

@ -1,6 +1,5 @@
use log::warn;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::action::{
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
};
@ -10,21 +9,20 @@ use satrs::pus::verification::{
VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper,
PusTcToRequestConverter,
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTmSender, EcssTmtcError,
GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler,
PusServiceHelper, PusTcToRequestConverter,
};
use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_ACTION_SERVICE;
use satrs_example::config::pus::PUS_ACTION_SERVICE;
use satrs_example::config::tmtc_err;
use std::sync::mpsc;
use std::time::Duration;
use crate::requests::GenericRequestRouter;
use crate::tmtc::sender::TmTcSender;
use super::{
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
@ -207,20 +205,20 @@ impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for Actio
}
}
pub fn create_action_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pub fn create_action_service(
tm_sender: TmTcSender,
tc_in_mem_converter: EcssTcInMemConverter,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
action_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> ActionServiceWrapper {
let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_ACTION_SERVICE.id(),
pus_action_rx,
tm_sender,
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
tc_in_mem_converter,
),
ActionRequestConverter::default(),
// TODO: Implementation which does not use run-time allocation? Maybe something like
@ -235,36 +233,9 @@ pub fn create_action_service_static(
}
}
pub fn create_action_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
action_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) -> ActionServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_ACTION_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(),
ActionReplyHandler::default(),
action_router,
reply_receiver,
);
ActionServiceWrapper {
service: action_request_handler,
}
}
pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub struct ActionServiceWrapper {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
ActionRequestConverter,
ActionReplyHandler,
@ -275,9 +246,7 @@ pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTc
>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for ActionServiceWrapper<TmSender, TcInMemConverter>
{
impl TargetedPusService for ActionServiceWrapper {
const SERVICE_ID: u8 = PusServiceId::Action as u8;
const SERVICE_STR: &'static str = "action";
@ -303,9 +272,10 @@ mod tests {
use satrs::pus::test_util::{
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
};
use satrs::pus::verification;
use satrs::pus::verification::test_util::TestVerificationReporter;
use satrs::pus::{verification, EcssTcInVecConverter};
use satrs::request::MessageMetadata;
use satrs::tmtc::PacketAsVec;
use satrs::ComponentId;
use satrs::{
res_code::ResultU16,
@ -338,7 +308,7 @@ mod tests {
{
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
let _ = env_logger::builder().is_test(true).try_init();
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(5);
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (action_reply_tx, action_reply_rx) = mpsc::channel();
let (action_req_tx, action_req_rx) = mpsc::sync_channel(10);
@ -352,9 +322,9 @@ mod tests {
PusServiceHelper::new(
owner_id,
pus_action_rx,
tm_funnel_tx.clone(),
TmTcSender::Heap(tm_funnel_tx.clone()),
verif_reporter,
EcssTcInVecConverter::default(),
EcssTcInMemConverter::Heap(EcssTcInVecConverter::default()),
),
ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(),

View File

@ -1,34 +1,32 @@
use std::sync::mpsc;
use crate::pus::create_verification_reporter;
use satrs::pool::SharedStaticMemoryPool;
use crate::tmtc::sender::TmTcSender;
use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::event_srv::PusEventServiceHandler;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver,
PartialPusHandlingError, PusServiceHelper,
};
use satrs::spacepackets::ecss::PusServiceId;
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
use satrs_example::config::pus::PUS_EVENT_MANAGEMENT;
use super::{DirectPusService, HandlingStatus};
pub fn create_event_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pub fn create_event_service(
tm_sender: TmTcSender,
tm_in_pool_converter: EcssTcInMemConverter,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> EventServiceWrapper {
let pus_5_handler = PusEventServiceHandler::new(
PusServiceHelper::new(
PUS_EVENT_MANAGEMENT.id(),
pus_event_rx,
tm_sender,
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
tm_in_pool_converter,
),
event_request_tx,
);
@ -37,34 +35,16 @@ pub fn create_event_service_static(
}
}
pub fn create_event_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> EventServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus_5_handler = PusEventServiceHandler::new(
PusServiceHelper::new(
PUS_EVENT_MANAGEMENT.id(),
pus_event_rx,
tm_funnel_tx,
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
EcssTcInVecConverter::default(),
),
event_request_tx,
);
EventServiceWrapper {
handler: pus_5_handler,
}
pub struct EventServiceWrapper {
pub handler: PusEventServiceHandler<
MpscTcReceiver,
TmTcSender,
EcssTcInMemConverter,
VerificationReporter,
>,
}
pub struct EventServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub handler:
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for EventServiceWrapper<TmSender, TcInMemConverter>
{
impl DirectPusService for EventServiceWrapper {
const SERVICE_ID: u8 = PusServiceId::Event as u8;
const SERVICE_STR: &'static str = "events";

View File

@ -1,28 +1,26 @@
use derive_new::new;
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::verification::{
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
EcssTcInMemConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
};
use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::res_code::ResultU16;
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_HK_SERVICE;
use satrs_example::config::pus::PUS_HK_SERVICE;
use satrs_example::config::{hk_err, tmtc_err};
use std::sync::mpsc;
use std::time::Duration;
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
use crate::requests::GenericRequestRouter;
use crate::tmtc::sender::TmTcSender;
use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService};
@ -242,20 +240,20 @@ impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConver
}
}
pub fn create_hk_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pub fn create_hk_service(
tm_sender: TmTcSender,
tc_in_mem_converter: EcssTcInMemConverter,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> HkServiceWrapper {
let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_HK_SERVICE.id(),
pus_hk_rx,
tm_sender,
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
tc_in_mem_converter,
),
HkRequestConverter::default(),
DefaultActiveRequestMap::default(),
@ -268,36 +266,9 @@ pub fn create_hk_service_static(
}
}
pub fn create_hk_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
) -> HkServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_HK_SERVICE.id(),
pus_hk_rx,
tm_funnel_tx,
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
EcssTcInVecConverter::default(),
),
HkRequestConverter::default(),
DefaultActiveRequestMap::default(),
HkReplyHandler::default(),
request_router,
reply_receiver,
);
HkServiceWrapper {
service: pus_3_handler,
}
}
pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub struct HkServiceWrapper {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
HkRequestConverter,
HkReplyHandler,
@ -308,9 +279,7 @@ pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMe
>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for HkServiceWrapper<TmSender, TcInMemConverter>
{
impl TargetedPusService for HkServiceWrapper {
const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8;
const SERVICE_STR: &'static str = "housekeeping";

View File

@ -1,4 +1,5 @@
use crate::requests::GenericRequestRouter;
use crate::tmtc::sender::TmTcSender;
use log::warn;
use satrs::pool::PoolAddr;
use satrs::pus::verification::{
@ -6,10 +7,10 @@ use satrs::pus::verification::{
VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
PusTcToRequestConverter, TcInMemory,
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConversionProvider,
EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError,
GenericRoutingError, HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter,
PusServiceHelper, PusTcToRequestConverter, TcInMemory,
};
use satrs::queue::{GenericReceiveError, GenericSendError};
use satrs::request::{Apid, GenericMessage, MessageMetadata};
@ -17,11 +18,11 @@ use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketInPool};
use satrs::ComponentId;
use satrs_example::config::components::PUS_ROUTING_SERVICE;
use satrs_example::config::pus::PUS_ROUTING_SERVICE;
use satrs_example::config::{tmtc_err, CustomPusServiceId};
use satrs_example::TimestampHelper;
use std::fmt::Debug;
use std::sync::mpsc::{self, Sender};
use std::sync::mpsc;
pub mod action;
pub mod event;
@ -40,24 +41,26 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
pub struct PusTcMpscRouter {
pub test_tc_sender: Sender<EcssTcAndToken>,
pub event_tc_sender: Sender<EcssTcAndToken>,
pub sched_tc_sender: Sender<EcssTcAndToken>,
pub hk_tc_sender: Sender<EcssTcAndToken>,
pub action_tc_sender: Sender<EcssTcAndToken>,
pub mode_tc_sender: Sender<EcssTcAndToken>,
pub test_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
pub event_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
pub sched_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
pub hk_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
#[allow(dead_code)]
pub action_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
pub mode_tc_sender: mpsc::SyncSender<EcssTcAndToken>,
}
pub struct PusTcDistributor<TmSender: EcssTmSender> {
pub struct PusTcDistributor {
#[allow(dead_code)]
pub id: ComponentId,
pub tm_sender: TmSender,
pub tm_sender: TmTcSender,
pub verif_reporter: VerificationReporter,
pub pus_router: PusTcMpscRouter,
stamp_helper: TimestampHelper,
}
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
impl PusTcDistributor {
pub fn new(tm_sender: TmTcSender, pus_router: PusTcMpscRouter) -> Self {
Self {
id: PUS_ROUTING_SERVICE.raw(),
tm_sender,
@ -266,8 +269,6 @@ pub trait DirectPusService {
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
pub struct PusTargetedRequestService<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
@ -277,7 +278,7 @@ pub struct PusTargetedRequestService<
ReplyType,
> {
pub service_helper:
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
PusServiceHelper<TcReceiver, TmTcSender, EcssTcInMemConverter, VerificationReporter>,
pub request_router: GenericRequestRouter,
pub request_converter: RequestConverter,
pub active_request_map: ActiveRequestMap,
@ -288,8 +289,6 @@ pub struct PusTargetedRequestService<
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
@ -300,8 +299,6 @@ impl<
>
PusTargetedRequestService<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
RequestConverter,
ReplyHandler,
@ -316,8 +313,8 @@ where
pub fn new(
service_helper: PusServiceHelper<
TcReceiver,
TmSender,
TcInMemConverter,
TmTcSender,
EcssTcInMemConverter,
VerificationReporter,
>,
request_converter: RequestConverter,
@ -543,7 +540,7 @@ pub(crate) mod tests {
use satrs::{
pus::{
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
ActiveRequestMapProvider, MpscTcReceiver,
},
request::UniqueApidTargetId,
spacepackets::{
@ -764,8 +761,6 @@ pub(crate) mod tests {
> {
pub service: PusTargetedRequestService<
MpscTcReceiver,
MpscTmAsVecSender,
EcssTcInVecConverter,
TestVerificationReporter,
RequestConverter,
ReplyHandler,

View File

@ -1,15 +1,15 @@
use derive_new::new;
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs::mode_tree::{ModeNode, ModeParent};
use satrs_example::config::pus::PUS_MODE_SERVICE;
use std::sync::mpsc;
use std::time::Duration;
use crate::requests::GenericRequestRouter;
use satrs::pool::SharedStaticMemoryPool;
use crate::tmtc::sender::TmTcSender;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError,
PusServiceHelper,
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver,
PusPacketHandlingError, PusServiceHelper,
};
use satrs::request::GenericMessage;
use satrs::{
@ -34,7 +34,6 @@ use satrs::{
},
ComponentId,
};
use satrs_example::config::components::PUS_MODE_SERVICE;
use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId};
use super::{
@ -110,6 +109,7 @@ impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
),
)?;
}
ModeReply::ModeInfo(_mode_and_submode) => (),
};
Ok(true)
}
@ -190,7 +190,13 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
}
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
.expect("mode and submode extraction failed");
Ok((active_request, ModeRequest::SetMode(mode_and_submode)))
Ok((
active_request,
ModeRequest::SetMode {
mode_and_submode,
forced: false,
},
))
}
Subservice::TcReadMode => Ok((active_request, ModeRequest::ReadMode)),
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
@ -202,20 +208,20 @@ impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestCo
}
}
pub fn create_mode_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pub fn create_mode_service(
tm_sender: TmTcSender,
tc_in_mem_converter: EcssTcInMemConverter,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
mode_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> ModeServiceWrapper {
let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_MODE_SERVICE.id(),
pus_action_rx,
tm_sender,
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
tc_in_mem_converter,
),
ModeRequestConverter::default(),
DefaultActiveRequestMap::default(),
@ -228,36 +234,9 @@ pub fn create_mode_service_static(
}
}
pub fn create_mode_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
mode_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
) -> ModeServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_MODE_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ModeRequestConverter::default(),
DefaultActiveRequestMap::default(),
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
mode_router,
reply_receiver,
);
ModeServiceWrapper {
service: mode_request_handler,
}
}
pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub struct ModeServiceWrapper {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
ModeRequestConverter,
ModeReplyHandler,
@ -268,9 +247,24 @@ pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcIn
>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for ModeServiceWrapper<TmSender, TcInMemConverter>
{
impl ModeNode for ModeServiceWrapper {
fn id(&self) -> ComponentId {
self.service.service_helper.id()
}
}
impl ModeParent for ModeServiceWrapper {
type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender) {
self.service
.request_router
.mode_router_map
.insert(id, request_sender);
}
}
impl TargetedPusService for ModeServiceWrapper {
const SERVICE_ID: u8 = CustomPusServiceId::Mode as u8;
const SERVICE_STR: &'static str = "mode";
@ -346,7 +340,13 @@ mod tests {
let (_active_req, req) = testbench
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion has failed");
assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
assert_eq!(
req,
ModeRequest::SetMode {
mode_and_submode,
forced: false
}
);
}
#[test]

View File

@ -2,28 +2,28 @@ use std::sync::mpsc;
use std::time::Duration;
use crate::pus::create_verification_reporter;
use crate::tmtc::sender::TmTcSender;
use log::info;
use satrs::pool::{PoolProvider, StaticMemoryPool};
use satrs::pus::scheduler::{PusScheduler, TcInfo};
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver,
PartialPusHandlingError, PusServiceHelper,
};
use satrs::spacepackets::ecss::PusServiceId;
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
use satrs::ComponentId;
use satrs_example::config::components::PUS_SCHED_SERVICE;
use satrs_example::config::pus::PUS_SCHED_SERVICE;
use super::{DirectPusService, HandlingStatus};
pub trait TcReleaser {
pub trait TcReleaseProvider {
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
}
impl TcReleaser for PacketSenderWithSharedPool {
impl TcReleaseProvider for PacketSenderWithSharedPool {
fn release(
&mut self,
sender_id: ComponentId,
@ -48,7 +48,7 @@ impl TcReleaser for PacketSenderWithSharedPool {
}
}
impl TcReleaser for mpsc::Sender<PacketAsVec> {
impl TcReleaseProvider for mpsc::SyncSender<PacketAsVec> {
fn release(
&mut self,
sender_id: ComponentId,
@ -65,23 +65,35 @@ impl TcReleaser for mpsc::Sender<PacketAsVec> {
}
}
pub struct SchedulingServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
{
#[allow(dead_code)]
pub enum TcReleaser {
Static(PacketSenderWithSharedPool),
Heap(mpsc::SyncSender<PacketAsVec>),
}
impl TcReleaseProvider for TcReleaser {
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool {
match self {
TcReleaser::Static(sender) => sender.release(sender_id, enabled, info, tc),
TcReleaser::Heap(sender) => sender.release(sender_id, enabled, info, tc),
}
}
}
pub struct SchedulingServiceWrapper {
pub pus_11_handler: PusSchedServiceHandler<
MpscTcReceiver,
TmSender,
TcInMemConverter,
TmTcSender,
EcssTcInMemConverter,
VerificationReporter,
PusScheduler,
>,
pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096],
pub tc_releaser: Box<dyn TcReleaser + Send>,
pub tc_releaser: TcReleaser,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for SchedulingServiceWrapper<TmSender, TcInMemConverter>
{
impl DirectPusService for SchedulingServiceWrapper {
const SERVICE_ID: u8 = PusServiceId::Verification as u8;
const SERVICE_STR: &'static str = "verification";
@ -134,9 +146,7 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusSe
}
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
SchedulingServiceWrapper<TmSender, TcInMemConverter>
{
impl SchedulingServiceWrapper {
pub fn release_tcs(&mut self) {
let id = self.pus_11_handler.service_helper.id();
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
@ -162,12 +172,13 @@ impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
}
}
pub fn create_scheduler_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_releaser: PacketSenderWithSharedPool,
pub fn create_scheduler_service(
tm_sender: TmTcSender,
tc_in_mem_converter: EcssTcInMemConverter,
tc_releaser: TcReleaser,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> SchedulingServiceWrapper {
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
.expect("Creating PUS Scheduler failed");
let pus_11_handler = PusSchedServiceHandler::new(
@ -176,7 +187,7 @@ pub fn create_scheduler_service_static(
pus_sched_rx,
tm_sender,
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
tc_in_mem_converter,
),
scheduler,
);
@ -184,34 +195,6 @@ pub fn create_scheduler_service_static(
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
tc_releaser: Box::new(tc_releaser),
}
}
pub fn create_scheduler_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
tc_source_sender: mpsc::Sender<PacketAsVec>,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> SchedulingServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
//let sched_srv_receiver =
//MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx);
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
.expect("Creating PUS Scheduler failed");
let pus_11_handler = PusSchedServiceHandler::new(
PusServiceHelper::new(
PUS_SCHED_SERVICE.id(),
pus_sched_rx,
tm_funnel_tx,
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
EcssTcInVecConverter::default(),
),
scheduler,
);
SchedulingServiceWrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
tc_releaser: Box::new(tc_source_sender),
tc_releaser,
}
}

View File

@ -1,9 +1,6 @@
use crate::pus::mode::ModeServiceWrapper;
use derive_new::new;
use satrs::{
pus::{EcssTcInMemConverter, EcssTmSender},
spacepackets::time::{cds, TimeWriter},
};
use satrs::spacepackets::time::{cds, TimeWriter};
use super::{
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
@ -11,21 +8,17 @@ use super::{
HandlingStatus, TargetedPusService,
};
// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one
// for targeted services..
#[derive(new)]
pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
pub struct PusStack {
pub test_srv: TestCustomServiceWrapper,
pub hk_srv_wrapper: HkServiceWrapper,
pub event_srv: EventServiceWrapper,
pub action_srv_wrapper: ActionServiceWrapper,
pub schedule_srv: SchedulingServiceWrapper,
pub mode_srv: ModeServiceWrapper,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
PusStack<TmSender, TcInMemConverter>
{
impl PusStack {
pub fn periodic_operation(&mut self) {
// Release all telecommands which reached their release time before calling the service
// handlers.

View File

@ -1,35 +1,34 @@
use crate::pus::create_verification_reporter;
use crate::tmtc::sender::TmTcSender;
use log::info;
use satrs::event_man::{EventMessage, EventMessageU32};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::test::PusService17TestHandler;
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
use satrs::pus::PartialPusHandlingError;
use satrs::pus::{
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter,
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConversionProvider,
EcssTcInMemConverter, MpscTcReceiver, PusServiceHelper,
};
use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_TEST_SERVICE;
use satrs_example::config::pus::PUS_TEST_SERVICE;
use satrs_example::config::{tmtc_err, TEST_EVENT};
use std::sync::mpsc;
use super::{DirectPusService, HandlingStatus};
pub fn create_test_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pub fn create_test_service(
tm_sender: TmTcSender,
tc_in_mem_converter: EcssTcInMemConverter,
event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
) -> TestCustomServiceWrapper {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
PUS_TEST_SERVICE.id(),
pus_test_rx,
tm_sender,
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
tc_in_mem_converter,
));
TestCustomServiceWrapper {
handler: pus17_handler,
@ -37,34 +36,17 @@ pub fn create_test_service_static(
}
}
pub fn create_test_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
PUS_TEST_SERVICE.id(),
pus_test_rx,
tm_funnel_tx,
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
EcssTcInVecConverter::default(),
));
TestCustomServiceWrapper {
handler: pus17_handler,
event_tx: event_sender,
}
}
pub struct TestCustomServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
{
pub handler:
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub struct TestCustomServiceWrapper {
pub handler: PusService17TestHandler<
MpscTcReceiver,
TmTcSender,
EcssTcInMemConverter,
VerificationReporter,
>,
pub event_tx: mpsc::SyncSender<EventMessageU32>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for TestCustomServiceWrapper<TmSender, TcInMemConverter>
{
impl DirectPusService for TestCustomServiceWrapper {
const SERVICE_ID: u8 = PusServiceId::Test as u8;
const SERVICE_STR: &'static str = "test";

View File

@ -14,7 +14,7 @@ use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::ComponentId;
use satrs_example::config::components::PUS_ROUTING_SERVICE;
use satrs_example::config::pus::PUS_ROUTING_SERVICE;
use satrs_example::config::tmtc_err;
#[derive(Clone, Debug)]
@ -26,6 +26,7 @@ pub enum CompositeRequest {
#[derive(Clone)]
pub struct GenericRequestRouter {
#[allow(dead_code)]
pub id: ComponentId,
// All messages which do not have a dedicated queue.
pub composite_router_map:

6
satrs-example/src/spi.rs Normal file
View File

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

View File

@ -1,2 +1,3 @@
pub mod sender;
pub mod tc_source;
pub mod tm_sink;

View File

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

View File

@ -1,12 +1,10 @@
use satrs::{
pool::PoolProvider,
pus::HandlingStatus,
tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
};
use std::sync::mpsc::{self, TryRecvError};
use satrs::pus::MpscTmAsVecSender;
use crate::pus::PusTcDistributor;
// TC source components where static pools are the backing memory of the received telecommands.
@ -14,14 +12,15 @@ pub struct TcSourceTaskStatic {
shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>,
tc_buf: [u8; 4096],
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
pus_distributor: PusTcDistributor,
}
#[allow(dead_code)]
impl TcSourceTaskStatic {
pub fn new(
shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>,
pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
pus_receiver: PusTcDistributor,
) -> Self {
Self {
shared_tc_pool,
@ -67,14 +66,12 @@ impl TcSourceTaskStatic {
// TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic {
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
pus_distributor: PusTcDistributor,
}
#[allow(dead_code)]
impl TcSourceTaskDynamic {
pub fn new(
tc_receiver: mpsc::Receiver<PacketAsVec>,
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
) -> Self {
pub fn new(tc_receiver: mpsc::Receiver<PacketAsVec>, pus_receiver: PusTcDistributor) -> Self {
Self {
tc_receiver,
pus_distributor: pus_receiver,
@ -105,3 +102,18 @@ impl TcSourceTaskDynamic {
}
}
}
#[allow(dead_code)]
pub enum TcSourceTask {
Static(TcSourceTaskStatic),
Heap(TcSourceTaskDynamic),
}
impl TcSourceTask {
pub fn periodic_operation(&mut self) {
match self {
TcSourceTask::Static(task) => task.periodic_operation(),
TcSourceTask::Heap(task) => task.periodic_operation(),
}
}
}

View File

@ -4,16 +4,19 @@ use std::{
};
use log::info;
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
use satrs::{
pool::PoolProvider,
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
spacepackets::{
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
seq_count::CcsdsSimpleSeqCountProvider,
time::cds::MIN_CDS_FIELD_LEN,
CcsdsPacket,
},
};
use satrs::{
spacepackets::seq_count::SequenceCountProvider,
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
};
use crate::interface::tcp::SyncTcpTmSource;
@ -86,6 +89,7 @@ pub struct TmSinkStatic {
tm_server_tx: mpsc::SyncSender<PacketInPool>,
}
#[allow(dead_code)]
impl TmSinkStatic {
pub fn new(
shared_tm_store: SharedPacketPool,
@ -129,14 +133,15 @@ impl TmSinkStatic {
pub struct TmSinkDynamic {
common: TmFunnelCommon,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
}
#[allow(dead_code)]
impl TmSinkDynamic {
pub fn new(
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
tm_server_tx: mpsc::SyncSender<PacketAsVec>,
) -> Self {
Self {
common: TmFunnelCommon::new(sync_tm_tcp_source),
@ -159,3 +164,18 @@ impl TmSinkDynamic {
}
}
}
#[allow(dead_code)]
pub enum TmSink {
Static(TmSinkStatic),
Heap(TmSinkDynamic),
}
impl TmSink {
pub fn operation(&mut self) {
match self {
TmSink::Static(static_sink) => static_sink.operation(),
TmSink::Heap(dynamic_sink) => dynamic_sink.operation(),
}
}
}

View File

@ -23,7 +23,7 @@ version = "1"
optional = true
[dependencies.satrs-shared]
version = ">=0.1.3, <0.2"
version = ">=0.1.3, <=0.2"
features = ["serde"]
[dependencies.satrs-mib-codegen]

View File

@ -28,7 +28,7 @@ features = ["full"]
trybuild = { version = "1", features = ["diff"] }
[dev-dependencies.satrs-shared]
version = ">=0.1.3, <0.2"
version = ">=0.1.3, <=0.2"
[dev-dependencies.satrs-mib]
path = ".."

View File

@ -1,6 +1,4 @@
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;

View File

@ -9,20 +9,18 @@ edition = "2021"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4"
thiserror = "1"
fern = "0.5"
thiserror = "2"
fern = "0.7"
strum = { version = "0.26", features = ["derive"] }
num_enum = "0.7"
humantime = "2"
tai-time = { version = "0.3", features = ["serde"] }
[dependencies.asynchronix]
version = "0.2.2"
# git = "https://github.com/asynchronics/asynchronix.git"
# branch = "main"
features = ["serde"]
[dependencies.nexosim]
version = "0.3.1"
[dependencies.satrs]
path = "../satrs"
[dev-dependencies]
delegate = "0.12"
delegate = "0.13"

View File

@ -1,8 +1,8 @@
use std::{f32::consts::PI, sync::mpsc, time::Duration};
use asynchronix::{
model::{Model, Output},
time::Scheduler,
use nexosim::{
model::{Context, Model},
ports::Output,
};
use satrs::power::SwitchStateBinary;
use satrs_minisim::{
@ -31,6 +31,7 @@ const PHASE_Z: f32 = 0.2;
/// might still be possible and is probably sufficient for many OBSW needs.
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
pub switch_state: SwitchStateBinary,
#[allow(dead_code)]
pub periodicity: Duration,
pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
pub reply_sender: mpsc::Sender<SimReply>,
@ -54,7 +55,7 @@ impl<ReplyProvider: MgmReplyProvider> MagnetometerModel<ReplyProvider> {
self.switch_state = switch_state;
}
pub async fn send_sensor_values(&mut self, _: (), scheduler: &Scheduler<Self>) {
pub async fn send_sensor_values(&mut self, _: (), scheduler: &mut Context<Self>) {
self.reply_sender
.send(ReplyProvider::create_mgm_reply(MgmReplyCommon {
switch_state: self.switch_state,
@ -113,11 +114,11 @@ impl MagnetorquerModel {
pub async fn apply_torque(
&mut self,
duration_and_dipole: (Duration, MgtDipole),
scheduler: &Scheduler<Self>,
cx: &mut Context<Self>,
) {
self.torque_dipole = duration_and_dipole.1;
self.torquing = true;
if scheduler
if cx
.schedule_event(duration_and_dipole.0, Self::clear_torque, ())
.is_err()
{
@ -137,12 +138,11 @@ impl MagnetorquerModel {
self.generate_magnetic_field(()).await;
}
pub async fn request_housekeeping_data(&mut self, _: (), scheduler: &Scheduler<Self>) {
pub async fn request_housekeeping_data(&mut self, _: (), cx: &mut Context<Self>) {
if self.switch_state != SwitchStateBinary::On {
return;
}
scheduler
.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
cx.schedule_event(Duration::from_millis(15), Self::send_housekeeping_data, ())
.expect("requesting housekeeping data failed")
}
@ -199,11 +199,11 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
let reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values");
assert_eq!(reply.common.switch_state, SwitchStateBinary::Off);
@ -222,21 +222,21 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let mut sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_some());
let mut sim_reply = sim_reply_res.unwrap();
assert_eq!(sim_reply.component(), SimComponent::MgmLis3Mdl);
assert_eq!(sim_reply.component(), SimComponent::Mgm0Lis3Mdl);
let first_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values");
sim_testbench.step_by(Duration::from_millis(50));
sim_testbench.step_until(Duration::from_millis(50)).unwrap();
request = SimRequest::new_with_epoch_time(MgmRequestLis3Mdl::RequestSensorData);
sim_testbench
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_some());
sim_reply = sim_reply_res.unwrap();
@ -271,7 +271,7 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_none());
}
@ -286,7 +286,7 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_some());
let sim_reply = sim_reply_res.unwrap();
@ -307,7 +307,7 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_some());
let sim_reply = sim_reply_res.unwrap();
@ -338,7 +338,7 @@ pub mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step_by(Duration::from_millis(5));
sim_testbench.step_until(Duration::from_millis(5)).unwrap();
check_mgt_hk(
&mut sim_testbench,
@ -347,7 +347,9 @@ pub mod tests {
torquing: true,
},
);
sim_testbench.step_by(Duration::from_millis(100));
sim_testbench
.step_until(Duration::from_millis(100))
.unwrap();
check_mgt_hk(
&mut sim_testbench,
MgtHkSet {

View File

@ -1,7 +1,7 @@
use std::{sync::mpsc, time::Duration};
use asynchronix::{
simulation::{Address, Simulation},
use nexosim::{
simulation::{Address, Scheduler, Simulation},
time::{Clock, MonotonicTime, SystemClock},
};
use satrs_minisim::{
@ -23,35 +23,55 @@ const MGM_REQ_WIRETAPPING: bool = false;
const PCDU_REQ_WIRETAPPING: bool = false;
const MGT_REQ_WIRETAPPING: bool = false;
pub struct ModelAddrWrapper {
mgm_0_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
mgm_1_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pcdu_addr: Address<PcduModel>,
mgt_addr: Address<MagnetorquerModel>,
}
// The simulation controller processes requests and drives the simulation.
#[allow(dead_code)]
pub struct SimController {
pub sys_clock: SystemClock,
pub request_receiver: mpsc::Receiver<SimRequest>,
pub reply_sender: mpsc::Sender<SimReply>,
pub simulation: Simulation,
pub mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pub pcdu_addr: Address<PcduModel>,
pub mgt_addr: Address<MagnetorquerModel>,
pub scheduler: Scheduler,
pub addr_wrapper: ModelAddrWrapper,
}
impl ModelAddrWrapper {
pub fn new(
mgm_0_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
mgm_1_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pcdu_addr: Address<PcduModel>,
mgt_addr: Address<MagnetorquerModel>,
) -> Self {
Self {
mgm_0_addr,
mgm_1_addr,
pcdu_addr,
mgt_addr,
}
}
}
impl SimController {
pub fn new(
sys_clock: SystemClock,
request_receiver: mpsc::Receiver<SimRequest>,
reply_sender: mpsc::Sender<SimReply>,
simulation: Simulation,
mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pcdu_addr: Address<PcduModel>,
mgt_addr: Address<MagnetorquerModel>,
scheduler: Scheduler,
addr_wrapper: ModelAddrWrapper,
) -> Self {
Self {
sys_clock,
request_receiver,
reply_sender,
simulation,
mgm_addr,
pcdu_addr,
mgt_addr,
scheduler,
addr_wrapper,
}
}
@ -62,7 +82,7 @@ impl SimController {
// Check for UDP requests every millisecond. Shift the simulator ahead here to prevent
// replies lying in the past.
t += Duration::from_millis(udp_polling_interval_ms);
self.sys_clock.synchronize(t);
let _synch_status = self.sys_clock.synchronize(t);
self.handle_sim_requests(t_old);
self.simulation
.step_until(t)
@ -79,7 +99,8 @@ impl SimController {
}
if let Err(e) = match request.component() {
SimComponent::SimCtrl => self.handle_ctrl_request(&request),
SimComponent::MgmLis3Mdl => self.handle_mgm_request(&request),
SimComponent::Mgm0Lis3Mdl => self.handle_mgm_request(0, &request),
SimComponent::Mgm1Lis3Mdl => self.handle_mgm_request(1, &request),
SimComponent::Mgt => self.handle_mgt_request(&request),
SimComponent::Pcdu => self.handle_pcdu_request(&request),
} {
@ -111,18 +132,26 @@ impl SimController {
Ok(())
}
fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
fn handle_mgm_request(
&mut self,
mgm_idx: usize,
request: &SimRequest,
) -> Result<(), SimRequestError> {
let mgm_request = MgmRequestLis3Mdl::from_sim_message(request)?;
if MGM_REQ_WIRETAPPING {
log::info!("received MGM request: {:?}", mgm_request);
}
match mgm_request {
MgmRequestLis3Mdl::RequestSensorData => {
self.simulation.send_event(
MagnetometerModel::send_sensor_values,
(),
&self.mgm_addr,
);
let addr = match mgm_idx {
0 => &self.addr_wrapper.mgm_0_addr,
1 => &self.addr_wrapper.mgm_1_addr,
_ => panic!("invalid mgm index"),
};
self.simulation
.process_event(MagnetometerModel::send_sensor_values, (), addr)
.expect("event execution error for mgm");
}
}
Ok(())
@ -136,14 +165,21 @@ impl SimController {
match pcdu_request {
PcduRequest::RequestSwitchInfo => {
self.simulation
.send_event(PcduModel::request_switch_info, (), &self.pcdu_addr);
.process_event(
PcduModel::request_switch_info,
(),
&self.addr_wrapper.pcdu_addr,
)
.unwrap();
}
PcduRequest::SwitchDevice { switch, state } => {
self.simulation.send_event(
PcduModel::switch_device,
(switch, state),
&self.pcdu_addr,
);
self.simulation
.process_event(
PcduModel::switch_device,
(switch, state),
&self.addr_wrapper.pcdu_addr,
)
.unwrap();
}
}
Ok(())
@ -155,17 +191,23 @@ impl SimController {
log::info!("received MGT request: {:?}", mgt_request);
}
match mgt_request {
MgtRequest::ApplyTorque { duration, dipole } => self.simulation.send_event(
MagnetorquerModel::apply_torque,
(duration, dipole),
&self.mgt_addr,
),
MgtRequest::RequestHk => self.simulation.send_event(
MagnetorquerModel::request_housekeeping_data,
(),
&self.mgt_addr,
),
}
MgtRequest::ApplyTorque { duration, dipole } => self
.simulation
.process_event(
MagnetorquerModel::apply_torque,
(duration, dipole),
&self.addr_wrapper.mgt_addr,
)
.unwrap(),
MgtRequest::RequestHk => self
.simulation
.process_event(
MagnetorquerModel::request_housekeeping_data,
(),
&self.addr_wrapper.mgt_addr,
)
.unwrap(),
};
Ok(())
}
@ -199,7 +241,7 @@ mod tests {
.send_request(request)
.expect("sending sim ctrl request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap();

View File

@ -1,8 +1,8 @@
use std::{sync::mpsc, time::Duration};
use asynchronix::{
model::{Model, Output},
time::Scheduler,
use nexosim::{
model::{Context, Model},
ports::Output,
};
use satrs::power::SwitchStateBinary;
use satrs_minisim::{
@ -14,7 +14,8 @@ pub const SWITCH_INFO_DELAY_MS: u64 = 10;
pub struct PcduModel {
pub switcher_map: SwitchMapBinaryWrapper,
pub mgm_switch: Output<SwitchStateBinary>,
pub mgm_0_switch: Output<SwitchStateBinary>,
pub mgm_1_switch: Output<SwitchStateBinary>,
pub mgt_switch: Output<SwitchStateBinary>,
pub reply_sender: mpsc::Sender<SimReply>,
}
@ -23,20 +24,20 @@ impl PcduModel {
pub fn new(reply_sender: mpsc::Sender<SimReply>) -> Self {
Self {
switcher_map: Default::default(),
mgm_switch: Output::new(),
mgm_0_switch: Output::new(),
mgm_1_switch: Output::new(),
mgt_switch: Output::new(),
reply_sender,
}
}
pub async fn request_switch_info(&mut self, _: (), scheduler: &Scheduler<Self>) {
scheduler
.schedule_event(
Duration::from_millis(SWITCH_INFO_DELAY_MS),
Self::send_switch_info,
(),
)
.expect("requesting switch info failed");
pub async fn request_switch_info(&mut self, _: (), cx: &mut Context<Self>) {
cx.schedule_event(
Duration::from_millis(SWITCH_INFO_DELAY_MS),
Self::send_switch_info,
(),
)
.expect("requesting switch info failed");
}
pub fn send_switch_info(&mut self) {
@ -56,7 +57,7 @@ impl PcduModel {
*val = switch_and_target_state.1;
match switch_and_target_state.0 {
PcduSwitch::Mgm => {
self.mgm_switch.send(switch_and_target_state.1).await;
self.mgm_0_switch.send(switch_and_target_state.1).await;
}
PcduSwitch::Mgt => {
self.mgt_switch.send(switch_and_target_state.1).await;
@ -92,7 +93,7 @@ pub(crate) mod tests {
.send_request(request)
.expect("sending MGM switch request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
}
#[allow(dead_code)]
@ -113,7 +114,7 @@ pub(crate) mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step();
sim_testbench.step().unwrap();
let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap();
@ -143,12 +144,12 @@ pub(crate) mod tests {
.send_request(request)
.expect("sending MGM request failed");
sim_testbench.handle_sim_requests_time_agnostic();
sim_testbench.step_by(Duration::from_millis(1));
sim_testbench.step_until(Duration::from_millis(1)).unwrap();
let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_none());
// Reply takes 20ms
sim_testbench.step_by(Duration::from_millis(25));
sim_testbench.step_until(Duration::from_millis(25)).unwrap();
let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap();

View File

@ -1,11 +1,12 @@
use asynchronix::time::MonotonicTime;
use nexosim::time::MonotonicTime;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum SimComponent {
SimCtrl,
MgmLis3Mdl,
Mgm0Lis3Mdl,
Mgm1Lis3Mdl,
Mgt,
Pcdu,
}
@ -277,7 +278,7 @@ pub mod acs {
}
impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl {
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
}
// Normally, small magnetometers generate their output as a signed 16 bit raw format or something
@ -368,7 +369,7 @@ pub mod acs {
}
impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply {
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
}
impl MgmReplyProvider for MgmLis3MdlReply {
@ -418,7 +419,7 @@ pub mod acs {
}
impl SerializableSimMsgPayload<SimReply> for MgtReply {
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
const TARGET: SimComponent = SimComponent::Mgm0Lis3Mdl;
}
}

View File

@ -1,8 +1,8 @@
use acs::{MagnetometerModel, MagnetorquerModel};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{MonotonicTime, SystemClock};
use controller::SimController;
use controller::{ModelAddrWrapper, SimController};
use eps::PcduModel;
use nexosim::simulation::{Mailbox, SimInit};
use nexosim::time::{MonotonicTime, SystemClock};
use satrs_minisim::udp::SIM_CTRL_PORT;
use satrs_minisim::{SimReply, SimRequest};
use std::sync::mpsc;
@ -31,11 +31,15 @@ fn create_sim_controller(
request_receiver: mpsc::Receiver<SimRequest>,
) -> SimController {
// Instantiate models and their mailboxes.
let mgm_model =
let mgm_0_model =
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
let mgm_1_model =
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
let mgm_mailbox = Mailbox::new();
let mgm_addr = mgm_mailbox.address();
let mgm_0_mailbox = Mailbox::new();
let mgm_0_addr = mgm_0_mailbox.address();
let mgm_1_mailbox = Mailbox::new();
let mgm_1_addr = mgm_1_mailbox.address();
let pcdu_mailbox = Mailbox::new();
let pcdu_addr = pcdu_mailbox.address();
let mgt_mailbox = Mailbox::new();
@ -43,8 +47,11 @@ fn create_sim_controller(
let mut pcdu_model = PcduModel::new(reply_sender.clone());
pcdu_model
.mgm_switch
.connect(MagnetometerModel::switch_device, &mgm_addr);
.mgm_0_switch
.connect(MagnetometerModel::switch_device, &mgm_0_addr);
pcdu_model
.mgm_1_switch
.connect(MagnetometerModel::switch_device, &mgm_1_addr);
let mut mgt_model = MagnetorquerModel::new(reply_sender.clone());
// Input connections.
@ -52,9 +59,14 @@ fn create_sim_controller(
.mgt_switch
.connect(MagnetorquerModel::switch_device, &mgt_addr);
// Output connections.
mgt_model
.gen_magnetic_field
.connect(MagnetometerModel::apply_external_magnetic_field, &mgm_addr);
mgt_model.gen_magnetic_field.connect(
MagnetometerModel::apply_external_magnetic_field,
&mgm_0_addr,
);
mgt_model.gen_magnetic_field.connect(
MagnetometerModel::apply_external_magnetic_field,
&mgm_1_addr,
);
// Instantiate the simulator
let sys_clock = SystemClock::from_system_time(start_time, SystemTime::now());
@ -63,19 +75,21 @@ fn create_sim_controller(
} else {
SimInit::new()
};
let simulation = sim_init
.add_model(mgm_model, mgm_mailbox)
.add_model(pcdu_model, pcdu_mailbox)
.add_model(mgt_model, mgt_mailbox)
.init(start_time);
let addrs = ModelAddrWrapper::new(mgm_0_addr, mgm_1_addr, pcdu_addr, mgt_addr);
let (simulation, scheduler) = sim_init
.add_model(mgm_0_model, mgm_0_mailbox, "MGM 0 model")
.add_model(mgm_1_model, mgm_1_mailbox, "MGM 1 model")
.add_model(pcdu_model, pcdu_mailbox, "PCDU model")
.add_model(mgt_model, mgt_mailbox, "MGT model")
.init(start_time)
.unwrap();
SimController::new(
sys_clock,
request_receiver,
reply_sender,
simulation,
mgm_addr,
pcdu_addr,
mgt_addr,
scheduler,
addrs,
)
}

View File

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

View File

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

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [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

View File

@ -1,7 +1,7 @@
[package]
name = "satrs-shared"
description = "Components shared by multiple sat-rs crates"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@ -22,7 +22,7 @@ version = "0.3"
optional = true
[dependencies.spacepackets]
version = ">0.9, <=0.12"
version = ">0.9, <=0.13"
default-features = false
[features]

View File

@ -8,14 +8,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
`spacepackets` v0.13
## Changed
- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new
`new` implementation expects a type struct instead of tuples.
- Moved `cfdp` module to [dedicated crate](https://egit.irs.uni-stuttgart.de/rust/cfdp)
- Moved `seq_count` module to [spacepackets](https://egit.irs.uni-stuttgart.de/rust/spacepackets)
crate
## Added
- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers.
- Scheduling table for systems with a standard runtime
- Mode Tree Feature which allows building a network of mode components which can send mode
messages to each other.
- Added first helper features like the `SubsystemExecutionHelper` and the
`SubsystemCommandingHelper` which allows to build subsystem components. Subsystem components
are able to execute mode sequences and perform target keeping based on a declarative table
format.
- Added `DevManagerCommandingHelper` which performs some of the boilerplate logik required
by Assembly and Device Management components. This includes forwarding mode requests and
handling mode replies.
- First basic health module with `HealthState`s and the `HealthTableProvider` trait. These
components are important for any FDIR components which get added in the future.
# [v0.2.1] 2024-05-19

View File

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

3
satrs/docs.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open

View File

@ -3,8 +3,7 @@ Checklist for new releases
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`.
1. Make sure any new modules are documented sufficiently enough and check docs by running `docs.sh`.
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.

File diff suppressed because it is too large Load Diff

View File

@ -1,769 +0,0 @@
use alloc::string::{String, ToString};
use core::fmt::Display;
use crc::{Crc, CRC_32_CKSUM};
use spacepackets::cfdp::ChecksumType;
use spacepackets::ByteConversionError;
#[cfg(feature = "std")]
use std::error::Error;
use std::path::Path;
#[cfg(feature = "std")]
pub use std_mod::*;
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
#[derive(Debug, Clone)]
pub enum FilestoreError {
FileDoesNotExist,
FileAlreadyExists,
DirDoesNotExist,
Permission,
IsNotFile,
IsNotDirectory,
ByteConversion(ByteConversionError),
Io {
raw_errno: Option<i32>,
string: String,
},
ChecksumTypeNotImplemented(ChecksumType),
}
impl From<ByteConversionError> for FilestoreError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl Display for FilestoreError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FilestoreError::FileDoesNotExist => {
write!(f, "file does not exist")
}
FilestoreError::FileAlreadyExists => {
write!(f, "file already exists")
}
FilestoreError::DirDoesNotExist => {
write!(f, "directory does not exist")
}
FilestoreError::Permission => {
write!(f, "permission error")
}
FilestoreError::IsNotFile => {
write!(f, "is not a file")
}
FilestoreError::IsNotDirectory => {
write!(f, "is not a directory")
}
FilestoreError::ByteConversion(e) => {
write!(f, "filestore error: {e}")
}
FilestoreError::Io { raw_errno, string } => {
write!(
f,
"filestore generic IO error with raw errno {:?}: {}",
raw_errno, string
)
}
FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
write!(f, "checksum {:?} not implemented", checksum_type)
}
}
}
}
impl Error for FilestoreError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
FilestoreError::ByteConversion(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for FilestoreError {
fn from(value: std::io::Error) -> Self {
Self::Io {
raw_errno: value.raw_os_error(),
string: value.to_string(),
}
}
}
pub trait VirtualFilestore {
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
/// Truncating a file means deleting all its data so the resulting file is empty.
/// This can be more efficient than removing and re-creating a file.
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
fn read_data(
&self,
file_path: &str,
offset: u64,
read_len: u64,
buf: &mut [u8],
) -> Result<(), FilestoreError>;
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
fn filename_from_full_path(path: &str) -> Option<&str>
where
Self: Sized,
{
// Convert the path string to a Path
let path = Path::new(path);
// Extract the file name using the file_name() method
path.file_name().and_then(|name| name.to_str())
}
fn is_file(&self, path: &str) -> bool;
fn is_dir(&self, path: &str) -> bool {
!self.is_file(path)
}
fn exists(&self, path: &str) -> bool;
/// This special function is the CFDP specific abstraction to verify the checksum of a file.
/// This allows to keep OS specific details like reading the whole file in the most efficient
/// manner inside the file system abstraction.
///
/// The passed verification buffer argument will be used by the specific implementation as
/// a buffer to read the file into. It is recommended to use common buffer sizes like
/// 4096 or 8192 bytes.
fn checksum_verify(
&self,
file_path: &str,
checksum_type: ChecksumType,
expected_checksum: u32,
verification_buf: &mut [u8],
) -> Result<bool, FilestoreError>;
}
#[cfg(feature = "std")]
pub mod std_mod {
use super::*;
use std::{
fs::{self, File, OpenOptions},
io::{BufReader, Read, Seek, SeekFrom, Write},
};
#[derive(Default)]
pub struct NativeFilestore {}
impl VirtualFilestore for NativeFilestore {
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if self.exists(file_path) {
return Err(FilestoreError::FileAlreadyExists);
}
File::create(file_path)?;
Ok(())
}
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if !self.exists(file_path) {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_path) {
return Err(FilestoreError::IsNotFile);
}
fs::remove_file(file_path)?;
Ok(())
}
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
if !self.exists(file_path) {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_path) {
return Err(FilestoreError::IsNotFile);
}
OpenOptions::new()
.write(true)
.truncate(true)
.open(file_path)?;
Ok(())
}
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
raw_errno: e.raw_os_error(),
string: e.to_string(),
})?;
Ok(())
}
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
if !self.exists(dir_path) {
return Err(FilestoreError::DirDoesNotExist);
}
if !self.is_dir(dir_path) {
return Err(FilestoreError::IsNotDirectory);
}
if !all {
fs::remove_dir(dir_path)?;
return Ok(());
}
fs::remove_dir_all(dir_path)?;
Ok(())
}
fn read_data(
&self,
file_name: &str,
offset: u64,
read_len: u64,
buf: &mut [u8],
) -> Result<(), FilestoreError> {
if buf.len() < read_len as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: read_len as usize,
}
.into());
}
if !self.exists(file_name) {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file_name) {
return Err(FilestoreError::IsNotFile);
}
let mut file = File::open(file_name)?;
file.seek(SeekFrom::Start(offset))?;
file.read_exact(&mut buf[0..read_len as usize])?;
Ok(())
}
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
if !self.exists(file) {
return Err(FilestoreError::FileDoesNotExist);
}
if !self.is_file(file) {
return Err(FilestoreError::IsNotFile);
}
let mut file = OpenOptions::new().write(true).open(file)?;
file.seek(SeekFrom::Start(offset))?;
file.write_all(buf)?;
Ok(())
}
fn is_file(&self, path: &str) -> bool {
let path = Path::new(path);
path.is_file()
}
fn exists(&self, path: &str) -> bool {
let path = Path::new(path);
if !path.exists() {
return false;
}
true
}
fn checksum_verify(
&self,
file_path: &str,
checksum_type: ChecksumType,
expected_checksum: u32,
verification_buf: &mut [u8],
) -> Result<bool, FilestoreError> {
match checksum_type {
ChecksumType::Modular => {
if self.calc_modular_checksum(file_path)? == expected_checksum {
return Ok(true);
}
Ok(false)
}
ChecksumType::Crc32 => {
let mut digest = CRC_32.digest();
let file_to_check = File::open(file_path)?;
let mut buf_reader = BufReader::new(file_to_check);
loop {
let bytes_read = buf_reader.read(verification_buf)?;
if bytes_read == 0 {
break;
}
digest.update(&verification_buf[0..bytes_read]);
}
if digest.finalize() == expected_checksum {
return Ok(true);
}
Ok(false)
}
ChecksumType::NullChecksum => Ok(true),
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
}
}
}
impl NativeFilestore {
pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
let mut checksum: u32 = 0;
let file = File::open(file_path)?;
let mut buf_reader = BufReader::new(file);
let mut buffer = [0; 4];
loop {
let bytes_read = buf_reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
// Perform padding directly in the buffer
(bytes_read..4).for_each(|i| {
buffer[i] = 0;
});
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
}
Ok(checksum)
}
}
}
#[cfg(test)]
mod tests {
use std::{fs, path::Path, println};
use super::*;
use alloc::format;
use tempfile::tempdir;
const EXAMPLE_DATA_CFDP: [u8; 15] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
];
const NATIVE_FS: NativeFilestore = NativeFilestore {};
#[test]
fn test_basic_native_filestore_create() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_ok());
let path = Path::new(&file_path);
assert!(path.exists());
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
}
#[test]
fn test_basic_native_fs_file_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
}
#[test]
fn test_basic_native_fs_dir_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("testdir");
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
assert!(NATIVE_FS.is_dir(dir_path.as_path().to_str().unwrap()));
}
#[test]
fn test_basic_native_fs_remove_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
NATIVE_FS
.remove_file(file_path.to_str().unwrap())
.expect("removing file failed");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
}
#[test]
fn test_basic_native_fs_write() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
println!("{}", file_path.to_str().unwrap());
let write_data = "hello world\n";
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
.expect("writing to file failed");
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
assert_eq!(read_back, write_data);
}
#[test]
fn test_basic_native_fs_read() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
println!("{}", file_path.to_str().unwrap());
let write_data = "hello world\n";
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
.expect("writing to file failed");
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
assert_eq!(read_back, write_data);
}
#[test]
fn test_truncate_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
NATIVE_FS
.truncate_file(file_path.to_str().unwrap())
.unwrap();
assert_eq!(fs::read(file_path.clone()).unwrap(), []);
}
#[test]
fn test_remove_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("testdir");
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
NATIVE_FS
.remove_dir(dir_path.to_str().unwrap(), false)
.unwrap();
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
}
#[test]
fn test_read_file() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.expect("creating file failed");
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
let read_buf: &mut [u8] = &mut [0; 4];
NATIVE_FS
.read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
.unwrap();
assert_eq!([1, 2, 3, 4], read_buf);
NATIVE_FS
.write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
.expect("writing to file failed");
NATIVE_FS
.read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
.unwrap();
assert_eq!([3, 4, 5, 6], read_buf);
}
#[test]
fn test_remove_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_file_already_exists() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_ok());
let result =
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileAlreadyExists = error {
assert_eq!(error.to_string(), "file already exists");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_file_with_dir_api() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotDirectory = error {
assert_eq!(error.to_string(), "is not a directory");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_dir_remove_all() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
let file_path = dir_path.as_path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
assert!(result.is_ok());
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
}
#[test]
fn test_remove_dir_with_file_api() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_dir_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::DirDoesNotExist = error {
assert_eq!(error.to_string(), "directory does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_remove_file_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_truncate_file_which_does_not_exist() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
assert_eq!(error.to_string(), "file does not exist");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_truncate_file_on_directory() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_byte_conversion_error_when_reading() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::ByteConversion(byte_conv_error) = error {
if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
assert_eq!(found, 0);
assert_eq!(expected, 2);
} else {
panic!("unexpected error");
}
assert_eq!(
error.to_string(),
format!("filestore error: {}", byte_conv_error)
);
} else {
panic!("unexpected error");
}
}
#[test]
fn test_read_file_on_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let dir_path = tmpdir.path().join("test");
NATIVE_FS
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
assert_eq!(error.to_string(), "is not a file");
} else {
panic!("unexpected error");
}
}
#[test]
fn test_write_file_non_existing() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::FileDoesNotExist = error {
} else {
panic!("unexpected error");
}
}
#[test]
fn test_write_file_on_dir() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test");
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::IsNotFile = error {
} else {
panic!("unexpected error");
}
}
#[test]
fn test_filename_extraction() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("test.txt");
NATIVE_FS
.create_file(file_path.to_str().expect("getting str for file failed"))
.unwrap();
NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
}
#[test]
fn test_modular_checksum() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
// Kind of re-writing the modular checksum impl here which we are trying to test, but the
// numbers/correctness were verified manually using calculators, so this is okay.
let mut checksum: u32 = 0;
let mut buffer: [u8; 4] = [0; 4];
for i in 0..3 {
buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
}
buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
buffer[3] = 0;
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
let mut verif_buf: [u8; 32] = [0; 32];
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::Modular,
checksum,
&mut verif_buf,
);
assert!(result.is_ok());
}
#[test]
fn test_null_checksum_impl() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
// The file to check does not even need to exist, and the verification buffer can be
// empty: the null checksum is always yields the same result.
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::NullChecksum,
0,
&mut [],
);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_checksum_not_implemented() {
let tmpdir = tempdir().expect("creating tmpdir failed");
let file_path = tmpdir.path().join("mod-crc.bin");
// The file to check does not even need to exist, and the verification buffer can be
// empty: the null checksum is always yields the same result.
let result = NATIVE_FS.checksum_verify(
file_path.to_str().unwrap(),
ChecksumType::Crc32Proximity1,
0,
&mut [],
);
assert!(result.is_err());
let error = result.unwrap_err();
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
assert_eq!(
error.to_string(),
format!("checksum {:?} not implemented", cksum_type)
);
} else {
panic!("unexpected error");
}
}
}

View File

@ -1,668 +0,0 @@
//! This module contains the implementation of the CFDP high level classes as specified in the
//! CCSDS 727.0-B-5.
use core::{cell::RefCell, fmt::Debug, hash::Hash};
use crc::{Crc, CRC_32_CKSUM};
use hashbrown::HashMap;
use spacepackets::{
cfdp::{
pdu::{FileDirectiveType, PduError, PduHeader},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
},
util::{UnsignedByteField, UnsignedEnum},
};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::time::CountdownProvider;
#[cfg(feature = "std")]
pub mod dest;
#[cfg(feature = "alloc")]
pub mod filestore;
#[cfg(feature = "std")]
pub mod source;
pub mod user;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityType {
Sending,
Receiving,
}
pub enum TimerContext {
CheckLimit {
local_id: UnsignedByteField,
remote_id: UnsignedByteField,
entity_type: EntityType,
},
NakActivity {
expiry_time_seconds: f32,
},
PositiveAck {
expiry_time_seconds: f32,
},
}
/// A generic trait which allows CFDP entities to create check timers which are required to
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
/// and 4.6.3.3.
///
/// This trait also allows the creation of different check timers depending on context and purpose
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
/// other factors.
///
/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
///
/// ## 1. Check limit handling
///
/// The first mechanism is the check limit handling for unacknowledged transfers as specified
/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
/// For this mechanism, the timer has different functionality depending on whether
/// the using entity is the sending entity or the receiving entity for the unacknowledged
/// transmission mode.
///
/// For the sending entity, this timer determines the expiry period for declaring a check limit
/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
/// Also see 4.6.3.2 of the CFDP standard.
///
/// For the receiving entity, this timer determines the expiry period for incrementing a check
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
///
/// ## 2. NAK activity limit
///
/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
/// standard. The expiration period will be provided by the NAK timer expiration limit of the
/// remote entity configuration.
///
/// ## 3. Positive ACK procedures
///
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
/// interval of the remote entity configuration.
#[cfg(feature = "alloc")]
pub trait CheckTimerCreator {
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CountdownProvider>;
}
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
/// It also assumes that a second accuracy of the check timer period is sufficient.
#[cfg(feature = "std")]
#[derive(Debug)]
pub struct StdCheckTimer {
expiry_time_seconds: u64,
start_time: std::time::Instant,
}
#[cfg(feature = "std")]
impl StdCheckTimer {
pub fn new(expiry_time_seconds: u64) -> Self {
Self {
expiry_time_seconds,
start_time: std::time::Instant::now(),
}
}
}
#[cfg(feature = "std")]
impl CountdownProvider for StdCheckTimer {
fn has_expired(&self) -> bool {
let elapsed_time = self.start_time.elapsed();
if elapsed_time.as_secs() > self.expiry_time_seconds {
return true;
}
false
}
fn reset(&mut self) {
self.start_time = std::time::Instant::now();
}
}
/// This structure models the remote entity configuration information as specified in chapter 8.3
/// of the CFDP standard.
/// Some of the fields which were not considered necessary for the Rust implementation
/// were omitted. Some other fields which are not contained inside the standard but are considered
/// necessary for the Rust implementation are included.
///
/// ## Notes on Positive Acknowledgment Procedures
///
/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
/// Once the expected ACK response has not been received for that interval, as counter will be
/// incremented and the timer will be reset. Once the counter exceeds the
/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
///
/// ## Notes on Deferred Lost Segment Procedures
///
/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
/// reset when missing segments or missing metadata is received. The timer will be deactivated if
/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
///
/// ## Fields
///
/// * `entity_id` - The ID of the remote entity.
/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
/// to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
/// of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
/// to None, the maximum file segment length will be derived from the maximum packet length.
/// If this has some value which is smaller than the segment value derived from
/// `max_packet_len`, this value will be picked.
/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
/// the Put Request, it will be determined from this field in the remote configuration.
/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
/// Request, it will be determined from this field in the remote configuration.
/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
/// Put Request, it will be determined from this field in the remote configuration.
/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
/// transaction cancellation. Defaults to False.
/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
/// this remote entity.
/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
/// after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
/// 2, so the check limit timer may expire twice.
/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
/// Procedures inside the class documentation. Expected as floating point seconds. Defaults to
/// 10 seconds.
/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
/// Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
/// file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
/// * `nak_timer_interval_seconds` - See the notes on the Deferred Lost Segment Procedure inside
/// the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
/// the class documentation. Defaults to 2, so the timer may expire two times.
#[derive(Debug, Copy, Clone)]
pub struct RemoteEntityConfig {
pub entity_id: UnsignedByteField,
pub max_packet_len: usize,
pub max_file_segment_len: usize,
pub closure_requested_by_default: bool,
pub crc_on_transmission_by_default: bool,
pub default_transmission_mode: TransmissionMode,
pub default_crc_type: ChecksumType,
pub positive_ack_timer_interval_seconds: f32,
pub positive_ack_timer_expiration_limit: u32,
pub check_limit: u32,
pub disposition_on_cancellation: bool,
pub immediate_nak_mode: bool,
pub nak_timer_interval_seconds: f32,
pub nak_timer_expiration_limit: u32,
}
impl RemoteEntityConfig {
pub fn new_with_default_values(
entity_id: UnsignedByteField,
max_file_segment_len: usize,
max_packet_len: usize,
closure_requested_by_default: bool,
crc_on_transmission_by_default: bool,
default_transmission_mode: TransmissionMode,
default_crc_type: ChecksumType,
) -> Self {
Self {
entity_id,
max_file_segment_len,
max_packet_len,
closure_requested_by_default,
crc_on_transmission_by_default,
default_transmission_mode,
default_crc_type,
check_limit: 2,
positive_ack_timer_interval_seconds: 10.0,
positive_ack_timer_expiration_limit: 2,
disposition_on_cancellation: false,
immediate_nak_mode: true,
nak_timer_interval_seconds: 10.0,
nak_timer_expiration_limit: 2,
}
}
}
pub trait RemoteEntityConfigProvider {
/// Retrieve the remote entity configuration for the given remote ID.
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
/// Add a new remote configuration. Return [true] if the configuration was
/// inserted successfully, and [false] if a configuration already exists.
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
/// Remote a configuration. Returns [true] if the configuration was removed successfully,
/// and [false] if no configuration exists for the given remote ID.
fn remove_config(&mut self, remote_id: u64) -> bool;
}
#[cfg(feature = "std")]
#[derive(Default)]
pub struct StdRemoteEntityConfigProvider {
remote_cfg_table: HashMap<u64, RemoteEntityConfig>,
}
#[cfg(feature = "std")]
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
self.remote_cfg_table.get(&remote_id)
}
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
self.remote_cfg_table.get_mut(&remote_id)
}
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
self.remote_cfg_table
.insert(cfg.entity_id.value(), *cfg)
.is_some()
}
fn remove_config(&mut self, remote_id: u64) -> bool {
self.remote_cfg_table.remove(&remote_id).is_some()
}
}
/// This trait introduces some callbacks which will be called when a particular CFDP fault
/// handler is called.
///
/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity
/// configuration and provides a way to specify custom user error handlers. This allows to
/// implement some CFDP features like fault handler logging, which would not be possible
/// generically otherwise.
///
/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback
/// will be called depending on the [FaultHandlerCode].
pub trait UserFaultHandler {
fn notice_of_suspension_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
);
fn notice_of_cancellation_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
);
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
}
/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
/// standard.
///
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
/// user-provided callback function provided by the [UserFaultHandler].
///
/// Some note on the provided default settings:
///
/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
/// cancelling the transfer immediately would interfere with the check limit mechanism specified
/// in chapter 4.6.3.3.
/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
/// not supported the file transfer might still have worked properly.
///
/// For all other faults, the default fault handling operation will be to cancel the transaction.
/// These defaults can be overriden by using the [Self::set_fault_handler] method.
/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
/// entity.
pub struct DefaultFaultHandler {
handler_array: [FaultHandlerCode; 10],
// Could also change the user fault handler trait to have non mutable methods, but that limits
// flexbility on the user side..
user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
}
impl DefaultFaultHandler {
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
Some(match conditon_code {
ConditionCode::PositiveAckLimitReached => 0,
ConditionCode::KeepAliveLimitReached => 1,
ConditionCode::InvalidTransmissionMode => 2,
ConditionCode::FilestoreRejection => 3,
ConditionCode::FileChecksumFailure => 4,
ConditionCode::FileSizeError => 5,
ConditionCode::NakLimitReached => 6,
ConditionCode::InactivityDetected => 7,
ConditionCode::CheckLimitReached => 8,
ConditionCode::UnsupportedChecksumType => 9,
_ => return None,
})
}
pub fn set_fault_handler(
&mut self,
condition_code: ConditionCode,
fault_handler: FaultHandlerCode,
) {
let array_idx = Self::condition_code_to_array_index(condition_code);
if array_idx.is_none() {
return;
}
self.handler_array[array_idx.unwrap()] = fault_handler;
}
pub fn new(user_fault_handler: Box<dyn UserFaultHandler + Send>) -> Self {
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
init_array
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
FaultHandlerCode::IgnoreError;
init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
.unwrap()] = FaultHandlerCode::IgnoreError;
Self {
handler_array: init_array,
user_fault_handler: RefCell::new(user_fault_handler),
}
}
pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
let array_idx = Self::condition_code_to_array_index(condition_code);
if array_idx.is_none() {
return FaultHandlerCode::IgnoreError;
}
self.handler_array[array_idx.unwrap()]
}
pub fn report_fault(
&self,
transaction_id: TransactionId,
condition: ConditionCode,
progress: u64,
) -> FaultHandlerCode {
let array_idx = Self::condition_code_to_array_index(condition);
if array_idx.is_none() {
return FaultHandlerCode::IgnoreError;
}
let fh_code = self.handler_array[array_idx.unwrap()];
let mut handler_mut = self.user_fault_handler.borrow_mut();
match fh_code {
FaultHandlerCode::NoticeOfCancellation => {
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
}
FaultHandlerCode::NoticeOfSuspension => {
handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
}
FaultHandlerCode::IgnoreError => {
handler_mut.ignore_cb(transaction_id, condition, progress);
}
FaultHandlerCode::AbandonTransaction => {
handler_mut.abandoned_cb(transaction_id, condition, progress);
}
}
fh_code
}
}
pub struct IndicationConfig {
pub eof_sent: bool,
pub eof_recv: bool,
pub file_segment_recv: bool,
pub transaction_finished: bool,
pub suspended: bool,
pub resumed: bool,
}
impl Default for IndicationConfig {
fn default() -> Self {
Self {
eof_sent: true,
eof_recv: true,
file_segment_recv: true,
transaction_finished: true,
suspended: true,
resumed: true,
}
}
}
pub struct LocalEntityConfig {
pub id: UnsignedByteField,
pub indication_cfg: IndicationConfig,
pub default_fault_handler: DefaultFaultHandler,
}
/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
/// number of that transfer which is also determined by the CFDP source entity.
#[derive(Debug, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TransactionId {
source_id: UnsignedByteField,
seq_num: UnsignedByteField,
}
impl TransactionId {
pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
Self { source_id, seq_num }
}
pub fn source_id(&self) -> &UnsignedByteField {
&self.source_id
}
pub fn seq_num(&self) -> &UnsignedByteField {
&self.seq_num
}
}
impl Hash for TransactionId {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.source_id.value().hash(state);
self.seq_num.value().hash(state);
}
}
impl PartialEq for TransactionId {
fn eq(&self, other: &Self) -> bool {
self.source_id.value() == other.source_id.value()
&& self.seq_num.value() == other.seq_num.value()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TransactionStep {
Idle = 0,
TransactionStart = 1,
ReceivingFileDataPdus = 2,
ReceivingFileDataPdusWithCheckLimitHandling = 3,
SendingAckPdu = 4,
TransferCompletion = 5,
SendingFinishedPdu = 6,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum State {
Idle = 0,
Busy = 1,
Suspended = 2,
}
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketTarget {
SourceEntity,
DestEntity,
}
/// This is a helper struct which contains base information about a particular PDU packet.
/// This is also necessary information for CFDP packet routing. For example, some packet types
/// like file data PDUs can only be used by CFDP source entities.
pub struct PacketInfo<'raw_packet> {
pdu_type: PduType,
pdu_directive: Option<FileDirectiveType>,
target: PacketTarget,
raw_packet: &'raw_packet [u8],
}
impl<'raw> PacketInfo<'raw> {
pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
if pdu_header.pdu_type() == PduType::FileData {
return Ok(Self {
pdu_type: pdu_header.pdu_type(),
pdu_directive: None,
target: PacketTarget::DestEntity,
raw_packet,
});
}
if pdu_header.pdu_datafield_len() < 1 {
return Err(PduError::FormatError);
}
// Route depending on PDU type and directive type if applicable. Retrieve directive type
// from the raw stream for better performance (with sanity and directive code check).
// The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
// procedure.
let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
PduError::InvalidDirectiveType {
found: raw_packet[header_len],
expected: None,
}
})?;
let packet_target = match directive {
// Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
// the source handler
FileDirectiveType::NakPdu
| FileDirectiveType::FinishedPdu
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
// Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
// the destination handler
FileDirectiveType::MetadataPdu
| FileDirectiveType::EofPdu
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
// Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
// extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
// the source handler, for a Finished PDU, it is passed to the destination handler.
FileDirectiveType::AckPdu => {
let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1])
.map_err(|_| PduError::InvalidDirectiveType {
found: raw_packet[header_len],
expected: None,
})?;
if acked_directive == FileDirectiveType::EofPdu {
PacketTarget::SourceEntity
} else if acked_directive == FileDirectiveType::FinishedPdu {
PacketTarget::DestEntity
} else {
// TODO: Maybe a better error? This might be confusing..
return Err(PduError::InvalidDirectiveType {
found: raw_packet[header_len + 1],
expected: None,
});
}
}
};
Ok(Self {
pdu_type: pdu_header.pdu_type(),
pdu_directive: Some(directive),
target: packet_target,
raw_packet,
})
}
pub fn pdu_type(&self) -> PduType {
self.pdu_type
}
pub fn pdu_directive(&self) -> Option<FileDirectiveType> {
self.pdu_directive
}
pub fn target(&self) -> PacketTarget {
self.target
}
pub fn raw_packet(&self) -> &[u8] {
self.raw_packet
}
}
#[cfg(test)]
mod tests {
use spacepackets::cfdp::{
lv::Lv,
pdu::{
eof::EofPdu,
file_data::FileDataPdu,
metadata::{MetadataGenericParams, MetadataPduCreator},
CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
},
PduType,
};
use crate::cfdp::PacketTarget;
use super::PacketInfo;
fn generic_pdu_header() -> PduHeader {
let pdu_conf = CommonPduConfig::default();
PduHeader::new_no_file_data(pdu_conf, 0)
}
#[test]
fn test_metadata_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let metadata_params = MetadataGenericParams::default();
let src_file_name = "hello.txt";
let dest_file_name = "hello-dest.txt";
let src_lv = Lv::new_from_str(src_file_name).unwrap();
let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
let metadata_pdu =
MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
metadata_pdu
.write_to_bytes(&mut buf)
.expect("writing metadata PDU failed");
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
assert!(packet_info.pdu_directive().is_some());
assert_eq!(
packet_info.pdu_directive().unwrap(),
FileDirectiveType::MetadataPdu
);
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
}
#[test]
fn test_filedata_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
file_data_pdu
.write_to_bytes(&mut buf)
.expect("writing file data PDU failed");
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
assert_eq!(packet_info.pdu_type(), PduType::FileData);
assert!(packet_info.pdu_directive().is_none());
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
}
#[test]
fn test_eof_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
eof_pdu
.write_to_bytes(&mut buf)
.expect("writing file data PDU failed");
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
assert!(packet_info.pdu_directive().is_some());
assert_eq!(
packet_info.pdu_directive().unwrap(),
FileDirectiveType::EofPdu
);
}
}

View File

@ -1,15 +0,0 @@
#![allow(dead_code)]
use spacepackets::util::UnsignedByteField;
pub struct SourceHandler {
id: UnsignedByteField,
}
impl SourceHandler {
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
Self { id: id.into() }
}
}
#[cfg(test)]
mod tests {}

View File

@ -1,96 +0,0 @@
use spacepackets::{
cfdp::{
pdu::{
file_data::SegmentMetadata,
finished::{DeliveryCode, FileStatus},
},
tlv::{msg_to_user::MsgToUserTlv, WritableTlv},
ConditionCode,
},
util::UnsignedByteField,
};
use super::TransactionId;
#[derive(Debug, Copy, Clone)]
pub struct TransactionFinishedParams {
pub id: TransactionId,
pub condition_code: ConditionCode,
pub delivery_code: DeliveryCode,
pub file_status: FileStatus,
}
#[derive(Debug)]
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
pub id: TransactionId,
pub source_id: UnsignedByteField,
pub file_size: u64,
pub src_file_name: &'src_file str,
pub dest_file_name: &'dest_file str,
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub struct OwnedMetadataRecvdParams {
pub id: TransactionId,
pub source_id: UnsignedByteField,
pub file_size: u64,
pub src_file_name: alloc::string::String,
pub dest_file_name: alloc::string::String,
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
#[cfg(feature = "alloc")]
impl From<MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
fn from(value: MetadataReceivedParams) -> Self {
Self::from(&value)
}
}
#[cfg(feature = "alloc")]
impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
fn from(value: &MetadataReceivedParams) -> Self {
Self {
id: value.id,
source_id: value.source_id,
file_size: value.file_size,
src_file_name: value.src_file_name.into(),
dest_file_name: value.dest_file_name.into(),
msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(),
}
}
}
#[derive(Debug)]
pub struct FileSegmentRecvdParams<'seg_meta> {
pub id: TransactionId,
pub offset: u64,
pub length: usize,
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
}
pub trait CfdpUser {
fn transaction_indication(&mut self, id: &TransactionId);
fn eof_sent_indication(&mut self, id: &TransactionId);
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
// TODO: The standard does not strictly specify how the report information looks..
fn report_indication(&mut self, id: &TransactionId);
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
fn fault_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
fn abandoned_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
fn eof_recvd_indication(&mut self, id: &TransactionId);
}

448
satrs/src/dev_mgmt.rs Normal file
View File

@ -0,0 +1,448 @@
use crate::{
mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender},
mode_tree::{ModeStoreProvider, ModeStoreVec},
queue::{GenericSendError, GenericTargetedMessagingError},
request::{GenericMessage, RequestId},
ComponentId,
};
use core::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ActiveModeCommandContext {
pub target_mode: ModeAndSubmode,
pub active_request_id: RequestId,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub enum DevManagerHelperResult {
#[default]
Idle,
Busy,
ModeCommandingDone(ActiveModeCommandContext),
}
#[derive(Debug)]
pub enum DevManagerHelperError {
ChildNotInStore,
}
pub trait DevManagerUserHook: Debug {
fn send_mode_cmd_to_child(
&self,
request_id: RequestId,
target_id: ComponentId,
mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError>;
fn send_mode_cmds_to_children(
&self,
request_id: RequestId,
commanded_parent_mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError>;
}
#[derive(Debug, Default)]
pub struct TransparentDevManagerHook {}
impl DevManagerUserHook for TransparentDevManagerHook {
fn send_mode_cmds_to_children(
&self,
request_id: RequestId,
commanded_parent_mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
for child in children_mode_store {
mode_req_sender.send_mode_request(
request_id,
child.id(),
ModeRequest::SetMode {
mode_and_submode: commanded_parent_mode,
forced,
},
)?;
child.awaiting_reply = true;
}
Ok(())
}
fn send_mode_cmd_to_child(
&self,
request_id: RequestId,
target_id: ComponentId,
mode: ModeAndSubmode,
forced: bool,
children_mode_store: &mut ModeStoreVec,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
let mut_val = children_mode_store
.get_mut(target_id)
.ok_or(GenericSendError::TargetDoesNotExist(target_id))?;
mut_val.awaiting_reply = true;
mode_req_sender.send_mode_request(
request_id,
target_id,
ModeRequest::SetMode {
mode_and_submode: mode,
forced,
},
)?;
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DevManagerCommandingState {
#[default]
Idle,
AwaitingReplies(ActiveModeCommandContext),
}
impl DevManagerCommandingState {
fn new_active_cmd(mode_and_submode: ModeAndSubmode, active_request_id: RequestId) -> Self {
DevManagerCommandingState::AwaitingReplies(ActiveModeCommandContext {
target_mode: mode_and_submode,
active_request_id,
})
}
}
/// A generic helper for manager components which manage child components in a mode tree.
///
/// Mode commands are usually forwarded to all children components transparently.
/// For example, this could be used in an Assembly component which manages multiple redundant
/// child components. It can also be used inside a manager component which only manages one device.
#[derive(Debug, Default)]
pub struct DevManagerCommandingHelper<UserHook: DevManagerUserHook> {
/// The IDs, modes and reply awaition status of all children are tracked in this data
/// structure.
pub children_mode_store: ModeStoreVec,
pub user_hook: UserHook,
pub state: DevManagerCommandingState,
}
impl<UserHook: DevManagerUserHook> DevManagerCommandingHelper<UserHook> {
pub fn new(user_hook: UserHook) -> Self {
Self {
children_mode_store: Default::default(),
user_hook,
state: Default::default(),
}
}
pub fn send_mode_cmd_to_one_child(
&mut self,
request_id: RequestId,
target_id: ComponentId,
mode_and_submode: ModeAndSubmode,
forced: bool,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
self.user_hook.send_mode_cmd_to_child(
request_id,
target_id,
mode_and_submode,
forced,
&mut self.children_mode_store,
mode_req_sender,
)?;
Ok(())
}
pub fn send_mode_cmd_to_all_children(
&mut self,
request_id: RequestId,
mode_and_submode: ModeAndSubmode,
forced: bool,
mode_req_sender: &impl ModeRequestSender,
) -> Result<(), GenericSendError> {
self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id);
self.user_hook.send_mode_cmds_to_children(
request_id,
mode_and_submode,
forced,
&mut self.children_mode_store,
mode_req_sender,
)?;
Ok(())
}
pub fn target_mode(&self) -> Option<ModeAndSubmode> {
match self.state {
DevManagerCommandingState::Idle => None,
DevManagerCommandingState::AwaitingReplies(context) => Some(context.target_mode),
}
}
pub fn state(&self) -> DevManagerCommandingState {
self.state
}
pub fn send_announce_mode_cmd_to_children(
&self,
request_id: RequestId,
mode_req_sender: &impl ModeRequestSender,
recursive: bool,
) -> Result<(), GenericTargetedMessagingError> {
let mut request = ModeRequest::AnnounceMode;
if recursive {
request = ModeRequest::AnnounceModeRecursive;
}
for child in self.children_mode_store.0.iter() {
mode_req_sender.send_mode_request(request_id, child.id(), request)?;
}
Ok(())
}
pub fn add_mode_child(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
self.children_mode_store.add_component(target_id, mode);
}
/// Helper method which counts the number of children which have a certain mode.
pub fn count_number_of_children_with_mode(&self, mode_and_submode: ModeAndSubmode) -> usize {
let mut children_in_target_mode = 0;
for child in &self.children_mode_store {
if child.mode_and_submode() == mode_and_submode {
children_in_target_mode += 1;
}
}
children_in_target_mode
}
pub fn handle_mode_reply(
&mut self,
mode_reply: &GenericMessage<ModeReply>,
) -> Result<DevManagerHelperResult, DevManagerHelperError> {
let context = match self.state {
DevManagerCommandingState::Idle => return Ok(DevManagerHelperResult::Idle),
DevManagerCommandingState::AwaitingReplies(active_mode_command_context) => {
Some(active_mode_command_context)
}
};
if !self
.children_mode_store
.has_component(mode_reply.sender_id())
{
return Err(DevManagerHelperError::ChildNotInStore);
}
let mut generic_mode_reply_handler = |mode_and_submode: Option<ModeAndSubmode>| {
// Tying the reply awaition to the request ID ensures that something like replies
// belonging to older requests do not interfere with the completion handling of
// the mode commanding. This is important for forced mode commands.
let mut handle_awaition = false;
if let DevManagerCommandingState::AwaitingReplies { .. } = self.state {
handle_awaition = true;
}
let still_awating_replies = self.children_mode_store.mode_reply_handler(
mode_reply.sender_id(),
mode_and_submode,
handle_awaition,
);
// It is okay to unwrap: If awaition should be handled, the returned value should
// always be some valid value.
if handle_awaition && !still_awating_replies.unwrap() {
self.state = DevManagerCommandingState::Idle;
return Ok(DevManagerHelperResult::ModeCommandingDone(context.unwrap()));
}
Ok(DevManagerHelperResult::Busy)
};
match mode_reply.message {
ModeReply::ModeInfo(mode_and_submode) | ModeReply::ModeReply(mode_and_submode) => {
generic_mode_reply_handler(Some(mode_and_submode))
}
ModeReply::CantReachMode(_result_u16) => generic_mode_reply_handler(None),
ModeReply::WrongMode {
expected: _,
reached,
} => generic_mode_reply_handler(Some(reached)),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
mode::{tests::ModeReqSenderMock, UNKNOWN_MODE},
request::MessageMetadata,
};
use super::*;
pub enum ExampleId {
Id1 = 1,
Id2 = 2,
}
pub enum ExampleMode {
Mode1 = 1,
Mode2 = 2,
}
#[test]
fn test_basic() {
let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
assert_eq!(assy_helper.state(), DevManagerCommandingState::Idle);
}
#[test]
fn test_mode_announce() {
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
assy_helper
.send_announce_mode_cmd_to_children(1, &mode_req_sender, false)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceMode);
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceMode);
}
#[test]
fn test_mode_announce_recursive() {
let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
assy_helper
.send_announce_mode_cmd_to_children(1, &mode_req_sender, true)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(req.request, ModeRequest::AnnounceModeRecursive);
}
#[test]
fn test_mode_commanding_one_child() {
let mut dev_mgmt_helper =
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0);
dev_mgmt_helper
.send_mode_cmd_to_one_child(
1,
ExampleId::Id1 as u64,
expected_mode,
false,
&mode_req_sender,
)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 1);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
matches!(
dev_mgmt_helper.state(),
DevManagerCommandingState::AwaitingReplies { .. }
);
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
assert_eq!(ctx.target_mode, expected_mode);
assert_eq!(ctx.active_request_id, 1);
}
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id1 as u64),
ModeReply::ModeReply(expected_mode),
);
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
target_mode,
active_request_id,
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
{
assert_eq!(target_mode, expected_mode);
assert_eq!(active_request_id, 1);
}
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
}
#[test]
fn test_mode_commanding_multi_child() {
let mut dev_mgmt_helper =
DevManagerCommandingHelper::new(TransparentDevManagerHook::default());
let mode_req_sender = ModeReqSenderMock::default();
dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE);
dev_mgmt_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE);
let expected_mode = ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0);
dev_mgmt_helper
.send_mode_cmd_to_all_children(1, expected_mode, false, &mode_req_sender)
.unwrap();
assert_eq!(mode_req_sender.requests.borrow().len(), 2);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id1 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap();
assert_eq!(req.target_id, ExampleId::Id2 as u64);
assert_eq!(req.request_id, 1);
assert_eq!(
req.request,
ModeRequest::SetMode {
mode_and_submode: expected_mode,
forced: false
}
);
matches!(
dev_mgmt_helper.state(),
DevManagerCommandingState::AwaitingReplies { .. }
);
if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() {
assert_eq!(ctx.target_mode, expected_mode);
assert_eq!(ctx.active_request_id, 1);
}
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id1 as u64),
ModeReply::ModeReply(expected_mode),
);
assert_eq!(
dev_mgmt_helper.handle_mode_reply(&reply).unwrap(),
DevManagerHelperResult::Busy
);
let reply = GenericMessage::new(
MessageMetadata::new(1, ExampleId::Id2 as u64),
ModeReply::ModeReply(expected_mode),
);
if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext {
target_mode,
active_request_id,
}) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap()
{
assert_eq!(target_mode, expected_mode);
assert_eq!(active_request_id, 1);
}
matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle);
}
}

View File

@ -15,20 +15,24 @@ pub enum OpResult {
TerminationRequested,
}
#[derive(Debug)]
pub enum ExecutionType {
Infinite,
Cycles(u32),
OneShot,
}
pub trait Executable: Send {
pub trait Executable {
type Error;
fn exec_type(&self) -> ExecutionType;
fn task_name(&self) -> &'static str;
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error>;
}
pub trait ExecutableWithType: Executable {
fn exec_type(&self) -> ExecutionType;
}
/// This function allows executing one task which implements the [Executable] trait
///
/// # Arguments
@ -39,7 +43,10 @@ pub trait Executable: Send {
/// * `op_code`: Operation code which is passed to the executable task
/// [operation call][Executable::periodic_op]
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
pub fn exec_sched_single<
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
E: Send + 'static,
>(
mut executable: Box<T>,
task_freq: Option<Duration>,
op_code: i32,
@ -88,7 +95,10 @@ pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E:
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
pub fn exec_sched_multi<
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
E: Send + 'static,
>(
task_name: &'static str,
mut executable_vec: Vec<Box<T>>,
task_freq: Option<Duration>,
@ -142,7 +152,10 @@ pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: S
#[cfg(test)]
mod tests {
use super::{exec_sched_multi, exec_sched_single, Executable, ExecutionType, OpResult};
use super::{
exec_sched_multi, exec_sched_single, Executable, ExecutableWithType, ExecutionType,
OpResult,
};
use bus::Bus;
use std::boxed::Box;
use std::error::Error;
@ -208,10 +221,6 @@ mod tests {
impl Executable for OneShotTask {
type Error = ExampleError;
fn exec_type(&self) -> ExecutionType {
ExecutionType::OneShot
}
fn task_name(&self) -> &'static str {
ONE_SHOT_TASK_NAME
}
@ -229,15 +238,17 @@ mod tests {
}
}
impl ExecutableWithType for OneShotTask {
fn exec_type(&self) -> ExecutionType {
ExecutionType::OneShot
}
}
const CYCLE_TASK_NAME: &str = "Fixed Cycles Task";
impl Executable for FixedCyclesTask {
type Error = ExampleError;
fn exec_type(&self) -> ExecutionType {
ExecutionType::Cycles(self.cycles)
}
fn task_name(&self) -> &'static str {
CYCLE_TASK_NAME
}
@ -255,15 +266,17 @@ mod tests {
}
}
impl ExecutableWithType for FixedCyclesTask {
fn exec_type(&self) -> ExecutionType {
ExecutionType::Cycles(self.cycles)
}
}
const PERIODIC_TASK_NAME: &str = "Periodic Task";
impl Executable for PeriodicTask {
type Error = ExampleError;
fn exec_type(&self) -> ExecutionType {
ExecutionType::Infinite
}
fn task_name(&self) -> &'static str {
PERIODIC_TASK_NAME
}
@ -281,6 +294,12 @@ mod tests {
}
}
impl ExecutableWithType for PeriodicTask {
fn exec_type(&self) -> ExecutionType {
ExecutionType::Infinite
}
}
#[test]
fn test_simple_one_shot() {
let expected_op_code = 42;
@ -423,7 +442,7 @@ mod tests {
});
assert_eq!(cycled_task_0.task_name(), CYCLE_TASK_NAME);
assert_eq!(one_shot_task.task_name(), ONE_SHOT_TASK_NAME);
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
vec![one_shot_task, cycled_task_0, cycled_task_1];
let jh = exec_sched_multi(
"multi-task-name",
@ -493,7 +512,7 @@ mod tests {
});
assert_eq!(periodic_task_0.task_name(), PERIODIC_TASK_NAME);
assert_eq!(periodic_task_1.task_name(), PERIODIC_TASK_NAME);
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
vec![cycled_task, periodic_task_0, periodic_task_1];
let jh = exec_sched_multi(
"multi-task-name",

39
satrs/src/health.rs Normal file
View File

@ -0,0 +1,39 @@
use crate::ComponentId;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HealthState {
Healthy = 1,
Faulty = 2,
PermanentFaulty = 3,
ExternalControl = 4,
NeedsRecovery = 5,
}
pub trait HealthTableProvider {
fn health(&self, id: ComponentId) -> Option<HealthState>;
fn set_health(&mut self, id: ComponentId, health: HealthState);
}
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
pub struct HealthTableMapSync(
std::sync::Arc<std::sync::Mutex<hashbrown::HashMap<ComponentId, HealthState>>>,
);
#[cfg(feature = "std")]
impl HealthTableMapSync {
pub fn new(health_table: hashbrown::HashMap<ComponentId, HealthState>) -> Self {
Self(std::sync::Arc::new(std::sync::Mutex::new(health_table)))
}
}
#[cfg(feature = "std")]
impl HealthTableProvider for HealthTableMapSync {
fn health(&self, id: ComponentId) -> Option<HealthState> {
self.0.lock().unwrap().get(&id).copied()
}
fn set_health(&mut self, id: ComponentId, health: HealthState) {
self.0.lock().unwrap().insert(id, health);
}
}

View File

@ -14,7 +14,7 @@
//! - The [pus] module which provides special support for projects using
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
#![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
@ -22,31 +22,34 @@ extern crate downcast_rs;
#[cfg(any(feature = "std", test))]
extern crate std;
pub mod action;
#[cfg(feature = "alloc")]
pub mod cfdp;
pub mod dev_mgmt;
pub mod encoding;
pub mod event_man;
pub mod events;
#[cfg(feature = "std")]
pub mod executable;
pub mod hal;
pub mod health;
pub mod hk;
pub mod mode;
#[cfg(feature = "std")]
pub mod mode_tree;
pub mod params;
pub mod pool;
pub mod power;
pub mod pus;
pub mod queue;
pub mod request;
pub mod res_code;
pub mod seq_count;
#[cfg(feature = "alloc")]
pub mod scheduling;
#[cfg(feature = "alloc")]
pub mod subsystem;
pub mod time;
pub mod tmtc;
pub mod action;
pub mod hk;
pub mod mode;
pub mod params;
pub use spacepackets;
use spacepackets::PacketId;

View File

@ -11,8 +11,10 @@ pub use alloc_mod::*;
pub use std_mod::*;
use crate::{
queue::GenericTargetedMessagingError,
request::{GenericMessage, MessageMetadata, MessageReceiver, MessageReceiverWithId, RequestId},
queue::{GenericReceiveError, GenericSendError},
request::{
GenericMessage, MessageMetadata, MessageReceiverProvider, MessageReceiverWithId, RequestId,
},
ComponentId,
};
@ -26,6 +28,11 @@ pub struct ModeAndSubmode {
submode: Submode,
}
pub const INVALID_MODE_VAL: Mode = Mode::MAX;
pub const UNKNOWN_MODE_VAL: Mode = Mode::MAX - 1;
pub const INVALID_MODE: ModeAndSubmode = ModeAndSubmode::new(INVALID_MODE_VAL, 0);
pub const UNKNOWN_MODE: ModeAndSubmode = ModeAndSubmode::new(UNKNOWN_MODE_VAL, 0);
impl ModeAndSubmode {
pub const RAW_LEN: usize = size_of::<Mode>() + size_of::<Submode>();
@ -111,7 +118,10 @@ impl TargetedModeCommand {
pub enum ModeRequest {
/// Mode information. Can be used to notify other components of changed modes.
ModeInfo(ModeAndSubmode),
SetMode(ModeAndSubmode),
SetMode {
mode_and_submode: ModeAndSubmode,
forced: bool,
},
ReadMode,
AnnounceMode,
AnnounceModeRecursive,
@ -127,6 +137,8 @@ pub struct TargetedModeRequest {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModeReply {
/// Mode information. Can be used to notify other components of changed modes.
ModeInfo(ModeAndSubmode),
/// Reply to a mode request to confirm the commanded mode was reached.
ModeReply(ModeAndSubmode),
// Can not reach the commanded mode. Contains a reason as a [ResultU16].
@ -147,34 +159,33 @@ pub trait ModeRequestSender {
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError>;
) -> Result<(), GenericSendError>;
}
pub trait ModeRequestReceiver {
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError>;
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError>;
}
impl<R: MessageReceiver<ModeRequest>> ModeRequestReceiver
impl<R: MessageReceiverProvider<ModeRequest>> ModeRequestReceiver
for MessageReceiverWithId<ModeRequest, R>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
self.try_recv_message()
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, thiserror::Error)]
pub enum ModeError {
Messaging(GenericTargetedMessagingError),
}
impl From<GenericTargetedMessagingError> for ModeError {
fn from(value: GenericTargetedMessagingError) -> Self {
Self::Messaging(value)
}
#[error("Messaging send error: {0}")]
Send(#[from] GenericSendError),
#[error("Messaging receive error: {0}")]
Receive(#[from] GenericReceiveError),
#[error("busy with other mode request")]
Busy,
}
pub trait ModeProvider {
@ -196,6 +207,7 @@ pub trait ModeRequestHandler: ModeProvider {
&mut self,
requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode,
forced: bool,
) -> Result<(), Self::Error>;
fn announce_mode(&self, requestor_info: Option<MessageMetadata>, recursive: bool);
@ -222,9 +234,10 @@ pub trait ModeRequestHandler: ModeProvider {
request: GenericMessage<ModeRequest>,
) -> Result<(), Self::Error> {
match request.message {
ModeRequest::SetMode(mode_and_submode) => {
self.start_transition(request.requestor_info, mode_and_submode)
}
ModeRequest::SetMode {
mode_and_submode,
forced,
} => self.start_transition(request.requestor_info, mode_and_submode, forced),
ModeRequest::ReadMode => self.send_mode_reply(
request.requestor_info,
ModeReply::ModeReply(self.mode_and_submode()),
@ -243,15 +256,16 @@ pub trait ModeRequestHandler: ModeProvider {
}
pub trait ModeReplyReceiver {
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError>;
fn try_recv_mode_reply(&self)
-> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError>;
}
impl<R: MessageReceiver<ModeReply>> ModeReplyReceiver for MessageReceiverWithId<ModeReply, R> {
impl<R: MessageReceiverProvider<ModeReply>> ModeReplyReceiver
for MessageReceiverWithId<ModeReply, R>
{
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
self.try_recv_message()
}
}
@ -264,24 +278,28 @@ pub trait ModeReplySender {
&self,
requestor_info: MessageMetadata,
reply: ModeReply,
) -> Result<(), GenericTargetedMessagingError>;
) -> Result<(), GenericSendError>;
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use crate::request::{
MessageSender, MessageSenderAndReceiver, MessageSenderMap, RequestAndReplySenderAndReceiver,
use crate::{
queue::{GenericReceiveError, GenericSendError},
request::{
MessageSenderAndReceiver, MessageSenderMap, MessageSenderProvider,
MessageSenderStoreProvider, RequestAndReplySenderAndReceiver,
},
};
use super::*;
impl<S: MessageSender<ModeReply>> MessageSenderMap<ModeReply, S> {
impl<S: MessageSenderProvider<ModeReply>> MessageSenderMap<ModeReply, S> {
pub fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
target_id: ComponentId,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(requestor_info, target_id, request)
}
@ -290,8 +308,13 @@ pub mod alloc_mod {
}
}
impl<FROM, S: MessageSender<ModeReply>, R: MessageReceiver<FROM>> ModeReplySender
for MessageSenderAndReceiver<ModeReply, FROM, S, R>
impl<
From,
Sender: MessageSenderProvider<ModeReply>,
Receiver: MessageReceiverProvider<From>,
SenderStore: MessageSenderStoreProvider<ModeReply, Sender>,
> ModeReplySender
for MessageSenderAndReceiver<ModeReply, From, Sender, Receiver, SenderStore>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
@ -301,8 +324,8 @@ pub mod alloc_mod {
&self,
requestor_info: MessageMetadata,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_mode_reply(
) -> Result<(), GenericSendError> {
self.message_sender_store.send_message(
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
requestor_info.sender_id(),
request,
@ -310,37 +333,67 @@ pub mod alloc_mod {
}
}
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeReply>> ModeReplyReceiver
for MessageSenderAndReceiver<TO, ModeReply, S, R>
impl<
To,
Sender: MessageSenderProvider<To>,
Receiver: MessageReceiverProvider<ModeReply>,
SenderStore: MessageSenderStoreProvider<To, Sender>,
> ModeReplyReceiver
for MessageSenderAndReceiver<To, ModeReply, Sender, Receiver, SenderStore>
{
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
self.message_receiver.try_recv_message()
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
> RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
Request,
ReqSender: MessageSenderProvider<Request>,
ReqReceiver: MessageReceiverProvider<Request>,
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
>
RequestAndReplySenderAndReceiver<
Request,
ReqSender,
ReqReceiver,
ReqSenderStore,
Reply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: S1) {
self.reply_sender_map
pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: ReplySender) {
self.reply_sender_store
.add_message_target(target_id, reply_sender)
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
> ModeReplySender for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
Request,
ReqSender: MessageSenderProvider<Request>,
ReqReceiver: MessageReceiverProvider<Request>,
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
ReplySender: MessageSenderProvider<ModeReply>,
ReplyReceiver: MessageReceiverProvider<ModeReply>,
ReplySenderStore: MessageSenderStoreProvider<ModeReply, ReplySender>,
> ModeReplySender
for RequestAndReplySenderAndReceiver<
Request,
ReqSender,
ReqReceiver,
ReqSenderStore,
ModeReply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
@ -349,42 +402,56 @@ pub mod alloc_mod {
fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.reply_sender_map.send_mode_reply(
reply: ModeReply,
) -> Result<(), GenericSendError> {
self.reply_sender_store.send_message(
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
requestor_info.sender_id(),
request,
reply,
)
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
Request,
ReqSender: MessageSenderProvider<Request>,
ReqReceiver: MessageReceiverProvider<Request>,
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
ReplySender: MessageSenderProvider<ModeReply>,
ReplyReceiver: MessageReceiverProvider<ModeReply>,
ReplySenderStore: MessageSenderStoreProvider<ModeReply, ReplySender>,
> ModeReplyReceiver
for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
for RequestAndReplySenderAndReceiver<
Request,
ReqSender,
ReqReceiver,
ReqSenderStore,
ModeReply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
self.reply_receiver.try_recv_message()
}
}
/// Helper type definition for a mode handler which can handle mode requests.
pub type ModeRequestHandlerInterface<S, R> =
MessageSenderAndReceiver<ModeReply, ModeRequest, S, R>;
pub type ModeRequestHandlerInterface<Sender, Receiver, ReplySenderStore> =
MessageSenderAndReceiver<ModeReply, ModeRequest, Sender, Receiver, ReplySenderStore>;
impl<S: MessageSender<ModeReply>, R: MessageReceiver<ModeRequest>>
ModeRequestHandlerInterface<S, R>
impl<
Sender: MessageSenderProvider<ModeReply>,
Receiver: MessageReceiverProvider<ModeRequest>,
ReplySenderStore: MessageSenderStoreProvider<ModeReply, Sender>,
> ModeRequestHandlerInterface<Sender, Receiver, ReplySenderStore>
{
pub fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
self.try_recv_message()
}
@ -392,7 +459,7 @@ pub mod alloc_mod {
&self,
requestor_info: MessageMetadata,
reply: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(
requestor_info.request_id(),
requestor_info.sender_id(),
@ -403,12 +470,18 @@ pub mod alloc_mod {
/// Helper type defintion for a mode handler object which can send mode requests and receive
/// mode replies.
pub type ModeRequestorInterface<S, R> = MessageSenderAndReceiver<ModeRequest, ModeReply, S, R>;
pub type ModeRequestorInterface<Sender, Receiver, RequestSenderStore> =
MessageSenderAndReceiver<ModeRequest, ModeReply, Sender, Receiver, RequestSenderStore>;
impl<S: MessageSender<ModeRequest>, R: MessageReceiver<ModeReply>> ModeRequestorInterface<S, R> {
impl<
Sender: MessageSenderProvider<ModeRequest>,
Receiver: MessageReceiverProvider<ModeReply>,
RequestSenderStore: MessageSenderStoreProvider<ModeRequest, Sender>,
> ModeRequestorInterface<Sender, Receiver, RequestSenderStore>
{
pub fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeReply>>, GenericReceiveError> {
self.try_recv_message()
}
@ -417,23 +490,38 @@ pub mod alloc_mod {
request_id: RequestId,
target_id: ComponentId,
reply: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(request_id, target_id, reply)
}
}
/// Helper type defintion for a mode handler object which can both send mode requests and
/// process mode requests.
pub type ModeInterface<S0, R0, S1, R1> =
RequestAndReplySenderAndReceiver<ModeRequest, ModeReply, S0, R0, S1, R1>;
pub type ModeInterface<
ReqSender,
ReqReceiver,
ReqSenderStore,
ReplySender,
ReplyReceiver,
ReplySenderStore,
> = RequestAndReplySenderAndReceiver<
ModeRequest,
ReqSender,
ReqReceiver,
ReqSenderStore,
ModeReply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>;
impl<S: MessageSender<ModeRequest>> MessageSenderMap<ModeRequest, S> {
impl<S: MessageSenderProvider<ModeRequest>> MessageSenderMap<ModeRequest, S> {
pub fn send_mode_request(
&self,
requestor_info: MessageMetadata,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(requestor_info, target_id, request)
}
@ -442,35 +530,28 @@ pub mod alloc_mod {
}
}
/*
impl<S: MessageSender<ModeRequest>> ModeRequestSender for MessageSenderMapWithId<ModeRequest, S> {
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id
}
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(request_id, target_id, request)
}
}
*/
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeRequest>> ModeRequestReceiver
for MessageSenderAndReceiver<TO, ModeRequest, S, R>
impl<
To,
Sender: MessageSenderProvider<To>,
Receiver: MessageReceiverProvider<ModeRequest>,
SenderStore: MessageSenderStoreProvider<To, Sender>,
> ModeRequestReceiver
for MessageSenderAndReceiver<To, ModeRequest, Sender, Receiver, SenderStore>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
self.message_receiver.try_recv_message()
}
}
impl<FROM, S: MessageSender<ModeRequest>, R: MessageReceiver<FROM>> ModeRequestSender
for MessageSenderAndReceiver<ModeRequest, FROM, S, R>
impl<
From,
Sender: MessageSenderProvider<ModeRequest>,
Receiver: MessageReceiverProvider<From>,
SenderStore: MessageSenderStoreProvider<ModeRequest, Sender>,
> ModeRequestSender
for MessageSenderAndReceiver<ModeRequest, From, Sender, Receiver, SenderStore>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
@ -481,8 +562,8 @@ pub mod alloc_mod {
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_mode_request(
) -> Result<(), GenericSendError> {
self.message_sender_store.send_message(
MessageMetadata::new(request_id, self.local_channel_id()),
target_id,
request,
@ -491,27 +572,50 @@ pub mod alloc_mod {
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
> RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
ReqSender: MessageSenderProvider<ModeRequest>,
ReqReceiver: MessageReceiverProvider<ModeRequest>,
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
>
RequestAndReplySenderAndReceiver<
ModeRequest,
ReqSender,
ReqReceiver,
ReqSenderStore,
Reply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: S0) {
self.request_sender_map
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: ReqSender) {
self.request_sender_store
.add_message_target(target_id, request_sender)
}
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
ReqSender: MessageSenderProvider<ModeRequest>,
ReqReceiver: MessageReceiverProvider<ModeRequest>,
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
> ModeRequestSender
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
for RequestAndReplySenderAndReceiver<
ModeRequest,
ReqSender,
ReqReceiver,
ReqSenderStore,
Reply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
@ -522,8 +626,8 @@ pub mod alloc_mod {
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.request_sender_map.send_mode_request(
) -> Result<(), GenericSendError> {
self.request_sender_store.send_message(
MessageMetadata::new(request_id, self.local_channel_id()),
target_id,
request,
@ -532,17 +636,28 @@ pub mod alloc_mod {
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
ReqSender: MessageSenderProvider<ModeRequest>,
ReqReceiver: MessageReceiverProvider<ModeRequest>,
ReqSenderStore: MessageSenderStoreProvider<ModeRequest, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
> ModeRequestReceiver
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
for RequestAndReplySenderAndReceiver<
ModeRequest,
ReqSender,
ReqReceiver,
ReqSenderStore,
Reply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ModeRequest>>, GenericReceiveError> {
self.request_receiver.try_recv_message()
}
}
@ -552,39 +667,97 @@ pub mod alloc_mod {
pub mod std_mod {
use std::sync::mpsc;
use crate::request::{MessageSenderList, OneMessageSender};
use super::*;
pub type ModeRequestHandlerMpsc = ModeRequestHandlerInterface<
mpsc::Sender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
MessageSenderList<ModeReply, mpsc::Sender<GenericMessage<ModeReply>>>,
>;
pub type ModeRequestHandlerMpscBounded = ModeRequestHandlerInterface<
mpsc::SyncSender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
MessageSenderList<ModeReply, mpsc::SyncSender<GenericMessage<ModeReply>>>,
>;
pub type ModeRequestorMpsc = ModeRequestorInterface<
pub type ModeRequestorOneChildMpsc = ModeRequestorInterface<
mpsc::Sender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
OneMessageSender<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
>;
pub type ModeRequestorBoundedMpsc = ModeRequestorInterface<
pub type ModeRequestorOneChildBoundedMpsc = ModeRequestorInterface<
mpsc::SyncSender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
OneMessageSender<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
>;
pub type ModeRequestorChildListMpsc = ModeRequestorInterface<
mpsc::Sender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
MessageSenderList<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
>;
pub type ModeRequestorChildListBoundedMpsc = ModeRequestorInterface<
mpsc::SyncSender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
MessageSenderList<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
>;
pub type ModeRequestorAndHandlerMpsc = ModeInterface<
mpsc::Sender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
mpsc::Sender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
MessageSenderList<ModeRequest, mpsc::Sender<GenericMessage<ModeRequest>>>,
mpsc::Sender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
MessageSenderList<ModeReply, mpsc::Sender<GenericMessage<ModeReply>>>,
>;
pub type ModeRequestorAndHandlerMpscBounded = ModeInterface<
mpsc::SyncSender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
mpsc::SyncSender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
MessageSenderList<ModeRequest, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
mpsc::SyncSender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
MessageSenderList<ModeReply, mpsc::SyncSender<GenericMessage<ModeReply>>>,
>;
}
#[cfg(test)]
mod tests {}
pub(crate) mod tests {
use core::cell::RefCell;
use std::collections::VecDeque;
use crate::{request::RequestId, ComponentId};
use super::*;
pub struct ModeReqWrapper {
pub request_id: RequestId,
pub target_id: ComponentId,
pub request: ModeRequest,
}
#[derive(Default)]
pub struct ModeReqSenderMock {
pub requests: RefCell<VecDeque<ModeReqWrapper>>,
}
impl ModeRequestSender for ModeReqSenderMock {
fn local_channel_id(&self) -> crate::ComponentId {
0
}
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericSendError> {
self.requests.borrow_mut().push_back(ModeReqWrapper {
request_id,
target_id,
request,
});
Ok(())
}
}
}

View File

@ -2,10 +2,57 @@ use alloc::vec::Vec;
use hashbrown::HashMap;
use crate::{
mode::{Mode, ModeAndSubmode, Submode},
mode::{Mode, ModeAndSubmode, ModeReply, ModeRequest, Submode},
request::MessageSenderProvider,
ComponentId,
};
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
/// Common trait for node modes which can have mode parents or mode children.
pub trait ModeNode {
fn id(&self) -> ComponentId;
}
/// Trait which denotes that an object is a parent in a mode tree.
///
/// A mode parent is capable of sending mode requests to child objects and has a unique component
/// ID.
pub trait ModeParent: ModeNode {
type Sender: MessageSenderProvider<ModeRequest>;
fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender);
}
/// Trait which denotes that an object is a child in a mode tree.
///
/// A child is capable of sending mode replies to parent objects and has a unique component ID.
pub trait ModeChild: ModeNode {
type Sender: MessageSenderProvider<ModeReply>;
fn add_mode_parent(&mut self, id: ComponentId, reply_sender: Self::Sender);
}
/// Utility method which connects a mode tree parent object to a child object by calling
/// [ModeParent::add_mode_child] on the [parent][ModeParent] and calling
/// [ModeChild::add_mode_parent] on the [child][ModeChild].
///
/// # Arguments
///
/// * `parent` - The parent object which implements [ModeParent].
/// * `request_sender` - Sender object to send mode requests to the child.
/// * `child` - The child object which implements [ModeChild].
/// * `reply_sender` - Sender object to send mode replies to the parent.
pub fn connect_mode_nodes<ReqSender, ReplySender>(
parent: &mut impl ModeParent<Sender = ReqSender>,
request_sender: ReqSender,
child: &mut impl ModeChild<Sender = ReplySender>,
reply_sender: ReplySender,
) {
parent.add_mode_child(child.id(), request_sender);
child.add_mode_parent(parent.id(), reply_sender);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TableEntryType {
/// Target table containing information of the expected children modes for given mode.
@ -15,23 +62,553 @@ pub enum TableEntryType {
Sequence,
}
pub struct ModeTableEntry {
/// Common fields required for both target and sequence table entries.
///
/// The most important parameters here are the target ID which this entry belongs to, and the mode
/// and submode the entry either will be commanded to for sequence table entries or which will be
/// monitored for target table entries.
#[derive(Debug, Copy, Clone)]
pub struct ModeTableEntryCommon {
/// Name of respective table entry.
pub name: &'static str,
/// Target channel ID.
pub channel_id: ComponentId,
/// Target component ID.
pub target_id: ComponentId,
/// Has a different meaning depending on whether this is a sequence table or a target table.
///
/// - For sequence tables, this denotes the mode which will be commanded
/// - For target tables, this is the mode which the target children should have and which
/// might be monitored depending on configuration.
pub mode_submode: ModeAndSubmode,
/// This mask allows to specify multiple allowed submodes for a given mode.
pub allowed_submode_mask: Option<Submode>,
}
impl ModeTableEntryCommon {
pub fn set_allowed_submode_mask(&mut self, mask: Submode) {
self.allowed_submode_mask = Some(mask);
}
pub fn allowed_submode_mask(&self) -> Option<Submode> {
self.allowed_submode_mask
}
}
/// An entry for the target tables.
#[derive(Debug)]
pub struct TargetTableEntry {
pub common: ModeTableEntryCommon,
pub monitor_state: bool,
}
impl TargetTableEntry {
pub fn new(
name: &'static str,
target_id: ComponentId,
mode_submode: ModeAndSubmode,
allowed_submode_mask: Option<Submode>,
) -> Self {
Self {
common: ModeTableEntryCommon {
name,
target_id,
mode_submode,
allowed_submode_mask,
},
monitor_state: true,
}
}
pub fn new_with_precise_submode(
name: &'static str,
target_id: ComponentId,
mode_submode: ModeAndSubmode,
) -> Self {
Self {
common: ModeTableEntryCommon {
name,
target_id,
mode_submode,
allowed_submode_mask: None,
},
monitor_state: true,
}
}
delegate::delegate! {
to self.common {
pub fn set_allowed_submode_mask(&mut self, mask: Submode);
pub fn allowed_submode_mask(&self) -> Option<Submode>;
}
}
}
/// An entry for the sequence tables.
///
/// The [Self::check_success] field specifies that a mode sequence executor should check that the
/// target mode was actually reached before executing the next sequence.
#[derive(Debug)]
pub struct SequenceTableEntry {
pub common: ModeTableEntryCommon,
pub check_success: bool,
}
pub struct ModeTableMapValue {
/// Name for a given mode table entry.
pub name: &'static str,
pub entries: Vec<ModeTableEntry>,
impl SequenceTableEntry {
pub fn new(
name: &'static str,
target_id: ComponentId,
mode_submode: ModeAndSubmode,
check_success: bool,
) -> Self {
Self {
common: ModeTableEntryCommon {
name,
target_id,
mode_submode,
allowed_submode_mask: None,
},
check_success,
}
}
delegate::delegate! {
to self.common {
pub fn set_allowed_submode_mask(&mut self, mask: Submode);
pub fn allowed_submode_mask(&self) -> Option<Submode>;
}
}
}
pub type ModeTable = HashMap<Mode, ModeTableMapValue>;
#[derive(Debug, thiserror::Error)]
#[error("target {0} not in mode store")]
pub struct TargetNotInModeStoreError(pub ComponentId);
/// Mode store value type.
#[derive(Debug, Copy, Clone)]
pub struct ModeStoreValue {
/// ID of the mode component.
id: ComponentId,
/// Current mode and submode of the component.
pub mode_and_submode: ModeAndSubmode,
/// State information to track whether a reply should be awaited for the mode component.
pub awaiting_reply: bool,
}
impl ModeStoreValue {
pub fn new(id: ComponentId, mode_and_submode: ModeAndSubmode) -> Self {
Self {
id,
mode_and_submode,
awaiting_reply: false,
}
}
pub fn id(&self) -> ComponentId {
self.id
}
pub fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_and_submode
}
}
pub trait ModeStoreProvider {
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode);
fn has_component(&self, target_id: ComponentId) -> bool;
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue>;
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue>;
/// Generic handler for mode replies received from child components.
///
/// Implementation should clear the awaition flag if the `handle_reply_awaition` argument is
/// true and returns whether any children are still awaiting replies. If the flag is not set
fn mode_reply_handler_with_reply_awaition(
&mut self,
sender_id: ComponentId,
reported_mode_and_submode: Option<ModeAndSubmode>,
) -> bool {
self.mode_reply_handler(sender_id, reported_mode_and_submode, true)
.unwrap_or(false)
}
fn mode_reply_handler_without_reply_awaition(
&mut self,
sender_id: ComponentId,
reported_mode_and_submode: Option<ModeAndSubmode>,
) {
self.mode_reply_handler(sender_id, reported_mode_and_submode, false);
}
fn mode_reply_handler(
&mut self,
sender_id: ComponentId,
reported_mode_and_submode: Option<ModeAndSubmode>,
with_reply_awaition: bool,
) -> Option<bool>;
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use super::*;
#[derive(Debug)]
pub struct TargetTablesMapValue {
/// Name for a given mode table entry.
pub name: &'static str,
/// Optional fallback mode if the target mode can not be kept.
pub fallback_mode: Option<Mode>,
/// These are the rows of the a target table.
pub entries: Vec<TargetTableEntry>,
}
impl TargetTablesMapValue {
pub fn new(name: &'static str, fallback_mode: Option<Mode>) -> Self {
Self {
name,
fallback_mode,
entries: Default::default(),
}
}
pub fn add_entry(&mut self, entry: TargetTableEntry) {
self.entries.push(entry);
}
}
/// One sequence of a [SequenceTablesMapValue] in a [SequenceModeTables].
///
/// It contains all mode requests which need to be executed for a sequence step and it also
/// associates a [Self::name] with the sequence.
#[derive(Debug)]
pub struct SequenceTableMapTable {
/// Name for a given mode sequence.
pub name: &'static str,
/// These are the rows of the a sequence table.
pub entries: Vec<SequenceTableEntry>,
}
impl SequenceTableMapTable {
pub fn new(name: &'static str) -> Self {
Self {
name,
entries: Default::default(),
}
}
pub fn add_entry(&mut self, entry: SequenceTableEntry) {
self.entries.push(entry);
}
}
/// A sequence table entry.
///
/// This is simply a list of [SequenceTableMapTable]s which also associates a [Self::name]
/// with the sequence. The order of sub-tables in the list also specifies the execution order
/// in the mode sequence.
#[derive(Debug)]
pub struct SequenceTablesMapValue {
/// Name for a given mode sequence.
pub name: &'static str,
/// Each sequence can consists of multiple sequences that are executed consecutively.
pub entries: Vec<SequenceTableMapTable>,
}
impl SequenceTablesMapValue {
pub fn new(name: &'static str) -> Self {
Self {
name,
entries: Default::default(),
}
}
pub fn add_sequence_table(&mut self, entry: SequenceTableMapTable) {
self.entries.push(entry);
}
}
#[derive(Debug, Default)]
pub struct TargetModeTables(pub HashMap<Mode, TargetTablesMapValue>);
impl TargetModeTables {
pub fn name(&self, mode: Mode) -> Option<&'static str> {
self.0.get(&mode).map(|value| value.name)
}
}
impl SequenceModeTables {
pub fn name(&self, mode: Mode) -> Option<&'static str> {
self.0.get(&mode).map(|value| value.name)
}
pub fn name_of_sequence(&self, mode: Mode, seq_idx: usize) -> Option<&'static str> {
self.0
.get(&mode)
.map(|value| value.entries.get(seq_idx).map(|v| v.name))?
}
}
/// This is the core data structure used to store mode sequence tables.
///
/// A mode sequence table specifies which commands have to be sent in which order
/// to reach a certain [Mode]. Therefore, it simply maps a [Mode] to a [SequenceTablesMapValue].
#[derive(Debug, Default)]
pub struct SequenceModeTables(pub HashMap<Mode, SequenceTablesMapValue>);
/// Mode store which tracks the [mode information][ModeStoreValue] inside a [Vec]
#[derive(Debug, Default)]
pub struct ModeStoreVec(pub alloc::vec::Vec<ModeStoreValue>);
impl<'a> IntoIterator for &'a ModeStoreVec {
type Item = &'a ModeStoreValue;
type IntoIter = std::slice::Iter<'a, ModeStoreValue>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a mut ModeStoreVec {
type Item = &'a mut ModeStoreValue;
type IntoIter = std::slice::IterMut<'a, ModeStoreValue>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
/// Mode store which tracks the mode information inside a [hashbrown::HashMap]
#[derive(Debug, Default)]
pub struct ModeStoreMap(pub hashbrown::HashMap<ComponentId, ModeStoreValue>);
impl<'a> IntoIterator for &'a ModeStoreMap {
type Item = (&'a ComponentId, &'a ModeStoreValue);
type IntoIter = hashbrown::hash_map::Iter<'a, ComponentId, ModeStoreValue>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl ModeStoreProvider for ModeStoreVec {
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
self.0.push(ModeStoreValue::new(target_id, mode));
}
fn has_component(&self, target_id: ComponentId) -> bool {
self.0.iter().any(|val| val.id == target_id)
}
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> {
self.0.iter().find(|val| val.id == target_id)
}
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> {
self.0.iter_mut().find(|val| val.id == target_id)
}
fn mode_reply_handler(
&mut self,
sender_id: ComponentId,
reported_mode_and_submode: Option<ModeAndSubmode>,
handle_reply_awaition: bool,
) -> Option<bool> {
let mut still_awating_replies = None;
if handle_reply_awaition {
still_awating_replies = Some(false);
}
self.0.iter_mut().for_each(|val| {
if val.id() == sender_id {
if let Some(mode_and_submode) = reported_mode_and_submode {
val.mode_and_submode = mode_and_submode;
}
if handle_reply_awaition {
val.awaiting_reply = false;
}
}
if handle_reply_awaition && val.awaiting_reply {
still_awating_replies = Some(true);
}
});
still_awating_replies
}
}
impl ModeStoreProvider for ModeStoreMap {
fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) {
self.0
.insert(target_id, ModeStoreValue::new(target_id, mode));
}
fn has_component(&self, target_id: ComponentId) -> bool {
self.0.contains_key(&target_id)
}
fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> {
self.0.get(&target_id)
}
fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> {
self.0.get_mut(&target_id)
}
fn mode_reply_handler(
&mut self,
sender_id: ComponentId,
reported_mode_and_submode: Option<ModeAndSubmode>,
handle_reply_awaition: bool,
) -> Option<bool> {
let mut still_awating_replies = None;
if handle_reply_awaition {
still_awating_replies = Some(false);
}
for val in self.0.values_mut() {
if val.id() == sender_id {
if let Some(mode_and_submode) = reported_mode_and_submode {
val.mode_and_submode = mode_and_submode;
}
if handle_reply_awaition {
val.awaiting_reply = false;
}
}
if handle_reply_awaition && val.awaiting_reply {
still_awating_replies = Some(true);
}
}
still_awating_replies
}
}
}
#[cfg(test)]
mod tests {}
mod tests {
use super::*;
fn generic_test(mode_store: &mut impl ModeStoreProvider) {
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
assert!(mode_store.has_component(1));
assert!(mode_store.has_component(2));
assert_eq!(
mode_store.get(1).unwrap().mode_and_submode(),
ModeAndSubmode::new(0, 0)
);
assert!(!mode_store.get(1).unwrap().awaiting_reply);
assert!(!mode_store.get(2).unwrap().awaiting_reply);
assert_eq!(mode_store.get(1).unwrap().id, 1);
assert_eq!(mode_store.get(2).unwrap().id, 2);
assert!(mode_store.get(3).is_none());
assert!(mode_store.get_mut(3).is_none());
}
fn generic_reply_handling_with_reply_awaition(mode_store: &mut impl ModeStoreProvider) {
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
mode_store.get_mut(1).unwrap().awaiting_reply = true;
mode_store.get_mut(2).unwrap().awaiting_reply = true;
let mut reply_awation_pending =
mode_store.mode_reply_handler_with_reply_awaition(1, Some(ModeAndSubmode::new(2, 0)));
assert!(reply_awation_pending);
reply_awation_pending = mode_store.mode_reply_handler_with_reply_awaition(2, None);
assert!(!reply_awation_pending);
assert!(!mode_store.get(1).unwrap().awaiting_reply);
assert!(!mode_store.get(2).unwrap().awaiting_reply);
assert_eq!(
mode_store.get(1).unwrap().mode_and_submode(),
ModeAndSubmode::new(2, 0)
);
assert_eq!(
mode_store.get(2).unwrap().mode_and_submode(),
ModeAndSubmode::new(1, 0)
);
}
fn generic_reply_handling_test_no_reply_awaition(mode_store: &mut impl ModeStoreProvider) {
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
mode_store.get_mut(1).unwrap().awaiting_reply = true;
mode_store.get_mut(2).unwrap().awaiting_reply = true;
mode_store.mode_reply_handler_without_reply_awaition(1, Some(ModeAndSubmode::new(2, 0)));
mode_store.mode_reply_handler_without_reply_awaition(2, None);
assert!(mode_store.get(1).unwrap().awaiting_reply);
assert!(mode_store.get(2).unwrap().awaiting_reply);
assert_eq!(
mode_store.get(1).unwrap().mode_and_submode(),
ModeAndSubmode::new(2, 0)
);
assert_eq!(
mode_store.get(2).unwrap().mode_and_submode(),
ModeAndSubmode::new(1, 0)
);
}
fn generic_reply_handling_with_reply_awaition_2(mode_store: &mut impl ModeStoreProvider) {
mode_store.add_component(1, ModeAndSubmode::new(0, 0));
mode_store.add_component(2, ModeAndSubmode::new(1, 0));
mode_store.get_mut(1).unwrap().awaiting_reply = true;
mode_store.get_mut(2).unwrap().awaiting_reply = true;
let mut reply_awation_pending =
mode_store.mode_reply_handler(1, Some(ModeAndSubmode::new(2, 0)), true);
assert!(reply_awation_pending.unwrap());
reply_awation_pending = mode_store.mode_reply_handler(2, None, true);
assert!(!reply_awation_pending.unwrap());
assert!(!mode_store.get(1).unwrap().awaiting_reply);
assert!(!mode_store.get(2).unwrap().awaiting_reply);
assert_eq!(
mode_store.get(1).unwrap().mode_and_submode(),
ModeAndSubmode::new(2, 0)
);
assert_eq!(
mode_store.get(2).unwrap().mode_and_submode(),
ModeAndSubmode::new(1, 0)
);
}
#[test]
fn test_vec_mode_store() {
let mut mode_store = ModeStoreVec::default();
generic_test(&mut mode_store);
}
#[test]
fn test_map_mode_store() {
let mut mode_store = ModeStoreMap::default();
generic_test(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_vec_with_reply_awaition() {
let mut mode_store = ModeStoreVec::default();
generic_reply_handling_with_reply_awaition(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_vec_with_reply_awaition_2() {
let mut mode_store = ModeStoreVec::default();
generic_reply_handling_with_reply_awaition_2(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_map_with_reply_awaition() {
let mut mode_store = ModeStoreMap::default();
generic_reply_handling_with_reply_awaition(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_map_with_reply_awaition_2() {
let mut mode_store = ModeStoreMap::default();
generic_reply_handling_with_reply_awaition_2(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_vec_no_reply_awaition() {
let mut mode_store = ModeStoreVec::default();
generic_reply_handling_test_no_reply_awaition(&mut mode_store);
}
#[test]
fn test_generic_reply_handler_map_no_reply_awaition() {
let mut mode_store = ModeStoreMap::default();
generic_reply_handling_test_no_reply_awaition(&mut mode_store);
}
}

View File

@ -387,18 +387,18 @@ pub mod heapless_mod {
#[macro_export]
macro_rules! static_subpool {
($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr) => {
static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> =
core::mem::MaybeUninit::new([0; $num_blocks * $block_size]);
static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> =
core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]);
static $pool_name: static_cell::ConstStaticCell<[u8; $num_blocks * $block_size]> =
static_cell::ConstStaticCell::new([0; $num_blocks * $block_size]);
static $sizes_list_name: static_cell::ConstStaticCell<[usize; $num_blocks]> =
static_cell::ConstStaticCell::new([$crate::pool::STORE_FREE; $num_blocks]);
};
($pool_name: ident, $sizes_list_name: ident, $num_blocks: expr, $block_size: expr, $meta_data: meta) => {
#[$meta_data]
static mut $pool_name: core::mem::MaybeUninit<[u8; $num_blocks * $block_size]> =
core::mem::MaybeUninit::new([0; $num_blocks * $block_size]);
static $pool_name: static_cell::ConstStaticCell<[u8; $num_blocks * $block_size]> =
static_cell::ConstStaticCell::new([0; $num_blocks * $block_size]);
#[$meta_data]
static mut $sizes_list_name: core::mem::MaybeUninit<[usize; $num_blocks]> =
core::mem::MaybeUninit::new([$crate::pool::STORE_FREE; $num_blocks]);
static $sizes_list_name: static_cell::ConstStaticCell<[usize; $num_blocks]> =
static_cell::ConstStaticCell::new([$crate::pool::STORE_FREE; $num_blocks]);
};
}
@ -435,17 +435,17 @@ pub mod heapless_mod {
///
/// let mut mem_pool: StaticHeaplessMemoryPool<2> = StaticHeaplessMemoryPool::new(true);
/// mem_pool.grow(
/// unsafe { SUBPOOL_SMALL.assume_init_mut() },
/// unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
/// SUBPOOL_SMALL.take(),
/// SUBPOOL_SMALL_SIZES.take(),
/// SUBPOOL_SMALL_NUM_BLOCKS,
/// false
/// );
/// ).unwrap();
/// mem_pool.grow(
/// unsafe { SUBPOOL_LARGE.assume_init_mut() },
/// unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
/// SUBPOOL_LARGE.take(),
/// SUBPOOL_LARGE_SIZES.take(),
/// SUBPOOL_LARGE_NUM_BLOCKS,
/// false
/// );
/// ).unwrap();
///
/// let mut read_buf: [u8; 16] = [0; 16];
/// let mut addr;
@ -522,12 +522,14 @@ pub mod heapless_mod {
num_blocks: NumBlocks,
set_sizes_list_to_all_free: bool,
) -> Result<(), PoolIsFull> {
assert!(
(subpool_memory.len() % num_blocks as usize) == 0,
assert_eq!(
(subpool_memory.len() % num_blocks as usize),
0,
"pool slice length must be multiple of number of blocks"
);
assert!(
num_blocks as usize == sizes_list.len(),
assert_eq!(
num_blocks as usize,
sizes_list.len(),
"used block size list slice must be of same length as number of blocks"
);
let subpool_config = SubpoolConfig {
@ -773,7 +775,7 @@ mod alloc_mod {
/// if the next fitting subpool is full. This is useful to ensure the pool remains useful
/// for all data sizes as long as possible. However, an undesirable side-effect might be
/// the chocking of larger subpools by underdimensioned smaller subpools.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct StaticPoolConfig {
cfg: Vec<SubpoolConfig>,
spill_to_higher_subpools: bool,
@ -832,6 +834,7 @@ mod alloc_mod {
/// [address][PoolAddr] type. Adding any data to the pool will yield a store address.
/// Modification and read operations are done using a reference to a store address. Deletion
/// will consume the store address.
#[derive(Debug)]
pub struct StaticMemoryPool {
pool_cfg: StaticPoolConfig,
pool: Vec<Vec<u8>>,
@ -1584,23 +1587,33 @@ mod tests {
mod heapless_tests {
use super::*;
use crate::static_subpool;
use core::mem::MaybeUninit;
use std::cell::UnsafeCell;
use std::sync::Mutex;
const SUBPOOL_1_BLOCK_SIZE: usize = 4;
const SUBPOOL_1_NUM_ELEMENTS: u16 = 4;
static mut SUBPOOL_1: MaybeUninit<
static SUBPOOL_1: static_cell::ConstStaticCell<
[u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE],
> = MaybeUninit::new([0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]);
static mut SUBPOOL_1_SIZES: MaybeUninit<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]> =
MaybeUninit::new([STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]);
> = static_cell::ConstStaticCell::new(
[0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE],
);
static SUBPOOL_1_SIZES: Mutex<UnsafeCell<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]>> =
Mutex::new(UnsafeCell::new(
[STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize],
));
const SUBPOOL_2_NUM_ELEMENTS: u16 = 2;
const SUBPOOL_2_BLOCK_SIZE: usize = 8;
static mut SUBPOOL_2: MaybeUninit<
static SUBPOOL_2: static_cell::ConstStaticCell<
[u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE],
> = MaybeUninit::new([0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]);
static mut SUBPOOL_2_SIZES: MaybeUninit<[usize; SUBPOOL_2_NUM_ELEMENTS as usize]> =
MaybeUninit::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]);
> = static_cell::ConstStaticCell::new(
[0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE],
);
static SUBPOOL_2_SIZES: static_cell::ConstStaticCell<
[usize; SUBPOOL_2_NUM_ELEMENTS as usize],
> = static_cell::ConstStaticCell::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]);
const SUBPOOL_3_NUM_ELEMENTS: u16 = 1;
const SUBPOOL_3_BLOCK_SIZE: usize = 16;
@ -1643,26 +1656,26 @@ mod tests {
StaticHeaplessMemoryPool::new(false);
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_1.assume_init_mut() },
unsafe { SUBPOOL_1_SIZES.assume_init_mut() },
SUBPOOL_1.take(),
unsafe { &mut *SUBPOOL_1_SIZES.lock().unwrap().get() },
SUBPOOL_1_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_2.assume_init_mut() },
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
SUBPOOL_2.take(),
SUBPOOL_2_SIZES.take(),
SUBPOOL_2_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_3.assume_init_mut() },
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
SUBPOOL_3.take(),
SUBPOOL_3_SIZES.take(),
SUBPOOL_3_NUM_ELEMENTS,
false
true
)
.is_ok());
heapless_pool
@ -1782,18 +1795,18 @@ mod tests {
StaticHeaplessMemoryPool::new(true);
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_2.assume_init_mut() },
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
SUBPOOL_2.take(),
SUBPOOL_2_SIZES.take(),
SUBPOOL_2_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_4.assume_init_mut() },
unsafe { SUBPOOL_4_SIZES.assume_init_mut() },
SUBPOOL_4.take(),
SUBPOOL_4_SIZES.take(),
SUBPOOL_4_NUM_ELEMENTS,
false
true
)
.is_ok());
generic_test_spills_to_higher_subpools(&mut heapless_pool);
@ -1805,18 +1818,18 @@ mod tests {
StaticHeaplessMemoryPool::new(true);
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_5.assume_init_mut() },
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
SUBPOOL_5.take(),
SUBPOOL_5_SIZES.take(),
SUBPOOL_5_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_3.assume_init_mut() },
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
SUBPOOL_3.take(),
SUBPOOL_3_SIZES.take(),
SUBPOOL_3_NUM_ELEMENTS,
false
true
)
.is_ok());
generic_test_spillage_fails_as_well(&mut heapless_pool);
@ -1828,26 +1841,26 @@ mod tests {
StaticHeaplessMemoryPool::new(true);
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_5.assume_init_mut() },
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
SUBPOOL_5.take(),
SUBPOOL_5_SIZES.take(),
SUBPOOL_5_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_6.assume_init_mut() },
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
SUBPOOL_6.take(),
SUBPOOL_6_SIZES.take(),
SUBPOOL_6_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_3.assume_init_mut() },
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
SUBPOOL_3.take(),
SUBPOOL_3_SIZES.take(),
SUBPOOL_3_NUM_ELEMENTS,
false
true
)
.is_ok());
generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool);
@ -1859,26 +1872,26 @@ mod tests {
StaticHeaplessMemoryPool::new(true);
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_5.assume_init_mut() },
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
SUBPOOL_5.take(),
SUBPOOL_5_SIZES.take(),
SUBPOOL_5_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_6.assume_init_mut() },
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
SUBPOOL_6.take(),
SUBPOOL_6_SIZES.take(),
SUBPOOL_6_NUM_ELEMENTS,
false
true
)
.is_ok());
assert!(heapless_pool
.grow(
unsafe { SUBPOOL_3.assume_init_mut() },
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
SUBPOOL_3.take(),
SUBPOOL_3_SIZES.take(),
SUBPOOL_3_NUM_ELEMENTS,
false
true
)
.is_ok());
generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool);

View File

@ -66,9 +66,10 @@ impl GenericActionReplyPus {
pub mod alloc_mod {
use crate::{
action::ActionRequest,
queue::GenericTargetedMessagingError,
queue::{GenericReceiveError, GenericSendError},
request::{
GenericMessage, MessageReceiver, MessageSender, MessageSenderAndReceiver, RequestId,
GenericMessage, MessageReceiverProvider, MessageSenderAndReceiver,
MessageSenderProvider, MessageSenderStoreProvider, RequestId,
},
ComponentId,
};
@ -76,15 +77,18 @@ pub mod alloc_mod {
use super::ActionReplyPus;
/// Helper type definition for a mode handler which can handle mode requests.
pub type ActionRequestHandlerInterface<S, R> =
MessageSenderAndReceiver<ActionReplyPus, ActionRequest, S, R>;
pub type ActionRequestHandlerInterface<Sender, Receiver, ReplySenderStore> =
MessageSenderAndReceiver<ActionReplyPus, ActionRequest, Sender, Receiver, ReplySenderStore>;
impl<S: MessageSender<ActionReplyPus>, R: MessageReceiver<ActionRequest>>
ActionRequestHandlerInterface<S, R>
impl<
Sender: MessageSenderProvider<ActionReplyPus>,
Receiver: MessageReceiverProvider<ActionRequest>,
ReplySender: MessageSenderStoreProvider<ActionReplyPus, Sender>,
> ActionRequestHandlerInterface<Sender, Receiver, ReplySender>
{
pub fn try_recv_action_request(
&self,
) -> Result<Option<GenericMessage<ActionRequest>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ActionRequest>>, GenericReceiveError> {
self.try_recv_message()
}
@ -93,22 +97,31 @@ pub mod alloc_mod {
request_id: RequestId,
target_id: ComponentId,
reply: ActionReplyPus,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(request_id, target_id, reply)
}
}
/// Helper type defintion for a mode handler object which can send mode requests and receive
/// mode replies.
pub type ActionRequestorInterface<S, R> =
MessageSenderAndReceiver<ActionRequest, ActionReplyPus, S, R>;
pub type ActionRequestorInterface<Sender, Receiver, RequestSenderStore> =
MessageSenderAndReceiver<
ActionRequest,
ActionReplyPus,
Sender,
Receiver,
RequestSenderStore,
>;
impl<S: MessageSender<ActionRequest>, R: MessageReceiver<ActionReplyPus>>
ActionRequestorInterface<S, R>
impl<
Sender: MessageSenderProvider<ActionRequest>,
Receiver: MessageReceiverProvider<ActionReplyPus>,
RequestSenderStore: MessageSenderStoreProvider<ActionRequest, Sender>,
> ActionRequestorInterface<Sender, Receiver, RequestSenderStore>
{
pub fn try_recv_action_reply(
&self,
) -> Result<Option<GenericMessage<ActionReplyPus>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<ActionReplyPus>>, GenericReceiveError> {
self.try_recv_message()
}
@ -117,7 +130,7 @@ pub mod alloc_mod {
request_id: RequestId,
target_id: ComponentId,
request: ActionRequest,
) -> Result<(), GenericTargetedMessagingError> {
) -> Result<(), GenericSendError> {
self.send_message(request_id, target_id, request)
}
}
@ -132,6 +145,7 @@ pub mod std_mod {
verification::{self, TcStateToken},
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap,
},
request::{MessageSenderMap, OneMessageSender},
ComponentId,
};
@ -174,22 +188,38 @@ pub mod std_mod {
}
pub type DefaultActiveActionRequestMap = DefaultActiveRequestMap<ActivePusActionRequestStd>;
pub type ActionRequestHandlerMpsc = ActionRequestHandlerInterface<
pub type ActionRequestHandlerOneSenderMpsc = ActionRequestHandlerInterface<
mpsc::Sender<GenericMessage<ActionReplyPus>>,
mpsc::Receiver<GenericMessage<ActionRequest>>,
OneMessageSender<
GenericMessage<ActionReplyPus>,
mpsc::Sender<GenericMessage<ActionReplyPus>>,
>,
>;
pub type ActionRequestHandlerMpscBounded = ActionRequestHandlerInterface<
pub type ActionRequestHandlerOneSenderMpscBounded = ActionRequestHandlerInterface<
mpsc::SyncSender<GenericMessage<ActionReplyPus>>,
mpsc::Receiver<GenericMessage<ActionRequest>>,
OneMessageSender<
GenericMessage<ActionReplyPus>,
mpsc::SyncSender<GenericMessage<ActionReplyPus>>,
>,
>;
pub type ActionRequestorMpsc = ActionRequestorInterface<
pub type ActionRequestorWithSenderMapMpsc = ActionRequestorInterface<
mpsc::Sender<GenericMessage<ActionRequest>>,
mpsc::Receiver<GenericMessage<ActionReplyPus>>,
MessageSenderMap<
GenericMessage<ActionRequest>,
mpsc::Sender<GenericMessage<ActionRequest>>,
>,
>;
pub type ActionRequestorBoundedMpsc = ActionRequestorInterface<
pub type ActionRequestorWithSenderMapBoundedMpsc = ActionRequestorInterface<
mpsc::SyncSender<GenericMessage<ActionRequest>>,
mpsc::Receiver<GenericMessage<ActionReplyPus>>,
MessageSenderMap<
GenericMessage<ActionRequest>,
mpsc::SyncSender<GenericMessage<ActionRequest>>,
>,
>;
}

View File

@ -9,14 +9,14 @@ use std::sync::mpsc::Sender;
use super::verification::VerificationReportingProvider;
use super::{
EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, GenericConversionError,
EcssTcInMemConversionProvider, EcssTcReceiver, EcssTmSender, GenericConversionError,
GenericRoutingError, HandlingStatus, PusServiceHelper,
};
pub struct PusEventServiceHandler<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> {
pub service_helper:
@ -27,7 +27,7 @@ pub struct PusEventServiceHandler<
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> PusEventServiceHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
{
@ -170,7 +170,7 @@ mod tests {
event_man::EventRequestWithToken,
tests::PusServiceHandlerWithSharedStoreCommon,
verification::{TcStateAccepted, VerificationToken},
DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, PusPacketHandlingError,
DirectPusPacketHandlerResult, EcssTcInSharedPoolConverter, PusPacketHandlingError,
},
};
@ -183,7 +183,7 @@ mod tests {
handler: PusEventServiceHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
>,
}

View File

@ -947,7 +947,7 @@ pub mod std_mod {
}
}
pub trait EcssTcInMemConverter {
pub trait EcssTcInMemConversionProvider {
fn cache(&mut self, possible_packet: &TcInMemory) -> Result<(), PusTcFromMemError>;
fn tc_slice_raw(&self) -> &[u8];
@ -980,7 +980,7 @@ pub mod std_mod {
pub pus_tc_raw: Option<Vec<u8>>,
}
impl EcssTcInMemConverter for EcssTcInVecConverter {
impl EcssTcInMemConversionProvider for EcssTcInVecConverter {
fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> {
self.pus_tc_raw = None;
match tc_in_memory {
@ -1011,24 +1011,25 @@ pub mod std_mod {
/// [SharedStaticMemoryPool] structure. This is useful if run-time allocation for these
/// packets should be avoided. Please note that this structure is not able to convert TCs which
/// are stored as a `Vec<u8>`.
pub struct EcssTcInSharedStoreConverter {
#[derive(Clone)]
pub struct EcssTcInSharedPoolConverter {
sender_id: Option<ComponentId>,
shared_tc_store: SharedStaticMemoryPool,
shared_tc_pool: SharedStaticMemoryPool,
pus_buf: Vec<u8>,
}
impl EcssTcInSharedStoreConverter {
impl EcssTcInSharedPoolConverter {
pub fn new(shared_tc_store: SharedStaticMemoryPool, max_expected_tc_size: usize) -> Self {
Self {
sender_id: None,
shared_tc_store,
shared_tc_pool: shared_tc_store,
pus_buf: alloc::vec![0; max_expected_tc_size],
}
}
pub fn copy_tc_to_buf(&mut self, addr: PoolAddr) -> Result<(), PusTcFromMemError> {
// Keep locked section as short as possible.
let mut tc_pool = self.shared_tc_store.write().map_err(|_| {
let mut tc_pool = self.shared_tc_pool.write().map_err(|_| {
PusTcFromMemError::EcssTmtc(EcssTmtcError::Store(PoolError::LockError))
})?;
let tc_size = tc_pool.len_of_data(&addr).map_err(EcssTmtcError::Store)?;
@ -1048,7 +1049,7 @@ pub mod std_mod {
}
}
impl EcssTcInMemConverter for EcssTcInSharedStoreConverter {
impl EcssTcInMemConversionProvider for EcssTcInSharedPoolConverter {
fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> {
match tc_in_memory {
super::TcInMemory::Pool(packet_in_pool) => {
@ -1071,6 +1072,44 @@ pub mod std_mod {
}
}
// TODO: alloc feature flag?
#[derive(Clone)]
pub enum EcssTcInMemConverter {
Static(EcssTcInSharedPoolConverter),
Heap(EcssTcInVecConverter),
}
impl EcssTcInMemConverter {
pub fn new_static(static_store_converter: EcssTcInSharedPoolConverter) -> Self {
EcssTcInMemConverter::Static(static_store_converter)
}
pub fn new_heap(heap_converter: EcssTcInVecConverter) -> Self {
EcssTcInMemConverter::Heap(heap_converter)
}
}
impl EcssTcInMemConversionProvider for EcssTcInMemConverter {
fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> {
match self {
EcssTcInMemConverter::Static(converter) => converter.cache(tc_in_memory),
EcssTcInMemConverter::Heap(converter) => converter.cache(tc_in_memory),
}
}
fn tc_slice_raw(&self) -> &[u8] {
match self {
EcssTcInMemConverter::Static(converter) => converter.tc_slice_raw(),
EcssTcInMemConverter::Heap(converter) => converter.tc_slice_raw(),
}
}
fn sender_id(&self) -> Option<ComponentId> {
match self {
EcssTcInMemConverter::Static(converter) => converter.sender_id(),
EcssTcInMemConverter::Heap(converter) => converter.sender_id(),
}
}
}
pub struct PusServiceBase<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
@ -1094,7 +1133,7 @@ pub mod std_mod {
pub struct PusServiceHelper<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> {
pub common: PusServiceBase<TcReceiver, TmSender, VerificationReporter>,
@ -1104,7 +1143,7 @@ pub mod std_mod {
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
{
@ -1221,9 +1260,10 @@ pub(crate) fn source_buffer_large_enough(
#[cfg(any(feature = "test_util", test))]
pub mod test_util {
use crate::request::UniqueApidTargetId;
use spacepackets::ecss::{tc::PusTcCreator, tm::PusTmReader};
use crate::request::UniqueApidTargetId;
use super::{
verification::{self, TcStateAccepted, VerificationToken},
DirectPusPacketHandlerResult, PusPacketHandlingError,
@ -1232,6 +1272,7 @@ pub mod test_util {
pub const TEST_APID: u16 = 0x101;
pub const TEST_UNIQUE_ID_0: u32 = 0x05;
pub const TEST_UNIQUE_ID_1: u32 = 0x06;
pub const TEST_COMPONENT_ID_0: UniqueApidTargetId =
UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0);
pub const TEST_COMPONENT_ID_1: UniqueApidTargetId =
@ -1268,14 +1309,13 @@ pub mod tests {
use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader};
use spacepackets::ecss::{PusPacket, WritablePusPacket};
use spacepackets::CcsdsPacket;
use test_util::{TEST_APID, TEST_COMPONENT_ID_0};
use crate::pool::{PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig};
use crate::pus::verification::{RequestId, VerificationReporter};
use crate::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool};
use crate::ComponentId;
use super::test_util::{TEST_APID, TEST_COMPONENT_ID_0};
use super::verification::test_util::TestVerificationReporter;
use super::verification::{
TcStateAccepted, VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
@ -1346,7 +1386,7 @@ pub mod tests {
pub type PusServiceHelperStatic = PusServiceHelper<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
>;
@ -1373,8 +1413,7 @@ pub mod tests {
VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg);
let test_srv_tm_sender =
PacketSenderWithSharedPool::new(tm_tx, shared_tm_pool_wrapper.clone());
let in_store_converter =
EcssTcInSharedStoreConverter::new(shared_tc_pool.clone(), 2048);
let in_store_converter = EcssTcInSharedPoolConverter::new(shared_tc_pool.clone(), 2048);
(
Self {
pus_buf: RefCell::new([0; 2048]),

View File

@ -39,7 +39,7 @@ mod tests {
use crate::{
mode::{
ModeAndSubmode, ModeReply, ModeReplySender, ModeRequest, ModeRequestSender,
ModeRequestorAndHandlerMpsc, ModeRequestorMpsc,
ModeRequestorAndHandlerMpsc, ModeRequestorOneChildMpsc,
},
request::{GenericMessage, MessageMetadata},
};
@ -52,7 +52,8 @@ mod tests {
fn test_simple_mode_requestor() {
let (reply_sender, reply_receiver) = mpsc::channel();
let (request_sender, request_receiver) = mpsc::channel();
let mut mode_requestor = ModeRequestorMpsc::new(TEST_COMPONENT_ID_0, reply_receiver);
let mut mode_requestor =
ModeRequestorOneChildMpsc::new(TEST_COMPONENT_ID_0, reply_receiver);
mode_requestor.add_message_target(TEST_COMPONENT_ID_1, request_sender);
// Send a request and verify it arrives at the receiver.

View File

@ -1,7 +1,7 @@
use super::scheduler::PusSchedulerProvider;
use super::verification::{VerificationReporter, VerificationReportingProvider};
use super::{
DirectPusPacketHandlerResult, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
DirectPusPacketHandlerResult, EcssTcInMemConversionProvider, EcssTcInSharedPoolConverter,
EcssTcInVecConverter, EcssTcReceiver, EcssTmSender, HandlingStatus, MpscTcReceiver,
PartialPusHandlingError, PusServiceHelper,
};
@ -24,7 +24,7 @@ use std::sync::mpsc;
pub struct PusSchedServiceHandler<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
PusScheduler: PusSchedulerProvider,
> {
@ -36,7 +36,7 @@ pub struct PusSchedServiceHandler<
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
Scheduler: PusSchedulerProvider,
>
@ -229,7 +229,7 @@ pub type PusService11SchedHandlerDynWithBoundedMpsc<PusScheduler> = PusSchedServ
pub type PusService11SchedHandlerStaticWithMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
PusScheduler,
>;
@ -238,7 +238,7 @@ pub type PusService11SchedHandlerStaticWithMpsc<PusScheduler> = PusSchedServiceH
pub type PusService11SchedHandlerStaticWithBoundedMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
PusScheduler,
>;
@ -253,7 +253,7 @@ mod tests {
scheduler::{self, PusSchedulerProvider, TcInfo},
tests::PusServiceHandlerWithSharedStoreCommon,
verification::{RequestId, TcStateAccepted, VerificationToken},
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
};
use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError};
use crate::tmtc::PacketSenderWithSharedPool;
@ -276,7 +276,7 @@ mod tests {
handler: PusSchedServiceHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
TestScheduler,
>,

View File

@ -9,8 +9,9 @@ use std::sync::mpsc;
use super::verification::{VerificationReporter, VerificationReportingProvider};
use super::{
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcReceiver,
EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver, PusServiceHelper,
EcssTcInMemConversionProvider, EcssTcInSharedPoolConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver,
PusServiceHelper,
};
/// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets.
@ -18,7 +19,7 @@ use super::{
pub struct PusService17TestHandler<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> {
pub service_helper:
@ -28,7 +29,7 @@ pub struct PusService17TestHandler<
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
TcInMemConverter: EcssTcInMemConversionProvider,
VerificationReporter: VerificationReportingProvider,
> PusService17TestHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
{
@ -127,7 +128,7 @@ pub type PusService17TestHandlerDynWithBoundedMpsc = PusService17TestHandler<
pub type PusService17TestHandlerStaticWithBoundedMpsc = PusService17TestHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
>;
@ -142,7 +143,7 @@ mod tests {
};
use crate::pus::verification::{TcStateAccepted, VerificationToken};
use crate::pus::{
DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
DirectPusPacketHandlerResult, EcssTcInSharedPoolConverter, EcssTcInVecConverter,
GenericConversionError, HandlingStatus, MpscTcReceiver, MpscTmAsVecSender,
PartialPusHandlingError, PusPacketHandlingError,
};
@ -162,7 +163,7 @@ mod tests {
handler: PusService17TestHandler<
MpscTcReceiver,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
EcssTcInSharedPoolConverter,
VerificationReporter,
>,
}

View File

@ -20,7 +20,7 @@
//! VerificationReportingProvider, VerificationReporterCfg, VerificationReporter
//! };
//! use satrs::tmtc::{SharedStaticMemoryPool, PacketSenderWithSharedPool};
//! use satrs::seq_count::SeqCountProviderSimple;
//! use satrs::spacepackets::seq_count::SeqCountProviderSimple;
//! use satrs::request::UniqueApidTargetId;
//! use spacepackets::ecss::PusPacket;
//! use spacepackets::SpHeader;
@ -97,8 +97,8 @@ use spacepackets::ecss::EcssEnumeration;
use spacepackets::{ByteConversionError, CcsdsPacket, PacketId, PacketSequenceCtrl};
use spacepackets::{SpHeader, MAX_APID};
pub use crate::seq_count::SeqCountProviderSimple;
pub use spacepackets::ecss::verification::*;
pub use spacepackets::seq_count::SeqCountProviderSimple;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
@ -1702,7 +1702,7 @@ pub mod tests {
};
use crate::pus::{ChannelWithId, PusTmVariant};
use crate::request::MessageMetadata;
use crate::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore};
use crate::spacepackets::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProvider};
use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use crate::ComponentId;
use alloc::format;

View File

@ -1,6 +1,3 @@
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::sync::mpsc;
@ -10,89 +7,31 @@ use crate::ComponentId;
pub type ChannelId = u32;
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum GenericSendError {
#[error("rx side has disconnected")]
RxDisconnected,
#[error("queue with max capacity of {0:?} is full")]
QueueFull(Option<u32>),
#[error("target queue with ID {0} does not exist")]
TargetDoesNotExist(ComponentId),
}
impl Display for GenericSendError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
GenericSendError::RxDisconnected => {
write!(f, "rx side has disconnected")
}
GenericSendError::QueueFull(max_cap) => {
write!(f, "queue with max capacity of {max_cap:?} is full")
}
GenericSendError::TargetDoesNotExist(target) => {
write!(f, "target queue with ID {target} does not exist")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericSendError {}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum GenericReceiveError {
#[error("nothing to receive")]
Empty,
#[error("tx side with id {0:?} has disconnected")]
TxDisconnected(Option<ComponentId>),
}
impl Display for GenericReceiveError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::TxDisconnected(channel_id) => {
write!(f, "tx side with id {channel_id:?} has disconnected")
}
Self::Empty => {
write!(f, "nothing to receive")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericReceiveError {}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, thiserror::Error)]
pub enum GenericTargetedMessagingError {
Send(GenericSendError),
Receive(GenericReceiveError),
}
impl From<GenericSendError> for GenericTargetedMessagingError {
fn from(value: GenericSendError) -> Self {
Self::Send(value)
}
}
impl From<GenericReceiveError> for GenericTargetedMessagingError {
fn from(value: GenericReceiveError) -> Self {
Self::Receive(value)
}
}
impl Display for GenericTargetedMessagingError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::Send(err) => write!(f, "generic targeted messaging error: {}", err),
Self::Receive(err) => write!(f, "generic targeted messaging error: {}", err),
}
}
}
#[cfg(feature = "std")]
impl Error for GenericTargetedMessagingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
GenericTargetedMessagingError::Send(send) => Some(send),
GenericTargetedMessagingError::Receive(receive) => Some(receive),
}
}
#[error("generic targeted messaging send error: {0}")]
Send(#[from] GenericSendError),
#[error("generic targeted messaging receive error: {0}")]
Receive(#[from] GenericReceiveError),
}
#[cfg(feature = "std")]

View File

@ -13,7 +13,10 @@ use spacepackets::{
ByteConversionError,
};
use crate::{queue::GenericTargetedMessagingError, ComponentId};
use crate::{
queue::{GenericReceiveError, GenericSendError},
ComponentId,
};
/// Generic request ID type. Requests can be associated with an ID to have a unique identifier
/// for them. This can be useful for tasks like tracking their progress.
@ -140,37 +143,38 @@ impl<Message> GenericMessage<Message> {
}
/// Generic trait for objects which can send targeted messages.
pub trait MessageSender<MSG>: Send {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError>;
pub trait MessageSenderProvider<MSG>: Send {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericSendError>;
}
// Generic trait for objects which can receive targeted messages.
pub trait MessageReceiver<MSG> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError>;
pub trait MessageReceiverProvider<MSG> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericReceiveError>;
}
pub struct MessageWithSenderIdReceiver<MSG, R: MessageReceiver<MSG>>(pub R, PhantomData<MSG>);
pub struct MessageWithSenderIdReceiver<Msg, Receiver: MessageReceiverProvider<Msg>>(
pub Receiver,
PhantomData<Msg>,
);
impl<MSG, R: MessageReceiver<MSG>> From<R> for MessageWithSenderIdReceiver<MSG, R> {
impl<MSG, R: MessageReceiverProvider<MSG>> From<R> for MessageWithSenderIdReceiver<MSG, R> {
fn from(receiver: R) -> Self {
MessageWithSenderIdReceiver(receiver, PhantomData)
}
}
impl<MSG, R: MessageReceiver<MSG>> MessageWithSenderIdReceiver<MSG, R> {
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
impl<MSG, R: MessageReceiverProvider<MSG>> MessageWithSenderIdReceiver<MSG, R> {
pub fn try_recv_message(&self) -> Result<Option<GenericMessage<MSG>>, GenericReceiveError> {
self.0.try_recv()
}
}
pub struct MessageReceiverWithId<MSG, R: MessageReceiver<MSG>> {
pub struct MessageReceiverWithId<MSG, R: MessageReceiverProvider<MSG>> {
local_channel_id: ComponentId,
reply_receiver: MessageWithSenderIdReceiver<MSG, R>,
}
impl<MSG, R: MessageReceiver<MSG>> MessageReceiverWithId<MSG, R> {
impl<MSG, R: MessageReceiverProvider<MSG>> MessageReceiverWithId<MSG, R> {
pub fn new(local_channel_id: ComponentId, reply_receiver: R) -> Self {
Self {
local_channel_id,
@ -183,43 +187,129 @@ impl<MSG, R: MessageReceiver<MSG>> MessageReceiverWithId<MSG, R> {
}
}
impl<MSG, R: MessageReceiver<MSG>> MessageReceiverWithId<MSG, R> {
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
impl<MSG, R: MessageReceiverProvider<MSG>> MessageReceiverWithId<MSG, R> {
pub fn try_recv_message(&self) -> Result<Option<GenericMessage<MSG>>, GenericReceiveError> {
self.reply_receiver.0.try_recv()
}
}
pub trait MessageSenderStoreProvider<Message, Sender>: Default {
fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender);
fn send_message(
&self,
requestor_info: MessageMetadata,
target_channel_id: ComponentId,
message: Message,
) -> Result<(), GenericSendError>;
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use crate::queue::GenericSendError;
use std::convert::From;
use super::*;
use hashbrown::HashMap;
pub struct MessageSenderMap<MSG, S: MessageSender<MSG>>(
pub HashMap<ComponentId, S>,
pub struct OneMessageSender<Msg, S: MessageSenderProvider<Msg>> {
pub id_and_sender: Option<(ComponentId, S)>,
pub(crate) phantom: PhantomData<Msg>,
}
impl<Msg, S: MessageSenderProvider<Msg>> Default for OneMessageSender<Msg, S> {
fn default() -> Self {
Self {
id_and_sender: Default::default(),
phantom: Default::default(),
}
}
}
impl<Msg, Sender: MessageSenderProvider<Msg>> MessageSenderStoreProvider<Msg, Sender>
for OneMessageSender<Msg, Sender>
{
fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) {
if self.id_and_sender.is_some() {
return;
}
self.id_and_sender = Some((target_id, message_sender));
}
fn send_message(
&self,
requestor_info: MessageMetadata,
target_channel_id: ComponentId,
message: Msg,
) -> Result<(), GenericSendError> {
if let Some((current_id, sender)) = &self.id_and_sender {
if *current_id == target_channel_id {
sender.send(GenericMessage::new(requestor_info, message))?;
return Ok(());
}
}
Err(GenericSendError::TargetDoesNotExist(target_channel_id))
}
}
pub struct MessageSenderList<MSG, S: MessageSenderProvider<MSG>>(
pub alloc::vec::Vec<(ComponentId, S)>,
pub(crate) PhantomData<MSG>,
);
impl<MSG, S: MessageSender<MSG>> Default for MessageSenderMap<MSG, S> {
impl<MSG, S: MessageSenderProvider<MSG>> Default for MessageSenderList<MSG, S> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}
impl<MSG, S: MessageSender<MSG>> MessageSenderMap<MSG, S> {
pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) {
self.0.insert(target_id, message_sender);
impl<Msg, Sender: MessageSenderProvider<Msg>> MessageSenderStoreProvider<Msg, Sender>
for MessageSenderList<Msg, Sender>
{
fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) {
self.0.push((target_id, message_sender));
}
pub fn send_message(
fn send_message(
&self,
requestor_info: MessageMetadata,
target_channel_id: ComponentId,
message: MSG,
) -> Result<(), GenericTargetedMessagingError> {
message: Msg,
) -> Result<(), GenericSendError> {
for (current_id, sender) in &self.0 {
if *current_id == target_channel_id {
sender.send(GenericMessage::new(requestor_info, message))?;
return Ok(());
}
}
Err(GenericSendError::TargetDoesNotExist(target_channel_id))
}
}
pub struct MessageSenderMap<MSG, S: MessageSenderProvider<MSG>>(
pub HashMap<ComponentId, S>,
pub(crate) PhantomData<MSG>,
);
impl<MSG, S: MessageSenderProvider<MSG>> Default for MessageSenderMap<MSG, S> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}
impl<Msg, Sender: MessageSenderProvider<Msg>> MessageSenderStoreProvider<Msg, Sender>
for MessageSenderMap<Msg, Sender>
{
fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) {
self.0.insert(target_id, message_sender);
}
fn send_message(
&self,
requestor_info: MessageMetadata,
target_channel_id: ComponentId,
message: Msg,
) -> Result<(), GenericSendError> {
if self.0.contains_key(&target_channel_id) {
return self
.0
@ -227,29 +317,42 @@ pub mod alloc_mod {
.unwrap()
.send(GenericMessage::new(requestor_info, message));
}
Err(GenericSendError::TargetDoesNotExist(target_channel_id).into())
Err(GenericSendError::TargetDoesNotExist(target_channel_id))
}
}
pub struct MessageSenderAndReceiver<TO, FROM, S: MessageSender<TO>, R: MessageReceiver<FROM>> {
pub struct MessageSenderAndReceiver<
To,
From,
Sender: MessageSenderProvider<To>,
Receiver: MessageReceiverProvider<From>,
SenderStore: MessageSenderStoreProvider<To, Sender>,
> {
pub local_channel_id: ComponentId,
pub message_sender_map: MessageSenderMap<TO, S>,
pub message_receiver: MessageWithSenderIdReceiver<FROM, R>,
pub message_sender_store: SenderStore,
pub message_receiver: MessageWithSenderIdReceiver<From, Receiver>,
pub(crate) phantom: PhantomData<(To, Sender)>,
}
impl<TO, FROM, S: MessageSender<TO>, R: MessageReceiver<FROM>>
MessageSenderAndReceiver<TO, FROM, S, R>
impl<
To,
From,
Sender: MessageSenderProvider<To>,
Receiver: MessageReceiverProvider<From>,
SenderStore: MessageSenderStoreProvider<To, Sender>,
> MessageSenderAndReceiver<To, From, Sender, Receiver, SenderStore>
{
pub fn new(local_channel_id: ComponentId, message_receiver: R) -> Self {
pub fn new(local_channel_id: ComponentId, message_receiver: Receiver) -> Self {
Self {
local_channel_id,
message_sender_map: Default::default(),
message_sender_store: Default::default(),
message_receiver: MessageWithSenderIdReceiver::from(message_receiver),
phantom: PhantomData,
}
}
pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) {
self.message_sender_map
pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) {
self.message_sender_store
.add_message_target(target_id, message_sender)
}
@ -262,9 +365,9 @@ pub mod alloc_mod {
&self,
request_id: RequestId,
target_id: ComponentId,
message: TO,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_message(
message: To,
) -> Result<(), GenericSendError> {
self.message_sender_store.send_message(
MessageMetadata::new(request_id, self.local_channel_id_generic()),
target_id,
message,
@ -274,48 +377,64 @@ pub mod alloc_mod {
/// Try to receive a message, which can be a reply or a request, depending on the generics.
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<FROM>>, GenericTargetedMessagingError> {
) -> Result<Option<GenericMessage<From>>, GenericReceiveError> {
self.message_receiver.try_recv_message()
}
}
pub struct RequestAndReplySenderAndReceiver<
REQUEST,
REPLY,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<REQUEST>,
Request,
ReqSender: MessageSenderProvider<Request>,
ReqReceiver: MessageReceiverProvider<Request>,
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
> {
pub local_channel_id: ComponentId,
// These 2 are a functional group.
pub request_sender_map: MessageSenderMap<REQUEST, S0>,
pub reply_receiver: MessageWithSenderIdReceiver<REPLY, R0>,
pub request_sender_store: ReqSenderStore,
pub reply_receiver: MessageWithSenderIdReceiver<Reply, ReplyReceiver>,
// These 2 are a functional group.
pub request_receiver: MessageWithSenderIdReceiver<REQUEST, R1>,
pub reply_sender_map: MessageSenderMap<REPLY, S1>,
pub request_receiver: MessageWithSenderIdReceiver<Request, ReqReceiver>,
pub reply_sender_store: ReplySenderStore,
phantom: PhantomData<(ReqSender, ReplySender)>,
}
impl<
REQUEST,
REPLY,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<REQUEST>,
> RequestAndReplySenderAndReceiver<REQUEST, REPLY, S0, R0, S1, R1>
Request,
ReqSender: MessageSenderProvider<Request>,
ReqReceiver: MessageReceiverProvider<Request>,
ReqSenderStore: MessageSenderStoreProvider<Request, ReqSender>,
Reply,
ReplySender: MessageSenderProvider<Reply>,
ReplyReceiver: MessageReceiverProvider<Reply>,
ReplySenderStore: MessageSenderStoreProvider<Reply, ReplySender>,
>
RequestAndReplySenderAndReceiver<
Request,
ReqSender,
ReqReceiver,
ReqSenderStore,
Reply,
ReplySender,
ReplyReceiver,
ReplySenderStore,
>
{
pub fn new(
local_channel_id: ComponentId,
request_receiver: R1,
reply_receiver: R0,
request_receiver: ReqReceiver,
reply_receiver: ReplyReceiver,
) -> Self {
Self {
local_channel_id,
request_receiver: request_receiver.into(),
reply_receiver: reply_receiver.into(),
request_sender_map: Default::default(),
reply_sender_map: Default::default(),
request_sender_store: Default::default(),
reply_sender_store: Default::default(),
phantom: PhantomData,
}
}
@ -333,21 +452,19 @@ pub mod std_mod {
use crate::queue::{GenericReceiveError, GenericSendError};
impl<MSG: Send> MessageSender<MSG> for mpsc::Sender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError> {
impl<MSG: Send> MessageSenderProvider<MSG> for mpsc::Sender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericSendError> {
self.send(message)
.map_err(|_| GenericSendError::RxDisconnected)?;
Ok(())
}
}
impl<MSG: Send> MessageSender<MSG> for mpsc::SyncSender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError> {
impl<MSG: Send> MessageSenderProvider<MSG> for mpsc::SyncSender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericSendError> {
if let Err(e) = self.try_send(message) {
return match e {
mpsc::TrySendError::Full(_) => Err(GenericSendError::QueueFull(None).into()),
mpsc::TrySendError::Disconnected(_) => {
Err(GenericSendError::RxDisconnected.into())
}
mpsc::TrySendError::Full(_) => Err(GenericSendError::QueueFull(None)),
mpsc::TrySendError::Disconnected(_) => Err(GenericSendError::RxDisconnected),
};
}
Ok(())
@ -357,14 +474,14 @@ pub mod std_mod {
pub type MessageSenderMapMpsc<MSG> = MessageReceiverWithId<MSG, mpsc::Sender<MSG>>;
pub type MessageSenderMapBoundedMpsc<MSG> = MessageReceiverWithId<MSG, mpsc::SyncSender<MSG>>;
impl<MSG> MessageReceiver<MSG> for mpsc::Receiver<GenericMessage<MSG>> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
impl<MSG> MessageReceiverProvider<MSG> for mpsc::Receiver<GenericMessage<MSG>> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericReceiveError> {
match self.try_recv() {
Ok(msg) => Ok(Some(msg)),
Err(e) => match e {
mpsc::TryRecvError::Empty => Ok(None),
mpsc::TryRecvError::Disconnected => {
Err(GenericReceiveError::TxDisconnected(None).into())
Err(GenericReceiveError::TxDisconnected(None))
}
},
}
@ -385,8 +502,8 @@ mod tests {
};
use crate::{
queue::{GenericReceiveError, GenericSendError, GenericTargetedMessagingError},
request::{MessageMetadata, MessageSenderMap},
queue::{GenericReceiveError, GenericSendError},
request::{MessageMetadata, MessageSenderMap, MessageSenderStoreProvider},
};
use super::{GenericMessage, MessageReceiverWithId, UniqueApidTargetId};
@ -478,9 +595,7 @@ mod tests {
let reply = receiver.try_recv_message();
assert!(reply.is_err());
let error = reply.unwrap_err();
if let GenericTargetedMessagingError::Receive(GenericReceiveError::TxDisconnected(None)) =
error
{
if let GenericReceiveError::TxDisconnected(None) = error {
} else {
panic!("unexpected error type");
}
@ -529,9 +644,7 @@ mod tests {
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::TargetDoesNotExist(target)) =
error
{
if let GenericSendError::TargetDoesNotExist(target) = error {
assert_eq!(target, TEST_CHANNEL_ID_2);
} else {
panic!("Unexpected error type");
@ -556,7 +669,7 @@ mod tests {
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::QueueFull(capacity)) = error {
if let GenericSendError::QueueFull(capacity) = error {
assert!(capacity.is_none());
} else {
panic!("Unexpected error type {}", error);
@ -576,7 +689,7 @@ mod tests {
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected) = error {
if let GenericSendError::RxDisconnected = error {
} else {
panic!("Unexpected error type {}", error);
}

416
satrs/src/scheduling.rs Normal file
View File

@ -0,0 +1,416 @@
use core::{convert::Infallible, fmt::Debug, time::Duration};
use std::time::Instant;
use thiserror::Error;
use crate::executable::Executable;
#[cfg(feature = "std")]
pub use std_mod::*;
#[derive(Debug, Default)]
pub struct SchedulingTable {
execution_frequency: Duration,
pub table: alloc::vec::Vec<u32>,
}
#[derive(Debug, Error)]
pub enum InvalidSlotError {
#[error("slot time is larger than the execution frequency")]
SlotTimeLargerThanFrequency,
#[error("slot time is smaller than previous slot")]
SmallerThanPreviousSlot {
slot_time_ms: u32,
prev_slot_time_ms: u32,
},
}
impl SchedulingTable {
pub fn new(execution_frequency: Duration) -> Self {
Self {
execution_frequency,
table: Default::default(),
}
}
pub fn add_slot(&mut self, relative_execution_time_ms: u32) -> Result<(), InvalidSlotError> {
if relative_execution_time_ms > self.execution_frequency.as_millis() as u32 {
return Err(InvalidSlotError::SlotTimeLargerThanFrequency);
}
if !self.table.is_empty() {
let prev_slot_ms = *self.table.last().unwrap();
if relative_execution_time_ms < prev_slot_ms {
return Err(InvalidSlotError::SmallerThanPreviousSlot {
slot_time_ms: relative_execution_time_ms,
prev_slot_time_ms: *self.table.last().unwrap(),
});
}
}
self.table.push(relative_execution_time_ms);
Ok(())
}
}
#[derive(Debug, Error)]
pub enum TaskWithSchedulingTableError {
#[error("scheudlig table error: {0}")]
InvalidSlot(#[from] InvalidSlotError),
#[error("task lock error")]
LockError,
#[error("task borrow error")]
BorrowError,
}
pub trait DeadlineMissedHandler {
fn deadline_missed_callback(&mut self, task_name: &'static str, op_code: i32);
}
pub trait TaskExecutor {
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F);
}
#[cfg(feature = "std")]
pub mod std_mod {
use core::cell::RefCell;
use std::{
rc::Rc,
sync::{Arc, Mutex},
vec::Vec,
};
use super::*;
impl TaskExecutor for Arc<Mutex<dyn Executable<Error = Infallible> + Send>> {
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F) {
let mut task = self.lock().unwrap();
f(&mut *task);
}
}
impl TaskExecutor for Rc<RefCell<dyn Executable<Error = Infallible>>> {
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F) {
let mut task = self.borrow_mut();
f(&mut *task);
}
}
pub struct TaskWithOpCode<T: TaskExecutor> {
task: T,
op_code: i32,
}
pub struct TaskWithSchedulingTable<T: TaskExecutor> {
start_of_slot: Instant,
end_of_slot: Instant,
deadline_missed_ms_count: u32,
table: SchedulingTable,
tasks: Vec<TaskWithOpCode<T>>,
}
impl TaskWithSchedulingTable<Rc<RefCell<dyn Executable<Error = Infallible>>>> {
/// Add a new task to the scheduling table
///
/// The task needs to be wrapped inside [Rc] and [RefCell]. The task is not sendable and
/// needs to be created inside the target thread.
pub fn add_task(
&mut self,
relative_execution_time_ms: u32,
task: Rc<RefCell<dyn Executable<Error = Infallible>>>,
op_code: i32,
) -> Result<(), TaskWithSchedulingTableError> {
self.table.add_slot(relative_execution_time_ms)?;
self.tasks.push(TaskWithOpCode { task, op_code });
Ok(())
}
}
impl TaskWithSchedulingTable<Arc<Mutex<dyn Executable<Error = Infallible> + Send>>> {
/// Add a new task to the scheduling table
///
/// The task needs to be wrapped inside [Arc] and [Mutex], but the task can be sent to
/// a different thread.
pub fn add_task_sendable(
&mut self,
relative_execution_time_ms: u32,
task: Arc<Mutex<dyn Executable<Error = Infallible> + Send>>,
op_code: i32,
) -> Result<(), TaskWithSchedulingTableError> {
self.table.add_slot(relative_execution_time_ms)?;
self.tasks.push(TaskWithOpCode { task, op_code });
Ok(())
}
}
impl<T: TaskExecutor> TaskWithSchedulingTable<T> {
pub fn new(execution_frequency: Duration) -> Self {
Self {
start_of_slot: Instant::now(),
end_of_slot: Instant::now(),
deadline_missed_ms_count: 10,
table: SchedulingTable::new(execution_frequency),
tasks: Default::default(),
}
}
/// Can be used to set the start of the slot to the current time. This is useful if a custom
/// runner implementation is used instead of the [Self::run_one_task_cycle] method.
pub fn init_start_of_slot(&mut self) {
self.start_of_slot = Instant::now();
}
pub fn run_one_task_cycle(
&mut self,
deadline_missed_cb: &mut impl DeadlineMissedHandler,
) -> Result<(), TaskWithSchedulingTableError> {
self.end_of_slot = self.start_of_slot + self.table.execution_frequency;
for (&relative_execution_time_ms, task_with_op_code) in
self.table.table.iter().zip(self.tasks.iter_mut())
{
let scheduled_execution_time = self.start_of_slot
+ core::time::Duration::from_millis(relative_execution_time_ms as u64);
let now = Instant::now();
if now < scheduled_execution_time {
std::thread::sleep(scheduled_execution_time - now);
} else if (now - scheduled_execution_time).as_millis()
> self.deadline_missed_ms_count.into()
{
task_with_op_code.task.with_task(|task| {
deadline_missed_cb
.deadline_missed_callback(task.task_name(), task_with_op_code.op_code);
});
}
task_with_op_code.task.with_task(|task| {
// Unwrapping is okay here because we constrain the tasks to be infallible.
task.periodic_op(task_with_op_code.op_code).unwrap();
});
}
let now = Instant::now();
if now <= self.end_of_slot {
let diff = self.end_of_slot - now;
std::thread::sleep(diff);
self.start_of_slot = self.end_of_slot;
} else if now > self.end_of_slot + self.table.execution_frequency {
// We're getting strongly out of sync. Set the new start timt to now.
self.start_of_slot = now;
}
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use core::{cell::RefCell, convert::Infallible, time::Duration};
use std::{
println,
rc::Rc,
sync::{
mpsc::{self, TryRecvError},
Arc, Mutex,
},
time::Instant,
};
use crate::executable::{Executable, OpResult};
use super::{DeadlineMissedHandler, TaskWithSchedulingTable};
#[derive(Debug)]
pub struct CallInfo {
time: std::time::Instant,
op_code: i32,
}
pub struct Task1 {
called_queue: mpsc::Sender<CallInfo>,
}
impl Executable for Task1 {
type Error = Infallible;
fn task_name(&self) -> &'static str {
"Task1"
}
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error> {
self.called_queue
.send(CallInfo {
time: Instant::now(),
op_code,
})
.unwrap();
Ok(OpResult::Ok)
}
}
pub struct Task2 {
called_queue: mpsc::Sender<CallInfo>,
}
impl Executable for Task2 {
type Error = Infallible;
fn task_name(&self) -> &'static str {
"Task2"
}
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error> {
self.called_queue
.send(CallInfo {
time: Instant::now(),
op_code,
})
.unwrap();
Ok(OpResult::Ok)
}
}
#[derive(Default)]
pub struct DeadlineMissed {
call_count: u32,
}
impl DeadlineMissedHandler for DeadlineMissed {
fn deadline_missed_callback(&mut self, task_name: &'static str, _op_code: i32) {
println!("task name {task_name} missed the deadline");
self.call_count += 1;
}
}
#[test]
pub fn basic_test() {
let (tx_t1, rx_t1) = mpsc::channel();
let (tx_t2, rx_t2) = mpsc::channel();
let t1 = Task1 {
called_queue: tx_t1,
};
let t2 = Task2 {
called_queue: tx_t2,
};
let mut deadline_missed_cb = DeadlineMissed::default();
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
let t1_first_slot = Rc::new(RefCell::new(t1));
let t1_second_slot = t1_first_slot.clone();
let t2_first_slot = Rc::new(RefCell::new(t2));
let t2_second_slot = t2_first_slot.clone();
exec_task.add_task(0, t1_first_slot, 0).unwrap();
exec_task.add_task(50, t1_second_slot, -1).unwrap();
exec_task.add_task(100, t2_first_slot, 1).unwrap();
exec_task.add_task(150, t2_second_slot, 2).unwrap();
let now = Instant::now();
exec_task.init_start_of_slot();
exec_task
.run_one_task_cycle(&mut deadline_missed_cb)
.unwrap();
let mut call_info = rx_t1.try_recv().unwrap();
assert_eq!(call_info.op_code, 0);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 30);
call_info = rx_t1.try_recv().unwrap();
assert_eq!(call_info.op_code, -1);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 80);
assert!(diff_call_to_start.as_millis() >= 50);
matches!(rx_t1.try_recv().unwrap_err(), TryRecvError::Empty);
call_info = rx_t2.try_recv().unwrap();
assert_eq!(call_info.op_code, 1);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 120);
assert!(diff_call_to_start.as_millis() >= 100);
call_info = rx_t2.try_recv().unwrap();
assert_eq!(call_info.op_code, 2);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 180);
assert!(diff_call_to_start.as_millis() >= 150);
matches!(rx_t2.try_recv().unwrap_err(), TryRecvError::Empty);
assert_eq!(deadline_missed_cb.call_count, 0);
}
#[test]
pub fn basic_test_with_arc_mutex() {
let (tx_t1, rx_t1) = mpsc::channel();
let (tx_t2, rx_t2) = mpsc::channel();
let t1 = Task1 {
called_queue: tx_t1,
};
let t2 = Task2 {
called_queue: tx_t2,
};
let mut deadline_missed_cb = DeadlineMissed::default();
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
let t1_first_slot = Arc::new(Mutex::new(t1));
let t1_second_slot = t1_first_slot.clone();
let t2_first_slot = Arc::new(Mutex::new(t2));
let t2_second_slot = t2_first_slot.clone();
exec_task.add_task_sendable(0, t1_first_slot, 0).unwrap();
exec_task.add_task_sendable(50, t1_second_slot, -1).unwrap();
exec_task.add_task_sendable(100, t2_first_slot, 1).unwrap();
exec_task.add_task_sendable(150, t2_second_slot, 2).unwrap();
let now = Instant::now();
exec_task.init_start_of_slot();
exec_task
.run_one_task_cycle(&mut deadline_missed_cb)
.unwrap();
let mut call_info = rx_t1.try_recv().unwrap();
assert_eq!(call_info.op_code, 0);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 30);
call_info = rx_t1.try_recv().unwrap();
assert_eq!(call_info.op_code, -1);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 80);
assert!(diff_call_to_start.as_millis() >= 50);
matches!(rx_t1.try_recv().unwrap_err(), TryRecvError::Empty);
call_info = rx_t2.try_recv().unwrap();
assert_eq!(call_info.op_code, 1);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 120);
assert!(diff_call_to_start.as_millis() >= 100);
call_info = rx_t2.try_recv().unwrap();
assert_eq!(call_info.op_code, 2);
let diff_call_to_start = call_info.time - now;
assert!(diff_call_to_start.as_millis() < 180);
assert!(diff_call_to_start.as_millis() >= 150);
matches!(rx_t2.try_recv().unwrap_err(), TryRecvError::Empty);
assert_eq!(deadline_missed_cb.call_count, 0);
}
#[test]
pub fn basic_test_in_thread() {
let mut deadline_missed_cb = DeadlineMissed::default();
std::thread::spawn(move || {
let (tx_t1, _rx_t1) = mpsc::channel();
let t1 = Task1 {
called_queue: tx_t1,
};
// Need to construct this in the thread, the task table in not [Send]
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
let t1_wrapper = Rc::new(RefCell::new(t1));
exec_task.add_task(0, t1_wrapper, 0).unwrap();
exec_task
.run_one_task_cycle(&mut deadline_missed_cb)
.unwrap();
});
let mut deadline_missed_cb = DeadlineMissed::default();
let (tx_t1, _rx_t1) = mpsc::channel();
let t1 = Task1 {
called_queue: tx_t1,
};
let mut exec_task_sendable = TaskWithSchedulingTable::new(Duration::from_millis(200));
exec_task_sendable
.add_task_sendable(0, Arc::new(Mutex::new(t1)), 1)
.unwrap();
std::thread::spawn(move || {
exec_task_sendable
.run_one_task_cycle(&mut deadline_missed_cb)
.unwrap();
});
}
}

View File

@ -1,250 +0,0 @@
use core::cell::Cell;
#[cfg(feature = "alloc")]
use dyn_clone::DynClone;
use paste::paste;
use spacepackets::MAX_SEQ_COUNT;
#[cfg(feature = "std")]
pub use stdmod::*;
/// Core trait for objects which can provide a sequence count.
///
/// The core functions are not mutable on purpose to allow easier usage with
/// static structs when using the interior mutability pattern. This can be achieved by using
/// [Cell], [core::cell::RefCell] or atomic types.
pub trait SequenceCountProviderCore<Raw> {
fn get(&self) -> Raw;
fn increment(&self);
fn get_and_increment(&self) -> Raw {
let val = self.get();
self.increment();
val
}
}
/// Extension trait which allows cloning a sequence count provider after it was turned into
/// a trait object.
#[cfg(feature = "alloc")]
pub trait SequenceCountProvider<Raw>: SequenceCountProviderCore<Raw> + DynClone {}
#[cfg(feature = "alloc")]
dyn_clone::clone_trait_object!(SequenceCountProvider<u16>);
#[cfg(feature = "alloc")]
impl<T, Raw> SequenceCountProvider<Raw> for T where T: SequenceCountProviderCore<Raw> + Clone {}
#[derive(Clone)]
pub struct SeqCountProviderSimple<T: Copy> {
seq_count: Cell<T>,
max_val: T,
}
macro_rules! impl_for_primitives {
($($ty: ident,)+) => {
$(
paste! {
impl SeqCountProviderSimple<$ty> {
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
Self {
seq_count: Cell::new(0),
max_val,
}
}
pub fn [<new_ $ty>]() -> Self {
Self {
seq_count: Cell::new(0),
max_val: $ty::MAX
}
}
}
impl Default for SeqCountProviderSimple<$ty> {
fn default() -> Self {
Self::[<new_ $ty>]()
}
}
impl SequenceCountProviderCore<$ty> for SeqCountProviderSimple<$ty> {
fn get(&self) -> $ty {
self.seq_count.get()
}
fn increment(&self) {
self.get_and_increment();
}
fn get_and_increment(&self) -> $ty {
let curr_count = self.seq_count.get();
if curr_count == self.max_val {
self.seq_count.set(0);
} else {
self.seq_count.set(curr_count + 1);
}
curr_count
}
}
}
)+
}
}
impl_for_primitives!(u8, u16, u32, u64,);
/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT].
#[derive(Clone)]
pub struct CcsdsSimpleSeqCountProvider {
provider: SeqCountProviderSimple<u16>,
}
impl Default for CcsdsSimpleSeqCountProvider {
fn default() -> Self {
Self {
provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT),
}
}
}
impl SequenceCountProviderCore<u16> for CcsdsSimpleSeqCountProvider {
delegate::delegate! {
to self.provider {
fn get(&self) -> u16;
fn increment(&self);
fn get_and_increment(&self) -> u16;
}
}
}
#[cfg(feature = "std")]
pub mod stdmod {
use super::*;
use std::sync::{Arc, Mutex};
macro_rules! sync_clonable_seq_counter_impl {
($($ty: ident,)+) => {
$(paste! {
/// These sequence counters can be shared between threads and can also be
/// configured to wrap around at specified maximum values. Please note that
/// that the API provided by this class will not panic und [Mutex] lock errors,
/// but it will yield 0 for the getter functions.
#[derive(Clone, Default)]
pub struct [<SeqCountProviderSync $ty:upper>] {
seq_count: Arc<Mutex<$ty>>,
max_val: $ty
}
impl [<SeqCountProviderSync $ty:upper>] {
pub fn new() -> Self {
Self::new_with_max_val($ty::MAX)
}
pub fn new_with_max_val(max_val: $ty) -> Self {
Self {
seq_count: Arc::default(),
max_val
}
}
}
impl SequenceCountProviderCore<$ty> for [<SeqCountProviderSync $ty:upper>] {
fn get(&self) -> $ty {
match self.seq_count.lock() {
Ok(counter) => *counter,
Err(_) => 0
}
}
fn increment(&self) {
self.get_and_increment();
}
fn get_and_increment(&self) -> $ty {
match self.seq_count.lock() {
Ok(mut counter) => {
let val = *counter;
if val == self.max_val {
*counter = 0;
} else {
*counter += 1;
}
val
}
Err(_) => 0,
}
}
}
})+
}
}
sync_clonable_seq_counter_impl!(u8, u16, u32, u64,);
}
#[cfg(test)]
mod tests {
use crate::seq_count::{
CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8,
SequenceCountProviderCore,
};
use spacepackets::MAX_SEQ_COUNT;
#[test]
fn test_u8_counter() {
let u8_counter = SeqCountProviderSimple::<u8>::default();
assert_eq!(u8_counter.get(), 0);
assert_eq!(u8_counter.get_and_increment(), 0);
assert_eq!(u8_counter.get_and_increment(), 1);
assert_eq!(u8_counter.get(), 2);
}
#[test]
fn test_u8_counter_overflow() {
let u8_counter = SeqCountProviderSimple::new_u8();
for _ in 0..256 {
u8_counter.increment();
}
assert_eq!(u8_counter.get(), 0);
}
#[test]
fn test_ccsds_counter() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
assert_eq!(ccsds_counter.get(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 1);
assert_eq!(ccsds_counter.get(), 2);
}
#[test]
fn test_ccsds_counter_overflow() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
for _ in 0..MAX_SEQ_COUNT + 1 {
ccsds_counter.increment();
}
assert_eq!(ccsds_counter.get(), 0);
}
#[test]
fn test_atomic_ref_counters() {
let sync_u8_counter = SeqCountProviderSyncU8::new();
assert_eq!(sync_u8_counter.get(), 0);
assert_eq!(sync_u8_counter.get_and_increment(), 0);
assert_eq!(sync_u8_counter.get_and_increment(), 1);
assert_eq!(sync_u8_counter.get(), 2);
}
#[test]
fn test_atomic_ref_counters_overflow() {
let sync_u8_counter = SeqCountProviderSyncU8::new();
for _ in 0..u8::MAX as u16 + 1 {
sync_u8_counter.increment();
}
assert_eq!(sync_u8_counter.get(), 0);
}
#[test]
fn test_atomic_ref_counters_overflow_custom_max_val() {
let sync_u8_counter = SeqCountProviderSyncU8::new_with_max_val(128);
for _ in 0..129 {
sync_u8_counter.increment();
}
assert_eq!(sync_u8_counter.get(), 0);
}
}

1610
satrs/src/subsystem.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ use crate::{
};
#[cfg(feature = "std")]
pub use alloc_mod::*;
use core::fmt::Debug;
#[cfg(feature = "alloc")]
use downcast_rs::{impl_downcast, Downcast};
use spacepackets::{
@ -170,7 +171,7 @@ where
}
/// Helper trait for any generic (static) store which allows storing raw or CCSDS packets.
pub trait CcsdsPacketPool {
pub trait CcsdsPacketPool: Debug {
fn add_ccsds_tc(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<PoolAddr, PoolError> {
self.add_raw_tc(tc_raw)
}
@ -190,7 +191,7 @@ pub trait PusTmPool {
}
/// Generic trait for any sender component able to send packets stored inside a pool structure.
pub trait PacketInPoolSender: Send {
pub trait PacketInPoolSender: Debug + Send {
fn send_packet(
&self,
sender_id: ComponentId,
@ -235,7 +236,7 @@ pub mod std_mod {
/// Newtype wrapper around the [SharedStaticMemoryPool] to enable extension helper traits on
/// top of the regular shared memory pool API.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct SharedPacketPool(pub SharedStaticMemoryPool);
impl SharedPacketPool {
@ -287,7 +288,6 @@ pub mod std_mod {
}
}
#[cfg(feature = "std")]
impl PacketSenderRaw for mpsc::Sender<PacketAsVec> {
type Error = GenericSendError;
@ -297,7 +297,6 @@ pub mod std_mod {
}
}
#[cfg(feature = "std")]
impl PacketSenderRaw for mpsc::SyncSender<PacketAsVec> {
type Error = GenericSendError;
@ -362,7 +361,7 @@ pub mod std_mod {
/// This is the primary structure used to send packets stored in a dedicated memory pool
/// structure.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct PacketSenderWithSharedPool<
Sender: PacketInPoolSender = mpsc::SyncSender<PacketInPool>,
PacketPool: CcsdsPacketPool = SharedPacketPool,

View File

@ -17,37 +17,37 @@ impl PusTmWithCdsShortHelper {
}
#[cfg(feature = "std")]
pub fn create_pus_tm_timestamp_now<'a>(
&'a mut self,
pub fn create_pus_tm_timestamp_now<'data>(
&mut self,
service: u8,
subservice: u8,
source_data: &'a [u8],
source_data: &'data [u8],
seq_count: u16,
) -> PusTmCreator {
) -> PusTmCreator<'_, 'data> {
let time_stamp = CdsTime::now_with_u16_days().unwrap();
time_stamp.write_to_bytes(&mut self.cds_short_buf).unwrap();
self.create_pus_tm_common(service, subservice, source_data, seq_count)
}
pub fn create_pus_tm_with_stamper<'a>(
&'a mut self,
pub fn create_pus_tm_with_stamper<'data>(
&mut self,
service: u8,
subservice: u8,
source_data: &'a [u8],
source_data: &'data [u8],
stamper: &CdsTime,
seq_count: u16,
) -> PusTmCreator {
) -> PusTmCreator<'_, 'data> {
stamper.write_to_bytes(&mut self.cds_short_buf).unwrap();
self.create_pus_tm_common(service, subservice, source_data, seq_count)
}
fn create_pus_tm_common<'a>(
&'a self,
fn create_pus_tm_common<'data>(
&self,
service: u8,
subservice: u8,
source_data: &'a [u8],
source_data: &'data [u8],
seq_count: u16,
) -> PusTmCreator {
) -> PusTmCreator<'_, 'data> {
let reply_header = SpHeader::new_for_unseg_tm(self.apid, seq_count, 0);
let tc_header = PusTmSecondaryHeader::new_simple(service, subservice, &self.cds_short_buf);
PusTmCreator::new(reply_header, tc_header, source_data, true)

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ use satrs::events::{EventU32, EventU32TypedSev, Severity, SeverityInfo};
use satrs::params::U32Pair;
use satrs::params::{Params, ParamsHeapless, WritableToBeBytes};
use satrs::pus::event_man::{DefaultPusEventReportingMap, EventReporter, PusEventTmCreatorWithMap};
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
use satrs::request::UniqueApidTargetId;
use satrs::tmtc::PacketAsVec;
use spacepackets::ecss::tm::PusTmReader;
@ -100,10 +99,7 @@ fn test_threaded_usage() {
// Event sender and TM checker thread
let jh1 = thread::spawn(move || {
event_tx
.send(EventMessage::new(
TEST_COMPONENT_ID_0.id(),
INFO_EVENT.into(),
))
.send(EventMessage::new(TEST_ID.id(), INFO_EVENT.into()))
.expect("Sending info event failed");
loop {
match event_packet_rx.try_recv() {
@ -130,7 +126,7 @@ fn test_threaded_usage() {
}
event_tx
.send(EventMessage::new_with_params(
TEST_COMPONENT_ID_0.id(),
TEST_ID.id(),
LOW_SEV_EVENT,
&Params::Heapless((2_u32, 3_u32).into()),
))