Compare commits
	
		
			193 Commits
		
	
	
		
			satrs-v0.2
			...
			ccsds-sche
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					
						
						
							
						
						d7b8a8c1d1
	
				 | 
					
					
						||
| 06e713a557 | |||
| 
						 | 
					778512d50e | ||
| 5d40638964 | |||
| 
						 | 
					1b07b845f2 | ||
| 2e58a311c8 | |||
| 
						 | 
					
						
						
							
						
						62d64e692a
	
				 | 
					
					
						||
| 3784f47b66 | |||
| 
						 | 
					e1911f1b6e | ||
| 2e53ce1871 | |||
| 
						 | 
					
						
						
							
						
						a6c460129b
	
				 | 
					
					
						||
| d5ecefd683 | |||
| 2f9f3b8183 | |||
| b28cff85de | |||
| da3de9f22c | |||
| aefdf0987d | |||
| 
						 | 
					7ae9e62d66 | ||
| fea1a9028b | |||
| 
						 | 
					
						
						
							
						
						abc145fb2d
	
				 | 
					
					
						||
| 490635dfcf | |||
| de3d22a022 | |||
| cbe211fe8b | |||
| 
						 | 
					e379bc3fd7 | ||
| a61ee85796 | |||
| 
						 | 
					
						
						
							
						
						81473b30f9
	
				 | 
					
					
						||
| 22675a73f9 | |||
| 
						 | 
					
						
						
							
						
						c68e3d4f75
	
				 | 
					
					
						||
| 3deedfba17 | |||
| 
						 | 
					
						
						
							
						
						533caea0fe
	
				 | 
					
					
						||
| 848fe6f207 | |||
| 
						 | 
					cf64fea7d9 | ||
| 4948db3fa5 | |||
| 
						 | 
					
						
						
							
						
						1920e4878c
	
				 | 
					
					
						||
| f46bc94b04 | |||
| 
						
						
							
						
						fb45da1890
	
				 | 
					
					
						|||
| f600ee499a | |||
| 3f28f60c59 | |||
| 44b1f2b037 | |||
| 4b22958b34 | |||
| a711c6acd9 | |||
| 
						
						
							
						
						99a954a1f5
	
				 | 
					
					
						|||
| 
						
						
							
						
						4a4fd7ac2c
	
				 | 
					
					
						|||
| 
						
						
							
						
						9bf08849a2
	
				 | 
					
					
						|||
| b8f7fefe26 | |||
| 
						
						
							
						
						a501832698
	
				 | 
					
					
						|||
| c7284d3f1c | |||
| 18263d4568 | |||
| 
						
						
							
						
						95519c1363
	
				 | 
					
					
						|||
| 2ec32717d0 | |||
| 
						
						
							
						
						bfdd777685
	
				 | 
					
					
						|||
| b54c2b7863 | |||
| 19f3355283 | |||
| 
						
						
							
						
						4aeb28d2f1
	
				 | 
					
					
						|||
| ddc4544456 | |||
| 9ab36c0362 | |||
| b95769c177 | |||
| bb20533ae1 | |||
| 
						
						
							
						
						27cacd0f43
	
				 | 
					
					
						|||
| bd6488e87b | |||
| 52ec0d44aa | |||
| 4fa9f8d685 | |||
| 767cf6b1a5 | |||
| 
						
						
							
						
						5bf5518591
	
				 | 
					
					
						|||
| f3e0609910 | |||
| 05106354e3 | |||
| 719b70d834 | |||
| d4082fa098 | |||
| c9c9e4f735 | |||
| 
						
						
							
						
						2c92a95184
	
				 | 
					
					
						|||
| 1a1d330814 | |||
| 
						
						
							
						
						ef7b5d66fe
	
				 | 
					
					
						|||
| b3497e0592 | |||
| 
						
						
							
						
						73286e36c2
	
				 | 
					
					
						|||
| ed266a11f6 | |||
| 
						
						
							
						
						af972e174f
	
				 | 
					
					
						|||
| 
						
						
							
						
						3d12083c16
	
				 | 
					
					
						|||
| e8d2c020fa | |||
| 69e172b633 | |||
| 
						
						
							
						
						e1dda751bc
	
				 | 
					
					
						|||
| b01628d8ef | |||
| 31844e4fe2 | |||
| 738872f421 | |||
| 
						
						
							
						
						309e39999f
	
				 | 
					
					
						|||
| 1c43c3adf9 | |||
| d9e0abffa7 | |||
| 
						
						
							
						
						abec9dd448
	
				 | 
					
					
						|||
| c18fbb59ad | |||
| 
						
						
							
						
						c91ddcd658
	
				 | 
					
					
						|||
| c5fa1955d7 | |||
| 
						
						
							
						
						c9708810e6
	
				 | 
					
					
						|||
| 79d0c2e222 | |||
| 8e87875c0e | |||
| 
						
						
							
						
						1ac6c02c06
	
				 | 
					
					
						|||
| 6e7907522e | |||
| 
						
						
							
						
						f747a5efdc
	
				 | 
					
					
						|||
| 6ffd55cec2 | |||
| 
						
						
							
						
						5e51b3de42
	
				 | 
					
					
						|||
| bd059a2541 | |||
| 1a4d764f25 | |||
| b38c617fae | |||
| edcd5491f1 | |||
| b4cb034b73 | |||
| 47c86aea5c | |||
| c8bba48e76 | |||
| 
						
						
							
						
						176da4838a
	
				 | 
					
					
						|||
| 8114195bc6 | |||
| 1726e34fa7 | |||
| 1f2d6c9474 | |||
| 
						
						
							
						
						9960930339
	
				 | 
					
					
						|||
| cb270964a1 | |||
| 
						
						
							
						
						a45e634214
	
				 | 
					
					
						|||
| 9e4132706c | |||
| 
						
						
							
						
						36d889a504
	
				 | 
					
					
						|||
| 97a6510af7 | |||
| 
						
						
							
						
						2e5d6a5c41
	
				 | 
					
					
						|||
| 
						
						
							
						
						29167736db
	
				 | 
					
					
						|||
| a4c433a7be | |||
| 472a8ce0f9 | |||
| e753319dac | |||
| 99dddf36f3 | |||
| a819feeaa2 | |||
| 46ce3fc772 | |||
| 8d27bdf3bf | |||
| 3d2a46f044 | |||
| 
						
						
							
						
						1f192af262
	
				 | 
					
					
						|||
| 
						
						
							
						
						3f78c200ad
	
				 | 
					
					
						|||
| 
						
						
							
						
						d73dfcdd67
	
				 | 
					
					
						|||
| 5cae0f7036 | |||
| 
						
						
							
						
						832250d211
	
				 | 
					
					
						|||
| 
						
						
							
						
						3c3b4349e8
	
				 | 
					
					
						|||
| 
						
						
							
						
						acf73e93b1
	
				 | 
					
					
						|||
| 0b2d4f6187 | |||
| 
						
						
							
						
						f7016b940a
	
				 | 
					
					
						|||
| 
						
						
							
						
						397ecd0c40
	
				 | 
					
					
						|||
| 422f2c11ab | |||
| 37e945fd91 | |||
| 45379858f0 | |||
| 7c194ab543 | |||
| 
						
						
							
						
						bca1d7292a
	
				 | 
					
					
						|||
| cdcb9cae1c | |||
| 
						
						
							
						
						9dcbd42862
	
				 | 
					
					
						|||
| 
						
						
							
						
						da05efc16d
	
				 | 
					
					
						|||
| e38e25a998 | |||
| 14b381cf4a | |||
| 3746e9ebb0 | |||
| d2fc783562 | |||
| 282f799203 | |||
| 
						
						
							
						
						46dbb4309b
	
				 | 
					
					
						|||
| 
						
						
							
						
						42d1257e83
	
				 | 
					
					
						|||
| 583f6ce4d2 | |||
| 
						
						
							
						
						408803fe86
	
				 | 
					
					
						|||
| 9ffe4d0ae0 | |||
| 
						
						
							
						
						e37061dcf0
	
				 | 
					
					
						|||
| 3a2ac11407 | |||
| 
						
						
							
						
						23327a7786
	
				 | 
					
					
						|||
| 89d5a1022f | |||
| 
						
						
							
						
						a00c843698
	
				 | 
					
					
						|||
| c586fd7fef | |||
| 
						
						
							
						
						7e78e70a17
	
				 | 
					
					
						|||
| 424dfc439c | |||
| 
						
						
							
						
						45eb2f1343
	
				 | 
					
					
						|||
| 
						
						
							
						
						736eb74e66
	
				 | 
					
					
						|||
| 29f71c2a57 | |||
| f0d08b65a4 | |||
| c7a74a844c | |||
| 9c60427f89 | |||
| 
						
						
							
						
						958ab9bab6
	
				 | 
					
					
						|||
| 312849bddb | |||
| 
						
						
							
						
						b0159a3ba7
	
				 | 
					
					
						|||
| 
						
						
							
						
						c477739f6d
	
				 | 
					
					
						|||
| 
						
						
							
						
						b7ce039406
	
				 | 
					
					
						|||
| 4736d40997 | |||
| 
						
						
							
						
						5ec5124ea3
	
				 | 
					
					
						|||
| 5e43259d4f | |||
| bfaddd0ebb | |||
| 423a068736 | |||
| 8022af1bf2 | |||
| acd2260dfd | |||
| e5ee698dc4 | |||
| 
						
						
							
						
						e8907c74d4
	
				 | 
					
					
						|||
| 536051e05b | |||
| 701db659e9 | |||
| 
						
						
							
						
						4b8e54b91b
	
				 | 
					
					
						|||
| 870d60cfd6 | |||
| 
						
						
							
						
						9e62e4292c
	
				 | 
					
					
						|||
| b2e77fbc09 | |||
| 
						
						
							
						
						5371928496
	
				 | 
					
					
						|||
| 975cd927f4 | |||
| 9039c1b59a | |||
| 
						
						
							
						
						972bf19188
	
				 | 
					
					
						|||
| 
						
						
							
						
						9d711d2b73
	
				 | 
					
					
						|||
| 
						
						
							
						
						d0005cdd63
	
				 | 
					
					
						|||
| 
						
						
							
						
						f00e6cf50c
	
				 | 
					
					
						
							
								
								
									
										70
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
name: ci
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check:
 | 
			
		||||
    name: Check build
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [ubuntu-latest, macos-latest, windows-latest]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@stable
 | 
			
		||||
      - run: cargo check
 | 
			
		||||
      # Check example with static pool configuration
 | 
			
		||||
      - run: cargo check -p satrs-example --no-default-features
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    name: Run Tests
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@stable
 | 
			
		||||
      - name: Install nextest
 | 
			
		||||
        uses: taiki-e/install-action@nextest
 | 
			
		||||
      - run: cargo nextest run --all-features
 | 
			
		||||
      - run: cargo test --doc --all-features
 | 
			
		||||
 | 
			
		||||
  cross-check:
 | 
			
		||||
    name: Check Cross-Compilation
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        target:
 | 
			
		||||
          - armv7-unknown-linux-gnueabihf
 | 
			
		||||
          - thumbv7em-none-eabihf
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@stable
 | 
			
		||||
        with:
 | 
			
		||||
          targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
 | 
			
		||||
      - run: cargo check -p satrs --target=${{matrix.target}} --no-default-features
 | 
			
		||||
 | 
			
		||||
  fmt:
 | 
			
		||||
    name: Check formatting
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@stable
 | 
			
		||||
        with:
 | 
			
		||||
          components: rustfmt
 | 
			
		||||
      - run: cargo fmt --all -- --check
 | 
			
		||||
 | 
			
		||||
  docs:
 | 
			
		||||
    name: Check Documentation Build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@nightly
 | 
			
		||||
      - run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features
 | 
			
		||||
 | 
			
		||||
  clippy:
 | 
			
		||||
    name: Clippy
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: dtolnay/rust-toolchain@stable
 | 
			
		||||
        with:
 | 
			
		||||
          components: clippy
 | 
			
		||||
      - run: cargo clippy -- -D warnings
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,6 +2,7 @@ target/
 | 
			
		||||
 | 
			
		||||
output.log
 | 
			
		||||
/Cargo.lock
 | 
			
		||||
output.log
 | 
			
		||||
 | 
			
		||||
output.log
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@ members = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
exclude = [
 | 
			
		||||
    "satrs-example-stm32f3-disco",
 | 
			
		||||
    "embedded-examples/stm32f3-disco-rtic",
 | 
			
		||||
    "embedded-examples/stm32h7-nucleo-rtic",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								README.md
									
									
									
									
									
								
							@@ -1,9 +1,10 @@
 | 
			
		||||
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
 | 
			
		||||
 | 
			
		||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
 | 
			
		||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
 | 
			
		||||
[](https://robamu.github.io/sat-rs/book/)
 | 
			
		||||
[](https://crates.io/crates/satrs)
 | 
			
		||||
[](https://docs.rs/satrs)
 | 
			
		||||
[](https://matrix.to/#/#sat-rs:matrix.org)
 | 
			
		||||
 | 
			
		||||
sat-rs
 | 
			
		||||
=========
 | 
			
		||||
@@ -11,7 +12,7 @@ sat-rs
 | 
			
		||||
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
 | 
			
		||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
 | 
			
		||||
for the special requirements for these systems. You can find an overview of the project and the
 | 
			
		||||
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
 | 
			
		||||
link to the [more high-level sat-rs book](https://robamu.github.io/sat-rs/book/)
 | 
			
		||||
at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
 | 
			
		||||
 | 
			
		||||
This is early-stage software. Important features are missing. New releases
 | 
			
		||||
@@ -37,22 +38,47 @@ This project currently contains following crates:
 | 
			
		||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
 | 
			
		||||
   Example of a simple example on-board software using various sat-rs components which can be run
 | 
			
		||||
   on a host computer or on any system with a standard runtime like a Raspberry Pi.
 | 
			
		||||
* [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim):
 | 
			
		||||
   Mini-Simulator based on [nexosim](https://github.com/asynchronics/nexosim) which
 | 
			
		||||
   simulates some physical devices for the `satrs-example` application device handlers.
 | 
			
		||||
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-mib):
 | 
			
		||||
   Components to build a mission information base from the on-board software directly.
 | 
			
		||||
* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco):
 | 
			
		||||
* [`satrs-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32f3-disco-rtic):
 | 
			
		||||
   Example of a simple example using low-level sat-rs components on a bare-metal system
 | 
			
		||||
   with constrained resources.
 | 
			
		||||
   with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
 | 
			
		||||
   framework on the STM32F3-Discovery device.
 | 
			
		||||
* [`satrs-stm32h-nucleo-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/stm32h7-nucleo-rtic):
 | 
			
		||||
   Example of a simple example using sat-rs components on a bare-metal system
 | 
			
		||||
   with constrained resources. This example uses the [RTIC](https://github.com/rtic-rs/rtic)
 | 
			
		||||
   framework on the STM32H743ZIT device.
 | 
			
		||||
 | 
			
		||||
Each project has its own `CHANGELOG.md`.
 | 
			
		||||
 | 
			
		||||
# Related projects
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
 In addition to the crates in this repository, the sat-rs project also maintains other libraries.
 | 
			
		||||
 | 
			
		||||
 * [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
 | 
			
		||||
   packet protocol implementations. This repository is re-exported in the
 | 
			
		||||
   [`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
 | 
			
		||||
   crate.
 | 
			
		||||
 * [`cfdp`](https://egit.irs.uni-stuttgart.de/rust/cfdp): CCSDS File Delivery Protocol
 | 
			
		||||
   (CFDP) high-level library components.
 | 
			
		||||
 | 
			
		||||
# Flight Heritage
 | 
			
		||||
 | 
			
		||||
There is an active and continuous effort to get early flight heritage for the sat-rs library.
 | 
			
		||||
Currently this library has the following flight heritage:
 | 
			
		||||
 | 
			
		||||
- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT)
 | 
			
		||||
  which has also
 | 
			
		||||
  [flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
 | 
			
		||||
  The application is strongly based on the sat-rs example application. You can find the repository
 | 
			
		||||
  of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
 | 
			
		||||
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
 | 
			
		||||
  alongside a Flight System Simulator in the context of a
 | 
			
		||||
  [Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
 | 
			
		||||
  at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
 | 
			
		||||
 | 
			
		||||
# Coverage
 | 
			
		||||
 | 
			
		||||
@@ -64,5 +90,5 @@ rustup component add llvm-tools-preview
 | 
			
		||||
cargo install grcov --locked
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
After that, you can simply run `coverage.py` to test the `satrs-core` crate with coverage. You can
 | 
			
		||||
After that, you can simply run `coverage.py` to test the `satrs` crate with coverage. You can
 | 
			
		||||
optionally supply the `--open` flag to open the coverage report in your webbrowser.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								automation/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								automation/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							@@ -33,6 +33,7 @@ pipeline {
 | 
			
		||||
    stage('Test') {
 | 
			
		||||
      steps {
 | 
			
		||||
        sh 'cargo nextest r --all-features'
 | 
			
		||||
        sh 'cargo test --doc --all-features'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stage('Check with all features') {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								docs.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								docs.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
 | 
			
		||||
cargo +nightly doc --all-features --open
 | 
			
		||||
@@ -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.2.0"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 | 
			
		||||
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.37"
 | 
			
		||||
version = "0.4.39"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
 | 
			
		||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-traits",
 | 
			
		||||
]
 | 
			
		||||
@@ -98,7 +89,17 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cobs"
 | 
			
		||||
version = "0.2.3"
 | 
			
		||||
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cobs"
 | 
			
		||||
version = "0.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cortex-m"
 | 
			
		||||
@@ -115,22 +116,22 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cortex-m-rt"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1"
 | 
			
		||||
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]]
 | 
			
		||||
@@ -144,9 +145,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc"
 | 
			
		||||
version = "3.0.1"
 | 
			
		||||
version = "3.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
 | 
			
		||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc-catalog",
 | 
			
		||||
]
 | 
			
		||||
@@ -159,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.8"
 | 
			
		||||
version = "0.20.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
 | 
			
		||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "darling_core",
 | 
			
		||||
 "darling_macro",
 | 
			
		||||
@@ -175,33 +176,33 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "darling_core"
 | 
			
		||||
version = "0.20.8"
 | 
			
		||||
version = "0.20.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
 | 
			
		||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "fnv",
 | 
			
		||||
 "ident_case",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "darling_macro"
 | 
			
		||||
version = "0.20.8"
 | 
			
		||||
version = "0.20.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
 | 
			
		||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "darling_core",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt"
 | 
			
		||||
version = "0.3.6"
 | 
			
		||||
version = "0.3.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3939552907426de152b3c2c6f51ed53f98f448babd26f28694c95f5906194595"
 | 
			
		||||
checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "defmt-macros",
 | 
			
		||||
@@ -219,22 +220,22 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-macros"
 | 
			
		||||
version = "0.3.7"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "18bdc7a7b92ac413e19e95240e75d3a73a8d8e78aa24a594c22cbb4d44b4bbda"
 | 
			
		||||
checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "defmt-parser",
 | 
			
		||||
 "proc-macro-error",
 | 
			
		||||
 "proc-macro-error2",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "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",
 | 
			
		||||
]
 | 
			
		||||
@@ -259,7 +260,7 @@ checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -273,6 +274,17 @@ dependencies = [
 | 
			
		||||
 "syn 1.0.109",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "derive-new"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-dma"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
@@ -298,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"
 | 
			
		||||
@@ -309,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.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -351,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",
 | 
			
		||||
@@ -409,9 +430,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hashbrown"
 | 
			
		||||
version = "0.14.3"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 | 
			
		||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "heapless"
 | 
			
		||||
@@ -431,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",
 | 
			
		||||
@@ -507,9 +528,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-iter"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
version = "0.1.45"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
 | 
			
		||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "num-integer",
 | 
			
		||||
@@ -529,38 +550,38 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-traits"
 | 
			
		||||
version = "0.2.18"
 | 
			
		||||
version = "0.2.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 | 
			
		||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[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.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "panic-probe"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
version = "0.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9"
 | 
			
		||||
checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "defmt",
 | 
			
		||||
@@ -568,15 +589,15 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "paste"
 | 
			
		||||
version = "1.0.14"
 | 
			
		||||
version = "1.0.15"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
 | 
			
		||||
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"
 | 
			
		||||
@@ -585,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.79"
 | 
			
		||||
version = "1.0.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
 | 
			
		||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.35"
 | 
			
		||||
version = "1.0.38"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 | 
			
		||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
@@ -637,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",
 | 
			
		||||
]
 | 
			
		||||
@@ -666,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.58",
 | 
			
		||||
 "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",
 | 
			
		||||
]
 | 
			
		||||
@@ -713,20 +740,24 @@ 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.22",
 | 
			
		||||
 "semver 1.0.25",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs"
 | 
			
		||||
version = "0.2.0-rc.0"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "866fcae3b683ccc37b5ad77982483a0ee01d5dc408dea5aad2117ad404b60fe1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cobs",
 | 
			
		||||
 "cobs 0.2.3",
 | 
			
		||||
 "crc",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "delegate",
 | 
			
		||||
 "derive-new",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num_enum",
 | 
			
		||||
 "paste",
 | 
			
		||||
@@ -736,10 +767,19 @@ dependencies = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs-example-stm32f3-disco"
 | 
			
		||||
name = "satrs-shared"
 | 
			
		||||
version = "0.1.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6042477018c2d43fffccaaa5099bc299a58485139b4d31c5b276889311e474f1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "spacepackets",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs-stm32f3-disco-rtic"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cobs",
 | 
			
		||||
 "cobs 0.3.0",
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "cortex-m-rt",
 | 
			
		||||
 "cortex-m-semihosting",
 | 
			
		||||
@@ -757,13 +797,6 @@ dependencies = [
 | 
			
		||||
 "stm32f3xx-hal",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs-shared"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "spacepackets",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver"
 | 
			
		||||
version = "0.9.0"
 | 
			
		||||
@@ -775,9 +808,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver"
 | 
			
		||||
version = "1.0.22"
 | 
			
		||||
version = "1.0.25"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
 | 
			
		||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver-parser"
 | 
			
		||||
@@ -799,9 +832,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "spacepackets"
 | 
			
		||||
version = "0.11.0-rc.2"
 | 
			
		||||
version = "0.11.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c2cfd5f9a4c7f10714d21f9bc61f2d176cb7ae092cdd687e7ade2d4e6f7d7125"
 | 
			
		||||
checksum = "e85574d113a06312010c0ba51aadccd4ba2806231ebe9a49fc6473d0534d8696"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc",
 | 
			
		||||
 "defmt",
 | 
			
		||||
@@ -899,9 +932,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "2.0.58"
 | 
			
		||||
version = "2.0.96"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
 | 
			
		||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -910,22 +943,22 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror"
 | 
			
		||||
version = "1.0.58"
 | 
			
		||||
version = "2.0.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 | 
			
		||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror-impl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror-impl"
 | 
			
		||||
version = "1.0.58"
 | 
			
		||||
version = "2.0.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 | 
			
		||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -936,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"
 | 
			
		||||
@@ -954,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"
 | 
			
		||||
@@ -975,9 +1008,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zerocopy"
 | 
			
		||||
version = "0.7.32"
 | 
			
		||||
version = "0.7.35"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
 | 
			
		||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "zerocopy-derive",
 | 
			
		||||
@@ -985,11 +1018,11 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zerocopy-derive"
 | 
			
		||||
version = "0.7.32"
 | 
			
		||||
version = "0.7.35"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 | 
			
		||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.58",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "satrs-example-stm32f3-disco"
 | 
			
		||||
name = "satrs-stm32f3-disco-rtic"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
default-run = "satrs-example-stm32f3-disco"
 | 
			
		||||
default-run = "satrs-stm32f3-disco-rtic"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
@@ -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]
 | 
			
		||||
@@ -46,7 +45,8 @@ branch = "complete-dma-update-hal"
 | 
			
		||||
# path = "../stm32f3-discovery"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs]
 | 
			
		||||
path = "../satrs"
 | 
			
		||||
# path = "satrs"
 | 
			
		||||
version = "0.2"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["defmt"]
 | 
			
		||||
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
sat-rs example for the STM32F3-Discovery board
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
This example application shows how the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad)
 | 
			
		||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
 | 
			
		||||
can be used on an embedded target.
 | 
			
		||||
It also shows how a relatively simple OBSW could be built when no standard runtime is available.
 | 
			
		||||
It uses [RTIC](https://rtic.rs/1/book/en/) as the concurrency framework and the
 | 
			
		||||
It uses [RTIC](https://rtic.rs/2/book/en/) as the concurrency framework and the
 | 
			
		||||
[defmt](https://defmt.ferrous-systems.com/) framework for logging.
 | 
			
		||||
 | 
			
		||||
The STM32F3-Discovery device was picked because it is a cheap Cortex-M4 based device which is also
 | 
			
		||||
@@ -9,7 +9,7 @@ from prompt_toolkit.history import FileHistory, History
 | 
			
		||||
from spacepackets.ecss.tm import CdsShortTimestamp
 | 
			
		||||
 | 
			
		||||
import tmtccmd
 | 
			
		||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusVerificator
 | 
			
		||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
 | 
			
		||||
from spacepackets.ecss.pus_17_test import Service17Tm
 | 
			
		||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +43,7 @@ from tmtccmd.tmtc import (
 | 
			
		||||
    DefaultPusQueueHelper,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.pus.s5_fsfw_event import Service5Tm
 | 
			
		||||
from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider
 | 
			
		||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
 | 
			
		||||
from tmtccmd.util.obj_id import ObjectIdDictT
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger()
 | 
			
		||||
@@ -53,7 +53,7 @@ EXAMPLE_PUS_APID = 0x02
 | 
			
		||||
 | 
			
		||||
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 (
 | 
			
		||||
@@ -111,9 +111,10 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
 | 
			
		||||
    def handle_tm(self, packet: bytes, _user_args: Any):
 | 
			
		||||
        time_reader = CdsShortTimestamp.empty()
 | 
			
		||||
        try:
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
 | 
			
		||||
            pus_tm = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            _LOGGER.warning("Could not generate PUS TM object from raw data")
 | 
			
		||||
            _LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
 | 
			
		||||
@@ -122,7 +123,7 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
        tm_packet = None
 | 
			
		||||
        if service == 1:
 | 
			
		||||
            tm_packet = Service1Tm.unpack(
 | 
			
		||||
                data=packet, params=UnpackParams(time_reader, 1, 2)
 | 
			
		||||
                data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
 | 
			
		||||
            )
 | 
			
		||||
            res = self.verif_wrapper.add_tm(tm_packet)
 | 
			
		||||
            if res is None:
 | 
			
		||||
@@ -139,16 +140,16 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
        if service == 3:
 | 
			
		||||
            _LOGGER.info("No handling for HK packets implemented")
 | 
			
		||||
            _LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.empty())
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if pus_tm.subservice == 25:
 | 
			
		||||
                if len(pus_tm.source_data) < 8:
 | 
			
		||||
                    raise ValueError("No addressable ID in HK packet")
 | 
			
		||||
                json_str = pus_tm.source_data[8:]
 | 
			
		||||
                _LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
 | 
			
		||||
        if service == 5:
 | 
			
		||||
            tm_packet = Service5Tm.unpack(packet, time_reader)
 | 
			
		||||
            tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
        if service == 17:
 | 
			
		||||
            tm_packet = Service17Tm.unpack(packet, time_reader)
 | 
			
		||||
            tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if tm_packet.subservice == 2:
 | 
			
		||||
                _LOGGER.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
            else:
 | 
			
		||||
@@ -159,7 +160,7 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
            _LOGGER.info(
 | 
			
		||||
                f"The service {service} is not implemented in Telemetry Factory"
 | 
			
		||||
            )
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(packet, time_reader)
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
        self.raw_logger.log_tm(pus_tm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -203,15 +204,15 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
            _LOGGER.info(log_entry.log_str)
 | 
			
		||||
 | 
			
		||||
    def queue_finished_cb(self, info: ProcedureWrapper):
 | 
			
		||||
        if info.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = info.to_def_procedure()
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            _LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
 | 
			
		||||
 | 
			
		||||
    def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
 | 
			
		||||
        q = self.queue_helper
 | 
			
		||||
        q.queue_wrapper = wrapper.queue_wrapper
 | 
			
		||||
        if info.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = info.to_def_procedure()
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            cmd_path = def_proc.cmd_path
 | 
			
		||||
            if cmd_path == "/ping":
 | 
			
		||||
                q.add_log_cmd("Sending PUS ping telecommand")
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
tmtccmd == 8.0.0rc.0
 | 
			
		||||
tmtccmd == 8.0.1
 | 
			
		||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
use satrs_example_stm32f3_disco as _;
 | 
			
		||||
use satrs_stm32f3_disco_rtic as _;
 | 
			
		||||
 | 
			
		||||
use stm32f3_discovery::leds::Leds;
 | 
			
		||||
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
 | 
			
		||||
@@ -9,14 +9,14 @@ use satrs::spacepackets::ecss::EcssEnumU16;
 | 
			
		||||
use satrs::spacepackets::CcsdsPacket;
 | 
			
		||||
use satrs::spacepackets::{ByteConversionError, SpHeader};
 | 
			
		||||
// global logger + panicking-behavior + memory layout
 | 
			
		||||
use satrs_example_stm32f3_disco as _;
 | 
			
		||||
use satrs_stm32f3_disco_rtic as _;
 | 
			
		||||
 | 
			
		||||
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());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
@@ -12,11 +12,11 @@
 | 
			
		||||
      "chip": "STM32F303VCTx",
 | 
			
		||||
      "coreConfigs": [
 | 
			
		||||
        {
 | 
			
		||||
          "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-example-stm32f3-disco",
 | 
			
		||||
          "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32f3-disco-rtic",
 | 
			
		||||
          "rttEnabled": true,
 | 
			
		||||
          "svdFile": "STM32F303.svd"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								embedded-examples/stm32h7-nucleo-rtic/.cargo/def_config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								embedded-examples/stm32h7-nucleo-rtic/.cargo/def_config.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
 | 
			
		||||
runner = "probe-rs run --chip STM32H743ZITx"
 | 
			
		||||
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
 | 
			
		||||
 | 
			
		||||
rustflags = [
 | 
			
		||||
  "-C", "linker=flip-link",
 | 
			
		||||
  "-C", "link-arg=-Tlink.x",
 | 
			
		||||
  "-C", "link-arg=-Tdefmt.x",
 | 
			
		||||
  # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
 | 
			
		||||
  # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
 | 
			
		||||
  "-C", "link-arg=--nmagic",
 | 
			
		||||
  # Can be useful for debugging.
 | 
			
		||||
  # "-Clink-args=-Map=app.map"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[build]
 | 
			
		||||
# (`thumbv6m-*` is compatible with all ARM Cortex-M chips but using the right
 | 
			
		||||
# target improves performance)
 | 
			
		||||
# target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
 | 
			
		||||
# target = "thumbv7m-none-eabi"    # Cortex-M3
 | 
			
		||||
# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
 | 
			
		||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
 | 
			
		||||
 | 
			
		||||
[alias]
 | 
			
		||||
rb = "run --bin"
 | 
			
		||||
rrb = "run --release --bin"
 | 
			
		||||
 | 
			
		||||
[env]
 | 
			
		||||
DEFMT_LOG = "info"
 | 
			
		||||
							
								
								
									
										4
									
								
								embedded-examples/stm32h7-nucleo-rtic/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								embedded-examples/stm32h7-nucleo-rtic/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
/target
 | 
			
		||||
/.cargo/config*
 | 
			
		||||
/.vscode
 | 
			
		||||
/app.map
 | 
			
		||||
							
								
								
									
										871
									
								
								embedded-examples/stm32h7-nucleo-rtic/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										871
									
								
								embedded-examples/stm32h7-nucleo-rtic/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,871 @@
 | 
			
		||||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 4
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "autocfg"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bare-metal"
 | 
			
		||||
version = "0.2.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "rustc_version",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bare-metal"
 | 
			
		||||
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"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "byteorder"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cast"
 | 
			
		||||
version = "0.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cobs"
 | 
			
		||||
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"
 | 
			
		||||
version = "0.7.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bare-metal 0.2.5",
 | 
			
		||||
 "bitfield",
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "embedded-hal 0.2.7",
 | 
			
		||||
 "volatile-register",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cortex-m-rt"
 | 
			
		||||
version = "0.7.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m-rt-macros",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cortex-m-rt-macros"
 | 
			
		||||
version = "0.7.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cortex-m-semihosting"
 | 
			
		||||
version = "0.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc"
 | 
			
		||||
version = "3.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc-catalog",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc-catalog"
 | 
			
		||||
version = "2.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "critical-section"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt"
 | 
			
		||||
version = "0.3.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "defmt-macros",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-brtt"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c2f0ac3635d0c89d12b8101fcb44a7625f5f030a1c0491124b74467eb5a58a78"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "defmt",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-macros"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "defmt-parser",
 | 
			
		||||
 "proc-macro-error2",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-parser"
 | 
			
		||||
version = "0.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-test"
 | 
			
		||||
version = "0.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "290966e8c38f94b11884877242de876280d0eab934900e9642d58868e77c5df1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m-rt",
 | 
			
		||||
 "cortex-m-semihosting",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "defmt-test-macros",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "defmt-test-macros"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "delegate"
 | 
			
		||||
version = "0.13.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "297806318ef30ad066b15792a8372858020ae3ca2e414ee6c2133b1eb9e9e945"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "derive-new"
 | 
			
		||||
version = "0.7.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-alloc"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "const-default",
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "linked_list_allocator",
 | 
			
		||||
 "rlsf",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-dma"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "stable_deref_trait",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-hal"
 | 
			
		||||
version = "0.2.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "nb 0.1.3",
 | 
			
		||||
 "void",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-hal"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "defmt",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-hal-async"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "embedded-hal 1.0.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-hal-bus"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "57b4e6ede84339ebdb418cd986e6320a34b017cdf99b5cc3efceec6450b06886"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "embedded-hal 1.0.0",
 | 
			
		||||
 "embedded-hal-async",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "embedded-storage"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "equivalent"
 | 
			
		||||
version = "1.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fugit"
 | 
			
		||||
version = "0.3.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "gcd",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-core"
 | 
			
		||||
version = "0.3.31"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-task"
 | 
			
		||||
version = "0.3.31"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-util"
 | 
			
		||||
version = "0.3.31"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "futures-task",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "pin-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gcd"
 | 
			
		||||
version = "2.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hash32"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "byteorder",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hashbrown"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "heapless"
 | 
			
		||||
version = "0.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "hash32",
 | 
			
		||||
 "stable_deref_trait",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "2.7.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
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 = "managed"
 | 
			
		||||
version = "0.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "nb"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "nb 1.1.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "nb"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-traits"
 | 
			
		||||
version = "0.2.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num_enum"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num_enum_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num_enum_derive"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "panic-probe"
 | 
			
		||||
version = "0.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "defmt",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "paste"
 | 
			
		||||
version = "1.0.15"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-project-lite"
 | 
			
		||||
version = "0.2.16"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-utils"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "portable-atomic"
 | 
			
		||||
version = "1.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
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-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro-error2"
 | 
			
		||||
version = "2.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro-error-attr2",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro2"
 | 
			
		||||
version = "1.0.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.38"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rlsf"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
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 = [
 | 
			
		||||
 "bare-metal 1.0.0",
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "portable-atomic",
 | 
			
		||||
 "rtic-core",
 | 
			
		||||
 "rtic-macros",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-common"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0786b50b81ef9d2a944a000f60405bb28bf30cd45da2d182f3fe636b2321f35c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "critical-section",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-core"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-macros"
 | 
			
		||||
version = "2.1.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ac22ab522d80079b48f46ac66ded4d349e1adf81b52430d6a74faa3a7790ed80"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "indexmap",
 | 
			
		||||
 "proc-macro-error2",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-monotonics"
 | 
			
		||||
version = "2.0.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f1cb90bcfdbbacf3ca37340cdab52ec2de5611c744095ef7889e9c50c233b748"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "fugit",
 | 
			
		||||
 "portable-atomic",
 | 
			
		||||
 "rtic-time",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-sync"
 | 
			
		||||
version = "1.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "49b1200137ccb2bf272a1801fa6e27264535facd356cb2c1d5bc8e12aa211bad"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "embedded-hal 1.0.0",
 | 
			
		||||
 "embedded-hal-async",
 | 
			
		||||
 "embedded-hal-bus",
 | 
			
		||||
 "heapless",
 | 
			
		||||
 "portable-atomic",
 | 
			
		||||
 "rtic-common",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rtic-time"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a7b1d853fa50dc125695414ce4510567a0d420221e455b1568cfa8c9aece9614"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "critical-section",
 | 
			
		||||
 "embedded-hal 1.0.0",
 | 
			
		||||
 "embedded-hal-async",
 | 
			
		||||
 "fugit",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
 "rtic-common",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustc_version"
 | 
			
		||||
version = "0.2.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "semver",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cobs",
 | 
			
		||||
 "crc",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "delegate",
 | 
			
		||||
 "derive-new",
 | 
			
		||||
 "heapless",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num_enum",
 | 
			
		||||
 "paste",
 | 
			
		||||
 "satrs-shared",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "spacepackets",
 | 
			
		||||
 "static_cell",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs-shared"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f10b962c131d0bcb0de06fefa5d4716cbd7f836167f90229f7f38405dc573bd3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "spacepackets",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "satrs-stm32h7-nucleo-rtic"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "cortex-m-rt",
 | 
			
		||||
 "cortex-m-semihosting",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "defmt-brtt",
 | 
			
		||||
 "defmt-test",
 | 
			
		||||
 "embedded-alloc",
 | 
			
		||||
 "panic-probe",
 | 
			
		||||
 "rtic",
 | 
			
		||||
 "rtic-monotonics",
 | 
			
		||||
 "rtic-sync",
 | 
			
		||||
 "satrs",
 | 
			
		||||
 "smoltcp",
 | 
			
		||||
 "stm32h7xx-hal",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver"
 | 
			
		||||
version = "0.9.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "semver-parser",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver-parser"
 | 
			
		||||
version = "0.7.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smallvec"
 | 
			
		||||
version = "1.13.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smoltcp"
 | 
			
		||||
version = "0.11.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "heapless",
 | 
			
		||||
 "managed",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "spacepackets"
 | 
			
		||||
version = "0.13.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "591d42bfa6af2ab308e5efd8f0870e2d0eb5dd19a141bdcb7da731f5ff9cc11c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc",
 | 
			
		||||
 "defmt",
 | 
			
		||||
 "delegate",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num_enum",
 | 
			
		||||
 "paste",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "zerocopy",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[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"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "362f288cd8341e9209587b889c385f323e82fc237b60c272868965bb879bb9b1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bare-metal 1.0.0",
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "cortex-m-rt",
 | 
			
		||||
 "vcell",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "stm32h7xx-hal"
 | 
			
		||||
version = "0.16.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3bd869329be25440b24e2b3583a1c016151b4a54bc36d96d82af7fcd9d010b98"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bare-metal 1.0.0",
 | 
			
		||||
 "cast",
 | 
			
		||||
 "cortex-m",
 | 
			
		||||
 "embedded-dma",
 | 
			
		||||
 "embedded-hal 0.2.7",
 | 
			
		||||
 "embedded-storage",
 | 
			
		||||
 "fugit",
 | 
			
		||||
 "nb 1.1.0",
 | 
			
		||||
 "paste",
 | 
			
		||||
 "smoltcp",
 | 
			
		||||
 "stm32h7",
 | 
			
		||||
 "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"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "2.0.96"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror"
 | 
			
		||||
version = "2.0.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror-impl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror-impl"
 | 
			
		||||
version = "2.0.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-ident"
 | 
			
		||||
version = "1.0.16"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
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"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "void"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "volatile-register"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "vcell",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zerocopy"
 | 
			
		||||
version = "0.8.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "zerocopy-derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zerocopy-derive"
 | 
			
		||||
version = "0.8.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.96",
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										85
									
								
								embedded-examples/stm32h7-nucleo-rtic/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								embedded-examples/stm32h7-nucleo-rtic/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
[package]
 | 
			
		||||
authors = ["Robin Mueller <robin.mueller.m@gmail.com>"]
 | 
			
		||||
name = "satrs-stm32h7-nucleo-rtic"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
default-run = "satrs-stm32h7-nucleo-rtic"
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
harness = false
 | 
			
		||||
 | 
			
		||||
# needed for each integration test
 | 
			
		||||
[[test]]
 | 
			
		||||
name = "integration"
 | 
			
		||||
harness = false
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
 | 
			
		||||
cortex-m-rt = "0.7"
 | 
			
		||||
defmt = "0.3"
 | 
			
		||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
 | 
			
		||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
 | 
			
		||||
cortex-m-semihosting = "0.5.0"
 | 
			
		||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
 | 
			
		||||
embedded-alloc = "0.6"
 | 
			
		||||
rtic-sync = { version = "1", features = ["defmt-03"] }
 | 
			
		||||
 | 
			
		||||
[dependencies.smoltcp]
 | 
			
		||||
version = "0.11"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
 | 
			
		||||
 | 
			
		||||
[dependencies.rtic]
 | 
			
		||||
version = "2"
 | 
			
		||||
features = ["thumbv7-backend"]
 | 
			
		||||
 | 
			
		||||
[dependencies.rtic-monotonics]
 | 
			
		||||
version = "2"
 | 
			
		||||
features = ["cortex-m-systick"]
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs]
 | 
			
		||||
path = "../../satrs"
 | 
			
		||||
version = "0.2"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["defmt", "heapless"]
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
defmt-test = "0.3"
 | 
			
		||||
 | 
			
		||||
# cargo build/run
 | 
			
		||||
[profile.dev]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = true # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
opt-level = 's'         # <-
 | 
			
		||||
overflow-checks = true  # <-
 | 
			
		||||
 | 
			
		||||
# cargo test
 | 
			
		||||
[profile.test]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = true # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
opt-level = 3           # <-
 | 
			
		||||
overflow-checks = true  # <-
 | 
			
		||||
 | 
			
		||||
# cargo build/run --release
 | 
			
		||||
[profile.release]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = false # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
lto = 'fat'
 | 
			
		||||
opt-level = 3            # <-
 | 
			
		||||
overflow-checks = false  # <-
 | 
			
		||||
 | 
			
		||||
# cargo test --release
 | 
			
		||||
[profile.bench]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = false # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
lto = 'fat'
 | 
			
		||||
opt-level = 3            # <-
 | 
			
		||||
overflow-checks = false  # <-
 | 
			
		||||
							
								
								
									
										201
									
								
								embedded-examples/stm32h7-nucleo-rtic/LICENSE-APACHE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								embedded-examples/stm32h7-nucleo-rtic/LICENSE-APACHE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
                              Apache License
 | 
			
		||||
                        Version 2.0, January 2004
 | 
			
		||||
                     http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
1. Definitions.
 | 
			
		||||
 | 
			
		||||
   "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
   and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
   "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
   the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
   "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
   other entities that control, are controlled by, or are under common
 | 
			
		||||
   control with that entity. For the purposes of this definition,
 | 
			
		||||
   "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
   direction or management of such entity, whether by contract or
 | 
			
		||||
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
   outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
   "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
   exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
   "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
   including but not limited to software source code, documentation
 | 
			
		||||
   source, and configuration files.
 | 
			
		||||
 | 
			
		||||
   "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
   transformation or translation of a Source form, including but
 | 
			
		||||
   not limited to compiled object code, generated documentation,
 | 
			
		||||
   and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
   "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
   Object form, made available under the License, as indicated by a
 | 
			
		||||
   copyright notice that is included in or attached to the work
 | 
			
		||||
   (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
   "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
   form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
   editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
   represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
   of this License, Derivative Works shall not include works that remain
 | 
			
		||||
   separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
   the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
   "Contribution" shall mean any work of authorship, including
 | 
			
		||||
   the original version of the Work and any modifications or additions
 | 
			
		||||
   to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
   submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
   or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
   the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
   means any form of electronic, verbal, or written communication sent
 | 
			
		||||
   to the Licensor or its representatives, including but not limited to
 | 
			
		||||
   communication on electronic mailing lists, source code control systems,
 | 
			
		||||
   and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
   Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
   excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
   designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
   "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
   on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
   subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
   this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
   copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
   publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
   Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
   this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
   (except as stated in this section) patent license to make, have made,
 | 
			
		||||
   use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
   where such license applies only to those patent claims licensable
 | 
			
		||||
   by such Contributor that are necessarily infringed by their
 | 
			
		||||
   Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
   with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
   institute patent litigation against any entity (including a
 | 
			
		||||
   cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
   or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
   or contributory patent infringement, then any patent licenses
 | 
			
		||||
   granted to You under this License for that Work shall terminate
 | 
			
		||||
   as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
   Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
   modifications, and in Source or Object form, provided that You
 | 
			
		||||
   meet the following conditions:
 | 
			
		||||
 | 
			
		||||
   (a) You must give any other recipients of the Work or
 | 
			
		||||
       Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
   (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
       stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
   (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
       that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
       attribution notices from the Source form of the Work,
 | 
			
		||||
       excluding those notices that do not pertain to any part of
 | 
			
		||||
       the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
   (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
       distribution, then any Derivative Works that You distribute must
 | 
			
		||||
       include a readable copy of the attribution notices contained
 | 
			
		||||
       within such NOTICE file, excluding those notices that do not
 | 
			
		||||
       pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
       of the following places: within a NOTICE text file distributed
 | 
			
		||||
       as part of the Derivative Works; within the Source form or
 | 
			
		||||
       documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
       within a display generated by the Derivative Works, if and
 | 
			
		||||
       wherever such third-party notices normally appear. The contents
 | 
			
		||||
       of the NOTICE file are for informational purposes only and
 | 
			
		||||
       do not modify the License. You may add Your own attribution
 | 
			
		||||
       notices within Derivative Works that You distribute, alongside
 | 
			
		||||
       or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
       that such additional attribution notices cannot be construed
 | 
			
		||||
       as modifying the License.
 | 
			
		||||
 | 
			
		||||
   You may add Your own copyright statement to Your modifications and
 | 
			
		||||
   may provide additional or different license terms and conditions
 | 
			
		||||
   for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
   for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
   reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
   the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
   any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
   by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
   this License, without any additional terms or conditions.
 | 
			
		||||
   Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
   the terms of any separate license agreement you may have executed
 | 
			
		||||
   with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
   names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
   except as required for reasonable and customary use in describing the
 | 
			
		||||
   origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
   agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
   Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
   implied, including, without limitation, any warranties or conditions
 | 
			
		||||
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
   PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
   appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
   risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
   whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
   unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
   negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
   liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
   incidental, or consequential damages of any character arising as a
 | 
			
		||||
   result of this License or out of the use or inability to use the
 | 
			
		||||
   Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
   work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
   other commercial damages or losses), even if such Contributor
 | 
			
		||||
   has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
   the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
   and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
   or other liability obligations and/or rights consistent with this
 | 
			
		||||
   License. However, in accepting such obligations, You may act only
 | 
			
		||||
   on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
   of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
   defend, and hold each Contributor harmless for any liability
 | 
			
		||||
   incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
   of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
   To apply the Apache License to your work, attach the following
 | 
			
		||||
   boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
   replaced with your own identifying information. (Don't include
 | 
			
		||||
   the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
   comment syntax for the file format. We also recommend that a
 | 
			
		||||
   file or class name and description of purpose be included on the
 | 
			
		||||
   same "printed page" as the copyright notice for easier
 | 
			
		||||
   identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
Copyright [yyyy] [name of copyright owner]
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
	http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
							
								
								
									
										118
									
								
								embedded-examples/stm32h7-nucleo-rtic/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								embedded-examples/stm32h7-nucleo-rtic/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
sat-rs example for the STM32H73ZI-Nucleo board
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
 | 
			
		||||
can be used on an embedded target.
 | 
			
		||||
It also shows how a relatively simple OBSW could be built when no standard runtime is available.
 | 
			
		||||
It uses [RTIC](https://rtic.rs/2/book/en/) as the concurrency framework and the
 | 
			
		||||
[defmt](https://defmt.ferrous-systems.com/) framework for logging.
 | 
			
		||||
 | 
			
		||||
The STM32H743ZIT device was picked because it is one of the more powerful Cortex-M based devices
 | 
			
		||||
available for STM with which also has a little bit more RAM available and also allows commanding
 | 
			
		||||
via TCP/IP.
 | 
			
		||||
 | 
			
		||||
## Pre-Requisites
 | 
			
		||||
 | 
			
		||||
Make sure the following tools are installed:
 | 
			
		||||
 | 
			
		||||
1. [`probe-rs`](https://probe.rs/): Application used to flash and debug the MCU.
 | 
			
		||||
2. Optional and recommended: [VS Code](https://code.visualstudio.com/) with
 | 
			
		||||
   [probe-rs plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger)
 | 
			
		||||
   for debugging.
 | 
			
		||||
 | 
			
		||||
## Preparing Rust and the repository
 | 
			
		||||
 | 
			
		||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
 | 
			
		||||
If you have not installed it yet, you can do so with
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
rustup target add thumbv7em-none-eabihf
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
A default `.cargo` config file is provided for this project, but needs to be copied to have
 | 
			
		||||
the correct name. This is so that the config file can be updated or edited for custom needs
 | 
			
		||||
without being tracked by git.
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
cp def_config.toml config.toml
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The configuration file will also set the target so it does not always have to be specified with
 | 
			
		||||
the `--target` argument.
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
 | 
			
		||||
After that, assuming that you have a `.cargo/config.toml` setting the correct build target,
 | 
			
		||||
you can simply build the application with
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
cargo build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Flashing from the command line
 | 
			
		||||
 | 
			
		||||
You can flash the application from the command line using `probe-rs`:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
probe-rs run --chip STM32H743ZITx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Debugging with VS Code
 | 
			
		||||
 | 
			
		||||
The STM32F3-Discovery comes with an on-board ST-Link so all that is required to flash and debug
 | 
			
		||||
the board is a Mini-USB cable. The code in this repository was debugged using [`probe-rs`](https://probe.rs/docs/tools/debuggerA)
 | 
			
		||||
and the VS Code [`probe-rs` plugin](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger).
 | 
			
		||||
Make sure to install this plugin first.
 | 
			
		||||
 | 
			
		||||
Sample configuration files are provided inside the `vscode` folder.
 | 
			
		||||
Use `cp vscode .vscode -r` to use them for your project.
 | 
			
		||||
 | 
			
		||||
Some sample configuration files for VS Code were provided as well. You can simply use `Run` and `Debug`
 | 
			
		||||
to automatically rebuild and flash your application.
 | 
			
		||||
 | 
			
		||||
The `tasks.json` and `launch.json` files are generic and you can use them immediately by opening
 | 
			
		||||
the folder in VS code or adding it to a workspace.
 | 
			
		||||
 | 
			
		||||
## Commanding with Python
 | 
			
		||||
 | 
			
		||||
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
 | 
			
		||||
using COBS encoded PUS packets.
 | 
			
		||||
 | 
			
		||||
It is recommended to use a virtual environment to do this. To set up one in the command line,
 | 
			
		||||
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
 | 
			
		||||
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
 | 
			
		||||
on how to activate the environment and then use the following command to install the required
 | 
			
		||||
dependency:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
pip install -r requirements.txt
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The packets are exchanged using a dedicated serial interface. You can use any generic USB-to-UART
 | 
			
		||||
converter device with the TX pin connected to the PA3 pin and the RX pin connected to the PA2 pin.
 | 
			
		||||
 | 
			
		||||
A default configuration file for the python application is provided and can be used by running
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
cp def_tmtc_conf.json tmtc_conf.json
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
After that, you can for example send a ping to the MCU using the following command
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./main.py -p /ping
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can configure the blinky frequency using
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./main.py -p /change_blink_freq
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
All these commands will package a PUS telecommand which will be sent to the MCU using the COBS
 | 
			
		||||
format as the packet framing format.
 | 
			
		||||
 | 
			
		||||
## Resources
 | 
			
		||||
 | 
			
		||||
- [STM32H743ZI Ethernet link checker example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/ethernet-nucleo-h743zi2.rs)
 | 
			
		||||
- [smoltcp DHCP client](https://github.com/smoltcp-rs/smoltcp/blob/main/examples/dhcp_client.rs)
 | 
			
		||||
							
								
								
									
										107782
									
								
								embedded-examples/stm32h7-nucleo-rtic/STM32H743.svd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107782
									
								
								embedded-examples/stm32h7-nucleo-rtic/STM32H743.svd
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								embedded-examples/stm32h7-nucleo-rtic/docs/stm32h743bi.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								embedded-examples/stm32h7-nucleo-rtic/docs/stm32h743bi.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										119
									
								
								embedded-examples/stm32h7-nucleo-rtic/memory.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								embedded-examples/stm32h7-nucleo-rtic/memory.x
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
/* Taken from https://github.com/stm32-rs/stm32h7xx-hal/pull/299, adapted slightly to work with */
 | 
			
		||||
/* flip-link */
 | 
			
		||||
MEMORY
 | 
			
		||||
{
 | 
			
		||||
  /* This file is intended for parts in the STM32H743/743v/753/753v families (RM0433), */
 | 
			
		||||
  /* with the exception of the STM32H742/742v parts which have a different RAM layout. */
 | 
			
		||||
  /* - FLASH and RAM are mandatory memory sections.                                    */
 | 
			
		||||
  /* - The sum of all non-FLASH sections must add to 1060K total device RAM.           */
 | 
			
		||||
  /* - The FLASH section size must match your device, see table below.                 */
 | 
			
		||||
 | 
			
		||||
  /* FLASH */
 | 
			
		||||
  /* Flash is divided in two independent banks (except 750xB). */
 | 
			
		||||
  /* Select the appropriate FLASH size for your device.        */
 | 
			
		||||
  /* - STM32H750xB                          128K (only FLASH1) */
 | 
			
		||||
  /* - STM32H750xB                            1M (512K + 512K) */
 | 
			
		||||
  /* - STM32H743xI/753xI                      2M (  1M +   1M) */
 | 
			
		||||
  FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M
 | 
			
		||||
  FLASH2  : ORIGIN = 0x08100000, LENGTH = 1M
 | 
			
		||||
 | 
			
		||||
  /* Data TCM  */
 | 
			
		||||
  /* - Two contiguous 64KB RAMs.                                     */
 | 
			
		||||
  /* - Used for interrupt handlers, stacks and general RAM.          */
 | 
			
		||||
  /* - Zero wait-states.                                             */
 | 
			
		||||
  /* - The DTCM is taken as the origin of the base ram. (See below.) */
 | 
			
		||||
  /*   This is also where the interrupt table and such will live,    */
 | 
			
		||||
  /*   which is required for deterministic performance.              */
 | 
			
		||||
  /* Need a region called RAM */
 | 
			
		||||
  /* DTCM : ORIGIN = 0x20000000, LENGTH = 128K */
 | 
			
		||||
  RAM : ORIGIN = 0x20000000, LENGTH = 128K
 | 
			
		||||
 | 
			
		||||
  /* Instruction TCM */
 | 
			
		||||
  /* - Used for latency-critical interrupt handlers etc. */
 | 
			
		||||
  /* - Zero wait-states.                                 */
 | 
			
		||||
  ITCM    : ORIGIN = 0x00000000, LENGTH = 64K
 | 
			
		||||
 | 
			
		||||
  /* AXI SRAM */
 | 
			
		||||
  /* - AXISRAM is in D1 and accessible by all system masters except BDMA. */
 | 
			
		||||
  /* - Suitable for application data not stored in DTCM.                  */
 | 
			
		||||
  /* - Zero wait-states.                                                  */
 | 
			
		||||
  AXISRAM : ORIGIN = 0x24000000, LENGTH = 512K
 | 
			
		||||
 | 
			
		||||
  /* AHB SRAM */
 | 
			
		||||
  /* - SRAM1-3 are in D2 and accessible by all system masters except BDMA.   */
 | 
			
		||||
  /*   Suitable for use as DMA buffers.                                      */
 | 
			
		||||
  /* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */
 | 
			
		||||
  /*   buffers, for storing application data in lower-power modes.           */
 | 
			
		||||
  /* - Zero wait-states.                                                     */
 | 
			
		||||
  SRAM1   : ORIGIN = 0x30000000, LENGTH = 128K
 | 
			
		||||
  SRAM2   : ORIGIN = 0x30020000, LENGTH = 128K
 | 
			
		||||
  SRAM3   : ORIGIN = 0x30040000, LENGTH = 32K
 | 
			
		||||
  SRAM4   : ORIGIN = 0x38000000, LENGTH = 64K
 | 
			
		||||
 | 
			
		||||
  /* Backup SRAM */
 | 
			
		||||
  BSRAM   : ORIGIN = 0x38800000, LENGTH = 4K
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
/* Assign the memory regions defined above for use. */
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
/* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */
 | 
			
		||||
/* These do not work with flip-link */
 | 
			
		||||
REGION_ALIAS(FLASH, FLASH1);
 | 
			
		||||
/* REGION_ALIAS(RAM,   DTCM); */
 | 
			
		||||
 | 
			
		||||
/* The location of the stack can be overridden using the `_stack_start` symbol. */
 | 
			
		||||
/* - Set the stack location at the end of RAM, using all remaining space.       */
 | 
			
		||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
 | 
			
		||||
 | 
			
		||||
/* The location of the .text section can be overridden using the  */
 | 
			
		||||
/* `_stext` symbol. By default it will place after .vector_table. */
 | 
			
		||||
/* _stext = ORIGIN(FLASH) + 0x40c; */
 | 
			
		||||
 | 
			
		||||
/* Define sections for placing symbols into the extra memory regions above.   */
 | 
			
		||||
/* This makes them accessible from code.                                      */
 | 
			
		||||
/* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */
 | 
			
		||||
/* - All other memories     connect to a 32-bit wide bus -> align to 4 bytes. */
 | 
			
		||||
SECTIONS {
 | 
			
		||||
  .flash2 (NOLOAD) : ALIGN(4) {
 | 
			
		||||
    *(.flash2 .flash2.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > FLASH2
 | 
			
		||||
 | 
			
		||||
  .itcm (NOLOAD) : ALIGN(8) {
 | 
			
		||||
    *(.itcm .itcm.*);
 | 
			
		||||
    . = ALIGN(8);
 | 
			
		||||
    } > ITCM
 | 
			
		||||
 | 
			
		||||
  .axisram (NOLOAD) : ALIGN(8) {
 | 
			
		||||
    *(.axisram .axisram.*);
 | 
			
		||||
    . = ALIGN(8);
 | 
			
		||||
    } > AXISRAM
 | 
			
		||||
 | 
			
		||||
  .sram1 (NOLOAD) : ALIGN(8) {
 | 
			
		||||
    *(.sram1 .sram1.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > SRAM1
 | 
			
		||||
 | 
			
		||||
  .sram2 (NOLOAD) : ALIGN(8) {
 | 
			
		||||
    *(.sram2 .sram2.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > SRAM2
 | 
			
		||||
 | 
			
		||||
  .sram3 (NOLOAD) : ALIGN(4) {
 | 
			
		||||
    *(.sram3 .sram3.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > SRAM3
 | 
			
		||||
 | 
			
		||||
  .sram4 (NOLOAD) : ALIGN(4) {
 | 
			
		||||
    *(.sram4 .sram4.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > SRAM4
 | 
			
		||||
 | 
			
		||||
  .bsram (NOLOAD) : ALIGN(4) {
 | 
			
		||||
    *(.bsram .bsram.*);
 | 
			
		||||
    . = ALIGN(4);
 | 
			
		||||
    } > BSRAM
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
__pycache__
 | 
			
		||||
 | 
			
		||||
/venv
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
/log
 | 
			
		||||
/.idea/*
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
 | 
			
		||||
/seqcnt.txt
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
/tmtc_conf.json
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
    "com_if": "udp",
 | 
			
		||||
    "tcpip_udp_port": 7301
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,24 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""Example client for the sat-rs example application"""
 | 
			
		||||
import struct
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
from typing import Any, Optional
 | 
			
		||||
from prompt_toolkit.history import History
 | 
			
		||||
from prompt_toolkit.history import FileHistory
 | 
			
		||||
from typing import Any, Optional, cast
 | 
			
		||||
from prompt_toolkit.history import FileHistory, History
 | 
			
		||||
from spacepackets.ecss.tm import CdsShortTimestamp
 | 
			
		||||
 | 
			
		||||
from spacepackets.ccsds import PacketId, PacketType
 | 
			
		||||
import tmtccmd
 | 
			
		||||
from spacepackets.ecss import PusTelemetry, PusVerificator
 | 
			
		||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
 | 
			
		||||
from spacepackets.ecss.pus_17_test import Service17Tm
 | 
			
		||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
 | 
			
		||||
from spacepackets.ccsds.time import CdsShortTimestamp
 | 
			
		||||
 | 
			
		||||
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
 | 
			
		||||
from tmtccmd.core.base import BackendRequest
 | 
			
		||||
from tmtccmd.core.ccsds_backend import QueueWrapper
 | 
			
		||||
from tmtccmd.logging import add_colorlog_console_logger
 | 
			
		||||
from tmtccmd.pus import VerificationWrapper
 | 
			
		||||
from tmtccmd.tmtc import CcsdsTmHandler, GenericApidHandlerBase
 | 
			
		||||
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
 | 
			
		||||
from tmtccmd.com import ComInterface
 | 
			
		||||
from tmtccmd.config import (
 | 
			
		||||
    CmdTreeNode,
 | 
			
		||||
@@ -26,8 +27,8 @@ from tmtccmd.config import (
 | 
			
		||||
    HookBase,
 | 
			
		||||
    params_to_procedure_conversion,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.config.com import SerialCfgWrapper
 | 
			
		||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
 | 
			
		||||
from tmtccmd.logging import add_colorlog_console_logger
 | 
			
		||||
from tmtccmd.logging.pus import (
 | 
			
		||||
    RegularTmtcLogWrapper,
 | 
			
		||||
    RawTmtcTimedLogWrapper,
 | 
			
		||||
@@ -40,21 +41,19 @@ from tmtccmd.tmtc import (
 | 
			
		||||
    FeedWrapper,
 | 
			
		||||
    SendCbParams,
 | 
			
		||||
    DefaultPusQueueHelper,
 | 
			
		||||
    QueueWrapper,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.pus.s5_fsfw_event import Service5Tm
 | 
			
		||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
 | 
			
		||||
from tmtccmd.util.obj_id import ObjectIdDictT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import pus_tc
 | 
			
		||||
from common import Apid, EventU32
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
EXAMPLE_PUS_APID = 0x02
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 (
 | 
			
		||||
@@ -63,20 +62,23 @@ class SatRsConfigHook(HookBase):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert self.cfg_path is not None
 | 
			
		||||
        packet_id_list = []
 | 
			
		||||
        for apid in Apid:
 | 
			
		||||
            packet_id_list.append(PacketId(PacketType.TM, True, apid))
 | 
			
		||||
        cfg = create_com_interface_cfg_default(
 | 
			
		||||
            com_if_key=com_if_key,
 | 
			
		||||
            json_cfg_path=self.cfg_path,
 | 
			
		||||
            space_packet_ids=packet_id_list,
 | 
			
		||||
            space_packet_ids=None,
 | 
			
		||||
        )
 | 
			
		||||
        assert cfg is not None
 | 
			
		||||
        if cfg is None:
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
 | 
			
		||||
            )
 | 
			
		||||
        if cfg.com_if_key == "serial_cobs":
 | 
			
		||||
            cfg = cast(SerialCfgWrapper, cfg)
 | 
			
		||||
            cfg.serial_cfg.serial_timeout = 0.5
 | 
			
		||||
        return create_com_interface_default(cfg)
 | 
			
		||||
 | 
			
		||||
    def get_command_definitions(self) -> CmdTreeNode:
 | 
			
		||||
        """This function should return the root node of the command definition tree."""
 | 
			
		||||
        return pus_tc.create_cmd_definition_tree()
 | 
			
		||||
        return create_cmd_definition_tree()
 | 
			
		||||
 | 
			
		||||
    def get_cmd_history(self) -> Optional[History]:
 | 
			
		||||
        """Optionlly return a history class for the past command paths which will be used
 | 
			
		||||
@@ -89,29 +91,39 @@ class SatRsConfigHook(HookBase):
 | 
			
		||||
        return get_core_object_ids()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PusHandler(GenericApidHandlerBase):
 | 
			
		||||
def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
    root_node = CmdTreeNode.root_node()
 | 
			
		||||
    root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
 | 
			
		||||
    root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
 | 
			
		||||
    return root_node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        file_logger: logging.Logger,
 | 
			
		||||
        verif_wrapper: VerificationWrapper,
 | 
			
		||||
        raw_logger: RawTmtcTimedLogWrapper,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(None)
 | 
			
		||||
        super().__init__(EXAMPLE_PUS_APID, None)
 | 
			
		||||
        self.file_logger = file_logger
 | 
			
		||||
        self.raw_logger = raw_logger
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
 | 
			
		||||
    def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
 | 
			
		||||
    def handle_tm(self, packet: bytes, _user_args: Any):
 | 
			
		||||
        try:
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
 | 
			
		||||
            pus_tm = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            _LOGGER.warning("Could not generate PUS TM object from raw data")
 | 
			
		||||
            _LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
 | 
			
		||||
            raise e
 | 
			
		||||
        service = pus_tm.service
 | 
			
		||||
        tm_packet = None
 | 
			
		||||
        if service == 1:
 | 
			
		||||
            tm_packet = Service1Tm.unpack(
 | 
			
		||||
                data=packet, params=UnpackParams(CdsShortTimestamp.empty(), 1, 2)
 | 
			
		||||
                data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
 | 
			
		||||
            )
 | 
			
		||||
            res = self.verif_wrapper.add_tm(tm_packet)
 | 
			
		||||
            if res is None:
 | 
			
		||||
@@ -125,48 +137,39 @@ class PusHandler(GenericApidHandlerBase):
 | 
			
		||||
            else:
 | 
			
		||||
                self.verif_wrapper.log_to_console(tm_packet, res)
 | 
			
		||||
                self.verif_wrapper.log_to_file(tm_packet, res)
 | 
			
		||||
        elif service == 3:
 | 
			
		||||
        if service == 3:
 | 
			
		||||
            _LOGGER.info("No handling for HK packets implemented")
 | 
			
		||||
            _LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if pus_tm.subservice == 25:
 | 
			
		||||
                if len(pus_tm.source_data) < 8:
 | 
			
		||||
                    raise ValueError("No addressable ID in HK packet")
 | 
			
		||||
                json_str = pus_tm.source_data[8:]
 | 
			
		||||
                _LOGGER.info(json_str)
 | 
			
		||||
        elif service == 5:
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(
 | 
			
		||||
                packet, time_reader=CdsShortTimestamp.empty()
 | 
			
		||||
            )
 | 
			
		||||
            src_data = tm_packet.source_data
 | 
			
		||||
            event_u32 = EventU32.unpack(src_data)
 | 
			
		||||
            _LOGGER.info(f"Received event packet. Event: {event_u32}")
 | 
			
		||||
            if event_u32.group_id == 0 and event_u32.unique_id == 0:
 | 
			
		||||
                _LOGGER.info("Received test event")
 | 
			
		||||
        elif service == 17:
 | 
			
		||||
            tm_packet = Service17Tm.unpack(
 | 
			
		||||
                packet, time_reader=CdsShortTimestamp.empty()
 | 
			
		||||
            )
 | 
			
		||||
                _LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
 | 
			
		||||
        if service == 5:
 | 
			
		||||
            tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
        if service == 17:
 | 
			
		||||
            tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if tm_packet.subservice == 2:
 | 
			
		||||
                self.file_logger.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
                _LOGGER.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
            else:
 | 
			
		||||
                self.file_logger.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
                _LOGGER.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
        if tm_packet is None:
 | 
			
		||||
            _LOGGER.info(
 | 
			
		||||
                f"The service {service} is not implemented in Telemetry Factory"
 | 
			
		||||
            )
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(
 | 
			
		||||
                packet, time_reader=CdsShortTimestamp.empty()
 | 
			
		||||
            )
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
        self.raw_logger.log_tm(pus_tm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
 | 
			
		||||
    byte_string = bytearray(struct.pack("!I", target_id))
 | 
			
		||||
    byte_string.extend(struct.pack("!I", unique_id))
 | 
			
		||||
    return byte_string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TcHandler(TcHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -178,10 +181,10 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
        self.queue_helper = DefaultPusQueueHelper(
 | 
			
		||||
            queue_wrapper=QueueWrapper.empty(),
 | 
			
		||||
            tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
 | 
			
		||||
            tc_sched_timestamp_len=7,
 | 
			
		||||
            seq_cnt_provider=seq_count_provider,
 | 
			
		||||
            pus_verificator=self.verif_wrapper.pus_verificator,
 | 
			
		||||
            default_pus_apid=None,
 | 
			
		||||
            pus_verificator=verif_wrapper.pus_verificator,
 | 
			
		||||
            default_pus_apid=EXAMPLE_PUS_APID,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_cb(self, send_params: SendCbParams):
 | 
			
		||||
@@ -189,6 +192,10 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
        if entry_helper.is_tc:
 | 
			
		||||
            if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
 | 
			
		||||
                pus_tc_wrapper = entry_helper.to_pus_tc_entry()
 | 
			
		||||
                pus_tc_wrapper.pus_tc.seq_count = (
 | 
			
		||||
                    self.seq_count_provider.get_and_increment()
 | 
			
		||||
                )
 | 
			
		||||
                self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
 | 
			
		||||
                raw_tc = pus_tc_wrapper.pus_tc.pack()
 | 
			
		||||
                _LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
 | 
			
		||||
                send_params.com_if.send(raw_tc)
 | 
			
		||||
@@ -197,17 +204,38 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
            _LOGGER.info(log_entry.log_str)
 | 
			
		||||
 | 
			
		||||
    def queue_finished_cb(self, info: ProcedureWrapper):
 | 
			
		||||
        if info.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = info.to_def_procedure()
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            _LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
 | 
			
		||||
 | 
			
		||||
    def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
 | 
			
		||||
        q = self.queue_helper
 | 
			
		||||
        q.queue_wrapper = wrapper.queue_wrapper
 | 
			
		||||
        if info.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = info.to_def_procedure()
 | 
			
		||||
            assert def_proc.cmd_path is not None
 | 
			
		||||
            pus_tc.pack_pus_telecommands(q, def_proc.cmd_path)
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            cmd_path = def_proc.cmd_path
 | 
			
		||||
            if cmd_path == "/ping":
 | 
			
		||||
                q.add_log_cmd("Sending PUS ping telecommand")
 | 
			
		||||
                q.add_pus_tc(PusTelecommand(service=17, subservice=1))
 | 
			
		||||
            if cmd_path == "/change_blink_freq":
 | 
			
		||||
                self.create_change_blink_freq_command(q)
 | 
			
		||||
 | 
			
		||||
    def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
 | 
			
		||||
        q.add_log_cmd("Changing blink frequency")
 | 
			
		||||
        while True:
 | 
			
		||||
            blink_freq = int(
 | 
			
		||||
                input(
 | 
			
		||||
                    "Please specify new blink frequency in ms. Valid Range [2..10000]: "
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            if blink_freq < 2 or blink_freq > 10000:
 | 
			
		||||
                print(
 | 
			
		||||
                    "Invalid blink frequency. Please specify a value between 2 and 10000."
 | 
			
		||||
                )
 | 
			
		||||
                continue
 | 
			
		||||
            break
 | 
			
		||||
        app_data = struct.pack("!I", blink_freq)
 | 
			
		||||
        q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
@@ -225,6 +253,7 @@ def main():
 | 
			
		||||
        post_args_wrapper.set_params_without_prompts(proc_wrapper)
 | 
			
		||||
    else:
 | 
			
		||||
        post_args_wrapper.set_params_with_prompts(proc_wrapper)
 | 
			
		||||
    params.apid = EXAMPLE_PUS_APID
 | 
			
		||||
    setup_args = SetupWrapper(
 | 
			
		||||
        hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
 | 
			
		||||
    )
 | 
			
		||||
@@ -236,9 +265,8 @@ def main():
 | 
			
		||||
    verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
 | 
			
		||||
    # Create primary TM handler and add it to the CCSDS Packet Handler
 | 
			
		||||
    tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
 | 
			
		||||
    ccsds_handler = CcsdsTmHandler(generic_handler=tm_handler)
 | 
			
		||||
    # TODO: We could add the CFDP handler for the CFDP APID at a later stage.
 | 
			
		||||
    # ccsds_handler.add_apid_handler(tm_handler)
 | 
			
		||||
    ccsds_handler = CcsdsTmHandler(generic_handler=None)
 | 
			
		||||
    ccsds_handler.add_apid_handler(tm_handler)
 | 
			
		||||
 | 
			
		||||
    # Create TC handler
 | 
			
		||||
    seq_count_provider = PusFileSeqCountProvider()
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
tmtccmd == 8.0.1
 | 
			
		||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
 | 
			
		||||
							
								
								
									
										55
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/bin/blinky.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/bin/blinky.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
//! Blinks an LED
 | 
			
		||||
//!
 | 
			
		||||
//! This assumes that LD2 (blue) is connected to pb7 and LD3 (red) is connected
 | 
			
		||||
//! to pb14. This assumption is true for the nucleo-h743zi board.
 | 
			
		||||
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
use satrs_stm32h7_nucleo_rtic as _;
 | 
			
		||||
 | 
			
		||||
use stm32h7xx_hal::{block, prelude::*, timer::Timer};
 | 
			
		||||
 | 
			
		||||
use cortex_m_rt::entry;
 | 
			
		||||
 | 
			
		||||
#[entry]
 | 
			
		||||
fn main() -> ! {
 | 
			
		||||
    defmt::println!("starting stm32h7 blinky example");
 | 
			
		||||
 | 
			
		||||
    // Get access to the device specific peripherals from the peripheral access crate
 | 
			
		||||
    let dp = stm32h7xx_hal::stm32::Peripherals::take().unwrap();
 | 
			
		||||
 | 
			
		||||
    // Take ownership over the RCC devices and convert them into the corresponding HAL structs
 | 
			
		||||
    let rcc = dp.RCC.constrain();
 | 
			
		||||
 | 
			
		||||
    let pwr = dp.PWR.constrain();
 | 
			
		||||
    let pwrcfg = pwr.freeze();
 | 
			
		||||
 | 
			
		||||
    // Freeze the configuration of all the clocks in the system and
 | 
			
		||||
    // retrieve the Core Clock Distribution and Reset (CCDR) object
 | 
			
		||||
    let rcc = rcc.use_hse(8.MHz()).bypass_hse();
 | 
			
		||||
    let ccdr = rcc.freeze(pwrcfg, &dp.SYSCFG);
 | 
			
		||||
 | 
			
		||||
    // Acquire the GPIOB peripheral
 | 
			
		||||
    let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
 | 
			
		||||
 | 
			
		||||
    // Configure gpio B pin 0 as a push-pull output.
 | 
			
		||||
    let mut ld1 = gpiob.pb0.into_push_pull_output();
 | 
			
		||||
 | 
			
		||||
    // Configure gpio B pin 7 as a push-pull output.
 | 
			
		||||
    let mut ld2 = gpiob.pb7.into_push_pull_output();
 | 
			
		||||
 | 
			
		||||
    // Configure gpio B pin 14 as a push-pull output.
 | 
			
		||||
    let mut ld3 = gpiob.pb14.into_push_pull_output();
 | 
			
		||||
 | 
			
		||||
    // Configure the timer to trigger an update every second
 | 
			
		||||
    let mut timer = Timer::tim1(dp.TIM1, ccdr.peripheral.TIM1, &ccdr.clocks);
 | 
			
		||||
    timer.start(1.Hz());
 | 
			
		||||
 | 
			
		||||
    // Wait for the timer to trigger an update and change the state of the LED
 | 
			
		||||
    loop {
 | 
			
		||||
        ld1.toggle();
 | 
			
		||||
        ld2.toggle();
 | 
			
		||||
        ld3.toggle();
 | 
			
		||||
        block!(timer.wait()).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/bin/hello.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/bin/hello.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![no_std]
 | 
			
		||||
 | 
			
		||||
use satrs_stm32h7_nucleo_rtic as _; // global logger + panicking-behavior + memory layout
 | 
			
		||||
 | 
			
		||||
#[cortex_m_rt::entry]
 | 
			
		||||
fn main() -> ! {
 | 
			
		||||
    defmt::println!("Hello, world!");
 | 
			
		||||
 | 
			
		||||
    satrs_stm32h7_nucleo_rtic::exit()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![no_std]
 | 
			
		||||
 | 
			
		||||
use cortex_m_semihosting::debug;
 | 
			
		||||
 | 
			
		||||
use defmt_brtt as _; // global logger
 | 
			
		||||
 | 
			
		||||
// TODO(5) adjust HAL import
 | 
			
		||||
use stm32h7xx_hal as _; // memory layout
 | 
			
		||||
 | 
			
		||||
use panic_probe as _;
 | 
			
		||||
 | 
			
		||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
 | 
			
		||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
 | 
			
		||||
#[defmt::panic_handler]
 | 
			
		||||
fn panic() -> ! {
 | 
			
		||||
    cortex_m::asm::udf()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Terminates the application and makes a semihosting-capable debug tool exit
 | 
			
		||||
/// with status code 0.
 | 
			
		||||
pub fn exit() -> ! {
 | 
			
		||||
    loop {
 | 
			
		||||
        debug::exit(debug::EXIT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Hardfault handler.
 | 
			
		||||
///
 | 
			
		||||
/// Terminates the application and makes a semihosting-capable debug tool exit
 | 
			
		||||
/// with an error. This seems better than the default, which is to spin in a
 | 
			
		||||
/// loop.
 | 
			
		||||
#[cortex_m_rt::exception]
 | 
			
		||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
 | 
			
		||||
    loop {
 | 
			
		||||
        debug::exit(debug::EXIT_FAILURE);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
 | 
			
		||||
// once within a crate. the module can be in any file but there can only be at most
 | 
			
		||||
// one `#[tests]` module in this library crate
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
#[defmt_test::tests]
 | 
			
		||||
mod unit_tests {
 | 
			
		||||
    use defmt::assert;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn it_works() {
 | 
			
		||||
        assert!(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										523
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										523
									
								
								embedded-examples/stm32h7-nucleo-rtic/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,523 @@
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![no_std]
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use rtic::app;
 | 
			
		||||
use rtic_monotonics::systick::prelude::*;
 | 
			
		||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
 | 
			
		||||
use satrs::static_subpool;
 | 
			
		||||
// global logger + panicking-behavior + memory layout
 | 
			
		||||
use satrs_stm32h7_nucleo_rtic as _;
 | 
			
		||||
use smoltcp::socket::udp::UdpMetadata;
 | 
			
		||||
use smoltcp::socket::{dhcpv4, udp};
 | 
			
		||||
 | 
			
		||||
use core::mem::MaybeUninit;
 | 
			
		||||
use embedded_alloc::LlffHeap as Heap;
 | 
			
		||||
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
 | 
			
		||||
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
 | 
			
		||||
use stm32h7xx_hal::ethernet;
 | 
			
		||||
 | 
			
		||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
 | 
			
		||||
const PORT: u16 = 7301;
 | 
			
		||||
 | 
			
		||||
const HEAP_SIZE: usize = 131_072;
 | 
			
		||||
 | 
			
		||||
const TC_SOURCE_CHANNEL_DEPTH: usize = 16;
 | 
			
		||||
pub type SharedPool = StaticHeaplessMemoryPool<3>;
 | 
			
		||||
pub type TcSourceChannel = rtic_sync::channel::Channel<PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
 | 
			
		||||
pub type TcSourceTx = rtic_sync::channel::Sender<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
 | 
			
		||||
pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
 | 
			
		||||
 | 
			
		||||
#[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;
 | 
			
		||||
pub const SUBPOOL_MEDIUM_NUM_BLOCKS: u16 = 16;
 | 
			
		||||
pub const SUBPOOL_MEDIUM_BLOCK_SIZE: usize = 128;
 | 
			
		||||
pub const SUBPOOL_LARGE_NUM_BLOCKS: u16 = 8;
 | 
			
		||||
pub const SUBPOOL_LARGE_BLOCK_SIZE: usize = 2048;
 | 
			
		||||
 | 
			
		||||
// This data will be held by Net through a mutable reference
 | 
			
		||||
pub struct NetStorageStatic<'a> {
 | 
			
		||||
    socket_storage: [SocketStorage<'a>; 8],
 | 
			
		||||
}
 | 
			
		||||
// MaybeUninit allows us write code that is correct even if STORE is not
 | 
			
		||||
// initialised by the runtime
 | 
			
		||||
static mut STORE: MaybeUninit<NetStorageStatic> = MaybeUninit::uninit();
 | 
			
		||||
 | 
			
		||||
static mut UDP_RX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
 | 
			
		||||
static mut UDP_RX: [u8; 2048] = [0; 2048];
 | 
			
		||||
static mut UDP_TX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
 | 
			
		||||
static mut UDP_TX: [u8; 2048] = [0; 2048];
 | 
			
		||||
 | 
			
		||||
/// Locally administered MAC address
 | 
			
		||||
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
 | 
			
		||||
 | 
			
		||||
pub struct Net {
 | 
			
		||||
    iface: Interface,
 | 
			
		||||
    ethdev: ethernet::EthernetDMA<4, 4>,
 | 
			
		||||
    dhcp_handle: SocketHandle,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Net {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        sockets: &mut SocketSet<'static>,
 | 
			
		||||
        mut ethdev: ethernet::EthernetDMA<4, 4>,
 | 
			
		||||
        ethernet_addr: HardwareAddress,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let config = Config::new(ethernet_addr);
 | 
			
		||||
        let mut iface = Interface::new(
 | 
			
		||||
            config,
 | 
			
		||||
            &mut ethdev,
 | 
			
		||||
            smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()),
 | 
			
		||||
        );
 | 
			
		||||
        // Create sockets
 | 
			
		||||
        let dhcp_socket = dhcpv4::Socket::new();
 | 
			
		||||
        iface.update_ip_addrs(|addrs| {
 | 
			
		||||
            let _ = addrs.push(IpCidr::new(IpAddress::v4(192, 168, 1, 99), 0));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let dhcp_handle = sockets.add(dhcp_socket);
 | 
			
		||||
        Net {
 | 
			
		||||
            iface,
 | 
			
		||||
            ethdev,
 | 
			
		||||
            dhcp_handle,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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 = Mono::now().duration_since_epoch();
 | 
			
		||||
        let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
 | 
			
		||||
 | 
			
		||||
        self.iface.poll(timestamp, &mut self.ethdev, sockets)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn poll_dhcp<'a>(&mut self, sockets: &'a mut SocketSet) -> Option<dhcpv4::Event<'a>> {
 | 
			
		||||
        let opt_event = sockets.get_mut::<dhcpv4::Socket>(self.dhcp_handle).poll();
 | 
			
		||||
        if let Some(event) = &opt_event {
 | 
			
		||||
            match event {
 | 
			
		||||
                dhcpv4::Event::Deconfigured => {
 | 
			
		||||
                    defmt::info!("DHCP lost configuration");
 | 
			
		||||
                    self.iface.update_ip_addrs(|addrs| addrs.clear());
 | 
			
		||||
                    self.iface.routes_mut().remove_default_ipv4_route();
 | 
			
		||||
                }
 | 
			
		||||
                dhcpv4::Event::Configured(config) => {
 | 
			
		||||
                    defmt::info!("DHCP configuration acquired");
 | 
			
		||||
                    defmt::info!("IP address: {}", config.address);
 | 
			
		||||
                    self.iface.update_ip_addrs(|addrs| {
 | 
			
		||||
                        addrs.clear();
 | 
			
		||||
                        addrs.push(IpCidr::Ipv4(config.address)).unwrap();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if let Some(router) = config.router {
 | 
			
		||||
                        defmt::debug!("Default gateway: {}", router);
 | 
			
		||||
                        self.iface
 | 
			
		||||
                            .routes_mut()
 | 
			
		||||
                            .add_default_ipv4_route(router)
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        defmt::debug!("Default gateway: None");
 | 
			
		||||
                        self.iface.routes_mut().remove_default_ipv4_route();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        opt_event
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct UdpNet {
 | 
			
		||||
    udp_handle: SocketHandle,
 | 
			
		||||
    last_client: Option<UdpMetadata>,
 | 
			
		||||
    tc_source_tx: TcSourceTx,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl UdpNet {
 | 
			
		||||
    pub fn new<'sockets>(sockets: &mut SocketSet<'sockets>, tc_source_tx: TcSourceTx) -> Self {
 | 
			
		||||
        // SAFETY: The RX and TX buffers are passed here and not used anywhere else.
 | 
			
		||||
        let udp_rx_buffer =
 | 
			
		||||
            smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_RX_META[..] }, unsafe {
 | 
			
		||||
                &mut UDP_RX[..]
 | 
			
		||||
            });
 | 
			
		||||
        let udp_tx_buffer =
 | 
			
		||||
            smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_TX_META[..] }, unsafe {
 | 
			
		||||
                &mut UDP_TX[..]
 | 
			
		||||
            });
 | 
			
		||||
        let udp_socket = smoltcp::socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
 | 
			
		||||
 | 
			
		||||
        let udp_handle = sockets.add(udp_socket);
 | 
			
		||||
        Self {
 | 
			
		||||
            udp_handle,
 | 
			
		||||
            last_client: None,
 | 
			
		||||
            tc_source_tx,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn poll<'sockets>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        sockets: &'sockets mut SocketSet,
 | 
			
		||||
        shared_pool: &mut SharedPool,
 | 
			
		||||
    ) {
 | 
			
		||||
        let socket = sockets.get_mut::<udp::Socket>(self.udp_handle);
 | 
			
		||||
        if !socket.is_open() {
 | 
			
		||||
            if let Err(e) = socket.bind(PORT) {
 | 
			
		||||
                defmt::warn!("binding UDP socket failed: {}", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        loop {
 | 
			
		||||
            match socket.recv() {
 | 
			
		||||
                Ok((data, client)) => {
 | 
			
		||||
                    match shared_pool.add(data) {
 | 
			
		||||
                        Ok(store_addr) => {
 | 
			
		||||
                            if let Err(e) = self.tc_source_tx.try_send(store_addr) {
 | 
			
		||||
                                defmt::warn!("TC source channel is full: {}", e);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(e) => {
 | 
			
		||||
                            defmt::warn!("could not add UDP packet to shared pool: {}", e);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    self.last_client = Some(client);
 | 
			
		||||
                    // TODO: Implement packet wiretapping.
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    udp::RecvError::Exhausted => {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    udp::RecvError::Truncated => {
 | 
			
		||||
                        defmt::warn!("UDP packet was truncacted");
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[app(device = stm32h7xx_hal::stm32, peripherals = true)]
 | 
			
		||||
mod app {
 | 
			
		||||
    use core::ptr::addr_of_mut;
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use rtic_monotonics::fugit::MillisDurationU32;
 | 
			
		||||
    use satrs::spacepackets::ecss::tc::PusTcReader;
 | 
			
		||||
    use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
 | 
			
		||||
    use stm32h7xx_hal::gpio::{Output, Pin};
 | 
			
		||||
    use stm32h7xx_hal::prelude::*;
 | 
			
		||||
    use stm32h7xx_hal::stm32::Interrupt;
 | 
			
		||||
 | 
			
		||||
    struct BlinkyLeds {
 | 
			
		||||
        led1: Pin<'B', 7, Output>,
 | 
			
		||||
        led2: Pin<'B', 14, Output>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[local]
 | 
			
		||||
    struct Local {
 | 
			
		||||
        leds: BlinkyLeds,
 | 
			
		||||
        link_led: Pin<'B', 0, Output>,
 | 
			
		||||
        net: Net,
 | 
			
		||||
        udp: UdpNet,
 | 
			
		||||
        tc_source_rx: TcSourceRx,
 | 
			
		||||
        phy: ethernet::phy::LAN8742A<EthernetMAC>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[shared]
 | 
			
		||||
    struct Shared {
 | 
			
		||||
        blink_freq: MillisDurationU32,
 | 
			
		||||
        eth_link_up: bool,
 | 
			
		||||
        sockets: SocketSet<'static>,
 | 
			
		||||
        shared_pool: SharedPool,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[init]
 | 
			
		||||
    fn init(mut cx: init::Context) -> (Shared, Local) {
 | 
			
		||||
        defmt::println!("Starting sat-rs demo application for the STM32H743ZIT");
 | 
			
		||||
 | 
			
		||||
        let pwr = cx.device.PWR.constrain();
 | 
			
		||||
        let pwrcfg = pwr.freeze();
 | 
			
		||||
 | 
			
		||||
        let rcc = cx.device.RCC.constrain();
 | 
			
		||||
        // Try to keep the clock configuration similar to one used in STM examples:
 | 
			
		||||
        // https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/NUCLEO-H743ZI/Examples/GPIO/GPIO_EXTI/Src/main.c
 | 
			
		||||
        let ccdr = rcc
 | 
			
		||||
            .sys_ck(400.MHz())
 | 
			
		||||
            .hclk(200.MHz())
 | 
			
		||||
            .use_hse(8.MHz())
 | 
			
		||||
            .bypass_hse()
 | 
			
		||||
            .pclk1(100.MHz())
 | 
			
		||||
            .pclk2(100.MHz())
 | 
			
		||||
            .pclk3(100.MHz())
 | 
			
		||||
            .pclk4(100.MHz())
 | 
			
		||||
            .freeze(pwrcfg, &cx.device.SYSCFG);
 | 
			
		||||
 | 
			
		||||
        // Initialize the systick interrupt & obtain the token to prove that we did
 | 
			
		||||
        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.
 | 
			
		||||
        cx.core.SCB.enable_icache();
 | 
			
		||||
        cx.core.DWT.enable_cycle_counter();
 | 
			
		||||
 | 
			
		||||
        let gpioa = cx.device.GPIOA.split(ccdr.peripheral.GPIOA);
 | 
			
		||||
        let gpiob = cx.device.GPIOB.split(ccdr.peripheral.GPIOB);
 | 
			
		||||
        let gpioc = cx.device.GPIOC.split(ccdr.peripheral.GPIOC);
 | 
			
		||||
        let gpiog = cx.device.GPIOG.split(ccdr.peripheral.GPIOG);
 | 
			
		||||
 | 
			
		||||
        let link_led = gpiob.pb0.into_push_pull_output();
 | 
			
		||||
        let mut led1 = gpiob.pb7.into_push_pull_output();
 | 
			
		||||
        let mut led2 = gpiob.pb14.into_push_pull_output();
 | 
			
		||||
 | 
			
		||||
        // Criss-cross pattern looks cooler.
 | 
			
		||||
        led1.set_high();
 | 
			
		||||
        led2.set_low();
 | 
			
		||||
        let leds = BlinkyLeds { led1, led2 };
 | 
			
		||||
 | 
			
		||||
        let rmii_ref_clk = gpioa.pa1.into_alternate::<11>();
 | 
			
		||||
        let rmii_mdio = gpioa.pa2.into_alternate::<11>();
 | 
			
		||||
        let rmii_mdc = gpioc.pc1.into_alternate::<11>();
 | 
			
		||||
        let rmii_crs_dv = gpioa.pa7.into_alternate::<11>();
 | 
			
		||||
        let rmii_rxd0 = gpioc.pc4.into_alternate::<11>();
 | 
			
		||||
        let rmii_rxd1 = gpioc.pc5.into_alternate::<11>();
 | 
			
		||||
        let rmii_tx_en = gpiog.pg11.into_alternate::<11>();
 | 
			
		||||
        let rmii_txd0 = gpiog.pg13.into_alternate::<11>();
 | 
			
		||||
        let rmii_txd1 = gpiob.pb13.into_alternate::<11>();
 | 
			
		||||
 | 
			
		||||
        let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS);
 | 
			
		||||
 | 
			
		||||
        /// Ethernet descriptor rings are a global singleton
 | 
			
		||||
        #[link_section = ".sram3.eth"]
 | 
			
		||||
        static mut DES_RING: MaybeUninit<ethernet::DesRing<4, 4>> = MaybeUninit::uninit();
 | 
			
		||||
 | 
			
		||||
        let (eth_dma, eth_mac) = ethernet::new(
 | 
			
		||||
            cx.device.ETHERNET_MAC,
 | 
			
		||||
            cx.device.ETHERNET_MTL,
 | 
			
		||||
            cx.device.ETHERNET_DMA,
 | 
			
		||||
            (
 | 
			
		||||
                rmii_ref_clk,
 | 
			
		||||
                rmii_mdio,
 | 
			
		||||
                rmii_mdc,
 | 
			
		||||
                rmii_crs_dv,
 | 
			
		||||
                rmii_rxd0,
 | 
			
		||||
                rmii_rxd1,
 | 
			
		||||
                rmii_tx_en,
 | 
			
		||||
                rmii_txd0,
 | 
			
		||||
                rmii_txd1,
 | 
			
		||||
            ),
 | 
			
		||||
            // SAFETY: We do not move the returned DMA struct across thread boundaries, so this
 | 
			
		||||
            // should be safe according to the docs.
 | 
			
		||||
            unsafe { DES_RING.assume_init_mut() },
 | 
			
		||||
            mac_addr,
 | 
			
		||||
            ccdr.peripheral.ETH1MAC,
 | 
			
		||||
            &ccdr.clocks,
 | 
			
		||||
        );
 | 
			
		||||
        // Initialise ethernet PHY...
 | 
			
		||||
        let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
 | 
			
		||||
        lan8742a.phy_reset();
 | 
			
		||||
        lan8742a.phy_init();
 | 
			
		||||
 | 
			
		||||
        unsafe {
 | 
			
		||||
            ethernet::enable_interrupt();
 | 
			
		||||
            cx.core.NVIC.set_priority(Interrupt::ETH, 196); // Mid prio
 | 
			
		||||
            cortex_m::peripheral::NVIC::unmask(Interrupt::ETH);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // unsafe: mutable reference to static storage, we only do this once
 | 
			
		||||
        let store = unsafe {
 | 
			
		||||
            let store_ptr = STORE.as_mut_ptr();
 | 
			
		||||
 | 
			
		||||
            // Initialise the socket_storage field. Using `write` instead of
 | 
			
		||||
            // assignment via `=` to not call `drop` on the old, uninitialised
 | 
			
		||||
            // value
 | 
			
		||||
            addr_of_mut!((*store_ptr).socket_storage).write([SocketStorage::EMPTY; 8]);
 | 
			
		||||
 | 
			
		||||
            // Now that all fields are initialised we can safely use
 | 
			
		||||
            // assume_init_mut to return a mutable reference to STORE
 | 
			
		||||
            STORE.assume_init_mut()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let (tc_source_tx, tc_source_rx) =
 | 
			
		||||
            rtic_sync::make_channel!(PoolAddr, TC_SOURCE_CHANNEL_DEPTH);
 | 
			
		||||
 | 
			
		||||
        let mut sockets = SocketSet::new(&mut store.socket_storage[..]);
 | 
			
		||||
        let net = Net::new(&mut sockets, eth_dma, mac_addr.into());
 | 
			
		||||
        let udp = UdpNet::new(&mut sockets, tc_source_tx);
 | 
			
		||||
 | 
			
		||||
        let mut shared_pool: SharedPool = StaticHeaplessMemoryPool::new(true);
 | 
			
		||||
        static_subpool!(
 | 
			
		||||
            SUBPOOL_SMALL,
 | 
			
		||||
            SUBPOOL_SMALL_SIZES,
 | 
			
		||||
            SUBPOOL_SMALL_NUM_BLOCKS as usize,
 | 
			
		||||
            SUBPOOL_SMALL_BLOCK_SIZE,
 | 
			
		||||
            link_section = ".axisram"
 | 
			
		||||
        );
 | 
			
		||||
        static_subpool!(
 | 
			
		||||
            SUBPOOL_MEDIUM,
 | 
			
		||||
            SUBPOOL_MEDIUM_SIZES,
 | 
			
		||||
            SUBPOOL_MEDIUM_NUM_BLOCKS as usize,
 | 
			
		||||
            SUBPOOL_MEDIUM_BLOCK_SIZE,
 | 
			
		||||
            link_section = ".axisram"
 | 
			
		||||
        );
 | 
			
		||||
        static_subpool!(
 | 
			
		||||
            SUBPOOL_LARGE,
 | 
			
		||||
            SUBPOOL_LARGE_SIZES,
 | 
			
		||||
            SUBPOOL_LARGE_NUM_BLOCKS as usize,
 | 
			
		||||
            SUBPOOL_LARGE_BLOCK_SIZE,
 | 
			
		||||
            link_section = ".axisram"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        shared_pool
 | 
			
		||||
            .grow(
 | 
			
		||||
                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(
 | 
			
		||||
                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(
 | 
			
		||||
                SUBPOOL_LARGE.get_mut().unwrap(),
 | 
			
		||||
                SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
 | 
			
		||||
                SUBPOOL_LARGE_NUM_BLOCKS,
 | 
			
		||||
                true,
 | 
			
		||||
            )
 | 
			
		||||
            .expect("growing heapless memory pool failed");
 | 
			
		||||
 | 
			
		||||
        // 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(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
 | 
			
		||||
 | 
			
		||||
        eth_link_check::spawn().expect("eth link check failed");
 | 
			
		||||
        blinky::spawn().expect("spawning blink task failed");
 | 
			
		||||
        udp_task::spawn().expect("spawning UDP task failed");
 | 
			
		||||
        tc_source_task::spawn().expect("spawning TC source task failed");
 | 
			
		||||
 | 
			
		||||
        (
 | 
			
		||||
            Shared {
 | 
			
		||||
                blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
 | 
			
		||||
                eth_link_up: false,
 | 
			
		||||
                sockets,
 | 
			
		||||
                shared_pool,
 | 
			
		||||
            },
 | 
			
		||||
            Local {
 | 
			
		||||
                link_led,
 | 
			
		||||
                leds,
 | 
			
		||||
                net,
 | 
			
		||||
                udp,
 | 
			
		||||
                tc_source_rx,
 | 
			
		||||
                phy: lan8742a,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(local = [leds], shared=[blink_freq])]
 | 
			
		||||
    async fn blinky(mut cx: blinky::Context) {
 | 
			
		||||
        let leds = cx.local.leds;
 | 
			
		||||
        loop {
 | 
			
		||||
            leds.led1.toggle();
 | 
			
		||||
            leds.led2.toggle();
 | 
			
		||||
            let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
 | 
			
		||||
            Mono::delay(current_blink_freq).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// This task checks for the network link.
 | 
			
		||||
    #[task(local=[link_led, phy], shared=[eth_link_up])]
 | 
			
		||||
    async fn eth_link_check(mut cx: eth_link_check::Context) {
 | 
			
		||||
        let phy = cx.local.phy;
 | 
			
		||||
        let link_led = cx.local.link_led;
 | 
			
		||||
        loop {
 | 
			
		||||
            let link_was_up = cx.shared.eth_link_up.lock(|link_up| *link_up);
 | 
			
		||||
            if phy.poll_link() {
 | 
			
		||||
                if !link_was_up {
 | 
			
		||||
                    link_led.set_high();
 | 
			
		||||
                    cx.shared.eth_link_up.lock(|link_up| *link_up = true);
 | 
			
		||||
                    defmt::info!("Ethernet link up");
 | 
			
		||||
                }
 | 
			
		||||
            } else if link_was_up {
 | 
			
		||||
                link_led.set_low();
 | 
			
		||||
                cx.shared.eth_link_up.lock(|link_up| *link_up = false);
 | 
			
		||||
                defmt::info!("Ethernet link down");
 | 
			
		||||
            }
 | 
			
		||||
            Mono::delay(100.millis()).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(binds=ETH, local=[net], shared=[sockets])]
 | 
			
		||||
    fn eth_isr(mut cx: eth_isr::Context) {
 | 
			
		||||
        // SAFETY: We do not write the register mentioned inside the docs anywhere else.
 | 
			
		||||
        unsafe {
 | 
			
		||||
            ethernet::interrupt_handler();
 | 
			
		||||
        }
 | 
			
		||||
        // Check and process ETH frames and DHCP. UDP is checked in a different task.
 | 
			
		||||
        cx.shared.sockets.lock(|sockets| {
 | 
			
		||||
            cx.local.net.poll(sockets);
 | 
			
		||||
            cx.local.net.poll_dhcp(sockets);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// This task routes UDP packets.
 | 
			
		||||
    #[task(local=[udp], shared=[sockets, shared_pool])]
 | 
			
		||||
    async fn udp_task(mut cx: udp_task::Context) {
 | 
			
		||||
        loop {
 | 
			
		||||
            cx.shared.sockets.lock(|sockets| {
 | 
			
		||||
                cx.shared.shared_pool.lock(|pool| {
 | 
			
		||||
                    cx.local.udp.poll(sockets, pool);
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
            Mono::delay(40.millis()).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// This task handles all the incoming telecommands.
 | 
			
		||||
    #[task(local=[read_buf: [u8; 1024] = [0; 1024], tc_source_rx], shared=[shared_pool])]
 | 
			
		||||
    async fn tc_source_task(mut cx: tc_source_task::Context) {
 | 
			
		||||
        loop {
 | 
			
		||||
            let recv_result = cx.local.tc_source_rx.recv().await;
 | 
			
		||||
            match recv_result {
 | 
			
		||||
                Ok(pool_addr) => {
 | 
			
		||||
                    cx.shared.shared_pool.lock(|pool| {
 | 
			
		||||
                        match pool.read(&pool_addr, cx.local.read_buf.as_mut()) {
 | 
			
		||||
                            Ok(packet_len) => {
 | 
			
		||||
                                defmt::info!("received {} bytes in the TC source task", packet_len);
 | 
			
		||||
                                match PusTcReader::new(&cx.local.read_buf[0..packet_len]) {
 | 
			
		||||
                                    Ok((packet, _tc_len)) => {
 | 
			
		||||
                                        // TODO: Handle packet here or dispatch to dedicated PUS
 | 
			
		||||
                                        // handler? Dispatching could simplify some things and make
 | 
			
		||||
                                        // the software more scalable..
 | 
			
		||||
                                        defmt::info!("received PUS packet: {}", packet);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    Err(e) => {
 | 
			
		||||
                                        defmt::info!("invalid TC format, not a PUS packet: {}", e);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                if let Err(e) = pool.delete(pool_addr) {
 | 
			
		||||
                                    defmt::warn!("deleting TC data failed: {}", e);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            Err(e) => {
 | 
			
		||||
                                defmt::warn!("TC packet read failed: {}", e);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    defmt::warn!("TC source reception error: {}", e);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								embedded-examples/stm32h7-nucleo-rtic/tests/integration.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								embedded-examples/stm32h7-nucleo-rtic/tests/integration.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
 | 
			
		||||
use stm32h7_testapp as _; // memory layout + panic handler
 | 
			
		||||
 | 
			
		||||
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
 | 
			
		||||
// feature)
 | 
			
		||||
#[defmt_test::tests]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use defmt::assert;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn it_works() {
 | 
			
		||||
        assert!(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
/settings.json
 | 
			
		||||
/.cortex-debug.*
 | 
			
		||||
							
								
								
									
										12
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/extensions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/extensions.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
	// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
 | 
			
		||||
	// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
 | 
			
		||||
 | 
			
		||||
	// List of extensions which should be recommended for users of this workspace.
 | 
			
		||||
	"recommendations": [
 | 
			
		||||
		"rust-lang.rust",
 | 
			
		||||
		"probe-rs.probe-rs-debugger"
 | 
			
		||||
	],
 | 
			
		||||
	// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
 | 
			
		||||
	"unwantedRecommendations": []
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/launch.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/launch.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": "0.2.0",
 | 
			
		||||
  "configurations": [
 | 
			
		||||
    {
 | 
			
		||||
      "preLaunchTask": "${defaultBuildTask}",
 | 
			
		||||
      "type": "probe-rs-debug",
 | 
			
		||||
      "request": "launch",
 | 
			
		||||
      "name": "probe-rs Debugging ",
 | 
			
		||||
      "flashingConfig": {
 | 
			
		||||
        "flashingEnabled": true
 | 
			
		||||
      },
 | 
			
		||||
      "chip": "STM32H743ZITx",
 | 
			
		||||
      "coreConfigs": [
 | 
			
		||||
        {
 | 
			
		||||
          "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32h7-nucleo-rtic",
 | 
			
		||||
          "rttEnabled": true,
 | 
			
		||||
          "svdFile": "STM32H743.svd"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/tasks.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								embedded-examples/stm32h7-nucleo-rtic/vscode/tasks.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
    // See https://go.microsoft.com/fwlink/?LinkId=733558 
 | 
			
		||||
    // for the documentation about the tasks.json format
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "cargo build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "~/.cargo/bin/cargo", // note: full path to the cargo
 | 
			
		||||
            "args": [
 | 
			
		||||
                "build"
 | 
			
		||||
            ],
 | 
			
		||||
            "group": {
 | 
			
		||||
                "kind": "build",
 | 
			
		||||
                "isDefault": true
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								experiments/satrs-gen/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								experiments/satrs-gen/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "satrs-gen"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2024"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
toml = "0.8"
 | 
			
		||||
heck = "0.5"
 | 
			
		||||
							
								
								
									
										34
									
								
								experiments/satrs-gen/components.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								experiments/satrs-gen/components.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
[apid]
 | 
			
		||||
Sched = 1
 | 
			
		||||
GenericPus = 2
 | 
			
		||||
Acs = 3
 | 
			
		||||
Cfdp = 4
 | 
			
		||||
Tmtc = 5
 | 
			
		||||
Eps = 6
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[ids]
 | 
			
		||||
[ids.Eps]
 | 
			
		||||
Pcdu = 0
 | 
			
		||||
Subsystem = 1
 | 
			
		||||
 | 
			
		||||
[ids.Tmtc]
 | 
			
		||||
UdpServer = 0
 | 
			
		||||
TcpServer = 1
 | 
			
		||||
 | 
			
		||||
[ids.GenericPus]
 | 
			
		||||
PusEventManagement = 0
 | 
			
		||||
PusRouting = 1
 | 
			
		||||
PusTest = 2
 | 
			
		||||
PusAction = 3
 | 
			
		||||
PusMode = 4
 | 
			
		||||
PusHk = 5
 | 
			
		||||
 | 
			
		||||
[ids.Sched]
 | 
			
		||||
PusSched = 0
 | 
			
		||||
 | 
			
		||||
[ids.Acs]
 | 
			
		||||
Subsystem = 1
 | 
			
		||||
Assembly = 2
 | 
			
		||||
Mgm0 = 3
 | 
			
		||||
Mgm1 = 4
 | 
			
		||||
							
								
								
									
										91
									
								
								experiments/satrs-gen/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								experiments/satrs-gen/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
use heck::{ToShoutySnakeCase, ToSnakeCase};
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::BTreeMap,
 | 
			
		||||
    fs::{self, File},
 | 
			
		||||
    io::{self, Write},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use toml::{Value, map::Map};
 | 
			
		||||
 | 
			
		||||
fn main() -> io::Result<()> {
 | 
			
		||||
    // Read the configuration file
 | 
			
		||||
    let config_str = fs::read_to_string("components.toml").expect("Unable to read file");
 | 
			
		||||
    let config: Value = toml::from_str(&config_str).expect("Unable to parse TOML");
 | 
			
		||||
 | 
			
		||||
    let mut output = File::create("../satrs-example/src/ids.rs")?;
 | 
			
		||||
 | 
			
		||||
    generate_rust_code(&config, &mut output);
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sort_enum_table(table_map: &Map<String, Value>) -> BTreeMap<u64, &str> {
 | 
			
		||||
    // Collect entries into a BTreeMap to sort them by key
 | 
			
		||||
    let mut sorted_entries: BTreeMap<u64, &str> = BTreeMap::new();
 | 
			
		||||
 | 
			
		||||
    for (key, value) in table_map {
 | 
			
		||||
        if let Some(value) = value.as_integer() {
 | 
			
		||||
            if !(0..=0x7FF).contains(&value) {
 | 
			
		||||
                panic!("Invalid APID value: {}", value);
 | 
			
		||||
            }
 | 
			
		||||
            sorted_entries.insert(value as u64, key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    sorted_entries
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn generate_rust_code(config: &Value, writer: &mut impl Write) {
 | 
			
		||||
    writeln!(
 | 
			
		||||
        writer,
 | 
			
		||||
        "//! This is an auto-generated configuration module."
 | 
			
		||||
    )
 | 
			
		||||
    .unwrap();
 | 
			
		||||
    writeln!(writer, "use satrs::request::UniqueApidTargetId;").unwrap();
 | 
			
		||||
    writeln!(writer).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Generate the main module
 | 
			
		||||
    writeln!(
 | 
			
		||||
        writer,
 | 
			
		||||
        "#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::EnumIter)]"
 | 
			
		||||
    )
 | 
			
		||||
    .unwrap();
 | 
			
		||||
    writeln!(writer, "pub enum Apid {{").unwrap();
 | 
			
		||||
 | 
			
		||||
    // Generate constants for the main module
 | 
			
		||||
    if let Some(apid_table) = config.get("apid").and_then(Value::as_table) {
 | 
			
		||||
        let sorted_entries = sort_enum_table(apid_table);
 | 
			
		||||
        // Write the sorted entries to the writer
 | 
			
		||||
        for (value, key) in sorted_entries {
 | 
			
		||||
            writeln!(writer, "    {} = {},", key, value).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    writeln!(writer, "}}").unwrap();
 | 
			
		||||
 | 
			
		||||
    // Generate ID tables.
 | 
			
		||||
    if let Some(id_tables) = config.get("ids").and_then(Value::as_table) {
 | 
			
		||||
        for (mod_name, table) in id_tables {
 | 
			
		||||
            let mod_name_as_snake = mod_name.to_snake_case();
 | 
			
		||||
            writeln!(writer).unwrap();
 | 
			
		||||
            writeln!(writer, "pub mod {} {{", mod_name_as_snake).unwrap();
 | 
			
		||||
            let sorted_entries = sort_enum_table(table.as_table().unwrap());
 | 
			
		||||
            writeln!(writer, "    #[derive(Debug, Copy, Clone, PartialEq, Eq)]").unwrap();
 | 
			
		||||
            writeln!(writer, "    pub enum Id {{").unwrap();
 | 
			
		||||
            // Write the sorted entries to the writer
 | 
			
		||||
            for (value, key) in &sorted_entries {
 | 
			
		||||
                writeln!(writer, "        {} = {},", key, value).unwrap();
 | 
			
		||||
            }
 | 
			
		||||
            writeln!(writer, "    }}").unwrap();
 | 
			
		||||
            writeln!(writer).unwrap();
 | 
			
		||||
 | 
			
		||||
            for (_value, key) in sorted_entries {
 | 
			
		||||
                let key_shouting = key.to_shouty_snake_case();
 | 
			
		||||
                writeln!(
 | 
			
		||||
                    writer,
 | 
			
		||||
                    "    pub const {}: super::UniqueApidTargetId = super::UniqueApidTargetId::new(super::Apid::{} as u16, Id::{} as u32);",
 | 
			
		||||
                key_shouting, mod_name, key
 | 
			
		||||
                ).unwrap();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            writeln!(writer, "}}").unwrap();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										260
									
								
								images/minisim-arch/minisim-arch.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								images/minisim-arch/minisim-arch.graphml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.23.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0" xml:space="preserve"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="360.0" width="479.0" x="771.3047672479152" y="458.0"/>
 | 
			
		||||
          <y:Fill hasColor="false" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="237.5" y="178.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="177.64799999999997" width="200.75199999999973" x="1037.5527672479152" y="470.15200000000027"/>
 | 
			
		||||
          <y:Fill hasColor="false" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.919921875" x="13.264464667588754" xml:space="preserve" y="8.302185845943427">Simulation<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.433926114471642" nodeRatioY="-0.45326608886143704" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="84.39999999999986" x="1068.8351781652768" y="508.2800000000002"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.638671875" x="23.380664062499818" xml:space="preserve" y="8.015625">PCDU<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="550.4800000000001"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.453125" x="13.973437499999818" xml:space="preserve" y="8.015625">Magnetometer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="1068.8351781652768" y="594.9000000000001"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="88.83203125" x="15.783984374999818" xml:space="preserve" y="8.015625">Magnetorquer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="783.4063563305535" y="545.2800000000002"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="85.931640625" x="17.234179687499932" xml:space="preserve" y="8.015625">SimController<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="840.5407126611072" y="677.8000000000002"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.05078125" x="7.674609374999932" xml:space="preserve" y="8.015625">UDP TC Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="1005.2814253222144" y="677.8000000000002"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.111328125" x="11.644335937499932" xml:space="preserve" y="8.015625">UDP TM Sender<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="34.0" width="120.39999999999986" x="931.6174253222144" y="775.5920000000002"/>
 | 
			
		||||
          <y:Fill color="#FFCC00" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.740234375" x="40.82988281249993" xml:space="preserve" y="8.015625">Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n5" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-5.199999999999932"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n5" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="60.19999999999993" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="1023.8695890826383" y="562.2800000000002"/>
 | 
			
		||||
            <y:Point x="1023.8695890826383" y="525.2800000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n5" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="48.72964366944643" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="1023.8695890826383" y="562.2800000000002"/>
 | 
			
		||||
            <y:Point x="1023.8695890826383" y="611.9000000000001"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.955078125" x="12.686124396959713" xml:space="preserve" y="-22.50440429687478">schedule_event<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="13.519999999999978" distanceToCenter="true" position="left" ratio="0.11621274698385183" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n6" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-5.329643669446341" ty="0.0">
 | 
			
		||||
            <y:Point x="900.7407126611072" y="628.5400000000002"/>
 | 
			
		||||
            <y:Point x="838.2767126611071" y="628.5400000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.923828125" x="-87.89792405764274" xml:space="preserve" y="-40.606550292968564">SimRequest<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="49.935999999999936" distanceToCenter="true" position="left" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n4" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="60.200000000000045" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="611.9000000000001"/>
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="694.8000000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n3" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="567.4800000000001"/>
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="694.8000000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n2" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="11.514125426161627" sy="-2.5781798912005343" tx="45.553752843062284" ty="0.0">
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="522.7018201087997"/>
 | 
			
		||||
            <y:Point x="1223.8814253222142" y="694.8000000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="60.4140625" x="-2.4087265765670054" xml:space="preserve" y="145.1356018470808">SimReply<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.97817989120062" distanceToCenter="true" position="right" ratio="0.679561684469248" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n8" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-25.212712661107275" sy="0.0" tx="-11.264000000000124" ty="0.0">
 | 
			
		||||
            <y:Point x="966.6047126611071" y="731.8000000000002"/>
 | 
			
		||||
            <y:Point x="889.4767126611071" y="731.8000000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="119.751953125" x="-132.27600022951788" xml:space="preserve" y="-32.03587548828091">SimRequest in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="20.73181250000017" distanceToCenter="true" position="left" ratio="0.9386993050513424" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n7" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="29.18399999999997" sy="0.0" tx="24.28800000000001" ty="0.0">
 | 
			
		||||
            <y:Point x="1094.6654253222143" y="731.8000000000002"/>
 | 
			
		||||
            <y:Point x="1016.1054253222144" y="731.8000000000002"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="104.2421875" x="-62.15307370122309" xml:space="preserve" y="34.80927001953137">SimReply in UDP<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="23.81218750000005" distanceToCenter="true" position="left" ratio="0.12769857433808468" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n5" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="23.921741802203996" sy="-3.0501798912007416" tx="0.0" ty="-56.27417989120056">
 | 
			
		||||
            <y:Point x="867.5280981327575" y="502.70182010879967"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.95703125" x="73.38950633588263" xml:space="preserve" y="-62.699758016200235">step<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="11.126187499999986" distanceToCenter="true" position="left" ratio="0.5889387894625147" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								images/minisim-arch/minisim-arch.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								images/minisim-arch/minisim-arch.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 98 KiB  | 
@@ -166,7 +166,7 @@ Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
 | 
			
		||||
                  <y:Geometry height="30.0" width="125.0" x="1151.9280499999995" y="281.84403125000006"/>
 | 
			
		||||
                  <y:Fill color="#CCFFFF" transparent="false"/>
 | 
			
		||||
                  <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.255859375" x="24.3720703125" xml:space="preserve" y="4.8515625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.837890625" x="33.0810546875" xml:space="preserve" y="4.8515625">TM Sink<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
                  <y:Shape type="rectangle"/>
 | 
			
		||||
                </y:ShapeNode>
 | 
			
		||||
              </data>
 | 
			
		||||
@@ -260,7 +260,7 @@ Mode Tree<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:
 | 
			
		||||
          <y:Geometry height="57.265600000000006" width="631.1152" x="810.8847999999999" y="411.39428125"/>
 | 
			
		||||
          <y:Fill hasColor="false" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="261.8125" x="166.89412267941418" xml:space="preserve" y="3.144146301369915">satrs-satellite
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="261.8125" x="166.89412267941418" xml:space="preserve" y="3.144146301369915">satrs-minisim
 | 
			
		||||
Simulator based on asynchronix<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
@@ -272,7 +272,7 @@ Simulator based on asynchronix<y:LabelModel><y:SmartNodeLabelModel distance="4.0
 | 
			
		||||
          <y:Geometry height="50.0" width="631.1152000000002" x="810.8847999999998" y="476.2958625000002"/>
 | 
			
		||||
          <y:Fill hasColor="false" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="374.8359375" x="110.3824039294143" xml:space="preserve" y="0.12842465753431043">satrs-tmtc
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="374.8359375" x="110.3824039294143" xml:space="preserve" y="0.12842465753431043">pytmtc
 | 
			
		||||
Command-line interface based TMTC handling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
 | 
			
		||||
          <y:Shape type="rectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								justfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								justfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
all: check embedded test fmt clippy docs
 | 
			
		||||
 | 
			
		||||
check:
 | 
			
		||||
  cargo check
 | 
			
		||||
  cargo check -p satrs-example --no-default-features
 | 
			
		||||
 | 
			
		||||
test:
 | 
			
		||||
  cargo nextest run --all-features
 | 
			
		||||
  cargo test --doc --all-features
 | 
			
		||||
 | 
			
		||||
embedded:
 | 
			
		||||
  cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
  cargo fmt --all -- --check
 | 
			
		||||
 | 
			
		||||
clippy:
 | 
			
		||||
  cargo clippy -- -D warnings
 | 
			
		||||
 | 
			
		||||
docs-satrs:
 | 
			
		||||
  RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p satrs --all-features
 | 
			
		||||
 | 
			
		||||
docs: docs-satrs
 | 
			
		||||
@@ -3,20 +3,20 @@
 | 
			
		||||
Software for space systems oftentimes has different requirements than the software for host
 | 
			
		||||
systems or servers. Currently, most space systems are considered embedded systems.
 | 
			
		||||
 | 
			
		||||
For these systems, the computation power and the available heap are the most important resources
 | 
			
		||||
which are constrained. This might make completeley heap based memory management schemes which
 | 
			
		||||
For these systems, the computation power and the available heap are important resources
 | 
			
		||||
which are also constrained. This might make completeley heap based memory management schemes which
 | 
			
		||||
are oftentimes used on host and server based systems unfeasable. Still, completely forbidding
 | 
			
		||||
heap allocations might make software development unnecessarilly difficult, especially in a
 | 
			
		||||
time where the OBSW might be running on Linux based systems with hundreds of MBs of RAM.
 | 
			
		||||
 | 
			
		||||
A useful pattern used commonly in space systems is to limit heap allocations to program
 | 
			
		||||
A useful pattern commonly used in space systems is to limit heap allocations to program
 | 
			
		||||
initialization time and avoid frequent run-time allocations. This prevents issues like
 | 
			
		||||
running out of memory (something even Rust can not protect from) or heap fragmentation on systems
 | 
			
		||||
without a MMU.
 | 
			
		||||
 | 
			
		||||
# Using pre-allocated pool structures
 | 
			
		||||
 | 
			
		||||
A huge candidate for heap allocations is the TMTC and  handling. TC, TMs and IPC data are all
 | 
			
		||||
A candidate for heap allocations is the TMTC and  handling. TC, TMs and IPC data are all
 | 
			
		||||
candidates where the data size might vary greatly. The regular solution for host systems
 | 
			
		||||
might be to send around this data as a `Vec<u8>` until it is dropped. `sat-rs` provides
 | 
			
		||||
another solution to avoid run-time allocations by offering pre-allocated static
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,24 @@
 | 
			
		||||
# Events
 | 
			
		||||
 | 
			
		||||
Events can be an extremely important mechanism used for remote systems to monitor unexpected
 | 
			
		||||
or expected anomalies and events occuring on these systems. They are oftentimes tied to
 | 
			
		||||
Events are an important mechanism used for remote systems to monitor unexpected
 | 
			
		||||
or expected anomalies and events occuring on these systems. 
 | 
			
		||||
 | 
			
		||||
One common use case for events on remote systems is to offer a light-weight publish-subscribe
 | 
			
		||||
mechanism and IPC mechanism for software and hardware events which are also packaged as telemetry
 | 
			
		||||
(TM) or can trigger a system response. They can also be tied to
 | 
			
		||||
Fault Detection, Isolation and Recovery (FDIR) operations, which need to happen autonomously.
 | 
			
		||||
 | 
			
		||||
Events can also be used as a convenient Inter-Process Communication (IPC) mechansism, which is
 | 
			
		||||
also observable for the Ground segment. The PUS Service 5 standardizes how the ground interface
 | 
			
		||||
for events might look like, but does not specify how other software components might react
 | 
			
		||||
to those events. There is the PUS Service 19, which might be used for that purpose, but the
 | 
			
		||||
event components recommended by this framework do not really need this service.
 | 
			
		||||
The PUS Service 5 standardizes how the ground interface for events might look like, but does not
 | 
			
		||||
specify how other software components might react to those events. There is the PUS Service 19,
 | 
			
		||||
which might be used for that purpose, but the event components recommended by this framework do not
 | 
			
		||||
rely on the present of this service.
 | 
			
		||||
 | 
			
		||||
The following images shows how the flow of events could look like in a system where components
 | 
			
		||||
can generate events, and where other system components might be interested in those events:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
For the concrete implementation of your own event management and/or event routing system, you
 | 
			
		||||
can have a look at the event management documentation inside the
 | 
			
		||||
[API documentation](https://docs.rs/satrs/latest/satrs/event_man/index.html) where you can also
 | 
			
		||||
find references to all examples.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,8 @@ This book is the primary information resource for the [sat-rs library](https://e
 | 
			
		||||
in addition to the regular API documentation. It contains the following resources:
 | 
			
		||||
 | 
			
		||||
1. Architecture informations and consideration which would exceeds the scope of the regular API.
 | 
			
		||||
2. General information on how to build On-Board Software and how `sat-rs` can help to fulfill
 | 
			
		||||
2. General information on how to build on-board Software and how `sat-rs` can help to fulfill
 | 
			
		||||
   the unique requirements of writing software for remote systems.
 | 
			
		||||
2. A Getting-Started workshop where a small On-Board Software is built from scratch using
 | 
			
		||||
   sat-rs components.
 | 
			
		||||
 | 
			
		||||
# Introduction
 | 
			
		||||
 | 
			
		||||
@@ -31,4 +29,21 @@ and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-
 | 
			
		||||
The [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
 | 
			
		||||
provides various practical usage examples of the `sat-rs` framework. If you are more interested in
 | 
			
		||||
the practical application of `sat-rs` inside an application, it is recommended to have a look at
 | 
			
		||||
the example application.
 | 
			
		||||
the example application. The [`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim)
 | 
			
		||||
applicatin complements the example application and can be used to simulate some physical devices
 | 
			
		||||
for the `satrs-example` device handlers.
 | 
			
		||||
 | 
			
		||||
# Flight Heritage
 | 
			
		||||
 | 
			
		||||
There is an active and continuous effort to get early flight heritage for the sat-rs library.
 | 
			
		||||
Currently this library has the following flight heritage:
 | 
			
		||||
 | 
			
		||||
- Submission as an [OPS-SAT experiment](https://www.esa.int/Enabling_Support/Operations/OPS-SAT)
 | 
			
		||||
  which has also
 | 
			
		||||
  [flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
 | 
			
		||||
  The application is strongly based on the sat-rs example application. You can find the repository
 | 
			
		||||
  of the experiment [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs).
 | 
			
		||||
- Development and use of a sat-rs-based [demonstration on-board software](https://egit.irs.uni-stuttgart.de/rust/eurosim-obsw)
 | 
			
		||||
  alongside a Flight System Simulator in the context of a
 | 
			
		||||
  [Bachelors Thesis](https://www.researchgate.net/publication/380785984_Design_and_Development_of_a_Hardware-in-the-Loop_EuroSim_Demonstrator)
 | 
			
		||||
  at [Airbus Netherlands](https://www.airbusdefenceandspacenetherlands.nl/).
 | 
			
		||||
 
 | 
			
		||||
@@ -7,3 +7,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 | 
			
		||||
and this project adheres to [Semantic Versioning](http://semver.org/).
 | 
			
		||||
 | 
			
		||||
# [unreleased]
 | 
			
		||||
 | 
			
		||||
# [v0.1.1] 2024-02-21
 | 
			
		||||
 | 
			
		||||
satrs v0.2.0-rc.0
 | 
			
		||||
satrs-mib v0.1.1
 | 
			
		||||
 | 
			
		||||
# [v0.1.0] 2024-02-13
 | 
			
		||||
 | 
			
		||||
satrs v0.1.1
 | 
			
		||||
satrs-mib v0.1.0
 | 
			
		||||
 
 | 
			
		||||
@@ -8,18 +8,21 @@ homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
 | 
			
		||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
fern = "0.6"
 | 
			
		||||
fern = "0.7"
 | 
			
		||||
chrono = "0.4"
 | 
			
		||||
log = "0.4"
 | 
			
		||||
crossbeam-channel = "0.5"
 | 
			
		||||
delegate = "0.10"
 | 
			
		||||
zerocopy = "0.6"
 | 
			
		||||
delegate = "0.13"
 | 
			
		||||
zerocopy = "0.8"
 | 
			
		||||
csv = "1"
 | 
			
		||||
num_enum = "0.7"
 | 
			
		||||
thiserror = "1"
 | 
			
		||||
thiserror = "2"
 | 
			
		||||
lazy_static = "1"
 | 
			
		||||
strum = { version = "0.26", features = ["derive"] }
 | 
			
		||||
derive-new = "0.5"
 | 
			
		||||
strum = { version = "0.27", features = ["derive"] }
 | 
			
		||||
derive-new = "0.7"
 | 
			
		||||
cfg-if = "1"
 | 
			
		||||
arbitrary-int = "2"
 | 
			
		||||
bitbybit = "1.4"
 | 
			
		||||
serde = { version = "1", features = ["derive"] }
 | 
			
		||||
serde_json = "1"
 | 
			
		||||
 | 
			
		||||
@@ -27,13 +30,16 @@ serde_json = "1"
 | 
			
		||||
path = "../satrs"
 | 
			
		||||
features = ["test_util"]
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs-minisim]
 | 
			
		||||
path = "../satrs-minisim"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs-mib]
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
path = "../satrs-mib"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
dyn_tmtc = []
 | 
			
		||||
default = ["dyn_tmtc"]
 | 
			
		||||
default = ["heap_tmtc"]
 | 
			
		||||
heap_tmtc = []
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
env_logger = "0.11"
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ You can run the application using `cargo run`.
 | 
			
		||||
 | 
			
		||||
# Features
 | 
			
		||||
 | 
			
		||||
The example has the `dyn_tmtc` feature which is enabled by default. With this feature enabled,
 | 
			
		||||
The example has the `heap_tmtc` feature which is enabled by default. With this feature enabled,
 | 
			
		||||
TMTC packets are exchanged using the heap as the backing memory instead of pre-allocated static
 | 
			
		||||
stores.
 | 
			
		||||
 | 
			
		||||
@@ -48,16 +48,17 @@ It is recommended to use a virtual environment to do this. To set up one in the
 | 
			
		||||
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
 | 
			
		||||
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
 | 
			
		||||
on how to activate the environment and then use the following command to install the required
 | 
			
		||||
dependency:
 | 
			
		||||
dependency interactively:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
pip install -r requirements.txt
 | 
			
		||||
pip install -e .
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Alternatively, if you would like to use the GUI functionality provided by `tmtccmd`, you can also
 | 
			
		||||
install it manually with
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
pip install -e .
 | 
			
		||||
pip install tmtccmd[gui]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -72,3 +73,22 @@ the `simpleclient`:
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can also simply call the script without any arguments to view the command tree.
 | 
			
		||||
 | 
			
		||||
## Adding the mini simulator application
 | 
			
		||||
 | 
			
		||||
This example application features a few device handlers. The
 | 
			
		||||
[`satrs-minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-minisim)
 | 
			
		||||
can be used to simulate the physical devices managed by these device handlers.
 | 
			
		||||
 | 
			
		||||
The example application will attempt communication with the mini simulator on UDP port 7303.
 | 
			
		||||
If this works, the device handlers will use communication interfaces dedicated to the communication
 | 
			
		||||
with the mini simulator. Otherwise, they will be replaced by dummy interfaces which either
 | 
			
		||||
return constant values or behave like ideal devices.
 | 
			
		||||
 | 
			
		||||
In summary, you can use the following command command to run the mini-simulator first:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
cargo run -p satrs-minisim
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
and then start the example using `cargo run -p satrs-example`.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								satrs-example/pytmtc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								satrs-example/pytmtc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
/tmtc_conf.json
 | 
			
		||||
__pycache__
 | 
			
		||||
 | 
			
		||||
/venv
 | 
			
		||||
/log
 | 
			
		||||
/.idea/*
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
 | 
			
		||||
/seqcnt.txt
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
 | 
			
		||||
# Byte-compiled / optimized / DLL files
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
*$py.class
 | 
			
		||||
 | 
			
		||||
# C extensions
 | 
			
		||||
*.so
 | 
			
		||||
 | 
			
		||||
# Distribution / packaging
 | 
			
		||||
.Python
 | 
			
		||||
build/
 | 
			
		||||
develop-eggs/
 | 
			
		||||
dist/
 | 
			
		||||
downloads/
 | 
			
		||||
eggs/
 | 
			
		||||
.eggs/
 | 
			
		||||
lib/
 | 
			
		||||
lib64/
 | 
			
		||||
parts/
 | 
			
		||||
sdist/
 | 
			
		||||
var/
 | 
			
		||||
wheels/
 | 
			
		||||
pip-wheel-metadata/
 | 
			
		||||
share/python-wheels/
 | 
			
		||||
*.egg-info/
 | 
			
		||||
.installed.cfg
 | 
			
		||||
*.egg
 | 
			
		||||
MANIFEST
 | 
			
		||||
 | 
			
		||||
# PyInstaller
 | 
			
		||||
#  Usually these files are written by a python script from a template
 | 
			
		||||
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
 | 
			
		||||
*.manifest
 | 
			
		||||
*.spec
 | 
			
		||||
 | 
			
		||||
# Installer logs
 | 
			
		||||
pip-log.txt
 | 
			
		||||
pip-delete-this-directory.txt
 | 
			
		||||
 | 
			
		||||
# Unit test / coverage reports
 | 
			
		||||
htmlcov/
 | 
			
		||||
.tox/
 | 
			
		||||
.nox/
 | 
			
		||||
.coverage
 | 
			
		||||
.coverage.*
 | 
			
		||||
.cache
 | 
			
		||||
nosetests.xml
 | 
			
		||||
coverage.xml
 | 
			
		||||
*.cover
 | 
			
		||||
*.py,cover
 | 
			
		||||
.hypothesis/
 | 
			
		||||
.pytest_cache/
 | 
			
		||||
 | 
			
		||||
# Translations
 | 
			
		||||
*.mo
 | 
			
		||||
*.pot
 | 
			
		||||
 | 
			
		||||
# Django stuff:
 | 
			
		||||
*.log
 | 
			
		||||
local_settings.py
 | 
			
		||||
db.sqlite3
 | 
			
		||||
db.sqlite3-journal
 | 
			
		||||
 | 
			
		||||
# Flask stuff:
 | 
			
		||||
instance/
 | 
			
		||||
.webassets-cache
 | 
			
		||||
 | 
			
		||||
# Scrapy stuff:
 | 
			
		||||
.scrapy
 | 
			
		||||
 | 
			
		||||
# Sphinx documentation
 | 
			
		||||
docs/_build/
 | 
			
		||||
 | 
			
		||||
# PyBuilder
 | 
			
		||||
target/
 | 
			
		||||
 | 
			
		||||
# Jupyter Notebook
 | 
			
		||||
.ipynb_checkpoints
 | 
			
		||||
 | 
			
		||||
# IPython
 | 
			
		||||
profile_default/
 | 
			
		||||
ipython_config.py
 | 
			
		||||
 | 
			
		||||
# pyenv
 | 
			
		||||
.python-version
 | 
			
		||||
 | 
			
		||||
# pipenv
 | 
			
		||||
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 | 
			
		||||
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
 | 
			
		||||
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
 | 
			
		||||
#   install all needed dependencies.
 | 
			
		||||
#Pipfile.lock
 | 
			
		||||
 | 
			
		||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
 | 
			
		||||
__pypackages__/
 | 
			
		||||
 | 
			
		||||
# Celery stuff
 | 
			
		||||
celerybeat-schedule
 | 
			
		||||
celerybeat.pid
 | 
			
		||||
 | 
			
		||||
# SageMath parsed files
 | 
			
		||||
*.sage.py
 | 
			
		||||
 | 
			
		||||
# Environments
 | 
			
		||||
.env
 | 
			
		||||
.venv
 | 
			
		||||
env/
 | 
			
		||||
venv/
 | 
			
		||||
ENV/
 | 
			
		||||
env.bak/
 | 
			
		||||
venv.bak/
 | 
			
		||||
 | 
			
		||||
# Spyder project settings
 | 
			
		||||
.spyderproject
 | 
			
		||||
.spyproject
 | 
			
		||||
 | 
			
		||||
# Rope project settings
 | 
			
		||||
.ropeproject
 | 
			
		||||
 | 
			
		||||
# mkdocs documentation
 | 
			
		||||
/site
 | 
			
		||||
 | 
			
		||||
# mypy
 | 
			
		||||
.mypy_cache/
 | 
			
		||||
.dmypy.json
 | 
			
		||||
dmypy.json
 | 
			
		||||
 | 
			
		||||
# Pyre type checker
 | 
			
		||||
.pyre/
 | 
			
		||||
 | 
			
		||||
# PyCharm
 | 
			
		||||
.idea
 | 
			
		||||
							
								
								
									
										103
									
								
								satrs-example/pytmtc/main.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										103
									
								
								satrs-example/pytmtc/main.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""Example client for the sat-rs example application"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import tmtccmd
 | 
			
		||||
from spacepackets.ecss import PusVerificator
 | 
			
		||||
 | 
			
		||||
from tmtccmd import ProcedureParamsWrapper
 | 
			
		||||
from tmtccmd.core.base import BackendRequest
 | 
			
		||||
from tmtccmd.pus import VerificationWrapper
 | 
			
		||||
from tmtccmd.tmtc import CcsdsTmHandler
 | 
			
		||||
from tmtccmd.config import (
 | 
			
		||||
    default_json_path,
 | 
			
		||||
    SetupParams,
 | 
			
		||||
    params_to_procedure_conversion,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
 | 
			
		||||
from tmtccmd.logging import add_colorlog_console_logger
 | 
			
		||||
from tmtccmd.logging.pus import (
 | 
			
		||||
    RegularTmtcLogWrapper,
 | 
			
		||||
    RawTmtcTimedLogWrapper,
 | 
			
		||||
    TimedLogWhen,
 | 
			
		||||
)
 | 
			
		||||
from spacepackets.seqcount import PusFileSeqCountProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from pytmtc.config import SatrsConfigHook
 | 
			
		||||
from pytmtc.pus_tc import TcHandler
 | 
			
		||||
from pytmtc.pus_tm import PusHandler
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    add_colorlog_console_logger(_LOGGER)
 | 
			
		||||
    tmtccmd.init_printout(False)
 | 
			
		||||
    hook_obj = SatrsConfigHook(json_cfg_path=default_json_path())
 | 
			
		||||
    parser_wrapper = PreArgsParsingWrapper()
 | 
			
		||||
    parser_wrapper.create_default_parent_parser()
 | 
			
		||||
    parser_wrapper.create_default_parser()
 | 
			
		||||
    parser_wrapper.add_def_proc_args()
 | 
			
		||||
    params = SetupParams()
 | 
			
		||||
    post_args_wrapper = parser_wrapper.parse(hook_obj, params)
 | 
			
		||||
    proc_wrapper = ProcedureParamsWrapper()
 | 
			
		||||
    if post_args_wrapper.use_gui:
 | 
			
		||||
        post_args_wrapper.set_params_without_prompts(proc_wrapper)
 | 
			
		||||
    else:
 | 
			
		||||
        post_args_wrapper.set_params_with_prompts(proc_wrapper)
 | 
			
		||||
    setup_args = SetupWrapper(
 | 
			
		||||
        hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
 | 
			
		||||
    )
 | 
			
		||||
    # Create console logger helper and file loggers
 | 
			
		||||
    tmtc_logger = RegularTmtcLogWrapper()
 | 
			
		||||
    file_logger = tmtc_logger.logger
 | 
			
		||||
    raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
 | 
			
		||||
    verificator = PusVerificator()
 | 
			
		||||
    verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
 | 
			
		||||
    # Create primary TM handler and add it to the CCSDS Packet Handler
 | 
			
		||||
    tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
 | 
			
		||||
    ccsds_handler = CcsdsTmHandler(generic_handler=tm_handler)
 | 
			
		||||
    # TODO: We could add the CFDP handler for the CFDP APID at a later stage.
 | 
			
		||||
    # ccsds_handler.add_apid_handler(tm_handler)
 | 
			
		||||
 | 
			
		||||
    # Create TC handler
 | 
			
		||||
    seq_count_provider = PusFileSeqCountProvider()
 | 
			
		||||
    tc_handler = TcHandler(seq_count_provider, verification_wrapper)
 | 
			
		||||
    tmtccmd.setup(setup_args=setup_args)
 | 
			
		||||
    init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
 | 
			
		||||
    tmtc_backend = tmtccmd.create_default_tmtc_backend(
 | 
			
		||||
        setup_wrapper=setup_args,
 | 
			
		||||
        tm_handler=ccsds_handler,
 | 
			
		||||
        tc_handler=tc_handler,
 | 
			
		||||
        init_procedure=init_proc,
 | 
			
		||||
    )
 | 
			
		||||
    tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
 | 
			
		||||
    try:
 | 
			
		||||
        while True:
 | 
			
		||||
            state = tmtc_backend.periodic_op(None)
 | 
			
		||||
            if state.request == BackendRequest.TERMINATION_NO_ERROR:
 | 
			
		||||
                tmtc_backend.close_com_if()
 | 
			
		||||
                sys.exit(0)
 | 
			
		||||
            elif state.request == BackendRequest.DELAY_IDLE:
 | 
			
		||||
                _LOGGER.info("TMTC Client in IDLE mode")
 | 
			
		||||
                time.sleep(3.0)
 | 
			
		||||
            elif state.request == BackendRequest.DELAY_LISTENER:
 | 
			
		||||
                time.sleep(0.8)
 | 
			
		||||
            elif state.request == BackendRequest.DELAY_CUSTOM:
 | 
			
		||||
                if state.next_delay.total_seconds() <= 0.4:
 | 
			
		||||
                    time.sleep(state.next_delay.total_seconds())
 | 
			
		||||
                else:
 | 
			
		||||
                    time.sleep(0.4)
 | 
			
		||||
            elif state.request == BackendRequest.CALL_NEXT:
 | 
			
		||||
                pass
 | 
			
		||||
    except KeyboardInterrupt:
 | 
			
		||||
        tmtc_backend.close_com_if()
 | 
			
		||||
        sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										27
									
								
								satrs-example/pytmtc/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								satrs-example/pytmtc/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = ["setuptools>=61.0"]
 | 
			
		||||
build-backend = "setuptools.build_meta"
 | 
			
		||||
 | 
			
		||||
[project]
 | 
			
		||||
name = "pytmtc"
 | 
			
		||||
description = "Python TMTC client for OPS-SAT"
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
requires-python = ">=3.8"
 | 
			
		||||
authors = [
 | 
			
		||||
  {name = "Robin Mueller", email = "robin.mueller.m@gmail.com"},
 | 
			
		||||
]
 | 
			
		||||
dependencies = [
 | 
			
		||||
    "tmtccmd~=8.1",
 | 
			
		||||
    "pydantic~=2.7"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[tool.setuptools.packages]
 | 
			
		||||
find = {}
 | 
			
		||||
 | 
			
		||||
[tool.ruff]
 | 
			
		||||
extend-exclude = ["archive"]
 | 
			
		||||
[tool.ruff.lint]
 | 
			
		||||
ignore = ["E501"]
 | 
			
		||||
[tool.ruff.lint.extend-per-file-ignores]
 | 
			
		||||
"__init__.py" = ["F401"]
 | 
			
		||||
							
								
								
									
										11
									
								
								satrs-example/pytmtc/pytmtc/acs/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								satrs-example/pytmtc/pytmtc/acs/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
from tmtccmd.config import CmdTreeNode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_acs_node(mode_node: CmdTreeNode, hk_node: CmdTreeNode) -> CmdTreeNode:
 | 
			
		||||
    acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
 | 
			
		||||
    mgm_node = CmdTreeNode("mgms", "MGM devices node")
 | 
			
		||||
    mgm_node.add_child(mode_node)
 | 
			
		||||
    mgm_node.add_child(hk_node)
 | 
			
		||||
 | 
			
		||||
    acs_node.add_child(mgm_node)
 | 
			
		||||
    return acs_node
 | 
			
		||||
							
								
								
									
										45
									
								
								satrs-example/pytmtc/pytmtc/acs/mgms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								satrs-example/pytmtc/pytmtc/acs/mgms.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import logging
 | 
			
		||||
import struct
 | 
			
		||||
import enum
 | 
			
		||||
from typing import List
 | 
			
		||||
from spacepackets.ecss import PusTm
 | 
			
		||||
from tmtccmd.tmtc import DefaultPusQueueHelper
 | 
			
		||||
 | 
			
		||||
from pytmtc.common import AcsId, Apid
 | 
			
		||||
from pytmtc.hk_common import create_request_one_shot_hk_cmd
 | 
			
		||||
from pytmtc.mode import handle_set_mode_cmd
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SetId(enum.IntEnum):
 | 
			
		||||
    SENSOR_SET = 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_mgm_cmds(q: DefaultPusQueueHelper, cmd_path: List[str]):
 | 
			
		||||
    assert len(cmd_path) >= 3
 | 
			
		||||
    if cmd_path[2] == "hk":
 | 
			
		||||
        if cmd_path[3] == "one_shot_hk":
 | 
			
		||||
            q.add_log_cmd("Sending HK one shot request")
 | 
			
		||||
            q.add_pus_tc(
 | 
			
		||||
                create_request_one_shot_hk_cmd(Apid.ACS, AcsId.MGM_0, SetId.SENSOR_SET)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    if cmd_path[2] == "mode":
 | 
			
		||||
        if cmd_path[3] == "set_mode":
 | 
			
		||||
            handle_set_mode_cmd(q, "MGM 0", cmd_path[4], Apid.ACS, AcsId.MGM_0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def handle_mgm_hk_report(pus_tm: PusTm, set_id: int, hk_data: bytes):
 | 
			
		||||
    if set_id == SetId.SENSOR_SET:
 | 
			
		||||
        if len(hk_data) != 13:
 | 
			
		||||
            raise ValueError(f"invalid HK data length, expected 13, got {len(hk_data)}")
 | 
			
		||||
        data_valid = hk_data[0]
 | 
			
		||||
        mgm_x = struct.unpack("!f", hk_data[1:5])[0]
 | 
			
		||||
        mgm_y = struct.unpack("!f", hk_data[5:9])[0]
 | 
			
		||||
        mgm_z = struct.unpack("!f", hk_data[9:13])[0]
 | 
			
		||||
        _LOGGER.info(
 | 
			
		||||
            f"received MGM HK set in uT: Valid {data_valid} X {mgm_x} Y {mgm_y} Z {mgm_z}"
 | 
			
		||||
        )
 | 
			
		||||
        pass
 | 
			
		||||
@@ -10,6 +10,7 @@ class Apid(enum.IntEnum):
 | 
			
		||||
    GENERIC_PUS = 2
 | 
			
		||||
    ACS = 3
 | 
			
		||||
    CFDP = 4
 | 
			
		||||
    TMTC = 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventSeverity(enum.IntEnum):
 | 
			
		||||
@@ -38,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):
 | 
			
		||||
							
								
								
									
										47
									
								
								satrs-example/pytmtc/pytmtc/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								satrs-example/pytmtc/pytmtc/config.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from prompt_toolkit.history import FileHistory, History
 | 
			
		||||
from spacepackets.ccsds import PacketId, PacketType
 | 
			
		||||
from tmtccmd import HookBase
 | 
			
		||||
from tmtccmd.com import ComInterface
 | 
			
		||||
from tmtccmd.config import CmdTreeNode
 | 
			
		||||
from tmtccmd.util.obj_id import ObjectIdDictT
 | 
			
		||||
 | 
			
		||||
from pytmtc.common import Apid
 | 
			
		||||
from pytmtc.pus_tc import create_cmd_definition_tree
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SatrsConfigHook(HookBase):
 | 
			
		||||
    def __init__(self, json_cfg_path: str):
 | 
			
		||||
        super().__init__(json_cfg_path)
 | 
			
		||||
 | 
			
		||||
    def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
 | 
			
		||||
        from tmtccmd.config.com import (
 | 
			
		||||
            create_com_interface_default,
 | 
			
		||||
            create_com_interface_cfg_default,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert self.cfg_path is not None
 | 
			
		||||
        packet_id_list = []
 | 
			
		||||
        for apid in Apid:
 | 
			
		||||
            packet_id_list.append(PacketId(PacketType.TM, True, apid))
 | 
			
		||||
        cfg = create_com_interface_cfg_default(
 | 
			
		||||
            com_if_key=com_if_key,
 | 
			
		||||
            json_cfg_path=self.cfg_path,
 | 
			
		||||
            space_packet_ids=packet_id_list,
 | 
			
		||||
        )
 | 
			
		||||
        assert cfg is not None
 | 
			
		||||
        return create_com_interface_default(cfg)
 | 
			
		||||
 | 
			
		||||
    def get_command_definitions(self) -> CmdTreeNode:
 | 
			
		||||
        """This function should return the root node of the command definition tree."""
 | 
			
		||||
        return create_cmd_definition_tree()
 | 
			
		||||
 | 
			
		||||
    def get_cmd_history(self) -> Optional[History]:
 | 
			
		||||
        """Optionlly return a history class for the past command paths which will be used
 | 
			
		||||
        when prompting a command path from the user in CLI mode."""
 | 
			
		||||
        return FileHistory(".tmtc-history.txt")
 | 
			
		||||
 | 
			
		||||
    def get_object_ids(self) -> ObjectIdDictT:
 | 
			
		||||
        from tmtccmd.config.objects import get_core_object_ids
 | 
			
		||||
 | 
			
		||||
        return get_core_object_ids()
 | 
			
		||||
							
								
								
									
										0
									
								
								satrs-example/pytmtc/pytmtc/eps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								satrs-example/pytmtc/pytmtc/eps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								satrs-example/pytmtc/pytmtc/eps/pcdu.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								satrs-example/pytmtc/pytmtc/eps/pcdu.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										42
									
								
								satrs-example/pytmtc/pytmtc/hk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								satrs-example/pytmtc/pytmtc/hk.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import logging
 | 
			
		||||
import struct
 | 
			
		||||
from spacepackets.ecss.pus_3_hk import Subservice
 | 
			
		||||
from spacepackets.ecss import PusTm
 | 
			
		||||
 | 
			
		||||
from pytmtc.common import AcsId, Apid
 | 
			
		||||
from pytmtc.acs.mgms import handle_mgm_hk_report
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def handle_hk_packet(pus_tm: PusTm):
 | 
			
		||||
    if len(pus_tm.source_data) < 4:
 | 
			
		||||
        raise ValueError("no unique ID in HK packet")
 | 
			
		||||
    unique_id = struct.unpack("!I", pus_tm.source_data[:4])[0]
 | 
			
		||||
    if (
 | 
			
		||||
        pus_tm.subservice == Subservice.TM_HK_REPORT
 | 
			
		||||
        or pus_tm.subservice == Subservice.TM_DIAGNOSTICS_REPORT
 | 
			
		||||
    ):
 | 
			
		||||
        if len(pus_tm.source_data) < 8:
 | 
			
		||||
            raise ValueError("no set ID in HK packet")
 | 
			
		||||
        set_id = struct.unpack("!I", pus_tm.source_data[4:8])[0]
 | 
			
		||||
        handle_hk_report(pus_tm, unique_id, set_id)
 | 
			
		||||
    _LOGGER.warning(
 | 
			
		||||
        f"handling for HK packet with subservice {pus_tm.subservice} not implemented yet"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def handle_hk_report(pus_tm: PusTm, unique_id: int, set_id: int):
 | 
			
		||||
    hk_data = pus_tm.source_data[8:]
 | 
			
		||||
    if pus_tm.apid == Apid.ACS:
 | 
			
		||||
        if unique_id == AcsId.MGM_0:
 | 
			
		||||
            handle_mgm_hk_report(pus_tm, set_id, hk_data)
 | 
			
		||||
        else:
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
                f"handling for HK report with unique ID {unique_id} not implemented yet"
 | 
			
		||||
            )
 | 
			
		||||
    else:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            f"handling for HK report with apid {pus_tm.apid} not implemented yet"
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										16
									
								
								satrs-example/pytmtc/pytmtc/hk_common.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								satrs-example/pytmtc/pytmtc/hk_common.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import struct
 | 
			
		||||
 | 
			
		||||
from spacepackets.ecss import PusService, PusTc
 | 
			
		||||
from spacepackets.ecss.pus_3_hk import Subservice
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_request_one_shot_hk_cmd(apid: int, unique_id: int, set_id: int) -> PusTc:
 | 
			
		||||
    app_data = bytearray()
 | 
			
		||||
    app_data.extend(struct.pack("!I", unique_id))
 | 
			
		||||
    app_data.extend(struct.pack("!I", set_id))
 | 
			
		||||
    return PusTc(
 | 
			
		||||
        service=PusService.S3_HOUSEKEEPING,
 | 
			
		||||
        subservice=Subservice.TC_GENERATE_ONE_PARAMETER_REPORT,
 | 
			
		||||
        apid=apid,
 | 
			
		||||
        app_data=app_data,
 | 
			
		||||
    )
 | 
			
		||||
							
								
								
									
										31
									
								
								satrs-example/pytmtc/pytmtc/mode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								satrs-example/pytmtc/pytmtc/mode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import struct
 | 
			
		||||
from spacepackets.ecss import PusTc
 | 
			
		||||
from tmtccmd.pus.s200_fsfw_mode import Mode, Subservice
 | 
			
		||||
from tmtccmd.tmtc import DefaultPusQueueHelper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_set_mode_cmd(apid: int, unique_id: int, mode: int, submode: int) -> PusTc:
 | 
			
		||||
    app_data = bytearray()
 | 
			
		||||
    app_data.extend(struct.pack("!I", unique_id))
 | 
			
		||||
    app_data.extend(struct.pack("!I", mode))
 | 
			
		||||
    app_data.extend(struct.pack("!H", submode))
 | 
			
		||||
    return PusTc(
 | 
			
		||||
        service=200,
 | 
			
		||||
        subservice=Subservice.TC_MODE_COMMAND,
 | 
			
		||||
        apid=apid,
 | 
			
		||||
        app_data=app_data,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def handle_set_mode_cmd(
 | 
			
		||||
    q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
 | 
			
		||||
):
 | 
			
		||||
    if mode_str == "off":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode OFF to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
 | 
			
		||||
    elif mode_str == "on":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode ON to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
 | 
			
		||||
    elif mode_str == "normal":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))
 | 
			
		||||
@@ -1,37 +1,73 @@
 | 
			
		||||
import datetime
 | 
			
		||||
import struct
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from spacepackets.ccsds import CdsShortTimestamp
 | 
			
		||||
from spacepackets.ecss import PusTelecommand
 | 
			
		||||
from spacepackets.seqcount import FileSeqCountProvider
 | 
			
		||||
from tmtccmd import ProcedureWrapper, TcHandlerBase
 | 
			
		||||
from tmtccmd.config import CmdTreeNode
 | 
			
		||||
from tmtccmd.pus.tc.s200_fsfw_mode import Mode
 | 
			
		||||
from tmtccmd.tmtc import DefaultPusQueueHelper
 | 
			
		||||
from tmtccmd.pus import VerificationWrapper
 | 
			
		||||
from tmtccmd.tmtc import (
 | 
			
		||||
    DefaultPusQueueHelper,
 | 
			
		||||
    FeedWrapper,
 | 
			
		||||
    QueueWrapper,
 | 
			
		||||
    SendCbParams,
 | 
			
		||||
    TcProcedureType,
 | 
			
		||||
    TcQueueEntryType,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
 | 
			
		||||
from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice
 | 
			
		||||
 | 
			
		||||
from common import AcsId, Apid
 | 
			
		||||
from pytmtc.acs import create_acs_node
 | 
			
		||||
from pytmtc.common import Apid
 | 
			
		||||
from pytmtc.acs.mgms import create_mgm_cmds
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_set_mode_cmd(
 | 
			
		||||
    apid: int, unique_id: int, mode: int, submode: int
 | 
			
		||||
) -> PusTelecommand:
 | 
			
		||||
    app_data = bytearray()
 | 
			
		||||
    app_data.extend(struct.pack("!I", unique_id))
 | 
			
		||||
    app_data.extend(struct.pack("!I", mode))
 | 
			
		||||
    app_data.extend(struct.pack("!H", submode))
 | 
			
		||||
    return PusTelecommand(
 | 
			
		||||
        service=200,
 | 
			
		||||
        subservice=ModeSubservice.TC_MODE_COMMAND,
 | 
			
		||||
        apid=apid,
 | 
			
		||||
        app_data=app_data,
 | 
			
		||||
    )
 | 
			
		||||
class TcHandler(TcHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        seq_count_provider: FileSeqCountProvider,
 | 
			
		||||
        verif_wrapper: VerificationWrapper,
 | 
			
		||||
    ):
 | 
			
		||||
        super(TcHandler, self).__init__()
 | 
			
		||||
        self.seq_count_provider = seq_count_provider
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
        self.queue_helper = DefaultPusQueueHelper(
 | 
			
		||||
            queue_wrapper=QueueWrapper.empty(),
 | 
			
		||||
            tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
 | 
			
		||||
            seq_cnt_provider=seq_count_provider,
 | 
			
		||||
            pus_verificator=self.verif_wrapper.pus_verificator,
 | 
			
		||||
            default_pus_apid=None,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_cb(self, send_params: SendCbParams):
 | 
			
		||||
        entry_helper = send_params.entry
 | 
			
		||||
        if entry_helper.is_tc:
 | 
			
		||||
            if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
 | 
			
		||||
                pus_tc_wrapper = entry_helper.to_pus_tc_entry()
 | 
			
		||||
                raw_tc = pus_tc_wrapper.pus_tc.pack()
 | 
			
		||||
                _LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
 | 
			
		||||
                send_params.com_if.send(raw_tc)
 | 
			
		||||
        elif entry_helper.entry_type == TcQueueEntryType.LOG:
 | 
			
		||||
            log_entry = entry_helper.to_log_entry()
 | 
			
		||||
            _LOGGER.info(log_entry.log_str)
 | 
			
		||||
 | 
			
		||||
    def queue_finished_cb(self, info: ProcedureWrapper):
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            _LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
 | 
			
		||||
 | 
			
		||||
    def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
 | 
			
		||||
        q = self.queue_helper
 | 
			
		||||
        q.queue_wrapper = wrapper.queue_wrapper
 | 
			
		||||
        if info.proc_type == TcProcedureType.TREE_COMMANDING:
 | 
			
		||||
            def_proc = info.to_tree_commanding_procedure()
 | 
			
		||||
            assert def_proc.cmd_path is not None
 | 
			
		||||
            pack_pus_telecommands(q, def_proc.cmd_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
 | 
			
		||||
    root_node = CmdTreeNode.root_node()
 | 
			
		||||
 | 
			
		||||
    hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
 | 
			
		||||
@@ -65,15 +101,7 @@ def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    root_node.add_child(scheduler_node)
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -112,32 +140,4 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
 | 
			
		||||
    if cmd_path_list[0] == "acs":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "mgms":
 | 
			
		||||
            assert len(cmd_path_list) >= 3
 | 
			
		||||
            if cmd_path_list[2] == "hk":
 | 
			
		||||
                if cmd_path_list[3] == "one_shot_hk":
 | 
			
		||||
                    q.add_log_cmd("Sending HK one shot request")
 | 
			
		||||
                    # TODO: Fix
 | 
			
		||||
                    # q.add_pus_tc(
 | 
			
		||||
                    #   create_request_one_hk_command(
 | 
			
		||||
                    #    make_addressable_id(Apid.ACS, AcsId.MGM_SET)
 | 
			
		||||
                    # )
 | 
			
		||||
                    # )
 | 
			
		||||
            if cmd_path_list[2] == "mode":
 | 
			
		||||
                if cmd_path_list[3] == "set_mode":
 | 
			
		||||
                    handle_set_mode_cmd(
 | 
			
		||||
                        q, "MGM 0", cmd_path_list[4], Apid.ACS, AcsId.MGM_0
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def handle_set_mode_cmd(
 | 
			
		||||
    q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
 | 
			
		||||
):
 | 
			
		||||
    if mode_str == "off":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode OFF to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
 | 
			
		||||
    elif mode_str == "on":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode ON to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
 | 
			
		||||
    elif mode_str == "normal":
 | 
			
		||||
        q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
 | 
			
		||||
        q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))
 | 
			
		||||
            create_mgm_cmds(q, cmd_path_list)
 | 
			
		||||
							
								
								
									
										93
									
								
								satrs-example/pytmtc/pytmtc/pus_tm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								satrs-example/pytmtc/pytmtc/pus_tm.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Any
 | 
			
		||||
from spacepackets.ccsds.time import CdsShortTimestamp
 | 
			
		||||
from spacepackets.ecss import PusTm
 | 
			
		||||
from spacepackets.ecss.pus_17_test import Service17Tm
 | 
			
		||||
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
 | 
			
		||||
from tmtccmd.logging.pus import RawTmtcTimedLogWrapper
 | 
			
		||||
from tmtccmd.pus import VerificationWrapper
 | 
			
		||||
from tmtccmd.tmtc import GenericApidHandlerBase
 | 
			
		||||
 | 
			
		||||
from pytmtc.common import Apid, EventU32
 | 
			
		||||
from pytmtc.hk import handle_hk_packet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PusHandler(GenericApidHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        file_logger: logging.Logger,
 | 
			
		||||
        verif_wrapper: VerificationWrapper,
 | 
			
		||||
        raw_logger: RawTmtcTimedLogWrapper,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(None)
 | 
			
		||||
        self.file_logger = file_logger
 | 
			
		||||
        self.raw_logger = raw_logger
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
 | 
			
		||||
    def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
 | 
			
		||||
        try:
 | 
			
		||||
            pus_tm = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            _LOGGER.warning("Could not generate PUS TM object from raw data")
 | 
			
		||||
            _LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
 | 
			
		||||
            raise e
 | 
			
		||||
        service = pus_tm.service
 | 
			
		||||
        if service == 1:
 | 
			
		||||
            tm_packet = Service1Tm.unpack(
 | 
			
		||||
                data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
 | 
			
		||||
            )
 | 
			
		||||
            res = self.verif_wrapper.add_tm(tm_packet)
 | 
			
		||||
            if res is None:
 | 
			
		||||
                _LOGGER.info(
 | 
			
		||||
                    f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
 | 
			
		||||
                    f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
 | 
			
		||||
                )
 | 
			
		||||
                _LOGGER.warning(
 | 
			
		||||
                    f"No matching telecommand found for {tm_packet.tc_req_id}"
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                self.verif_wrapper.log_to_console(tm_packet, res)
 | 
			
		||||
                self.verif_wrapper.log_to_file(tm_packet, res)
 | 
			
		||||
        elif service == 3:
 | 
			
		||||
            pus_tm = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
            handle_hk_packet(pus_tm)
 | 
			
		||||
        elif service == 5:
 | 
			
		||||
            tm_packet = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
            src_data = tm_packet.source_data
 | 
			
		||||
            event_u32 = EventU32.unpack(src_data)
 | 
			
		||||
            _LOGGER.info(
 | 
			
		||||
                f"Received event packet. Source APID: {Apid(tm_packet.apid)!r}, Event: {event_u32}"
 | 
			
		||||
            )
 | 
			
		||||
            if event_u32.group_id == 0 and event_u32.unique_id == 0:
 | 
			
		||||
                _LOGGER.info("Received test event")
 | 
			
		||||
        elif service == 17:
 | 
			
		||||
            tm_packet = Service17Tm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
            if tm_packet.subservice == 2:
 | 
			
		||||
                self.file_logger.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
                _LOGGER.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
            else:
 | 
			
		||||
                self.file_logger.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
                _LOGGER.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
            _LOGGER.info(
 | 
			
		||||
                f"The service {service} is not implemented in Telemetry Factory"
 | 
			
		||||
            )
 | 
			
		||||
            tm_packet = PusTm.unpack(
 | 
			
		||||
                packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
 | 
			
		||||
            )
 | 
			
		||||
        self.raw_logger.log_tm(pus_tm)
 | 
			
		||||
							
								
								
									
										1
									
								
								satrs-example/pytmtc/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								satrs-example/pytmtc/requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
.
 | 
			
		||||
							
								
								
									
										0
									
								
								satrs-example/pytmtc/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								satrs-example/pytmtc/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										48
									
								
								satrs-example/pytmtc/tests/test_tc_mods.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								satrs-example/pytmtc/tests/test_tc_mods.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
from unittest import TestCase
 | 
			
		||||
 | 
			
		||||
from spacepackets.ccsds import CdsShortTimestamp
 | 
			
		||||
from tmtccmd.tmtc import DefaultPusQueueHelper, QueueEntryHelper
 | 
			
		||||
from tmtccmd.tmtc.queue import QueueWrapper
 | 
			
		||||
 | 
			
		||||
from pytmtc.config import SatrsConfigHook
 | 
			
		||||
from pytmtc.pus_tc import pack_pus_telecommands
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestTcModules(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.hook = SatrsConfigHook(json_cfg_path="tmtc_conf.json")
 | 
			
		||||
        self.queue_helper = DefaultPusQueueHelper(
 | 
			
		||||
            queue_wrapper=QueueWrapper.empty(),
 | 
			
		||||
            tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
 | 
			
		||||
            seq_cnt_provider=None,
 | 
			
		||||
            pus_verificator=None,
 | 
			
		||||
            default_pus_apid=None,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_cmd_tree_creation_works_without_errors(self):
 | 
			
		||||
        cmd_defs = self.hook.get_command_definitions()
 | 
			
		||||
        self.assertIsNotNone(cmd_defs)
 | 
			
		||||
 | 
			
		||||
    def test_ping_cmd_generation(self):
 | 
			
		||||
        pack_pus_telecommands(self.queue_helper, "/test/ping")
 | 
			
		||||
        queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
 | 
			
		||||
        entry_helper = QueueEntryHelper(queue_entry)
 | 
			
		||||
        log_queue = entry_helper.to_log_entry()
 | 
			
		||||
        self.assertEqual(log_queue.log_str, "Sending PUS ping telecommand")
 | 
			
		||||
        queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
 | 
			
		||||
        entry_helper.entry = queue_entry
 | 
			
		||||
        pus_tc_entry = entry_helper.to_pus_tc_entry()
 | 
			
		||||
        self.assertEqual(pus_tc_entry.pus_tc.service, 17)
 | 
			
		||||
        self.assertEqual(pus_tc_entry.pus_tc.subservice, 1)
 | 
			
		||||
 | 
			
		||||
    def test_event_trigger_generation(self):
 | 
			
		||||
        pack_pus_telecommands(self.queue_helper, "/test/trigger_event")
 | 
			
		||||
        queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
 | 
			
		||||
        entry_helper = QueueEntryHelper(queue_entry)
 | 
			
		||||
        log_queue = entry_helper.to_log_entry()
 | 
			
		||||
        self.assertEqual(log_queue.log_str, "Triggering test event")
 | 
			
		||||
        queue_entry = self.queue_helper.queue_wrapper.queue.popleft()
 | 
			
		||||
        entry_helper.entry = queue_entry
 | 
			
		||||
        pus_tc_entry = entry_helper.to_pus_tc_entry()
 | 
			
		||||
        self.assertEqual(pus_tc_entry.pus_tc.service, 17)
 | 
			
		||||
        self.assertEqual(pus_tc_entry.pus_tc.subservice, 128)
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
tmtccmd == 8.0.0rc1
 | 
			
		||||
# -e git+https://github.com/robamu-org/tmtccmd@97e5e51101a08b21472b3ddecc2063359f7e307a#egg=tmtccmd
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
from tmtccmd.config import OpCodeEntry, TmtcDefinitionWrapper, CoreServiceList
 | 
			
		||||
from tmtccmd.config.globals import get_default_tmtc_defs
 | 
			
		||||
 | 
			
		||||
from common import HkOpCodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def tc_definitions() -> TmtcDefinitionWrapper:
 | 
			
		||||
    defs = get_default_tmtc_defs()
 | 
			
		||||
    srv_5 = OpCodeEntry()
 | 
			
		||||
    srv_5.add("0", "Event Test")
 | 
			
		||||
    defs.add_service(
 | 
			
		||||
        name=CoreServiceList.SERVICE_5.value,
 | 
			
		||||
        info="PUS Service 5 Event",
 | 
			
		||||
        op_code_entry=srv_5,
 | 
			
		||||
    )
 | 
			
		||||
    srv_17 = OpCodeEntry()
 | 
			
		||||
    srv_17.add("ping", "Ping Test")
 | 
			
		||||
    srv_17.add("trigger_event", "Trigger Event")
 | 
			
		||||
    defs.add_service(
 | 
			
		||||
        name=CoreServiceList.SERVICE_17_ALT,
 | 
			
		||||
        info="PUS Service 17 Test",
 | 
			
		||||
        op_code_entry=srv_17,
 | 
			
		||||
    )
 | 
			
		||||
    srv_3 = OpCodeEntry()
 | 
			
		||||
    srv_3.add(HkOpCodes.GENERATE_ONE_SHOT, "Generate AOCS one shot HK")
 | 
			
		||||
    defs.add_service(
 | 
			
		||||
        name=CoreServiceList.SERVICE_3,
 | 
			
		||||
        info="PUS Service 3 Housekeeping",
 | 
			
		||||
        op_code_entry=srv_3,
 | 
			
		||||
    )
 | 
			
		||||
    srv_11 = OpCodeEntry()
 | 
			
		||||
    srv_11.add("0", "Scheduled TC Test")
 | 
			
		||||
    defs.add_service(
 | 
			
		||||
        name=CoreServiceList.SERVICE_11,
 | 
			
		||||
        info="PUS Service 11 TC Scheduling",
 | 
			
		||||
        op_code_entry=srv_11,
 | 
			
		||||
    )
 | 
			
		||||
    return defs
 | 
			
		||||
							
								
								
									
										1
									
								
								satrs-example/src/acs/assembly.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								satrs-example/src/acs/assembly.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
// TODO: Write the assembly
 | 
			
		||||
							
								
								
									
										1
									
								
								satrs-example/src/acs/ctrl.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								satrs-example/src/acs/ctrl.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
// TODO: Write dummy controller
 | 
			
		||||
@@ -1,52 +1,125 @@
 | 
			
		||||
use derive_new::new;
 | 
			
		||||
use satrs::hk::{HkRequest, HkRequestVariant};
 | 
			
		||||
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
 | 
			
		||||
use satrs::spacepackets::ecss::hk;
 | 
			
		||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
 | 
			
		||||
use satrs::spacepackets::SpHeader;
 | 
			
		||||
use satrs_example::{DeviceMode, TimeStampHelper};
 | 
			
		||||
use satrs::mode_tree::{ModeChild, ModeNode};
 | 
			
		||||
use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender};
 | 
			
		||||
use satrs_example::ids::generic_pus::PUS_MODE;
 | 
			
		||||
use satrs_example::{DeviceMode, TimestampHelper};
 | 
			
		||||
use satrs_minisim::acs::lis3mdl::{
 | 
			
		||||
    MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR,
 | 
			
		||||
};
 | 
			
		||||
use satrs_minisim::acs::MgmRequestLis3Mdl;
 | 
			
		||||
use satrs_minisim::eps::PcduSwitch;
 | 
			
		||||
use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::sync::mpsc::{self};
 | 
			
		||||
use std::sync::{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::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};
 | 
			
		||||
 | 
			
		||||
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0;
 | 
			
		||||
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
 | 
			
		||||
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
 | 
			
		||||
pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14;
 | 
			
		||||
 | 
			
		||||
pub trait SpiInterface {
 | 
			
		||||
    type Error;
 | 
			
		||||
    fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
 | 
			
		||||
// Register adresses to access various bytes from the raw reply.
 | 
			
		||||
pub const X_LOWBYTE_IDX: usize = 9;
 | 
			
		||||
pub const Y_LOWBYTE_IDX: usize = 11;
 | 
			
		||||
pub const Z_LOWBYTE_IDX: usize = 13;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
 | 
			
		||||
#[repr(u32)]
 | 
			
		||||
pub enum SetId {
 | 
			
		||||
    SensorData = 0,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug, PartialEq, Eq)]
 | 
			
		||||
pub enum TransitionState {
 | 
			
		||||
    #[default]
 | 
			
		||||
    Idle,
 | 
			
		||||
    PowerSwitching,
 | 
			
		||||
    Done,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct SpiDummyInterface {
 | 
			
		||||
    pub dummy_val_0: i16,
 | 
			
		||||
    pub dummy_val_1: i16,
 | 
			
		||||
    pub dummy_val_2: i16,
 | 
			
		||||
    pub dummy_values: MgmLis3RawValues,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpiInterface for SpiDummyInterface {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        rx[0..2].copy_from_slice(&self.dummy_val_0.to_be_bytes());
 | 
			
		||||
        rx[2..4].copy_from_slice(&self.dummy_val_1.to_be_bytes());
 | 
			
		||||
        rx[4..6].copy_from_slice(&self.dummy_val_2.to_be_bytes());
 | 
			
		||||
        rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.x.to_le_bytes());
 | 
			
		||||
        rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.y.to_be_bytes());
 | 
			
		||||
        rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_values.z.to_be_bytes());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct SpiSimInterface {
 | 
			
		||||
    pub sim_request_tx: mpsc::Sender<SimRequest>,
 | 
			
		||||
    pub sim_reply_rx: mpsc::Receiver<SimReply>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpiInterface for SpiSimInterface {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    // Right now, we only support requesting sensor data and not configuration of the sensor.
 | 
			
		||||
    fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        let mgm_sensor_request = MgmRequestLis3Mdl::RequestSensorData;
 | 
			
		||||
        if let Err(e) = self
 | 
			
		||||
            .sim_request_tx
 | 
			
		||||
            .send(SimRequest::new_with_epoch_time(mgm_sensor_request))
 | 
			
		||||
        {
 | 
			
		||||
            log::error!("failed to send MGM LIS3 request: {e}");
 | 
			
		||||
        }
 | 
			
		||||
        match self.sim_reply_rx.recv_timeout(Duration::from_millis(50)) {
 | 
			
		||||
            Ok(sim_reply) => {
 | 
			
		||||
                let sim_reply_lis3 = MgmLis3MdlReply::from_sim_message(&sim_reply)
 | 
			
		||||
                    .expect("failed to parse LIS3 reply");
 | 
			
		||||
                rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
 | 
			
		||||
                    .copy_from_slice(&sim_reply_lis3.raw.x.to_le_bytes());
 | 
			
		||||
                rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
 | 
			
		||||
                    .copy_from_slice(&sim_reply_lis3.raw.y.to_le_bytes());
 | 
			
		||||
                rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
 | 
			
		||||
                    .copy_from_slice(&sim_reply_lis3.raw.z.to_le_bytes());
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::warn!("MGM LIS3 SIM reply timeout: {e}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum SpiSimInterfaceWrapper {
 | 
			
		||||
    Dummy(SpiDummyInterface),
 | 
			
		||||
    Sim(SpiSimInterface),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpiInterface for SpiSimInterfaceWrapper {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            SpiSimInterfaceWrapper::Dummy(dummy) => dummy.transfer(tx, rx),
 | 
			
		||||
            SpiSimInterfaceWrapper::Sim(sim_if) => sim_if.transfer(tx, rx),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct MgmData {
 | 
			
		||||
    pub valid: bool,
 | 
			
		||||
@@ -55,63 +128,79 @@ pub struct MgmData {
 | 
			
		||||
    pub z: f32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct MpscModeLeafInterface {
 | 
			
		||||
    pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
 | 
			
		||||
    pub reply_tx_to_pus: mpsc::Sender<GenericMessage<ModeReply>>,
 | 
			
		||||
    pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>,
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct BufWrapper {
 | 
			
		||||
    tx_buf: [u8; 32],
 | 
			
		||||
    rx_buf: [u8; 32],
 | 
			
		||||
    tm_buf: [u8; 32],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct ModeHelpers {
 | 
			
		||||
    current: ModeAndSubmode,
 | 
			
		||||
    target: Option<ModeAndSubmode>,
 | 
			
		||||
    requestor_info: Option<MessageMetadata>,
 | 
			
		||||
    transition_state: TransitionState,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ModeHelpers {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            current: ModeAndSubmode::new(DeviceMode::Off as u32, 0),
 | 
			
		||||
            target: Default::default(),
 | 
			
		||||
            requestor_info: Default::default(),
 | 
			
		||||
            transition_state: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender> {
 | 
			
		||||
pub struct MgmHandlerLis3Mdl<
 | 
			
		||||
    ComInterface: SpiInterface,
 | 
			
		||||
    SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
> {
 | 
			
		||||
    id: UniqueApidTargetId,
 | 
			
		||||
    dev_str: &'static str,
 | 
			
		||||
    mode_interface: MpscModeLeafInterface,
 | 
			
		||||
    composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
 | 
			
		||||
    hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
 | 
			
		||||
    tm_sender: TmSender,
 | 
			
		||||
    com_interface: ComInterface,
 | 
			
		||||
    mode_node: ModeRequestHandlerMpscBounded,
 | 
			
		||||
    composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
 | 
			
		||||
    hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
 | 
			
		||||
    switch_helper: SwitchHelper,
 | 
			
		||||
    tm_sender: TmTcSender,
 | 
			
		||||
    pub com_interface: ComInterface,
 | 
			
		||||
    shared_mgm_set: Arc<Mutex<MgmData>>,
 | 
			
		||||
    #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
 | 
			
		||||
    mode_and_submode: ModeAndSubmode,
 | 
			
		||||
    #[new(value = "PusHkHelper::new(id)")]
 | 
			
		||||
    hk_helper: PusHkHelper,
 | 
			
		||||
    #[new(default)]
 | 
			
		||||
    tx_buf: [u8; 12],
 | 
			
		||||
    mode_helpers: ModeHelpers,
 | 
			
		||||
    #[new(default)]
 | 
			
		||||
    rx_buf: [u8; 12],
 | 
			
		||||
    bufs: BufWrapper,
 | 
			
		||||
    #[new(default)]
 | 
			
		||||
    tm_buf: [u8; 16],
 | 
			
		||||
    #[new(default)]
 | 
			
		||||
    stamp_helper: TimeStampHelper,
 | 
			
		||||
    stamp_helper: TimestampHelper,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
 | 
			
		||||
impl<
 | 
			
		||||
        ComInterface: SpiInterface,
 | 
			
		||||
        SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
    > MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
 | 
			
		||||
{
 | 
			
		||||
    pub fn periodic_operation(&mut self) {
 | 
			
		||||
        self.stamp_helper.update_from_now();
 | 
			
		||||
        // Handle requests.
 | 
			
		||||
        self.handle_composite_requests();
 | 
			
		||||
        self.handle_mode_requests();
 | 
			
		||||
        if let Some(target_mode_submode) = self.mode_helpers.target {
 | 
			
		||||
            self.handle_mode_transition(target_mode_submode);
 | 
			
		||||
        }
 | 
			
		||||
        if self.mode() == DeviceMode::Normal as u32 {
 | 
			
		||||
            log::trace!("polling LIS3MDL sensor {}", self.dev_str);
 | 
			
		||||
            // Communicate with the device.
 | 
			
		||||
            let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf);
 | 
			
		||||
            assert!(result.is_ok());
 | 
			
		||||
            // Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
 | 
			
		||||
            let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap());
 | 
			
		||||
            let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap());
 | 
			
		||||
            let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap());
 | 
			
		||||
            // Simple scaling to retrieve the float value, assuming a sensor resolution of
 | 
			
		||||
            let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
 | 
			
		||||
            mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
            mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
            mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
            drop(mgm_guard);
 | 
			
		||||
            self.poll_sensor();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_composite_requests(&mut self) {
 | 
			
		||||
        loop {
 | 
			
		||||
            match self.composite_request_receiver.try_recv() {
 | 
			
		||||
            match self.composite_request_rx.try_recv() {
 | 
			
		||||
                Ok(ref msg) => match &msg.message {
 | 
			
		||||
                    CompositeRequest::Hk(hk_request) => {
 | 
			
		||||
                        self.handle_hk_request(&msg.requestor_info, hk_request)
 | 
			
		||||
@@ -139,34 +228,33 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
 | 
			
		||||
    pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
 | 
			
		||||
        match hk_request.variant {
 | 
			
		||||
            HkRequestVariant::OneShot => {
 | 
			
		||||
                self.hk_reply_sender
 | 
			
		||||
                    .send(GenericMessage::new(
 | 
			
		||||
                        *requestor_info,
 | 
			
		||||
                        HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
 | 
			
		||||
                    ))
 | 
			
		||||
                    .expect("failed to send HK reply");
 | 
			
		||||
                let sec_header = PusTmSecondaryHeader::new(
 | 
			
		||||
                    3,
 | 
			
		||||
                    hk::Subservice::TmHkPacket as u8,
 | 
			
		||||
                    0,
 | 
			
		||||
                    0,
 | 
			
		||||
                    self.stamp_helper.stamp(),
 | 
			
		||||
                );
 | 
			
		||||
                let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
 | 
			
		||||
                // Use binary serialization here. We want the data to be tightly packed.
 | 
			
		||||
                self.tm_buf[0] = mgm_snapshot.valid as u8;
 | 
			
		||||
                self.tm_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
 | 
			
		||||
                self.tm_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
 | 
			
		||||
                self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
 | 
			
		||||
                let hk_tm = PusTmCreator::new(
 | 
			
		||||
                    SpHeader::new_from_apid(self.id.apid),
 | 
			
		||||
                    sec_header,
 | 
			
		||||
                    &self.tm_buf[0..12],
 | 
			
		||||
                    true,
 | 
			
		||||
                );
 | 
			
		||||
                self.tm_sender
 | 
			
		||||
                    .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
 | 
			
		||||
                    .expect("failed to send HK TM");
 | 
			
		||||
                if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
 | 
			
		||||
                    self.stamp_helper.stamp(),
 | 
			
		||||
                    SetId::SensorData as u32,
 | 
			
		||||
                    &mut |hk_buf| {
 | 
			
		||||
                        hk_buf[0] = mgm_snapshot.valid as u8;
 | 
			
		||||
                        hk_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
 | 
			
		||||
                        hk_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
 | 
			
		||||
                        hk_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
 | 
			
		||||
                        Ok(13)
 | 
			
		||||
                    },
 | 
			
		||||
                    &mut self.bufs.tm_buf,
 | 
			
		||||
                ) {
 | 
			
		||||
                    // TODO: If sending the TM fails, we should also send a failure reply.
 | 
			
		||||
                    self.tm_sender
 | 
			
		||||
                        .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
 | 
			
		||||
                        .expect("failed to send HK TM");
 | 
			
		||||
                    self.hk_reply_tx
 | 
			
		||||
                        .send(GenericMessage::new(
 | 
			
		||||
                            *requestor_info,
 | 
			
		||||
                            HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .expect("failed to send HK reply");
 | 
			
		||||
                } else {
 | 
			
		||||
                    // TODO: Send back failure reply. Need result code for this.
 | 
			
		||||
                    log::error!("TM buffer too small to generate HK data");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            HkRequestVariant::EnablePeriodic => todo!(),
 | 
			
		||||
            HkRequestVariant::DisablePeriodic => todo!(),
 | 
			
		||||
@@ -177,54 +265,137 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
 | 
			
		||||
    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);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn poll_sensor(&mut self) {
 | 
			
		||||
        // Communicate with the device. This is actually how to read the data from the LIS3 device
 | 
			
		||||
        // SPI interface.
 | 
			
		||||
        self.com_interface
 | 
			
		||||
            .transfer(
 | 
			
		||||
                &self.bufs.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
 | 
			
		||||
                &mut self.bufs.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
 | 
			
		||||
            )
 | 
			
		||||
            .expect("failed to transfer data");
 | 
			
		||||
        let x_raw = i16::from_le_bytes(
 | 
			
		||||
            self.bufs.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
 | 
			
		||||
                .try_into()
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
        let y_raw = i16::from_le_bytes(
 | 
			
		||||
            self.bufs.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
 | 
			
		||||
                .try_into()
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
        let z_raw = i16::from_le_bytes(
 | 
			
		||||
            self.bufs.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
 | 
			
		||||
                .try_into()
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
        // Simple scaling to retrieve the float value, assuming the best sensor resolution.
 | 
			
		||||
        let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
 | 
			
		||||
        mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        mgm_guard.valid = true;
 | 
			
		||||
        drop(mgm_guard);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_mode_transition(&mut self, target_mode_submode: ModeAndSubmode) {
 | 
			
		||||
        if target_mode_submode.mode() == DeviceMode::On as u32
 | 
			
		||||
            || target_mode_submode.mode() == DeviceMode::Normal as u32
 | 
			
		||||
        {
 | 
			
		||||
            if self.mode_helpers.transition_state == TransitionState::Idle {
 | 
			
		||||
                let result = self
 | 
			
		||||
                    .switch_helper
 | 
			
		||||
                    .send_switch_on_cmd(MessageMetadata::new(0, self.id.id()), PcduSwitch::Mgm);
 | 
			
		||||
                if result.is_err() {
 | 
			
		||||
                    // Could not send switch command.. still continue with transition.
 | 
			
		||||
                    log::error!("failed to send switch on command");
 | 
			
		||||
                }
 | 
			
		||||
                self.mode_helpers.transition_state = TransitionState::PowerSwitching;
 | 
			
		||||
            }
 | 
			
		||||
            if self.mode_helpers.transition_state == TransitionState::PowerSwitching
 | 
			
		||||
                && self
 | 
			
		||||
                    .switch_helper
 | 
			
		||||
                    .is_switch_on(PcduSwitch::Mgm)
 | 
			
		||||
                    .expect("switch info error")
 | 
			
		||||
            {
 | 
			
		||||
                self.mode_helpers.transition_state = TransitionState::Done;
 | 
			
		||||
            }
 | 
			
		||||
            if self.mode_helpers.transition_state == TransitionState::Done {
 | 
			
		||||
                self.mode_helpers.current = self.mode_helpers.target.unwrap();
 | 
			
		||||
                self.handle_mode_reached(self.mode_helpers.requestor_info)
 | 
			
		||||
                    .expect("failed to handle mode reached");
 | 
			
		||||
                self.mode_helpers.transition_state = TransitionState::Idle;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeProvider
 | 
			
		||||
    for MgmHandlerLis3Mdl<ComInterface, TmSender>
 | 
			
		||||
impl<
 | 
			
		||||
        ComInterface: SpiInterface,
 | 
			
		||||
        SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
    > ModeProvider for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
 | 
			
		||||
{
 | 
			
		||||
    fn mode_and_submode(&self) -> ModeAndSubmode {
 | 
			
		||||
        self.mode_and_submode
 | 
			
		||||
        self.mode_helpers.current
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
 | 
			
		||||
    for MgmHandlerLis3Mdl<ComInterface, TmSender>
 | 
			
		||||
impl<
 | 
			
		||||
        ComInterface: SpiInterface,
 | 
			
		||||
        SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
    > ModeRequestHandler for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
 | 
			
		||||
{
 | 
			
		||||
    type Error = ModeError;
 | 
			
		||||
 | 
			
		||||
    fn start_transition(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        requestor: MessageMetadata,
 | 
			
		||||
        mode_and_submode: ModeAndSubmode,
 | 
			
		||||
        _forced: bool,
 | 
			
		||||
    ) -> Result<(), satrs::mode::ModeError> {
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "{}: transitioning to mode {:?}",
 | 
			
		||||
            self.dev_str,
 | 
			
		||||
            mode_and_submode
 | 
			
		||||
        );
 | 
			
		||||
        self.mode_and_submode = mode_and_submode;
 | 
			
		||||
        self.handle_mode_reached(Some(requestor))?;
 | 
			
		||||
        self.mode_helpers.current = mode_and_submode;
 | 
			
		||||
        if mode_and_submode.mode() == DeviceMode::Off as u32 {
 | 
			
		||||
            self.shared_mgm_set.lock().unwrap().valid = false;
 | 
			
		||||
            self.handle_mode_reached(Some(requestor))?;
 | 
			
		||||
        } else if mode_and_submode.mode() == DeviceMode::Normal as u32
 | 
			
		||||
            || mode_and_submode.mode() == DeviceMode::On as u32
 | 
			
		||||
        {
 | 
			
		||||
            // TODO: Write helper method for the struct? Might help for other handlers as well..
 | 
			
		||||
            self.mode_helpers.transition_state = TransitionState::Idle;
 | 
			
		||||
            self.mode_helpers.requestor_info = Some(requestor);
 | 
			
		||||
            self.mode_helpers.target = Some(mode_and_submode);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -232,7 +403,7 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "{} announcing mode: {:?}",
 | 
			
		||||
            self.dev_str,
 | 
			
		||||
            self.mode_and_submode
 | 
			
		||||
            self.mode_and_submode()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -240,11 +411,15 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
 | 
			
		||||
        &mut self,
 | 
			
		||||
        requestor: Option<MessageMetadata>,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        self.mode_helpers.target = None;
 | 
			
		||||
        self.announce_mode(requestor, false);
 | 
			
		||||
        if let Some(requestor) = requestor {
 | 
			
		||||
            if requestor.sender_id() != PUS_MODE_SERVICE.id() {
 | 
			
		||||
            if requestor.sender_id() == NO_SENDER {
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
            if requestor.sender_id() != PUS_MODE.id() {
 | 
			
		||||
                log::warn!(
 | 
			
		||||
                    "can not send back mode reply to sender {}",
 | 
			
		||||
                    "can not send back mode reply to sender {:x}",
 | 
			
		||||
                    requestor.sender_id()
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -259,16 +434,15 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
 | 
			
		||||
        requestor: MessageMetadata,
 | 
			
		||||
        reply: ModeReply,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        if requestor.sender_id() != PUS_MODE_SERVICE.id() {
 | 
			
		||||
        if requestor.sender_id() != PUS_MODE.id() {
 | 
			
		||||
            log::warn!(
 | 
			
		||||
                "can not send back mode reply to sender {}",
 | 
			
		||||
                requestor.sender_id()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        self.mode_interface
 | 
			
		||||
            .reply_tx_to_pus
 | 
			
		||||
            .send(GenericMessage::new(requestor, reply))
 | 
			
		||||
            .map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
 | 
			
		||||
        self.mode_node
 | 
			
		||||
            .send_mode_reply(requestor, reply)
 | 
			
		||||
            .map_err(ModeError::Send)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -280,3 +454,265 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<
 | 
			
		||||
        ComInterface: SpiInterface,
 | 
			
		||||
        SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
    > ModeNode for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
 | 
			
		||||
{
 | 
			
		||||
    fn id(&self) -> satrs::ComponentId {
 | 
			
		||||
        self.id.into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<
 | 
			
		||||
        ComInterface: SpiInterface,
 | 
			
		||||
        SwitchHelper: PowerSwitchInfo<PcduSwitch> + PowerSwitcherCommandSender<PcduSwitch>,
 | 
			
		||||
    > ModeChild for MgmHandlerLis3Mdl<ComInterface, SwitchHelper>
 | 
			
		||||
{
 | 
			
		||||
    type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
 | 
			
		||||
 | 
			
		||||
    fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
 | 
			
		||||
        self.mode_node.add_message_target(id, reply_sender);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::{
 | 
			
		||||
        collections::HashMap,
 | 
			
		||||
        sync::{mpsc, Arc},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    use satrs::{
 | 
			
		||||
        mode::{ModeReply, ModeRequest},
 | 
			
		||||
        mode_tree::ModeParent,
 | 
			
		||||
        power::SwitchStateBinary,
 | 
			
		||||
        request::{GenericMessage, UniqueApidTargetId},
 | 
			
		||||
        tmtc::PacketAsVec,
 | 
			
		||||
        ComponentId,
 | 
			
		||||
    };
 | 
			
		||||
    use satrs_example::ids::{acs::ASSEMBLY, Apid};
 | 
			
		||||
    use satrs_minisim::acs::lis3mdl::MgmLis3RawValues;
 | 
			
		||||
 | 
			
		||||
    use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest};
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[derive(Default)]
 | 
			
		||||
    pub struct TestSpiInterface {
 | 
			
		||||
        pub call_count: u32,
 | 
			
		||||
        pub next_mgm_data: MgmLis3RawValues,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl SpiInterface for TestSpiInterface {
 | 
			
		||||
        type Error = ();
 | 
			
		||||
 | 
			
		||||
        fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
 | 
			
		||||
            rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
 | 
			
		||||
                .copy_from_slice(&self.next_mgm_data.x.to_le_bytes());
 | 
			
		||||
            rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
 | 
			
		||||
                .copy_from_slice(&self.next_mgm_data.y.to_le_bytes());
 | 
			
		||||
            rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
 | 
			
		||||
                .copy_from_slice(&self.next_mgm_data.z.to_le_bytes());
 | 
			
		||||
            self.call_count += 1;
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub struct MgmTestbench {
 | 
			
		||||
        pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
 | 
			
		||||
        pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
 | 
			
		||||
        pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
 | 
			
		||||
        pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
 | 
			
		||||
        pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
 | 
			
		||||
        pub tm_rx: mpsc::Receiver<PacketAsVec>,
 | 
			
		||||
        pub handler: MgmHandlerLis3Mdl<TestSpiInterface, TestSwitchHelper>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Default)]
 | 
			
		||||
    pub struct MgmAssemblyMock(
 | 
			
		||||
        pub HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    impl ModeNode for MgmAssemblyMock {
 | 
			
		||||
        fn id(&self) -> satrs::ComponentId {
 | 
			
		||||
            PUS_MODE.into()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl ModeParent for MgmAssemblyMock {
 | 
			
		||||
        type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
 | 
			
		||||
 | 
			
		||||
        fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
 | 
			
		||||
            self.0.insert(id, request_sender);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Default)]
 | 
			
		||||
    pub struct PusMock {
 | 
			
		||||
        pub request_sender_map: HashMap<ComponentId, mpsc::SyncSender<GenericMessage<ModeRequest>>>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl ModeNode for PusMock {
 | 
			
		||||
        fn id(&self) -> satrs::ComponentId {
 | 
			
		||||
            PUS_MODE.into()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl ModeParent for PusMock {
 | 
			
		||||
        type Sender = mpsc::SyncSender<GenericMessage<ModeRequest>>;
 | 
			
		||||
 | 
			
		||||
        fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) {
 | 
			
		||||
            self.request_sender_map.insert(id, request_sender);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl MgmTestbench {
 | 
			
		||||
        pub fn new() -> Self {
 | 
			
		||||
            let (request_tx, request_rx) = mpsc::sync_channel(5);
 | 
			
		||||
            let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5);
 | 
			
		||||
            let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5);
 | 
			
		||||
            let id = UniqueApidTargetId::new(Apid::Acs.raw_value(), 1);
 | 
			
		||||
            let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx);
 | 
			
		||||
            let (composite_request_tx, composite_request_rx) = mpsc::channel();
 | 
			
		||||
            let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
 | 
			
		||||
            let (tm_tx, tm_rx) = mpsc::sync_channel(10);
 | 
			
		||||
            let tm_sender = TmTcSender::Heap(tm_tx);
 | 
			
		||||
            let shared_mgm_set = Arc::default();
 | 
			
		||||
            let mut handler = MgmHandlerLis3Mdl::new(
 | 
			
		||||
                id,
 | 
			
		||||
                "TEST_MGM",
 | 
			
		||||
                mode_node,
 | 
			
		||||
                composite_request_rx,
 | 
			
		||||
                hk_reply_tx,
 | 
			
		||||
                TestSwitchHelper::default(),
 | 
			
		||||
                tm_sender,
 | 
			
		||||
                TestSpiInterface::default(),
 | 
			
		||||
                shared_mgm_set,
 | 
			
		||||
            );
 | 
			
		||||
            handler.add_mode_parent(PUS_MODE.into(), reply_tx_to_pus);
 | 
			
		||||
            handler.add_mode_parent(ASSEMBLY.into(), reply_tx_to_parent);
 | 
			
		||||
            Self {
 | 
			
		||||
                mode_request_tx: request_tx,
 | 
			
		||||
                mode_reply_rx_to_pus: reply_rx_to_pus,
 | 
			
		||||
                mode_reply_rx_to_parent: reply_rx_to_parent,
 | 
			
		||||
                composite_request_tx,
 | 
			
		||||
                handler,
 | 
			
		||||
                tm_rx,
 | 
			
		||||
                hk_reply_rx,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_basic_handler() {
 | 
			
		||||
        let mut testbench = MgmTestbench::new();
 | 
			
		||||
        assert_eq!(testbench.handler.com_interface.call_count, 0);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Off as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
 | 
			
		||||
        testbench.handler.periodic_operation();
 | 
			
		||||
        // Handler is OFF, no changes expected.
 | 
			
		||||
        assert_eq!(testbench.handler.com_interface.call_count, 0);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Off as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_normal_handler() {
 | 
			
		||||
        let mut testbench = MgmTestbench::new();
 | 
			
		||||
        testbench
 | 
			
		||||
            .mode_request_tx
 | 
			
		||||
            .send(GenericMessage::new(
 | 
			
		||||
                MessageMetadata::new(0, PUS_MODE.id()),
 | 
			
		||||
                ModeRequest::SetMode {
 | 
			
		||||
                    mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
 | 
			
		||||
                    forced: false,
 | 
			
		||||
                },
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send mode request");
 | 
			
		||||
        testbench.handler.periodic_operation();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Normal as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
 | 
			
		||||
 | 
			
		||||
        // Verify power switch handling.
 | 
			
		||||
        let mut switch_requests = testbench.handler.switch_helper.switch_requests.borrow_mut();
 | 
			
		||||
        assert_eq!(switch_requests.len(), 1);
 | 
			
		||||
        let switch_req = switch_requests.pop_front().expect("no switch request");
 | 
			
		||||
        assert_eq!(switch_req.target_state, SwitchStateBinary::On);
 | 
			
		||||
        assert_eq!(switch_req.switch_id, PcduSwitch::Mgm);
 | 
			
		||||
        let mut switch_info_requests = testbench
 | 
			
		||||
            .handler
 | 
			
		||||
            .switch_helper
 | 
			
		||||
            .switch_info_requests
 | 
			
		||||
            .borrow_mut();
 | 
			
		||||
        assert_eq!(switch_info_requests.len(), 1);
 | 
			
		||||
        let switch_info_req = switch_info_requests.pop_front().expect("no switch request");
 | 
			
		||||
        assert_eq!(switch_info_req, PcduSwitch::Mgm);
 | 
			
		||||
 | 
			
		||||
        let mode_reply = testbench
 | 
			
		||||
            .mode_reply_rx_to_pus
 | 
			
		||||
            .try_recv()
 | 
			
		||||
            .expect("no mode reply generated");
 | 
			
		||||
        match mode_reply.message {
 | 
			
		||||
            ModeReply::ModeReply(mode) => {
 | 
			
		||||
                assert_eq!(mode.mode(), DeviceMode::Normal as u32);
 | 
			
		||||
                assert_eq!(mode.submode(), 0);
 | 
			
		||||
            }
 | 
			
		||||
            _ => panic!("unexpected mode reply"),
 | 
			
		||||
        }
 | 
			
		||||
        // The device should have been polled once.
 | 
			
		||||
        assert_eq!(testbench.handler.com_interface.call_count, 1);
 | 
			
		||||
        let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
 | 
			
		||||
        assert!(mgm_set.x < 0.001);
 | 
			
		||||
        assert!(mgm_set.y < 0.001);
 | 
			
		||||
        assert!(mgm_set.z < 0.001);
 | 
			
		||||
        assert!(mgm_set.valid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_normal_handler_mgm_set_conversion() {
 | 
			
		||||
        let mut testbench = MgmTestbench::new();
 | 
			
		||||
        let raw_values = MgmLis3RawValues {
 | 
			
		||||
            x: 1000,
 | 
			
		||||
            y: -1000,
 | 
			
		||||
            z: 1000,
 | 
			
		||||
        };
 | 
			
		||||
        testbench.handler.com_interface.next_mgm_data = raw_values;
 | 
			
		||||
        testbench
 | 
			
		||||
            .mode_request_tx
 | 
			
		||||
            .send(GenericMessage::new(
 | 
			
		||||
                MessageMetadata::new(0, PUS_MODE.id()),
 | 
			
		||||
                ModeRequest::SetMode {
 | 
			
		||||
                    mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
 | 
			
		||||
                    forced: false,
 | 
			
		||||
                },
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send mode request");
 | 
			
		||||
        testbench.handler.periodic_operation();
 | 
			
		||||
        let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
 | 
			
		||||
        let expected_x =
 | 
			
		||||
            raw_values.x as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        let expected_y =
 | 
			
		||||
            raw_values.y as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        let expected_z =
 | 
			
		||||
            raw_values.z as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
 | 
			
		||||
        let x_diff = (mgm_set.x - expected_x).abs();
 | 
			
		||||
        let y_diff = (mgm_set.y - expected_y).abs();
 | 
			
		||||
        let z_diff = (mgm_set.z - expected_z).abs();
 | 
			
		||||
        assert!(x_diff < 0.001, "x diff too large: {}", x_diff);
 | 
			
		||||
        assert!(y_diff < 0.001, "y diff too large: {}", y_diff);
 | 
			
		||||
        assert!(z_diff < 0.001, "z diff too large: {}", z_diff);
 | 
			
		||||
        assert!(mgm_set.valid);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,4 @@
 | 
			
		||||
pub mod assembly;
 | 
			
		||||
pub mod ctrl;
 | 
			
		||||
pub mod mgm;
 | 
			
		||||
pub mod subsystem;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								satrs-example/src/acs/subsystem.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								satrs-example/src/acs/subsystem.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
// TODO: Write subsystem
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
use arbitrary_int::u11;
 | 
			
		||||
use satrs::pus::verification::RequestId;
 | 
			
		||||
use satrs::spacepackets::ecss::tc::PusTcCreator;
 | 
			
		||||
use satrs::spacepackets::ecss::tm::PusTmReader;
 | 
			
		||||
use satrs::{
 | 
			
		||||
    spacepackets::ecss::{PusPacket, WritablePusPacket},
 | 
			
		||||
    spacepackets::SpHeader,
 | 
			
		||||
};
 | 
			
		||||
use satrs::spacepackets::ecss::CreatorConfig;
 | 
			
		||||
use satrs::spacepackets::SpHeader;
 | 
			
		||||
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
 | 
			
		||||
use std::net::{IpAddr, SocketAddr, UdpSocket};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
@@ -12,7 +11,13 @@ use std::time::Duration;
 | 
			
		||||
fn main() {
 | 
			
		||||
    let mut buf = [0; 32];
 | 
			
		||||
    let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
 | 
			
		||||
    let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
 | 
			
		||||
    let pus_tc = PusTcCreator::new_simple(
 | 
			
		||||
        SpHeader::new_from_apid(u11::new(0x02)),
 | 
			
		||||
        17,
 | 
			
		||||
        1,
 | 
			
		||||
        &[],
 | 
			
		||||
        CreatorConfig::default(),
 | 
			
		||||
    );
 | 
			
		||||
    let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
 | 
			
		||||
    let tc_req_id = RequestId::new(&pus_tc);
 | 
			
		||||
    println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
 | 
			
		||||
@@ -29,7 +34,7 @@ fn main() {
 | 
			
		||||
        let res = client.recv(&mut buf);
 | 
			
		||||
        match res {
 | 
			
		||||
            Ok(_len) => {
 | 
			
		||||
                let (pus_tm, size) = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
 | 
			
		||||
                let pus_tm = PusTmReader::new(&buf, 7).expect("Parsing PUS TM failed");
 | 
			
		||||
                if pus_tm.service() == 17 && pus_tm.subservice() == 2 {
 | 
			
		||||
                    println!("Received PUS Ping Reply TM[17,2]")
 | 
			
		||||
                } else if pus_tm.service() == 1 {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
use crossbeam_channel::{bounded, Receiver, Sender};
 | 
			
		||||
use std::sync::atomic::{AtomicU16, Ordering};
 | 
			
		||||
use std::thread;
 | 
			
		||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
 | 
			
		||||
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
 | 
			
		||||
 | 
			
		||||
trait FieldDataProvider: Send {
 | 
			
		||||
    fn get_data(&self) -> &[u8];
 | 
			
		||||
@@ -35,7 +35,7 @@ struct ExampleMgmSet {
 | 
			
		||||
    temperature: u16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(FromBytes, AsBytes, Unaligned)]
 | 
			
		||||
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
struct ExampleMgmSetZc {
 | 
			
		||||
    mgm_vec: [u8; 12],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use arbitrary_int::u11;
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use satrs::{
 | 
			
		||||
    res_code::ResultU16,
 | 
			
		||||
@@ -10,7 +11,7 @@ use strum::IntoEnumIterator;
 | 
			
		||||
 | 
			
		||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
 | 
			
		||||
use satrs::{
 | 
			
		||||
    events::{EventU32TypedSev, SeverityInfo},
 | 
			
		||||
    events_legacy::{EventU32TypedSev, SeverityInfo},
 | 
			
		||||
    pool::{StaticMemoryPool, StaticPoolConfig},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -38,20 +39,19 @@ pub enum GroupId {
 | 
			
		||||
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
 | 
			
		||||
pub const SERVER_PORT: u16 = 7301;
 | 
			
		||||
 | 
			
		||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
 | 
			
		||||
    EventU32TypedSev::<SeverityInfo>::const_new(0, 0);
 | 
			
		||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(0, 0);
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
 | 
			
		||||
        let mut set = HashSet::new();
 | 
			
		||||
        for id in components::Apid::iter() {
 | 
			
		||||
            set.insert(PacketId::new(PacketType::Tc, true, id as u16));
 | 
			
		||||
        for id in crate::ids::Apid::iter() {
 | 
			
		||||
            set.insert(PacketId::new(PacketType::Tc, true, u11::new(id as u16)));
 | 
			
		||||
        }
 | 
			
		||||
        set
 | 
			
		||||
    };
 | 
			
		||||
    pub static ref APID_VALIDATOR: HashSet<u16> = {
 | 
			
		||||
        let mut set = HashSet::new();
 | 
			
		||||
        for id in components::Apid::iter() {
 | 
			
		||||
        for id in crate::ids::Apid::iter() {
 | 
			
		||||
            set.insert(id as u16);
 | 
			
		||||
        }
 | 
			
		||||
        set
 | 
			
		||||
@@ -123,67 +123,16 @@ pub mod mode_err {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub mod components {
 | 
			
		||||
    use satrs::request::UniqueApidTargetId;
 | 
			
		||||
    use strum::EnumIter;
 | 
			
		||||
    use satrs::ComponentId;
 | 
			
		||||
 | 
			
		||||
    #[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
 | 
			
		||||
    pub enum Apid {
 | 
			
		||||
        Sched = 1,
 | 
			
		||||
        GenericPus = 2,
 | 
			
		||||
        Acs = 3,
 | 
			
		||||
        Cfdp = 4,
 | 
			
		||||
        Tmtc = 5,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Component IDs for components with the PUS APID.
 | 
			
		||||
    #[derive(Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
    pub enum PusId {
 | 
			
		||||
        PusEventManagement = 0,
 | 
			
		||||
        PusRouting = 1,
 | 
			
		||||
        PusTest = 2,
 | 
			
		||||
        PusAction = 3,
 | 
			
		||||
        PusMode = 4,
 | 
			
		||||
        PusHk = 5,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
    pub enum AcsId {
 | 
			
		||||
        Mgm0 = 0,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
    pub enum TmtcId {
 | 
			
		||||
        UdpServer = 0,
 | 
			
		||||
        TcpServer = 1,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
 | 
			
		||||
    pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
 | 
			
		||||
    pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
 | 
			
		||||
    pub const PUS_TEST_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
 | 
			
		||||
    pub const PUS_MODE_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
 | 
			
		||||
    pub const PUS_HK_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
 | 
			
		||||
    pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::Sched as u16, 0);
 | 
			
		||||
    pub const MGM_HANDLER_0: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
 | 
			
		||||
    pub const UDP_SERVER: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
 | 
			
		||||
    pub const TCP_SERVER: UniqueApidTargetId =
 | 
			
		||||
        UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
 | 
			
		||||
    pub const NO_SENDER: ComponentId = ComponentId::MAX;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub mod pool {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) {
 | 
			
		||||
        (
 | 
			
		||||
            StaticMemoryPool::new(StaticPoolConfig::new(
 | 
			
		||||
            StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
 | 
			
		||||
                vec![
 | 
			
		||||
                    (30, 32),
 | 
			
		||||
                    (15, 64),
 | 
			
		||||
@@ -194,7 +143,7 @@ pub mod pool {
 | 
			
		||||
                ],
 | 
			
		||||
                true,
 | 
			
		||||
            )),
 | 
			
		||||
            StaticMemoryPool::new(StaticPoolConfig::new(
 | 
			
		||||
            StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
 | 
			
		||||
                vec![
 | 
			
		||||
                    (30, 32),
 | 
			
		||||
                    (15, 64),
 | 
			
		||||
@@ -209,7 +158,7 @@ pub mod pool {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn create_sched_tc_pool() -> StaticMemoryPool {
 | 
			
		||||
        StaticMemoryPool::new(StaticPoolConfig::new(
 | 
			
		||||
        StaticMemoryPool::new(StaticPoolConfig::new_from_subpool_cfg_tuples(
 | 
			
		||||
            vec![
 | 
			
		||||
                (30, 32),
 | 
			
		||||
                (15, 64),
 | 
			
		||||
@@ -225,7 +174,7 @@ pub mod pool {
 | 
			
		||||
 | 
			
		||||
pub mod tasks {
 | 
			
		||||
    pub const FREQ_MS_UDP_TMTC: u64 = 200;
 | 
			
		||||
    pub const FREQ_MS_EVENT_HANDLING: u64 = 400;
 | 
			
		||||
    pub const FREQ_MS_AOCS: u64 = 500;
 | 
			
		||||
    pub const FREQ_MS_PUS_STACK: u64 = 200;
 | 
			
		||||
    pub const SIM_CLIENT_IDLE_DELAY_MS: u64 = 5;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										197
									
								
								satrs-example/src/eps/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								satrs-example/src/eps/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,197 @@
 | 
			
		||||
use derive_new::new;
 | 
			
		||||
use std::{cell::RefCell, collections::VecDeque, sync::mpsc, time::Duration};
 | 
			
		||||
 | 
			
		||||
use satrs::{
 | 
			
		||||
    power::{
 | 
			
		||||
        PowerSwitchInfo, PowerSwitcherCommandSender, SwitchRequest, SwitchState, SwitchStateBinary,
 | 
			
		||||
    },
 | 
			
		||||
    queue::GenericSendError,
 | 
			
		||||
    request::{GenericMessage, MessageMetadata},
 | 
			
		||||
};
 | 
			
		||||
use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
use self::pcdu::SharedSwitchSet;
 | 
			
		||||
 | 
			
		||||
pub mod pcdu;
 | 
			
		||||
 | 
			
		||||
#[derive(new, Clone)]
 | 
			
		||||
pub struct PowerSwitchHelper {
 | 
			
		||||
    switcher_tx: mpsc::SyncSender<GenericMessage<SwitchRequest>>,
 | 
			
		||||
    shared_switch_set: SharedSwitchSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
pub enum SwitchCommandingError {
 | 
			
		||||
    #[error("send error: {0}")]
 | 
			
		||||
    Send(#[from] GenericSendError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
pub enum SwitchInfoError {
 | 
			
		||||
    /// This is a configuration error which should not occur.
 | 
			
		||||
    #[error("switch ID not in map")]
 | 
			
		||||
    SwitchIdNotInMap(PcduSwitch),
 | 
			
		||||
    #[error("switch set invalid")]
 | 
			
		||||
    SwitchSetInvalid,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PowerSwitchInfo<PcduSwitch> for PowerSwitchHelper {
 | 
			
		||||
    type Error = SwitchInfoError;
 | 
			
		||||
 | 
			
		||||
    fn switch_state(
 | 
			
		||||
        &self,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<satrs::power::SwitchState, Self::Error> {
 | 
			
		||||
        let switch_set = self
 | 
			
		||||
            .shared_switch_set
 | 
			
		||||
            .lock()
 | 
			
		||||
            .expect("failed to lock switch set");
 | 
			
		||||
        if !switch_set.valid {
 | 
			
		||||
            return Err(SwitchInfoError::SwitchSetInvalid);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(state) = switch_set.switch_map.get(&switch_id) {
 | 
			
		||||
            return Ok(*state);
 | 
			
		||||
        }
 | 
			
		||||
        Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn switch_delay_ms(&self) -> Duration {
 | 
			
		||||
        // Here, we could set device specific switch delays theoretically. Set it to this value
 | 
			
		||||
        // for now.
 | 
			
		||||
        Duration::from_millis(1000)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
 | 
			
		||||
    type Error = SwitchCommandingError;
 | 
			
		||||
 | 
			
		||||
    fn send_switch_on_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
        requestor_info: satrs::request::MessageMetadata,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        self.switcher_tx
 | 
			
		||||
            .send_switch_on_cmd(requestor_info, switch_id)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_switch_off_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
        requestor_info: satrs::request::MessageMetadata,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        self.switcher_tx
 | 
			
		||||
            .send_switch_off_cmd(requestor_info, switch_id)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
pub struct SwitchRequestInfo {
 | 
			
		||||
    pub requestor_info: MessageMetadata,
 | 
			
		||||
    pub switch_id: PcduSwitch,
 | 
			
		||||
    pub target_state: satrs::power::SwitchStateBinary,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test switch helper which can be used for unittests.
 | 
			
		||||
pub struct TestSwitchHelper {
 | 
			
		||||
    pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
 | 
			
		||||
    pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub switch_delay_request_count: u32,
 | 
			
		||||
    pub next_switch_delay: Duration,
 | 
			
		||||
    pub switch_map: RefCell<SwitchMapWrapper>,
 | 
			
		||||
    pub switch_map_valid: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for TestSwitchHelper {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            switch_requests: Default::default(),
 | 
			
		||||
            switch_info_requests: Default::default(),
 | 
			
		||||
            switch_delay_request_count: Default::default(),
 | 
			
		||||
            next_switch_delay: Duration::from_millis(1000),
 | 
			
		||||
            switch_map: Default::default(),
 | 
			
		||||
            switch_map_valid: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PowerSwitchInfo<PcduSwitch> for TestSwitchHelper {
 | 
			
		||||
    type Error = SwitchInfoError;
 | 
			
		||||
 | 
			
		||||
    fn switch_state(
 | 
			
		||||
        &self,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<satrs::power::SwitchState, Self::Error> {
 | 
			
		||||
        let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut();
 | 
			
		||||
        switch_info_requests_mut.push_back(switch_id);
 | 
			
		||||
        if !self.switch_map_valid {
 | 
			
		||||
            return Err(SwitchInfoError::SwitchSetInvalid);
 | 
			
		||||
        }
 | 
			
		||||
        let switch_map_mut = self.switch_map.borrow_mut();
 | 
			
		||||
        if let Some(state) = switch_map_mut.0.get(&switch_id) {
 | 
			
		||||
            return Ok(*state);
 | 
			
		||||
        }
 | 
			
		||||
        Err(SwitchInfoError::SwitchIdNotInMap(switch_id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn switch_delay_ms(&self) -> Duration {
 | 
			
		||||
        self.next_switch_delay
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PowerSwitcherCommandSender<PcduSwitch> for TestSwitchHelper {
 | 
			
		||||
    type Error = SwitchCommandingError;
 | 
			
		||||
 | 
			
		||||
    fn send_switch_on_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
        requestor_info: MessageMetadata,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        let mut switch_requests_mut = self.switch_requests.borrow_mut();
 | 
			
		||||
        switch_requests_mut.push_back(SwitchRequestInfo {
 | 
			
		||||
            requestor_info,
 | 
			
		||||
            switch_id,
 | 
			
		||||
            target_state: SwitchStateBinary::On,
 | 
			
		||||
        });
 | 
			
		||||
        // By default, the test helper immediately acknowledges the switch request by setting
 | 
			
		||||
        // the appropriate switch state in the internal switch map.
 | 
			
		||||
        let mut switch_map_mut = self.switch_map.borrow_mut();
 | 
			
		||||
        if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
 | 
			
		||||
            *switch_state = SwitchState::On;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_switch_off_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
        requestor_info: MessageMetadata,
 | 
			
		||||
        switch_id: PcduSwitch,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        let mut switch_requests_mut = self.switch_requests.borrow_mut();
 | 
			
		||||
        switch_requests_mut.push_back(SwitchRequestInfo {
 | 
			
		||||
            requestor_info,
 | 
			
		||||
            switch_id,
 | 
			
		||||
            target_state: SwitchStateBinary::Off,
 | 
			
		||||
        });
 | 
			
		||||
        // By default, the test helper immediately acknowledges the switch request by setting
 | 
			
		||||
        // the appropriate switch state in the internal switch map.
 | 
			
		||||
        let mut switch_map_mut = self.switch_map.borrow_mut();
 | 
			
		||||
        if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) {
 | 
			
		||||
            *switch_state = SwitchState::Off;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl TestSwitchHelper {
 | 
			
		||||
    // Helper function which can be used to force a switch to another state for test purposes.
 | 
			
		||||
    pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) {
 | 
			
		||||
        self.switch_map.get_mut().0.insert(switch, state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										747
									
								
								satrs-example/src/eps/pcdu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										747
									
								
								satrs-example/src/eps/pcdu.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,747 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    cell::RefCell,
 | 
			
		||||
    collections::VecDeque,
 | 
			
		||||
    sync::{mpsc, Arc, Mutex},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use derive_new::new;
 | 
			
		||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
 | 
			
		||||
use satrs::{
 | 
			
		||||
    hk::{HkRequest, HkRequestVariant},
 | 
			
		||||
    mode::{
 | 
			
		||||
        ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler,
 | 
			
		||||
        ModeRequestHandlerMpscBounded,
 | 
			
		||||
    },
 | 
			
		||||
    mode_tree::{ModeChild, ModeNode},
 | 
			
		||||
    power::SwitchRequest,
 | 
			
		||||
    pus::{EcssTmSender, PusTmVariant},
 | 
			
		||||
    queue::GenericSendError,
 | 
			
		||||
    request::{GenericMessage, MessageMetadata, UniqueApidTargetId},
 | 
			
		||||
    spacepackets::ByteConversionError,
 | 
			
		||||
};
 | 
			
		||||
use satrs_example::{
 | 
			
		||||
    config::components::NO_SENDER,
 | 
			
		||||
    ids::{eps::PCDU, generic_pus::PUS_MODE},
 | 
			
		||||
    DeviceMode, TimestampHelper,
 | 
			
		||||
};
 | 
			
		||||
use satrs_minisim::{
 | 
			
		||||
    eps::{
 | 
			
		||||
        PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper, SwitchMapWrapper,
 | 
			
		||||
    },
 | 
			
		||||
    SerializableSimMsgPayload, SimReply, SimRequest,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    hk::PusHkHelper,
 | 
			
		||||
    pus::hk::{HkReply, HkReplyVariant},
 | 
			
		||||
    requests::CompositeRequest,
 | 
			
		||||
    tmtc::sender::TmTcSender,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub trait SerialInterface {
 | 
			
		||||
    type Error: core::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
    /// Send some data via the serial interface.
 | 
			
		||||
    fn send(&self, data: &[u8]) -> Result<(), Self::Error>;
 | 
			
		||||
    /// Receive all replies received on the serial interface so far. This function takes a closure
 | 
			
		||||
    /// and call its for each received packet, passing the received packet into it.
 | 
			
		||||
    fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: ReplyHandler,
 | 
			
		||||
    ) -> Result<(), Self::Error>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
pub struct SerialInterfaceToSim {
 | 
			
		||||
    pub sim_request_tx: mpsc::Sender<SimRequest>,
 | 
			
		||||
    pub sim_reply_rx: mpsc::Receiver<SimReply>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
 | 
			
		||||
#[repr(u32)]
 | 
			
		||||
pub enum SetId {
 | 
			
		||||
    SwitcherSet = 0,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SerialInterface for SerialInterfaceToSim {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        let request: PcduRequest = serde_json::from_slice(data).expect("expected a PCDU request");
 | 
			
		||||
        self.sim_request_tx
 | 
			
		||||
            .send(SimRequest::new_with_epoch_time(request))
 | 
			
		||||
            .expect("failed to send request to simulation");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
 | 
			
		||||
        &self,
 | 
			
		||||
        mut f: ReplyHandler,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        loop {
 | 
			
		||||
            match self.sim_reply_rx.try_recv() {
 | 
			
		||||
                Ok(reply) => {
 | 
			
		||||
                    let reply = serde_json::to_string(&reply).unwrap();
 | 
			
		||||
                    f(reply.as_bytes());
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    mpsc::TryRecvError::Empty => break,
 | 
			
		||||
                    mpsc::TryRecvError::Disconnected => {
 | 
			
		||||
                        log::warn!("sim reply sender has disconnected");
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct SerialInterfaceDummy {
 | 
			
		||||
    // Need interior mutability here for both fields.
 | 
			
		||||
    pub switch_map: RefCell<SwitchMapBinaryWrapper>,
 | 
			
		||||
    pub reply_deque: RefCell<VecDeque<SimReply>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SerialInterface for SerialInterfaceDummy {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        let pcdu_req: PcduRequest = serde_json::from_slice(data).unwrap();
 | 
			
		||||
        let switch_map_mut = &mut self.switch_map.borrow_mut().0;
 | 
			
		||||
        match pcdu_req {
 | 
			
		||||
            PcduRequest::SwitchDevice { switch, state } => {
 | 
			
		||||
                match switch_map_mut.entry(switch) {
 | 
			
		||||
                    std::collections::hash_map::Entry::Occupied(mut val) => {
 | 
			
		||||
                        *val.get_mut() = state;
 | 
			
		||||
                    }
 | 
			
		||||
                    std::collections::hash_map::Entry::Vacant(vacant) => {
 | 
			
		||||
                        vacant.insert(state);
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            PcduRequest::RequestSwitchInfo => {
 | 
			
		||||
                let mut reply_deque_mut = self.reply_deque.borrow_mut();
 | 
			
		||||
                reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo(
 | 
			
		||||
                    switch_map_mut.clone(),
 | 
			
		||||
                )));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
 | 
			
		||||
        &self,
 | 
			
		||||
        mut f: ReplyHandler,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        if self.reply_queue_empty() {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
        loop {
 | 
			
		||||
            let reply = self.get_next_reply_as_string();
 | 
			
		||||
            f(reply.as_bytes());
 | 
			
		||||
            if self.reply_queue_empty() {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SerialInterfaceDummy {
 | 
			
		||||
    fn get_next_reply_as_string(&self) -> String {
 | 
			
		||||
        let mut reply_deque_mut = self.reply_deque.borrow_mut();
 | 
			
		||||
        let next_reply = reply_deque_mut.pop_front().unwrap();
 | 
			
		||||
        serde_json::to_string(&next_reply).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn reply_queue_empty(&self) -> bool {
 | 
			
		||||
        self.reply_deque.borrow().is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum SerialSimInterfaceWrapper {
 | 
			
		||||
    Dummy(SerialInterfaceDummy),
 | 
			
		||||
    Sim(SerialInterfaceToSim),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SerialInterface for SerialSimInterfaceWrapper {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            SerialSimInterfaceWrapper::Dummy(dummy) => dummy.send(data),
 | 
			
		||||
            SerialSimInterfaceWrapper::Sim(sim) => sim.send(data),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: ReplyHandler,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            SerialSimInterfaceWrapper::Dummy(dummy) => dummy.try_recv_replies(f),
 | 
			
		||||
            SerialSimInterfaceWrapper::Sim(sim) => sim.try_recv_replies(f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 | 
			
		||||
pub enum OpCode {
 | 
			
		||||
    RegularOp = 0,
 | 
			
		||||
    PollAndRecvReplies = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
 | 
			
		||||
pub struct SwitchSet {
 | 
			
		||||
    pub valid: bool,
 | 
			
		||||
    pub switch_map: SwitchMap,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type SharedSwitchSet = Arc<Mutex<SwitchSet>>;
 | 
			
		||||
 | 
			
		||||
/// Example PCDU device handler.
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
pub struct PcduHandler<ComInterface: SerialInterface> {
 | 
			
		||||
    id: UniqueApidTargetId,
 | 
			
		||||
    dev_str: &'static str,
 | 
			
		||||
    mode_node: ModeRequestHandlerMpscBounded,
 | 
			
		||||
    composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
 | 
			
		||||
    hk_reply_tx: mpsc::SyncSender<GenericMessage<HkReply>>,
 | 
			
		||||
    switch_request_rx: mpsc::Receiver<GenericMessage<SwitchRequest>>,
 | 
			
		||||
    tm_sender: TmTcSender,
 | 
			
		||||
    pub com_interface: ComInterface,
 | 
			
		||||
    shared_switch_map: Arc<Mutex<SwitchSet>>,
 | 
			
		||||
    #[new(value = "PusHkHelper::new(id)")]
 | 
			
		||||
    hk_helper: PusHkHelper,
 | 
			
		||||
    #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
 | 
			
		||||
    mode_and_submode: ModeAndSubmode,
 | 
			
		||||
    #[new(default)]
 | 
			
		||||
    stamp_helper: TimestampHelper,
 | 
			
		||||
    #[new(value = "[0; 256]")]
 | 
			
		||||
    tm_buf: [u8; 256],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SerialInterface> PcduHandler<ComInterface> {
 | 
			
		||||
    pub fn periodic_operation(&mut self, op_code: OpCode) {
 | 
			
		||||
        match op_code {
 | 
			
		||||
            OpCode::RegularOp => {
 | 
			
		||||
                self.stamp_helper.update_from_now();
 | 
			
		||||
                // Handle requests.
 | 
			
		||||
                self.handle_composite_requests();
 | 
			
		||||
                self.handle_mode_requests();
 | 
			
		||||
                self.handle_switch_requests();
 | 
			
		||||
                // Poll the switch states and/or telemetry regularly here.
 | 
			
		||||
                if self.mode() == DeviceMode::Normal as u32 || self.mode() == DeviceMode::On as u32
 | 
			
		||||
                {
 | 
			
		||||
                    self.handle_periodic_commands();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            OpCode::PollAndRecvReplies => {
 | 
			
		||||
                self.poll_and_handle_replies();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_composite_requests(&mut self) {
 | 
			
		||||
        loop {
 | 
			
		||||
            match self.composite_request_rx.try_recv() {
 | 
			
		||||
                Ok(ref msg) => match &msg.message {
 | 
			
		||||
                    CompositeRequest::Hk(hk_request) => {
 | 
			
		||||
                        self.handle_hk_request(&msg.requestor_info, hk_request)
 | 
			
		||||
                    }
 | 
			
		||||
                    // TODO: This object does not have actions (yet).. Still send back completion failure
 | 
			
		||||
                    // reply.
 | 
			
		||||
                    CompositeRequest::Action(_action_req) => {}
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    if e != mpsc::TryRecvError::Empty {
 | 
			
		||||
                        log::warn!(
 | 
			
		||||
                            "{}: failed to receive composite request: {:?}",
 | 
			
		||||
                            self.dev_str,
 | 
			
		||||
                            e
 | 
			
		||||
                        );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
 | 
			
		||||
        match hk_request.variant {
 | 
			
		||||
            HkRequestVariant::OneShot => {
 | 
			
		||||
                if hk_request.unique_id == SetId::SwitcherSet as u32 {
 | 
			
		||||
                    if let Ok(hk_tm) = self.hk_helper.generate_hk_report_packet(
 | 
			
		||||
                        self.stamp_helper.stamp(),
 | 
			
		||||
                        SetId::SwitcherSet as u32,
 | 
			
		||||
                        &mut |hk_buf| {
 | 
			
		||||
                            // Send TM down as JSON.
 | 
			
		||||
                            let switch_map_snapshot = self
 | 
			
		||||
                                .shared_switch_map
 | 
			
		||||
                                .lock()
 | 
			
		||||
                                .expect("failed to lock switch map")
 | 
			
		||||
                                .clone();
 | 
			
		||||
                            let switch_map_json = serde_json::to_string(&switch_map_snapshot)
 | 
			
		||||
                                .expect("failed to serialize switch map");
 | 
			
		||||
                            if switch_map_json.len() > hk_buf.len() {
 | 
			
		||||
                                log::error!("switch map JSON too large for HK buffer");
 | 
			
		||||
                                return Err(ByteConversionError::ToSliceTooSmall {
 | 
			
		||||
                                    found: hk_buf.len(),
 | 
			
		||||
                                    expected: switch_map_json.len(),
 | 
			
		||||
                                });
 | 
			
		||||
                            }
 | 
			
		||||
                            Ok(switch_map_json.len())
 | 
			
		||||
                        },
 | 
			
		||||
                        &mut self.tm_buf,
 | 
			
		||||
                    ) {
 | 
			
		||||
                        self.tm_sender
 | 
			
		||||
                            .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
 | 
			
		||||
                            .expect("failed to send HK TM");
 | 
			
		||||
                        self.hk_reply_tx
 | 
			
		||||
                            .send(GenericMessage::new(
 | 
			
		||||
                                *requestor_info,
 | 
			
		||||
                                HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
 | 
			
		||||
                            ))
 | 
			
		||||
                            .expect("failed to send HK reply");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            HkRequestVariant::EnablePeriodic => todo!(),
 | 
			
		||||
            HkRequestVariant::DisablePeriodic => todo!(),
 | 
			
		||||
            HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_periodic_commands(&self) {
 | 
			
		||||
        let pcdu_req = PcduRequest::RequestSwitchInfo;
 | 
			
		||||
        let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
 | 
			
		||||
        if let Err(_e) = self.com_interface.send(pcdu_req_ser.as_bytes()) {
 | 
			
		||||
            log::warn!("polling PCDU switch info failed");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_mode_requests(&mut self) {
 | 
			
		||||
        loop {
 | 
			
		||||
            // TODO: Only allow one set mode request per cycle?
 | 
			
		||||
            match self.mode_node.try_recv_mode_request() {
 | 
			
		||||
                Ok(opt_msg) => {
 | 
			
		||||
                    if let Some(msg) = opt_msg {
 | 
			
		||||
                        let result = self.handle_mode_request(msg);
 | 
			
		||||
                        // TODO: Trigger event?
 | 
			
		||||
                        if result.is_err() {
 | 
			
		||||
                            log::warn!(
 | 
			
		||||
                                "{}: mode request failed with error {:?}",
 | 
			
		||||
                                self.dev_str,
 | 
			
		||||
                                result.err().unwrap()
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    satrs::queue::GenericReceiveError::Empty => {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    satrs::queue::GenericReceiveError::TxDisconnected(_) => {
 | 
			
		||||
                        log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_switch_requests(&mut self) {
 | 
			
		||||
        loop {
 | 
			
		||||
            match self.switch_request_rx.try_recv() {
 | 
			
		||||
                Ok(switch_req) => match PcduSwitch::try_from(switch_req.message.switch_id()) {
 | 
			
		||||
                    Ok(pcdu_switch) => {
 | 
			
		||||
                        let pcdu_req = PcduRequest::SwitchDevice {
 | 
			
		||||
                            switch: pcdu_switch,
 | 
			
		||||
                            state: switch_req.message.target_state(),
 | 
			
		||||
                        };
 | 
			
		||||
                        let pcdu_req_ser = serde_json::to_string(&pcdu_req).unwrap();
 | 
			
		||||
                        self.com_interface
 | 
			
		||||
                            .send(pcdu_req_ser.as_bytes())
 | 
			
		||||
                            .expect("failed to send switch request to PCDU");
 | 
			
		||||
                    }
 | 
			
		||||
                    Err(e) => todo!("failed to convert switch ID {:?} to typed PCDU switch", e),
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    mpsc::TryRecvError::Empty => break,
 | 
			
		||||
                    mpsc::TryRecvError::Disconnected => {
 | 
			
		||||
                        log::warn!("switch request receiver has disconnected");
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn poll_and_handle_replies(&mut self) {
 | 
			
		||||
        if let Err(e) = self.com_interface.try_recv_replies(|reply| {
 | 
			
		||||
            let sim_reply: SimReply = serde_json::from_slice(reply).expect("invalid reply format");
 | 
			
		||||
            let pcdu_reply = PcduReply::from_sim_message(&sim_reply).expect("invalid reply format");
 | 
			
		||||
            match pcdu_reply {
 | 
			
		||||
                PcduReply::SwitchInfo(switch_info) => {
 | 
			
		||||
                    let switch_map_wrapper =
 | 
			
		||||
                        SwitchMapWrapper::from_binary_switch_map_ref(&switch_info);
 | 
			
		||||
                    let mut shared_switch_map = self
 | 
			
		||||
                        .shared_switch_map
 | 
			
		||||
                        .lock()
 | 
			
		||||
                        .expect("failed to lock switch map");
 | 
			
		||||
                    shared_switch_map.switch_map = switch_map_wrapper.0;
 | 
			
		||||
                    shared_switch_map.valid = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }) {
 | 
			
		||||
            log::warn!("receiving PCDU replies failed: {e:?}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SerialInterface> ModeProvider for PcduHandler<ComInterface> {
 | 
			
		||||
    fn mode_and_submode(&self) -> ModeAndSubmode {
 | 
			
		||||
        self.mode_and_submode
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SerialInterface> ModeRequestHandler for PcduHandler<ComInterface> {
 | 
			
		||||
    type Error = ModeError;
 | 
			
		||||
    fn start_transition(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        requestor: MessageMetadata,
 | 
			
		||||
        mode_and_submode: ModeAndSubmode,
 | 
			
		||||
        _forced: bool,
 | 
			
		||||
    ) -> Result<(), satrs::mode::ModeError> {
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "{}: transitioning to mode {:?}",
 | 
			
		||||
            self.dev_str,
 | 
			
		||||
            mode_and_submode
 | 
			
		||||
        );
 | 
			
		||||
        self.mode_and_submode = mode_and_submode;
 | 
			
		||||
        if mode_and_submode.mode() == DeviceMode::Off as u32 {
 | 
			
		||||
            self.shared_switch_map.lock().unwrap().valid = false;
 | 
			
		||||
        }
 | 
			
		||||
        self.handle_mode_reached(Some(requestor))?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "{} announcing mode: {:?}",
 | 
			
		||||
            self.dev_str,
 | 
			
		||||
            self.mode_and_submode
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_mode_reached(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        requestor: Option<MessageMetadata>,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        self.announce_mode(requestor, false);
 | 
			
		||||
        if let Some(requestor) = requestor {
 | 
			
		||||
            if requestor.sender_id() == NO_SENDER {
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
            if requestor.sender_id() != PUS_MODE.id() {
 | 
			
		||||
                log::warn!(
 | 
			
		||||
                    "can not send back mode reply to sender {}",
 | 
			
		||||
                    requestor.sender_id()
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_mode_reply(
 | 
			
		||||
        &self,
 | 
			
		||||
        requestor: MessageMetadata,
 | 
			
		||||
        reply: ModeReply,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        if requestor.sender_id() != PUS_MODE.id() {
 | 
			
		||||
            log::warn!(
 | 
			
		||||
                "can not send back mode reply to sender {}",
 | 
			
		||||
                requestor.sender_id()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        self.mode_node
 | 
			
		||||
            .send_mode_reply(requestor, reply)
 | 
			
		||||
            .map_err(|_| GenericSendError::RxDisconnected)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_mode_info(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        _requestor_info: MessageMetadata,
 | 
			
		||||
        _info: ModeAndSubmode,
 | 
			
		||||
    ) -> Result<(), Self::Error> {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SerialInterface> ModeNode for PcduHandler<ComInterface> {
 | 
			
		||||
    fn id(&self) -> satrs::ComponentId {
 | 
			
		||||
        PCDU.into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<ComInterface: SerialInterface> ModeChild for PcduHandler<ComInterface> {
 | 
			
		||||
    type Sender = mpsc::SyncSender<GenericMessage<ModeReply>>;
 | 
			
		||||
 | 
			
		||||
    fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) {
 | 
			
		||||
        self.mode_node.add_message_target(id, reply_sender);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::sync::mpsc;
 | 
			
		||||
 | 
			
		||||
    use satrs::{
 | 
			
		||||
        mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec,
 | 
			
		||||
    };
 | 
			
		||||
    use satrs_example::ids::{self, Apid};
 | 
			
		||||
    use satrs_minisim::eps::SwitchMapBinary;
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[derive(Default)]
 | 
			
		||||
    pub struct SerialInterfaceTest {
 | 
			
		||||
        pub inner: SerialInterfaceDummy,
 | 
			
		||||
        pub send_queue: RefCell<VecDeque<Vec<u8>>>,
 | 
			
		||||
        pub reply_queue: RefCell<VecDeque<String>>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl SerialInterface for SerialInterfaceTest {
 | 
			
		||||
        type Error = ();
 | 
			
		||||
 | 
			
		||||
        fn send(&self, data: &[u8]) -> Result<(), Self::Error> {
 | 
			
		||||
            let mut send_queue_mut = self.send_queue.borrow_mut();
 | 
			
		||||
            send_queue_mut.push_back(data.to_vec());
 | 
			
		||||
            self.inner.send(data)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn try_recv_replies<ReplyHandler: FnMut(&[u8])>(
 | 
			
		||||
            &self,
 | 
			
		||||
            mut f: ReplyHandler,
 | 
			
		||||
        ) -> Result<(), Self::Error> {
 | 
			
		||||
            if self.inner.reply_queue_empty() {
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
            loop {
 | 
			
		||||
                let reply = self.inner.get_next_reply_as_string();
 | 
			
		||||
                self.reply_queue.borrow_mut().push_back(reply.clone());
 | 
			
		||||
                f(reply.as_bytes());
 | 
			
		||||
                if self.inner.reply_queue_empty() {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub struct PcduTestbench {
 | 
			
		||||
        pub mode_request_tx: mpsc::SyncSender<GenericMessage<ModeRequest>>,
 | 
			
		||||
        pub mode_reply_rx_to_pus: mpsc::Receiver<GenericMessage<ModeReply>>,
 | 
			
		||||
        pub mode_reply_rx_to_parent: mpsc::Receiver<GenericMessage<ModeReply>>,
 | 
			
		||||
        pub composite_request_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
 | 
			
		||||
        pub hk_reply_rx: mpsc::Receiver<GenericMessage<HkReply>>,
 | 
			
		||||
        pub tm_rx: mpsc::Receiver<PacketAsVec>,
 | 
			
		||||
        pub switch_request_tx: mpsc::Sender<GenericMessage<SwitchRequest>>,
 | 
			
		||||
        pub handler: PcduHandler<SerialInterfaceTest>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl PcduTestbench {
 | 
			
		||||
        pub fn new() -> Self {
 | 
			
		||||
            let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5);
 | 
			
		||||
            let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5);
 | 
			
		||||
            let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5);
 | 
			
		||||
            let mode_node = ModeRequestHandlerMpscBounded::new(PCDU.into(), mode_request_rx);
 | 
			
		||||
            let (composite_request_tx, composite_request_rx) = mpsc::channel();
 | 
			
		||||
            let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10);
 | 
			
		||||
            let (tm_tx, tm_rx) = mpsc::sync_channel::<PacketAsVec>(5);
 | 
			
		||||
            let (switch_request_tx, switch_reqest_rx) = mpsc::channel();
 | 
			
		||||
            let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default()));
 | 
			
		||||
            let mut handler = PcduHandler::new(
 | 
			
		||||
                UniqueApidTargetId::new(Apid::Eps.raw_value(), 0),
 | 
			
		||||
                "TEST_PCDU",
 | 
			
		||||
                mode_node,
 | 
			
		||||
                composite_request_rx,
 | 
			
		||||
                hk_reply_tx,
 | 
			
		||||
                switch_reqest_rx,
 | 
			
		||||
                TmTcSender::Heap(tm_tx.clone()),
 | 
			
		||||
                SerialInterfaceTest::default(),
 | 
			
		||||
                shared_switch_map,
 | 
			
		||||
            );
 | 
			
		||||
            handler.add_mode_parent(ids::eps::SUBSYSTEM.into(), mode_reply_tx_to_parent);
 | 
			
		||||
            handler.add_mode_parent(PUS_MODE.into(), mode_reply_tx_to_pus);
 | 
			
		||||
            Self {
 | 
			
		||||
                mode_request_tx,
 | 
			
		||||
                mode_reply_rx_to_pus,
 | 
			
		||||
                mode_reply_rx_to_parent,
 | 
			
		||||
                composite_request_tx,
 | 
			
		||||
                hk_reply_rx,
 | 
			
		||||
                tm_rx,
 | 
			
		||||
                switch_request_tx,
 | 
			
		||||
                handler,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn verify_switch_info_req_was_sent(&self, expected_queue_len: usize) {
 | 
			
		||||
            // Check that there is now communication happening.
 | 
			
		||||
            let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
 | 
			
		||||
            assert_eq!(send_queue_mut.len(), expected_queue_len);
 | 
			
		||||
            let packet_sent = send_queue_mut.pop_front().unwrap();
 | 
			
		||||
            drop(send_queue_mut);
 | 
			
		||||
            let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
 | 
			
		||||
            assert_eq!(pcdu_req, PcduRequest::RequestSwitchInfo);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn verify_switch_req_was_sent(
 | 
			
		||||
            &self,
 | 
			
		||||
            expected_queue_len: usize,
 | 
			
		||||
            switch_id: PcduSwitch,
 | 
			
		||||
            target_state: SwitchStateBinary,
 | 
			
		||||
        ) {
 | 
			
		||||
            // Check that there is now communication happening.
 | 
			
		||||
            let mut send_queue_mut = self.handler.com_interface.send_queue.borrow_mut();
 | 
			
		||||
            assert_eq!(send_queue_mut.len(), expected_queue_len);
 | 
			
		||||
            let packet_sent = send_queue_mut.pop_front().unwrap();
 | 
			
		||||
            drop(send_queue_mut);
 | 
			
		||||
            let pcdu_req: PcduRequest = serde_json::from_slice(&packet_sent).unwrap();
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                pcdu_req,
 | 
			
		||||
                PcduRequest::SwitchDevice {
 | 
			
		||||
                    switch: switch_id,
 | 
			
		||||
                    state: target_state
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn verify_switch_reply_received(
 | 
			
		||||
            &self,
 | 
			
		||||
            expected_queue_len: usize,
 | 
			
		||||
            expected_map: SwitchMapBinary,
 | 
			
		||||
        ) {
 | 
			
		||||
            // Check that a switch reply was read back.
 | 
			
		||||
            let mut reply_received_mut = self.handler.com_interface.reply_queue.borrow_mut();
 | 
			
		||||
            assert_eq!(reply_received_mut.len(), expected_queue_len);
 | 
			
		||||
            let reply_received = reply_received_mut.pop_front().unwrap();
 | 
			
		||||
            let sim_reply: SimReply = serde_json::from_str(&reply_received).unwrap();
 | 
			
		||||
            let pcdu_reply = PcduReply::from_sim_message(&sim_reply).unwrap();
 | 
			
		||||
            assert_eq!(pcdu_reply, PcduReply::SwitchInfo(expected_map));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_basic_handler() {
 | 
			
		||||
        let mut testbench = PcduTestbench::new();
 | 
			
		||||
        assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.com_interface.reply_queue.borrow().len(),
 | 
			
		||||
            0
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Off as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
 | 
			
		||||
        testbench.handler.periodic_operation(OpCode::RegularOp);
 | 
			
		||||
        testbench
 | 
			
		||||
            .handler
 | 
			
		||||
            .periodic_operation(OpCode::PollAndRecvReplies);
 | 
			
		||||
        // Handler is OFF, no changes expected.
 | 
			
		||||
        assert_eq!(testbench.handler.com_interface.send_queue.borrow().len(), 0);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.com_interface.reply_queue.borrow().len(),
 | 
			
		||||
            0
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Off as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0_u16);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_normal_mode() {
 | 
			
		||||
        let mut testbench = PcduTestbench::new();
 | 
			
		||||
        testbench
 | 
			
		||||
            .mode_request_tx
 | 
			
		||||
            .send(GenericMessage::new(
 | 
			
		||||
                MessageMetadata::new(0, PUS_MODE.id()),
 | 
			
		||||
                ModeRequest::SetMode {
 | 
			
		||||
                    mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
 | 
			
		||||
                    forced: false,
 | 
			
		||||
                },
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send mode request");
 | 
			
		||||
        let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
 | 
			
		||||
        assert!(!switch_map_shared.valid);
 | 
			
		||||
        drop(switch_map_shared);
 | 
			
		||||
        testbench.handler.periodic_operation(OpCode::RegularOp);
 | 
			
		||||
        testbench
 | 
			
		||||
            .handler
 | 
			
		||||
            .periodic_operation(OpCode::PollAndRecvReplies);
 | 
			
		||||
        // Check correctness of mode.
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            testbench.handler.mode_and_submode().mode(),
 | 
			
		||||
            DeviceMode::Normal as u32
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(testbench.handler.mode_and_submode().submode(), 0);
 | 
			
		||||
 | 
			
		||||
        testbench.verify_switch_info_req_was_sent(1);
 | 
			
		||||
        testbench.verify_switch_reply_received(1, SwitchMapBinaryWrapper::default().0);
 | 
			
		||||
 | 
			
		||||
        let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
 | 
			
		||||
        assert!(switch_map_shared.valid);
 | 
			
		||||
        drop(switch_map_shared);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_switch_request_handling() {
 | 
			
		||||
        let mut testbench = PcduTestbench::new();
 | 
			
		||||
        testbench
 | 
			
		||||
            .mode_request_tx
 | 
			
		||||
            .send(GenericMessage::new(
 | 
			
		||||
                MessageMetadata::new(0, PUS_MODE.id()),
 | 
			
		||||
                ModeRequest::SetMode {
 | 
			
		||||
                    mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0),
 | 
			
		||||
                    forced: false,
 | 
			
		||||
                },
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send mode request");
 | 
			
		||||
        testbench
 | 
			
		||||
            .switch_request_tx
 | 
			
		||||
            .send(GenericMessage::new(
 | 
			
		||||
                MessageMetadata::new(0, ids::acs::MGM0.id()),
 | 
			
		||||
                SwitchRequest::new(0, SwitchStateBinary::On),
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send switch request");
 | 
			
		||||
        testbench.handler.periodic_operation(OpCode::RegularOp);
 | 
			
		||||
        testbench
 | 
			
		||||
            .handler
 | 
			
		||||
            .periodic_operation(OpCode::PollAndRecvReplies);
 | 
			
		||||
 | 
			
		||||
        testbench.verify_switch_req_was_sent(2, PcduSwitch::Mgm, SwitchStateBinary::On);
 | 
			
		||||
        testbench.verify_switch_info_req_was_sent(1);
 | 
			
		||||
        let mut switch_map = SwitchMapBinaryWrapper::default().0;
 | 
			
		||||
        *switch_map
 | 
			
		||||
            .get_mut(&PcduSwitch::Mgm)
 | 
			
		||||
            .expect("switch state setting failed") = SwitchStateBinary::On;
 | 
			
		||||
        testbench.verify_switch_reply_received(1, switch_map);
 | 
			
		||||
 | 
			
		||||
        let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap();
 | 
			
		||||
        assert!(switch_map_shared.valid);
 | 
			
		||||
        drop(switch_map_shared);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +1,34 @@
 | 
			
		||||
use std::sync::mpsc::{self};
 | 
			
		||||
 | 
			
		||||
use crate::pus::create_verification_reporter;
 | 
			
		||||
use satrs::event_man::{EventMessageU32, EventRoutingError};
 | 
			
		||||
use satrs::params::WritableToBeBytes;
 | 
			
		||||
use satrs::pus::event::EventTmHookProvider;
 | 
			
		||||
use arbitrary_int::traits::Integer as _;
 | 
			
		||||
use arbitrary_int::u11;
 | 
			
		||||
use satrs::event_man_legacy::{EventMessageU32, EventRoutingError};
 | 
			
		||||
use satrs::pus::event::EventTmHook;
 | 
			
		||||
use satrs::pus::verification::VerificationReporter;
 | 
			
		||||
use satrs::pus::EcssTmSender;
 | 
			
		||||
use satrs::request::UniqueApidTargetId;
 | 
			
		||||
use satrs::{
 | 
			
		||||
    event_man::{
 | 
			
		||||
        EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded,
 | 
			
		||||
        MpscEventReceiver,
 | 
			
		||||
    },
 | 
			
		||||
    event_man_legacy::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
 | 
			
		||||
    pus::{
 | 
			
		||||
        event_man::{
 | 
			
		||||
            DefaultPusEventU32Dispatcher, EventReporter, EventRequest, EventRequestWithToken,
 | 
			
		||||
            DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
 | 
			
		||||
        },
 | 
			
		||||
        verification::{TcStateStarted, VerificationReportingProvider, VerificationToken},
 | 
			
		||||
    },
 | 
			
		||||
    spacepackets::time::cds::CdsTime,
 | 
			
		||||
};
 | 
			
		||||
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
 | 
			
		||||
use satrs_example::ids::generic_pus::PUS_EVENT_MANAGEMENT;
 | 
			
		||||
 | 
			
		||||
use crate::update_time;
 | 
			
		||||
 | 
			
		||||
// This helper sets the APID of the event sender for the PUS telemetry.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct EventApidSetter {
 | 
			
		||||
    pub next_apid: u16,
 | 
			
		||||
    pub next_apid: u11,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl EventTmHookProvider for EventApidSetter {
 | 
			
		||||
impl EventTmHook for EventApidSetter {
 | 
			
		||||
    fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
 | 
			
		||||
        tm.set_apid(self.next_apid);
 | 
			
		||||
    }
 | 
			
		||||
@@ -40,13 +38,13 @@ impl EventTmHookProvider for EventApidSetter {
 | 
			
		||||
/// packets. It also handles the verification completion of PUS event service requests.
 | 
			
		||||
pub struct PusEventHandler<TmSender: EcssTmSender> {
 | 
			
		||||
    event_request_rx: mpsc::Receiver<EventRequestWithToken>,
 | 
			
		||||
    pus_event_dispatcher: DefaultPusEventU32Dispatcher<()>,
 | 
			
		||||
    pus_event_tm_creator: DefaultPusEventU32TmCreator<EventApidSetter>,
 | 
			
		||||
    pus_event_man_rx: mpsc::Receiver<EventMessageU32>,
 | 
			
		||||
    tm_sender: TmSender,
 | 
			
		||||
    time_provider: CdsTime,
 | 
			
		||||
    timestamp: [u8; 7],
 | 
			
		||||
    small_data_buf: [u8; 64],
 | 
			
		||||
    verif_handler: VerificationReporter,
 | 
			
		||||
    event_apid_setter: EventApidSetter,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
 | 
			
		||||
@@ -61,9 +59,15 @@ impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
 | 
			
		||||
 | 
			
		||||
        // All events sent to the manager are routed to the PUS event manager, which generates PUS event
 | 
			
		||||
        // telemetry for each event.
 | 
			
		||||
        let event_reporter = EventReporter::new(PUS_EVENT_MANAGEMENT.raw(), 0, 0, 128).unwrap();
 | 
			
		||||
        let event_reporter = EventReporter::new_with_hook(
 | 
			
		||||
            PUS_EVENT_MANAGEMENT.raw(),
 | 
			
		||||
            u11::ZERO,
 | 
			
		||||
            0,
 | 
			
		||||
            128,
 | 
			
		||||
            EventApidSetter::default(),
 | 
			
		||||
        );
 | 
			
		||||
        let pus_event_dispatcher =
 | 
			
		||||
            DefaultPusEventU32Dispatcher::new_with_default_backend(event_reporter);
 | 
			
		||||
            DefaultPusEventU32TmCreator::new_with_default_backend(event_reporter);
 | 
			
		||||
        let pus_event_man_send_provider = EventU32SenderMpscBounded::new(
 | 
			
		||||
            PUS_EVENT_MANAGEMENT.raw(),
 | 
			
		||||
            pus_event_man_tx,
 | 
			
		||||
@@ -75,13 +79,13 @@ impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            event_request_rx,
 | 
			
		||||
            pus_event_dispatcher,
 | 
			
		||||
            pus_event_tm_creator: pus_event_dispatcher,
 | 
			
		||||
            pus_event_man_rx,
 | 
			
		||||
            time_provider: CdsTime::new_with_u16_days(0, 0),
 | 
			
		||||
            timestamp: [0; 7],
 | 
			
		||||
            small_data_buf: [0; 64],
 | 
			
		||||
            verif_handler,
 | 
			
		||||
            tm_sender,
 | 
			
		||||
            event_apid_setter: EventApidSetter::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -95,75 +99,110 @@ impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
 | 
			
		||||
                .completion_success(&self.tm_sender, started_token, timestamp)
 | 
			
		||||
                .expect("Sending completion success failed");
 | 
			
		||||
        };
 | 
			
		||||
        // handle event requests
 | 
			
		||||
        if let Ok(event_req) = self.event_request_rx.try_recv() {
 | 
			
		||||
            match event_req.request {
 | 
			
		||||
                EventRequest::Enable(event) => {
 | 
			
		||||
                    self.pus_event_dispatcher
 | 
			
		||||
                        .enable_tm_for_event(&event)
 | 
			
		||||
                        .expect("Enabling TM failed");
 | 
			
		||||
                    update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
                    report_completion(event_req, &self.timestamp);
 | 
			
		||||
                }
 | 
			
		||||
                EventRequest::Disable(event) => {
 | 
			
		||||
                    self.pus_event_dispatcher
 | 
			
		||||
                        .disable_tm_for_event(&event)
 | 
			
		||||
                        .expect("Disabling TM failed");
 | 
			
		||||
                    update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
                    report_completion(event_req, &self.timestamp);
 | 
			
		||||
                }
 | 
			
		||||
        loop {
 | 
			
		||||
            // handle event requests
 | 
			
		||||
            match self.event_request_rx.try_recv() {
 | 
			
		||||
                Ok(event_req) => match event_req.request {
 | 
			
		||||
                    EventRequest::Enable(event) => {
 | 
			
		||||
                        self.pus_event_tm_creator
 | 
			
		||||
                            .enable_tm_for_event(&event)
 | 
			
		||||
                            .expect("Enabling TM failed");
 | 
			
		||||
                        update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
                        report_completion(event_req, &self.timestamp);
 | 
			
		||||
                    }
 | 
			
		||||
                    EventRequest::Disable(event) => {
 | 
			
		||||
                        self.pus_event_tm_creator
 | 
			
		||||
                            .disable_tm_for_event(&event)
 | 
			
		||||
                            .expect("Disabling TM failed");
 | 
			
		||||
                        update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
                        report_completion(event_req, &self.timestamp);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    mpsc::TryRecvError::Empty => break,
 | 
			
		||||
                    mpsc::TryRecvError::Disconnected => {
 | 
			
		||||
                        log::warn!("all event request senders have disconnected");
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn generate_pus_event_tm(&mut self) {
 | 
			
		||||
        // Perform the generation of PUS event packets
 | 
			
		||||
        if let Ok(event_msg) = self.pus_event_man_rx.try_recv() {
 | 
			
		||||
            update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
            let param_vec = event_msg.params().map_or(Vec::new(), |param| {
 | 
			
		||||
                param.to_vec().expect("failed to convert params to vec")
 | 
			
		||||
            });
 | 
			
		||||
            self.event_apid_setter.next_apid = UniqueApidTargetId::from(event_msg.sender_id()).apid;
 | 
			
		||||
            self.pus_event_dispatcher
 | 
			
		||||
                .generate_pus_event_tm_generic(
 | 
			
		||||
                    &self.tm_sender,
 | 
			
		||||
                    &self.timestamp,
 | 
			
		||||
                    event_msg.event(),
 | 
			
		||||
                    Some(¶m_vec),
 | 
			
		||||
                )
 | 
			
		||||
                .expect("Sending TM as event failed");
 | 
			
		||||
        loop {
 | 
			
		||||
            // Perform the generation of PUS event packets
 | 
			
		||||
            match self.pus_event_man_rx.try_recv() {
 | 
			
		||||
                Ok(event_msg) => {
 | 
			
		||||
                    // We use the TM modification hook to set the sender APID for each event.
 | 
			
		||||
                    self.pus_event_tm_creator.reporter.tm_hook.next_apid =
 | 
			
		||||
                        UniqueApidTargetId::from(event_msg.sender_id()).apid;
 | 
			
		||||
                    update_time(&mut self.time_provider, &mut self.timestamp);
 | 
			
		||||
                    let generation_result = self
 | 
			
		||||
                        .pus_event_tm_creator
 | 
			
		||||
                        .generate_pus_event_tm_generic_with_generic_params(
 | 
			
		||||
                            &self.tm_sender,
 | 
			
		||||
                            &self.timestamp,
 | 
			
		||||
                            event_msg.event(),
 | 
			
		||||
                            &mut self.small_data_buf,
 | 
			
		||||
                            event_msg.params(),
 | 
			
		||||
                        )
 | 
			
		||||
                        .expect("Sending TM as event failed");
 | 
			
		||||
                    if !generation_result.params_were_propagated {
 | 
			
		||||
                        log::warn!(
 | 
			
		||||
                            "Event TM parameters were not propagated: {:?}",
 | 
			
		||||
                            event_msg.params()
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => match e {
 | 
			
		||||
                    mpsc::TryRecvError::Empty => break,
 | 
			
		||||
                    mpsc::TryRecvError::Disconnected => {
 | 
			
		||||
                        log::warn!("All event senders have disconnected");
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This is a thin wrapper around the event manager which also caches the sender component
 | 
			
		||||
/// used to send events to the event manager.
 | 
			
		||||
pub struct EventManagerWrapper {
 | 
			
		||||
pub struct EventHandler<TmSender: EcssTmSender> {
 | 
			
		||||
    pub pus_event_handler: PusEventHandler<TmSender>,
 | 
			
		||||
    event_manager: EventManagerWithBoundedMpsc,
 | 
			
		||||
    event_sender: mpsc::Sender<EventMessageU32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl EventManagerWrapper {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        // The sender handle is the primary sender handle for all components which want to create events.
 | 
			
		||||
        // The event manager will receive the RX handle to receive all the events.
 | 
			
		||||
        let (event_sender, event_man_rx) = mpsc::channel();
 | 
			
		||||
        let event_recv = MpscEventReceiver::new(event_man_rx);
 | 
			
		||||
impl<TmSender: EcssTmSender> EventHandler<TmSender> {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        tm_sender: TmSender,
 | 
			
		||||
        event_rx: mpsc::Receiver<EventMessageU32>,
 | 
			
		||||
        event_request_rx: mpsc::Receiver<EventRequestWithToken>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
 | 
			
		||||
        let pus_event_handler = PusEventHandler::new(
 | 
			
		||||
            tm_sender,
 | 
			
		||||
            create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
 | 
			
		||||
            &mut event_manager,
 | 
			
		||||
            event_request_rx,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            event_manager: EventManagerWithBoundedMpsc::new(event_recv),
 | 
			
		||||
            event_sender,
 | 
			
		||||
            pus_event_handler,
 | 
			
		||||
            event_manager,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Returns a cached event sender to send events to the event manager for routing.
 | 
			
		||||
    pub fn clone_event_sender(&self) -> mpsc::Sender<EventMessageU32> {
 | 
			
		||||
        self.event_sender.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
 | 
			
		||||
        &mut self.event_manager
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn periodic_operation(&mut self) {
 | 
			
		||||
        self.pus_event_handler.handle_event_requests();
 | 
			
		||||
        self.try_event_routing();
 | 
			
		||||
        self.pus_event_handler.generate_pus_event_tm();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn try_event_routing(&mut self) {
 | 
			
		||||
        let error_handler = |event_msg: &EventMessageU32, error: EventRoutingError| {
 | 
			
		||||
            self.routing_error_handler(event_msg, error)
 | 
			
		||||
@@ -177,41 +216,78 @@ impl EventManagerWrapper {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct EventHandler<TmSender: EcssTmSender> {
 | 
			
		||||
    pub event_man_wrapper: EventManagerWrapper,
 | 
			
		||||
    pub pus_event_handler: PusEventHandler<TmSender>,
 | 
			
		||||
}
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use satrs::{
 | 
			
		||||
        events_legacy::EventU32,
 | 
			
		||||
        pus::verification::VerificationReporterConfig,
 | 
			
		||||
        spacepackets::ecss::{tm::PusTmReader, PusPacket},
 | 
			
		||||
        tmtc::PacketAsVec,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
impl<TmSender: EcssTmSender> EventHandler<TmSender> {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        tm_sender: TmSender,
 | 
			
		||||
        event_request_rx: mpsc::Receiver<EventRequestWithToken>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let mut event_man_wrapper = EventManagerWrapper::new();
 | 
			
		||||
        let pus_event_handler = PusEventHandler::new(
 | 
			
		||||
            tm_sender,
 | 
			
		||||
            create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
 | 
			
		||||
            event_man_wrapper.event_manager(),
 | 
			
		||||
            event_request_rx,
 | 
			
		||||
        );
 | 
			
		||||
        Self {
 | 
			
		||||
            event_man_wrapper,
 | 
			
		||||
            pus_event_handler,
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(u11::new(1), 2);
 | 
			
		||||
    const TEST_EVENT: EventU32 = EventU32::new(satrs::events_legacy::Severity::Info, 1, 1);
 | 
			
		||||
 | 
			
		||||
    pub struct EventManagementTestbench {
 | 
			
		||||
        pub event_tx: mpsc::SyncSender<EventMessageU32>,
 | 
			
		||||
        pub event_manager: EventManagerWithBoundedMpsc,
 | 
			
		||||
        pub tm_receiver: mpsc::Receiver<PacketAsVec>,
 | 
			
		||||
        pub pus_event_handler: PusEventHandler<mpsc::Sender<PacketAsVec>>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl EventManagementTestbench {
 | 
			
		||||
        pub fn new() -> Self {
 | 
			
		||||
            let (event_tx, event_rx) = mpsc::sync_channel(10);
 | 
			
		||||
            let (_event_req_tx, event_req_rx) = mpsc::sync_channel(10);
 | 
			
		||||
            let (tm_sender, tm_receiver) = mpsc::channel();
 | 
			
		||||
            let verif_reporter_cfg = VerificationReporterConfig::new(u11::new(0x05), 2, 2, 128);
 | 
			
		||||
            let verif_reporter =
 | 
			
		||||
                VerificationReporter::new(PUS_EVENT_MANAGEMENT.id(), &verif_reporter_cfg);
 | 
			
		||||
            let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
 | 
			
		||||
            let pus_event_handler = PusEventHandler::<mpsc::Sender<PacketAsVec>>::new(
 | 
			
		||||
                tm_sender,
 | 
			
		||||
                verif_reporter,
 | 
			
		||||
                &mut event_manager,
 | 
			
		||||
                event_req_rx,
 | 
			
		||||
            );
 | 
			
		||||
            Self {
 | 
			
		||||
                event_tx,
 | 
			
		||||
                tm_receiver,
 | 
			
		||||
                event_manager,
 | 
			
		||||
                pus_event_handler,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clone_event_sender(&self) -> mpsc::Sender<EventMessageU32> {
 | 
			
		||||
        self.event_man_wrapper.clone_event_sender()
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_basic_event_generation() {
 | 
			
		||||
        let mut testbench = EventManagementTestbench::new();
 | 
			
		||||
        testbench
 | 
			
		||||
            .event_tx
 | 
			
		||||
            .send(EventMessageU32::new(
 | 
			
		||||
                TEST_CREATOR_ID.id(),
 | 
			
		||||
                EventU32::new(satrs::events_legacy::Severity::Info, 1, 1),
 | 
			
		||||
            ))
 | 
			
		||||
            .expect("failed to send event");
 | 
			
		||||
        testbench.pus_event_handler.handle_event_requests();
 | 
			
		||||
        testbench.event_manager.try_event_handling(|_, _| {});
 | 
			
		||||
        testbench.pus_event_handler.generate_pus_event_tm();
 | 
			
		||||
        let tm_packet = testbench
 | 
			
		||||
            .tm_receiver
 | 
			
		||||
            .try_recv()
 | 
			
		||||
            .expect("failed to receive TM packet");
 | 
			
		||||
        assert_eq!(tm_packet.sender_id, PUS_EVENT_MANAGEMENT.id());
 | 
			
		||||
        let tm_reader = PusTmReader::new(&tm_packet.packet, 7).expect("failed to create TM reader");
 | 
			
		||||
        assert_eq!(tm_reader.apid(), TEST_CREATOR_ID.apid);
 | 
			
		||||
        assert_eq!(tm_reader.user_data().len(), 4);
 | 
			
		||||
        let event_read_back = EventU32::from_be_bytes(tm_reader.user_data().try_into().unwrap());
 | 
			
		||||
        assert_eq!(event_read_back, TEST_EVENT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
 | 
			
		||||
        self.event_man_wrapper.event_manager()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn periodic_operation(&mut self) {
 | 
			
		||||
        self.pus_event_handler.handle_event_requests();
 | 
			
		||||
        self.event_man_wrapper.try_event_routing();
 | 
			
		||||
        self.pus_event_handler.generate_pus_event_tm();
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_basic_event_disabled() {
 | 
			
		||||
        // TODO: Add test.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
use derive_new::new;
 | 
			
		||||
use satrs::hk::UniqueId;
 | 
			
		||||
use satrs::request::UniqueApidTargetId;
 | 
			
		||||
use satrs::spacepackets::ByteConversionError;
 | 
			
		||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
 | 
			
		||||
use satrs::spacepackets::ecss::{hk, CreatorConfig};
 | 
			
		||||
use satrs::spacepackets::{ByteConversionError, SpHeader};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, new, Copy, Clone)]
 | 
			
		||||
pub struct HkUniqueId {
 | 
			
		||||
@@ -33,3 +35,35 @@ impl HkUniqueId {
 | 
			
		||||
        Ok(8)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(new)]
 | 
			
		||||
pub struct PusHkHelper {
 | 
			
		||||
    component_id: UniqueApidTargetId,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PusHkHelper {
 | 
			
		||||
    pub fn generate_hk_report_packet<
 | 
			
		||||
        'a,
 | 
			
		||||
        'b,
 | 
			
		||||
        HkWriter: FnMut(&mut [u8]) -> Result<usize, ByteConversionError>,
 | 
			
		||||
    >(
 | 
			
		||||
        &self,
 | 
			
		||||
        timestamp: &'a [u8],
 | 
			
		||||
        set_id: u32,
 | 
			
		||||
        hk_data_writer: &mut HkWriter,
 | 
			
		||||
        buf: &'b mut [u8],
 | 
			
		||||
    ) -> Result<PusTmCreator<'a, 'b>, ByteConversionError> {
 | 
			
		||||
        let sec_header =
 | 
			
		||||
            PusTmSecondaryHeader::new(3, hk::Subservice::TmHkPacket as u8, 0, 0, timestamp);
 | 
			
		||||
        buf[0..4].copy_from_slice(&self.component_id.unique_id.to_be_bytes());
 | 
			
		||||
        buf[4..8].copy_from_slice(&set_id.to_be_bytes());
 | 
			
		||||
        let (_, second_half) = buf.split_at_mut(8);
 | 
			
		||||
        let hk_data_len = hk_data_writer(second_half)?;
 | 
			
		||||
        Ok(PusTmCreator::new(
 | 
			
		||||
            SpHeader::new_from_apid(self.component_id.apid),
 | 
			
		||||
            sec_header,
 | 
			
		||||
            &buf[0..8 + hk_data_len],
 | 
			
		||||
            CreatorConfig::default(),
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user