Compare commits
	
		
			268 Commits
		
	
	
		
			satrs-exam
			...
			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
	
				 | 
					
					
						|||
| 31cddbd99b | |||
| 7c00e13e70 | |||
| aa72063454 | |||
| 7b37b76695 | |||
| ea5d95c12d | |||
| c62adbb300 | |||
| 9242b8a607 | |||
| 
						
						
							
						
						4a27d2605d
	
				 | 
					
					
						|||
| 
						
						
							
						
						8195245481
	
				 | 
					
					
						|||
| f6f7519625 | |||
| 
						
						
							
						
						0f0fbc1a18
	
				 | 
					
					
						|||
| 6e55e2ac95 | |||
| 
						
						
							
						
						2f96bfe992
	
				 | 
					
					
						|||
| 
						
						
							
						
						52aafb3aab
	
				 | 
					
					
						|||
| 6ce9cb5ead | |||
| 
						
						
							
						
						273f79d1e6
	
				 | 
					
					
						|||
| 622221835e | |||
| 
						
						
							
						
						e396ad2e7a
	
				 | 
					
					
						|||
| 
						
						
							
						
						772927d50b
	
				 | 
					
					
						|||
| be9a45e55f | |||
| 
						
						
							
						
						eee8a69550
	
				 | 
					
					
						|||
| f7a6d3ce47 | |||
| 
						
						
							
						
						df97a3a93e
	
				 | 
					
					
						|||
| 
						
						
							
						
						42750e08c0
	
				 | 
					
					
						|||
| 786671bbd7 | |||
| 63f37f0917 | |||
| 8cfe3b81e7 | |||
| 
						
						
							
						
						de50bec562
	
				 | 
					
					
						|||
| 39ab9fa27b | |||
| 
						
						
							
						
						1dbc81a8f5
	
				 | 
					
					
						|||
| 1ad74ee1d5 | |||
| 
						
						
							
						
						f96fe6bdc0
	
				 | 
					
					
						|||
| d43a8eb571 | |||
| 0bbada90ef | |||
| 3375780e00 | |||
| 
						
						
							
						
						de028ed827
	
				 | 
					
					
						|||
| 
						
						
							
						
						d27ac5dfc9
	
				 | 
					
					
						|||
| 
						
						
							
						
						c67b7cb93a
	
				 | 
					
					
						|||
| f71ba3e8d8 | |||
| 975cd927f4 | |||
| 3cc9dd3c48 | |||
| 0fec994028 | |||
| 
						
						
							
						
						226a134aff
	
				 | 
					
					
						|||
| aac59ec7c1 | |||
| ce7eb8650f | |||
| 
						
						
							
						
						df2733a176
	
				 | 
					
					
						|||
| 344fe6a4c0 | |||
| 
						
						
							
						
						a5941751d7
	
				 | 
					
					
						|||
| 977e29894b | |||
| 
						
						
							
						
						dd1417a368
	
				 | 
					
					
						|||
| 
						
						
							
						
						3195cf5111
	
				 | 
					
					
						|||
| 8280c70682 | |||
| 
						
						
							
						
						19c5aa9b83
	
				 | 
					
					
						|||
| 
						
						
							
						
						713b4e097b
	
				 | 
					
					
						|||
| 9039c1b59a | |||
| 746b31ec5d | |||
| 
						
						
							
						
						2318cd4293
	
				 | 
					
					
						|||
| a6b57d3eb9 | |||
| bddd3132d4 | |||
| 6a6ffba754 | |||
| d27a41e4de | |||
| 
						
						
							
						
						972bf19188
	
				 | 
					
					
						|||
| 
						
						
							
						
						9d711d2b73
	
				 | 
					
					
						|||
| 
						
						
							
						
						d0005cdd63
	
				 | 
					
					
						|||
| 
						
						
							
						
						f00e6cf50c
	
				 | 
					
					
						|||
| 128df9a813 | |||
| 
						
						
							
						
						7387be3bc3
	
				 | 
					
					
						|||
| 
						
						
							
						
						d3fb504545
	
				 | 
					
					
						|||
| 
						
						
							
						
						ae8e39f626
	
				 | 
					
					
						|||
| ab3d907d4e | |||
| 
						
						
							
						
						3de5954898
	
				 | 
					
					
						|||
| 5600aa576c | |||
| 
						
						
							
						
						88793cfa87
	
				 | 
					
					
						|||
| 
						
						
							
						
						223b637eb8
	
				 | 
					
					
						|||
| cf9b115e1e | |||
| 
						
						
							
						
						eea9b11b39
	
				 | 
					
					
						|||
| f21ab0017e | |||
| 
						
						
							
						
						a7ca00317f
	
				 | 
					
					
						|||
| 
						
						
							
						
						75fda42f4f
	
				 | 
					
					
						|||
| faf0f6f6c6 | |||
| a690c7720d | 
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,10 @@
 | 
			
		||||
/target
 | 
			
		||||
target/
 | 
			
		||||
 | 
			
		||||
output.log
 | 
			
		||||
/Cargo.lock
 | 
			
		||||
output.log
 | 
			
		||||
 | 
			
		||||
output.log
 | 
			
		||||
 | 
			
		||||
/.idea/*
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@ members = [
 | 
			
		||||
    "satrs",
 | 
			
		||||
    "satrs-mib",
 | 
			
		||||
    "satrs-example",
 | 
			
		||||
    "satrs-minisim",
 | 
			
		||||
    "satrs-shared",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
exclude = [
 | 
			
		||||
    "satrs-example-stm32f3-disco",
 | 
			
		||||
    "embedded-examples/stm32f3-disco-rtic",
 | 
			
		||||
    "embedded-examples/stm32h7-nucleo-rtic",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
								
							@@ -1,19 +1,25 @@
 | 
			
		||||
<p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p>
 | 
			
		||||
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
 | 
			
		||||
 | 
			
		||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
 | 
			
		||||
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/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
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
This is the repository of the sat-rs framework. Its primary goal is to provide re-usable components
 | 
			
		||||
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
 | 
			
		||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
 | 
			
		||||
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
 | 
			
		||||
with breaking changes are released regularly, with all changes documented inside respective
 | 
			
		||||
changelog files. You should only use this library if your are willing to work in this
 | 
			
		||||
environment.
 | 
			
		||||
 | 
			
		||||
A lot of the architecture and general design considerations are based on the
 | 
			
		||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
 | 
			
		||||
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
 | 
			
		||||
@@ -32,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):
 | 
			
		||||
   Example of a simple example on-board software using sat-rs components on a bare-metal system
 | 
			
		||||
   with constrained resources.
 | 
			
		||||
* [`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. 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
 | 
			
		||||
 | 
			
		||||
@@ -59,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') {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								coverage.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								coverage.py
									
									
									
									
									
								
							@@ -18,15 +18,19 @@ def generate_cov_report(open_report: bool, format: str, package: str):
 | 
			
		||||
    out_path = "./target/debug/coverage"
 | 
			
		||||
    if format == "lcov":
 | 
			
		||||
        out_path = "./target/debug/lcov.info"
 | 
			
		||||
    os.system(
 | 
			
		||||
    grcov_cmd = (
 | 
			
		||||
        f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
 | 
			
		||||
        f"-o {out_path}"
 | 
			
		||||
    )
 | 
			
		||||
    print(f"Running: {grcov_cmd}")
 | 
			
		||||
    os.system(grcov_cmd)
 | 
			
		||||
    if format == "lcov":
 | 
			
		||||
        os.system(
 | 
			
		||||
        lcov_cmd = (
 | 
			
		||||
            "genhtml -o ./target/debug/coverage/ --show-details --highlight --ignore-errors source "
 | 
			
		||||
            "--legend ./target/debug/lcov.info"
 | 
			
		||||
        )
 | 
			
		||||
        print(f"Running: {lcov_cmd}")
 | 
			
		||||
        os.system(lcov_cmd)
 | 
			
		||||
    if open_report:
 | 
			
		||||
        coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
 | 
			
		||||
        webbrowser.open_new_tab(coverage_report_path)
 | 
			
		||||
@@ -43,7 +47,7 @@ def main():
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-p",
 | 
			
		||||
        "--package",
 | 
			
		||||
        choices=["satrs"],
 | 
			
		||||
        choices=["satrs", "satrs-minisim", "satrs-example"],
 | 
			
		||||
        default="satrs",
 | 
			
		||||
        help="Choose project to generate coverage for",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										37
									
								
								embedded-examples/stm32f3-disco-rtic/.cargo/def_config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								embedded-examples/stm32f3-disco-rtic/.cargo/def_config.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
 | 
			
		||||
# uncomment ONE of these three option to make `cargo run` start a GDB session
 | 
			
		||||
# which option to pick depends on your system
 | 
			
		||||
# You can also replace openocd.gdb by jlink.gdb when using a J-Link.
 | 
			
		||||
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
 | 
			
		||||
# runner = "gdb-multiarch -q -x openocd.gdb"
 | 
			
		||||
# runner = "gdb -q -x openocd.gdb"
 | 
			
		||||
runner = "probe-rs run --chip STM32F303VCTx"
 | 
			
		||||
 | 
			
		||||
rustflags = [
 | 
			
		||||
  "-C", "linker=flip-link",
 | 
			
		||||
  # LLD (shipped with the Rust toolchain) is used as the default linker
 | 
			
		||||
  "-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",
 | 
			
		||||
 | 
			
		||||
  # if you run into problems with LLD switch to the GNU linker by commenting out
 | 
			
		||||
  # this line
 | 
			
		||||
  # "-C", "linker=arm-none-eabi-ld",
 | 
			
		||||
 | 
			
		||||
  # if you need to link to pre-compiled C libraries provided by a C toolchain
 | 
			
		||||
  # use GCC as the linker by commenting out both lines above and then
 | 
			
		||||
  # uncommenting the three lines below
 | 
			
		||||
  # "-C", "linker=arm-none-eabi-gcc",
 | 
			
		||||
  # "-C", "link-arg=-Wl,-Tlink.x",
 | 
			
		||||
  # "-C", "link-arg=-nostartfiles",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[build]
 | 
			
		||||
# comment out the following line if you intend to run unit tests on host machine
 | 
			
		||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
 | 
			
		||||
 | 
			
		||||
[env]
 | 
			
		||||
DEFMT_LOG = "info"
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/target
 | 
			
		||||
/itm.txt
 | 
			
		||||
/.cargo/config*
 | 
			
		||||
/.vscode
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										83
									
								
								embedded-examples/stm32f3-disco-rtic/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								embedded-examples/stm32f3-disco-rtic/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "satrs-stm32f3-disco-rtic"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
default-run = "satrs-stm32f3-disco-rtic"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[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"] }
 | 
			
		||||
embedded-hal = "0.2.7"
 | 
			
		||||
cortex-m-semihosting = "0.5.0"
 | 
			
		||||
enumset = "1"
 | 
			
		||||
heapless = "0.8"
 | 
			
		||||
 | 
			
		||||
[dependencies.rtic]
 | 
			
		||||
version = "2"
 | 
			
		||||
features = ["thumbv7-backend"]
 | 
			
		||||
 | 
			
		||||
[dependencies.rtic-monotonics]
 | 
			
		||||
version = "2"
 | 
			
		||||
features = ["cortex-m-systick"]
 | 
			
		||||
 | 
			
		||||
[dependencies.cobs]
 | 
			
		||||
version = "0.3"
 | 
			
		||||
default-features = false
 | 
			
		||||
 | 
			
		||||
[dependencies.stm32f3xx-hal]
 | 
			
		||||
git = "https://github.com/robamu/stm32f3xx-hal"
 | 
			
		||||
version = "0.11.0-alpha.0"
 | 
			
		||||
features = ["stm32f303xc", "rt", "enumset"]
 | 
			
		||||
branch = "complete-dma-update"
 | 
			
		||||
# Can be used in workspace to develop and update HAL
 | 
			
		||||
# path = "../stm32f3xx-hal"
 | 
			
		||||
 | 
			
		||||
[dependencies.stm32f3-discovery]
 | 
			
		||||
git = "https://github.com/robamu/stm32f3-discovery"
 | 
			
		||||
version = "0.8.0-alpha.0"
 | 
			
		||||
branch = "complete-dma-update-hal"
 | 
			
		||||
# Can be used in workspace to develop and update BSP
 | 
			
		||||
# path = "../stm32f3-discovery"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs]
 | 
			
		||||
# path = "satrs"
 | 
			
		||||
version = "0.2"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["defmt"]
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
defmt-test = "0.3"
 | 
			
		||||
 | 
			
		||||
# cargo test
 | 
			
		||||
[profile.test]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = true # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
opt-level = "s" # <-
 | 
			
		||||
overflow-checks = true # <-
 | 
			
		||||
 | 
			
		||||
# cargo build/run --release
 | 
			
		||||
[profile.release]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = false # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
lto = 'fat'
 | 
			
		||||
opt-level = "s" # <-
 | 
			
		||||
overflow-checks = false # <-
 | 
			
		||||
 | 
			
		||||
# cargo test --release
 | 
			
		||||
[profile.bench]
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
debug = 2
 | 
			
		||||
debug-assertions = false # <-
 | 
			
		||||
incremental = false
 | 
			
		||||
lto = 'fat'
 | 
			
		||||
opt-level = "s" # <-
 | 
			
		||||
overflow-checks = false # <-
 | 
			
		||||
							
								
								
									
										114
									
								
								embedded-examples/stm32f3-disco-rtic/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								embedded-examples/stm32f3-disco-rtic/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
sat-rs example for the STM32F3-Discovery board
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
This example application shows how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
 | 
			
		||||
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 STM32F3-Discovery device was picked because it is a cheap Cortex-M4 based device which is also
 | 
			
		||||
used by the [Rust Embedded Book](https://docs.rust-embedded.org/book/intro/hardware.html) and the
 | 
			
		||||
[Rust Discovery](https://docs.rust-embedded.org/discovery/f3discovery/) book as an introduction
 | 
			
		||||
to embedded Rust.
 | 
			
		||||
 | 
			
		||||
## 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 STM32F303VCTx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
							
								
								
									
										38601
									
								
								embedded-examples/stm32f3-disco-rtic/STM32F303.svd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38601
									
								
								embedded-examples/stm32f3-disco-rtic/STM32F303.svd
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -24,7 +24,9 @@ break main
 | 
			
		||||
# # send captured ITM to the file itm.fifo
 | 
			
		||||
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
 | 
			
		||||
# # 8000000 must match the core clock frequency
 | 
			
		||||
monitor tpiu config internal itm.txt uart off 8000000
 | 
			
		||||
# # 2000000 is the frequency of the SWO pin. This was added for newer
 | 
			
		||||
# openocd versions like v0.12.0.
 | 
			
		||||
# monitor tpiu config internal itm.txt uart off 8000000 2000000
 | 
			
		||||
 | 
			
		||||
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
 | 
			
		||||
# # 8000000 must match the core clock frequency
 | 
			
		||||
@@ -32,7 +34,7 @@ monitor tpiu config internal itm.txt uart off 8000000
 | 
			
		||||
# monitor tpiu config external uart off 8000000 2000000
 | 
			
		||||
 | 
			
		||||
# # enable ITM port 0
 | 
			
		||||
monitor itm port 0 on
 | 
			
		||||
# monitor itm port 0 on
 | 
			
		||||
 | 
			
		||||
load
 | 
			
		||||
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
__pycache__
 | 
			
		||||
 | 
			
		||||
/venv
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
/log
 | 
			
		||||
/.idea/*
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
 | 
			
		||||
/seqcnt.txt
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
/tmtc_conf.json
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
    "com_if": "serial_cobs",
 | 
			
		||||
    "serial_baudrate": 115200
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +1,22 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""Example client for the sat-rs example application"""
 | 
			
		||||
import struct
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
from typing import 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
 | 
			
		||||
 | 
			
		||||
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, SpecificApidHandlerBase
 | 
			
		||||
from tmtccmd.com import ComInterface
 | 
			
		||||
@@ -25,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,
 | 
			
		||||
@@ -39,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 EXAMPLE_PUS_APID, TM_PACKET_IDS, 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 (
 | 
			
		||||
@@ -65,14 +65,20 @@ class SatRsConfigHook(HookBase):
 | 
			
		||||
        cfg = create_com_interface_cfg_default(
 | 
			
		||||
            com_if_key=com_if_key,
 | 
			
		||||
            json_cfg_path=self.cfg_path,
 | 
			
		||||
            space_packet_ids=TM_PACKET_IDS,
 | 
			
		||||
            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
 | 
			
		||||
@@ -85,6 +91,13 @@ class SatRsConfigHook(HookBase):
 | 
			
		||||
        return get_core_object_ids()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
    root_node = CmdTreeNode.root_node()
 | 
			
		||||
    root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
 | 
			
		||||
    root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
 | 
			
		||||
    return root_node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -97,17 +110,20 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
        self.raw_logger = raw_logger
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
 | 
			
		||||
    def handle_tm(self, 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:
 | 
			
		||||
@@ -121,48 +137,39 @@ class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
            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,
 | 
			
		||||
@@ -174,9 +181,9 @@ 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,
 | 
			
		||||
            pus_verificator=verif_wrapper.pus_verificator,
 | 
			
		||||
            default_pus_apid=EXAMPLE_PUS_APID,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -185,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)
 | 
			
		||||
@@ -193,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():
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
tmtccmd == 4.0.0a0
 | 
			
		||||
tmtccmd == 8.0.1
 | 
			
		||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
 | 
			
		||||
@@ -1,17 +1,15 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
use satrs_stm32f3_disco_rtic as _;
 | 
			
		||||
 | 
			
		||||
extern crate panic_itm;
 | 
			
		||||
 | 
			
		||||
use cortex_m_rt::entry;
 | 
			
		||||
 | 
			
		||||
use stm32f3_discovery::leds::Leds;
 | 
			
		||||
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
 | 
			
		||||
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
 | 
			
		||||
use stm32f3_discovery::leds::Leds;
 | 
			
		||||
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
 | 
			
		||||
 | 
			
		||||
#[entry]
 | 
			
		||||
fn main()-> ! {
 | 
			
		||||
#[cortex_m_rt::entry]
 | 
			
		||||
fn main() -> ! {
 | 
			
		||||
    defmt::println!("STM32F3 Discovery Blinky");
 | 
			
		||||
    let dp = pac::Peripherals::take().unwrap();
 | 
			
		||||
    let mut rcc = dp.RCC.constrain();
 | 
			
		||||
    let cp = cortex_m::Peripherals::take().unwrap();
 | 
			
		||||
@@ -30,49 +28,49 @@ fn main()-> ! {
 | 
			
		||||
        gpioe.pe14,
 | 
			
		||||
        gpioe.pe15,
 | 
			
		||||
        &mut gpioe.moder,
 | 
			
		||||
        &mut gpioe.otyper
 | 
			
		||||
        &mut gpioe.otyper,
 | 
			
		||||
    );
 | 
			
		||||
    let delay_ms = 200u16;
 | 
			
		||||
    loop {
 | 
			
		||||
        leds.ld3.toggle().ok();
 | 
			
		||||
        leds.ld3_n.toggle().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld3.toggle().ok();
 | 
			
		||||
        leds.ld3_n.toggle().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        //explicit on/off
 | 
			
		||||
        leds.ld4.on().ok();
 | 
			
		||||
        leds.ld4_nw.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld4.off().ok();
 | 
			
		||||
        leds.ld4_nw.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld5.on().ok();
 | 
			
		||||
        leds.ld5_ne.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld5.off().ok();
 | 
			
		||||
        leds.ld5_ne.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld6.on().ok();
 | 
			
		||||
        leds.ld6_w.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld6.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        
 | 
			
		||||
        leds.ld7.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld7.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        
 | 
			
		||||
        leds.ld8.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld8.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        
 | 
			
		||||
        leds.ld9.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld9.off().ok();
 | 
			
		||||
        leds.ld6_w.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld10.on().ok();
 | 
			
		||||
        leds.ld7_e.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld10.off().ok();
 | 
			
		||||
        leds.ld7_e.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld8_sw.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld8_sw.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld9_se.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld9_se.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
 | 
			
		||||
        leds.ld10_s.on().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
        leds.ld10_s.off().ok();
 | 
			
		||||
        delay.delay_ms(delay_ms);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								embedded-examples/stm32f3-disco-rtic/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								embedded-examples/stm32f3-disco-rtic/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![no_std]
 | 
			
		||||
 | 
			
		||||
use cortex_m_semihosting::debug;
 | 
			
		||||
 | 
			
		||||
use defmt_brtt as _; // global logger
 | 
			
		||||
 | 
			
		||||
use stm32f3xx_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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										682
									
								
								embedded-examples/stm32f3-disco-rtic/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										682
									
								
								embedded-examples/stm32f3-disco-rtic/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,682 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
use satrs::pus::verification::{
 | 
			
		||||
    FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
 | 
			
		||||
};
 | 
			
		||||
use satrs::spacepackets::ecss::tc::PusTcReader;
 | 
			
		||||
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
 | 
			
		||||
use satrs::spacepackets::ecss::EcssEnumU16;
 | 
			
		||||
use satrs::spacepackets::CcsdsPacket;
 | 
			
		||||
use satrs::spacepackets::{ByteConversionError, SpHeader};
 | 
			
		||||
// global logger + panicking-behavior + memory layout
 | 
			
		||||
use satrs_stm32f3_disco_rtic as _;
 | 
			
		||||
 | 
			
		||||
use rtic::app;
 | 
			
		||||
 | 
			
		||||
use heapless::{mpmc::Q8, Vec};
 | 
			
		||||
#[allow(unused_imports)]
 | 
			
		||||
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;
 | 
			
		||||
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
 | 
			
		||||
use stm32f3xx_hal::pac::USART2;
 | 
			
		||||
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
 | 
			
		||||
 | 
			
		||||
const UART_BAUD: u32 = 115200;
 | 
			
		||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
 | 
			
		||||
const TX_HANDLER_FREQ_MS: u32 = 20;
 | 
			
		||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
 | 
			
		||||
const MAX_TC_LEN: usize = 128;
 | 
			
		||||
const MAX_TM_LEN: usize = 128;
 | 
			
		||||
pub const PUS_APID: u16 = 0x02;
 | 
			
		||||
 | 
			
		||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
 | 
			
		||||
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
 | 
			
		||||
type InstantFugit = TimerInstantU32<1000>;
 | 
			
		||||
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
 | 
			
		||||
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
 | 
			
		||||
 | 
			
		||||
// This is the predictable maximum overhead of the COBS encoding scheme.
 | 
			
		||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
 | 
			
		||||
const COBS_TC_OVERHEAD: usize = (MAX_TC_LEN + 254 - 1) / 254;
 | 
			
		||||
const COBS_TM_OVERHEAD: usize = (MAX_TM_LEN + 254 - 1) / 254;
 | 
			
		||||
 | 
			
		||||
const TC_BUF_LEN: usize = MAX_TC_LEN + COBS_TC_OVERHEAD;
 | 
			
		||||
const TM_BUF_LEN: usize = MAX_TC_LEN + COBS_TM_OVERHEAD;
 | 
			
		||||
 | 
			
		||||
// This is a static buffer which should ONLY (!) be used as the TX DMA
 | 
			
		||||
// transfer buffer.
 | 
			
		||||
static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
 | 
			
		||||
// This is a static buffer which should ONLY (!) be used as the RX DMA
 | 
			
		||||
// transfer buffer.
 | 
			
		||||
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
 | 
			
		||||
 | 
			
		||||
type TmPacket = Vec<u8, MAX_TM_LEN>;
 | 
			
		||||
type TcPacket = Vec<u8, MAX_TC_LEN>;
 | 
			
		||||
 | 
			
		||||
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
 | 
			
		||||
 | 
			
		||||
use core::sync::atomic::{AtomicU16, Ordering};
 | 
			
		||||
 | 
			
		||||
pub struct SeqCountProviderAtomicRef {
 | 
			
		||||
    atomic: AtomicU16,
 | 
			
		||||
    ordering: Ordering,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SeqCountProviderAtomicRef {
 | 
			
		||||
    pub const fn new(ordering: Ordering) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            atomic: AtomicU16::new(0),
 | 
			
		||||
            ordering,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
 | 
			
		||||
    fn get(&self) -> u16 {
 | 
			
		||||
        self.atomic.load(self.ordering)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn increment(&self) {
 | 
			
		||||
        self.atomic.fetch_add(1, self.ordering);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_and_increment(&self) -> u16 {
 | 
			
		||||
        self.atomic.fetch_add(1, self.ordering)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
 | 
			
		||||
    SeqCountProviderAtomicRef::new(Ordering::Relaxed);
 | 
			
		||||
 | 
			
		||||
pub struct TxIdle {
 | 
			
		||||
    tx: TxType,
 | 
			
		||||
    dma_channel: dma1::C7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, defmt::Format)]
 | 
			
		||||
pub enum TmSendError {
 | 
			
		||||
    ByteConversion(ByteConversionError),
 | 
			
		||||
    Queue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ByteConversionError> for TmSendError {
 | 
			
		||||
    fn from(value: ByteConversionError) -> Self {
 | 
			
		||||
        Self::ByteConversion(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
 | 
			
		||||
    if tm_creator.len_written() > MAX_TM_LEN {
 | 
			
		||||
        return Err(ByteConversionError::ToSliceTooSmall {
 | 
			
		||||
            expected: tm_creator.len_written(),
 | 
			
		||||
            found: MAX_TM_LEN,
 | 
			
		||||
        }
 | 
			
		||||
        .into());
 | 
			
		||||
    }
 | 
			
		||||
    let mut tm_vec = TmPacket::new();
 | 
			
		||||
    tm_vec
 | 
			
		||||
        .resize(tm_creator.len_written(), 0)
 | 
			
		||||
        .expect("vec resize failed");
 | 
			
		||||
    tm_creator.write_to_bytes(tm_vec.as_mut_slice())?;
 | 
			
		||||
    defmt::info!(
 | 
			
		||||
        "Sending TM[{},{}] with size {}",
 | 
			
		||||
        tm_creator.service(),
 | 
			
		||||
        tm_creator.subservice(),
 | 
			
		||||
        tm_creator.len_written()
 | 
			
		||||
    );
 | 
			
		||||
    TM_REQUESTS
 | 
			
		||||
        .enqueue(tm_vec)
 | 
			
		||||
        .map_err(|_| TmSendError::Queue)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handle_tm_send_error(error: TmSendError) {
 | 
			
		||||
    defmt::warn!("sending tm failed with error {}", error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum UartTxState {
 | 
			
		||||
    // Wrapped in an option because we need an owned type later.
 | 
			
		||||
    Idle(Option<TxIdle>),
 | 
			
		||||
    // Same as above
 | 
			
		||||
    Transmitting(Option<TxDmaTransferType>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct UartTxShared {
 | 
			
		||||
    last_completed: Option<InstantFugit>,
 | 
			
		||||
    state: UartTxState,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct RequestWithToken {
 | 
			
		||||
    token: VerificationToken<TcStateAccepted>,
 | 
			
		||||
    request: Request,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, defmt::Format)]
 | 
			
		||||
pub enum Request {
 | 
			
		||||
    Ping,
 | 
			
		||||
    ChangeBlinkFrequency(u32),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, defmt::Format)]
 | 
			
		||||
pub enum RequestError {
 | 
			
		||||
    InvalidApid = 1,
 | 
			
		||||
    InvalidService = 2,
 | 
			
		||||
    InvalidSubservice = 3,
 | 
			
		||||
    NotEnoughAppData = 4,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn convert_pus_tc_to_request(
 | 
			
		||||
    tc: &PusTcReader,
 | 
			
		||||
    verif_reporter: &mut VerificationReportCreator,
 | 
			
		||||
    src_data_buf: &mut [u8],
 | 
			
		||||
    timestamp: &[u8],
 | 
			
		||||
) -> Result<RequestWithToken, RequestError> {
 | 
			
		||||
    defmt::info!(
 | 
			
		||||
        "Found PUS TC [{},{}] with length {}",
 | 
			
		||||
        tc.service(),
 | 
			
		||||
        tc.subservice(),
 | 
			
		||||
        tc.len_packed()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let token = verif_reporter.add_tc(tc);
 | 
			
		||||
    if tc.apid() != PUS_APID {
 | 
			
		||||
        defmt::warn!("Received tc with unknown APID {}", tc.apid());
 | 
			
		||||
        let result = send_tm(
 | 
			
		||||
            verif_reporter
 | 
			
		||||
                .acceptance_failure(
 | 
			
		||||
                    src_data_buf,
 | 
			
		||||
                    token,
 | 
			
		||||
                    SEQ_COUNT_PROVIDER.get_and_increment(),
 | 
			
		||||
                    0,
 | 
			
		||||
                    FailParams::new(timestamp, &EcssEnumU16::new(0), &[]),
 | 
			
		||||
                )
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
        if let Err(e) = result {
 | 
			
		||||
            handle_tm_send_error(e);
 | 
			
		||||
        }
 | 
			
		||||
        return Err(RequestError::InvalidApid);
 | 
			
		||||
    }
 | 
			
		||||
    let (tm_creator, accepted_token) = verif_reporter
 | 
			
		||||
        .acceptance_success(
 | 
			
		||||
            src_data_buf,
 | 
			
		||||
            token,
 | 
			
		||||
            SEQ_COUNT_PROVIDER.get_and_increment(),
 | 
			
		||||
            0,
 | 
			
		||||
            timestamp,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = send_tm(tm_creator) {
 | 
			
		||||
        handle_tm_send_error(e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if tc.service() == 17 && tc.subservice() == 1 {
 | 
			
		||||
        if tc.subservice() == 1 {
 | 
			
		||||
            return Ok(RequestWithToken {
 | 
			
		||||
                request: Request::Ping,
 | 
			
		||||
                token: accepted_token,
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err(RequestError::InvalidSubservice);
 | 
			
		||||
        }
 | 
			
		||||
    } else if tc.service() == 8 {
 | 
			
		||||
        if tc.subservice() == 1 {
 | 
			
		||||
            if tc.user_data().len() < 4 {
 | 
			
		||||
                return Err(RequestError::NotEnoughAppData);
 | 
			
		||||
            }
 | 
			
		||||
            let new_freq_ms = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
 | 
			
		||||
            return Ok(RequestWithToken {
 | 
			
		||||
                request: Request::ChangeBlinkFrequency(new_freq_ms),
 | 
			
		||||
                token: accepted_token,
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err(RequestError::InvalidSubservice);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        return Err(RequestError::InvalidService);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[app(device = stm32f3xx_hal::pac, peripherals = true)]
 | 
			
		||||
mod app {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use core::slice::Iter;
 | 
			
		||||
    use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
 | 
			
		||||
    use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
 | 
			
		||||
    #[allow(unused_imports)]
 | 
			
		||||
    use stm32f3_discovery::leds::Direction;
 | 
			
		||||
    use stm32f3_discovery::leds::Leds;
 | 
			
		||||
    use stm32f3xx_hal::prelude::*;
 | 
			
		||||
 | 
			
		||||
    use stm32f3_discovery::switch_hal::OutputSwitch;
 | 
			
		||||
    use stm32f3xx_hal::Switch;
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
 | 
			
		||||
 | 
			
		||||
    systick_monotonic!(Mono, 1000);
 | 
			
		||||
 | 
			
		||||
    #[shared]
 | 
			
		||||
    struct Shared {
 | 
			
		||||
        blink_freq: MillisDurationU32,
 | 
			
		||||
        tx_shared: UartTxShared,
 | 
			
		||||
        rx_transfer: Option<RxDmaTransferType>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[local]
 | 
			
		||||
    struct Local {
 | 
			
		||||
        verif_reporter: VerificationReportCreator,
 | 
			
		||||
        leds: Leds,
 | 
			
		||||
        last_dir: Direction,
 | 
			
		||||
        curr_dir: Iter<'static, Direction>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[init]
 | 
			
		||||
    fn init(cx: init::Context) -> (Shared, Local) {
 | 
			
		||||
        let mut rcc = cx.device.RCC.constrain();
 | 
			
		||||
 | 
			
		||||
        // Initialize the systick interrupt & obtain the token to prove that we did
 | 
			
		||||
        Mono::start(cx.core.SYST, 8_000_000);
 | 
			
		||||
 | 
			
		||||
        let mut flash = cx.device.FLASH.constrain();
 | 
			
		||||
        let clocks = rcc
 | 
			
		||||
            .cfgr
 | 
			
		||||
            .use_hse(8.MHz())
 | 
			
		||||
            .sysclk(8.MHz())
 | 
			
		||||
            .pclk1(8.MHz())
 | 
			
		||||
            .freeze(&mut flash.acr);
 | 
			
		||||
 | 
			
		||||
        // Set up monotonic timer.
 | 
			
		||||
        //let mono_timer = MonoTimer::new(cx.core.DWT, clocks, &mut cx.core.DCB);
 | 
			
		||||
 | 
			
		||||
        defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
 | 
			
		||||
        let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
 | 
			
		||||
 | 
			
		||||
        let leds = Leds::new(
 | 
			
		||||
            gpioe.pe8,
 | 
			
		||||
            gpioe.pe9,
 | 
			
		||||
            gpioe.pe10,
 | 
			
		||||
            gpioe.pe11,
 | 
			
		||||
            gpioe.pe12,
 | 
			
		||||
            gpioe.pe13,
 | 
			
		||||
            gpioe.pe14,
 | 
			
		||||
            gpioe.pe15,
 | 
			
		||||
            &mut gpioe.moder,
 | 
			
		||||
            &mut gpioe.otyper,
 | 
			
		||||
        );
 | 
			
		||||
        let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
 | 
			
		||||
        // USART2 pins
 | 
			
		||||
        let mut pins = (
 | 
			
		||||
            // TX pin: PA2
 | 
			
		||||
            gpioa
 | 
			
		||||
                .pa2
 | 
			
		||||
                .into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
 | 
			
		||||
            // RX pin: PA3
 | 
			
		||||
            gpioa
 | 
			
		||||
                .pa3
 | 
			
		||||
                .into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
 | 
			
		||||
        );
 | 
			
		||||
        pins.1.internal_pull_up(&mut gpioa.pupdr, true);
 | 
			
		||||
        let mut usart2 = Serial::new(
 | 
			
		||||
            cx.device.USART2,
 | 
			
		||||
            pins,
 | 
			
		||||
            UART_BAUD.Bd(),
 | 
			
		||||
            clocks,
 | 
			
		||||
            &mut rcc.apb1,
 | 
			
		||||
        );
 | 
			
		||||
        usart2.configure_rx_interrupt(RxEvent::Idle, Switch::On);
 | 
			
		||||
        // This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
 | 
			
		||||
        usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Switch::On);
 | 
			
		||||
 | 
			
		||||
        let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
 | 
			
		||||
        let (mut tx_serial, mut rx_serial) = usart2.split();
 | 
			
		||||
 | 
			
		||||
        // This interrupt is immediately triggered, clear it. It will only be reset
 | 
			
		||||
        // by the hardware when data is received on RX (RXNE event)
 | 
			
		||||
        rx_serial.clear_event(RxEvent::Idle);
 | 
			
		||||
        // For some reason, this is also immediately triggered..
 | 
			
		||||
        tx_serial.clear_event(TxEvent::TransmissionComplete);
 | 
			
		||||
        let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
 | 
			
		||||
        defmt::info!("Spawning tasks");
 | 
			
		||||
        blink::spawn().unwrap();
 | 
			
		||||
        serial_tx_handler::spawn().unwrap();
 | 
			
		||||
 | 
			
		||||
        let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
 | 
			
		||||
 | 
			
		||||
        (
 | 
			
		||||
            Shared {
 | 
			
		||||
                blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
 | 
			
		||||
                tx_shared: UartTxShared {
 | 
			
		||||
                    last_completed: None,
 | 
			
		||||
                    state: UartTxState::Idle(Some(TxIdle {
 | 
			
		||||
                        tx: tx_serial,
 | 
			
		||||
                        dma_channel: dma1.ch7,
 | 
			
		||||
                    })),
 | 
			
		||||
                },
 | 
			
		||||
                rx_transfer: Some(rx_transfer),
 | 
			
		||||
            },
 | 
			
		||||
            Local {
 | 
			
		||||
                verif_reporter,
 | 
			
		||||
                leds,
 | 
			
		||||
                last_dir: Direction::North,
 | 
			
		||||
                curr_dir: Direction::iter(),
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
 | 
			
		||||
    async fn blink(mut cx: blink::Context) {
 | 
			
		||||
        let blink::LocalResources {
 | 
			
		||||
            leds,
 | 
			
		||||
            curr_dir,
 | 
			
		||||
            last_dir,
 | 
			
		||||
            ..
 | 
			
		||||
        } = cx.local;
 | 
			
		||||
        let mut toggle_leds = |dir: &Direction| {
 | 
			
		||||
            let last_led = leds.for_direction(*last_dir);
 | 
			
		||||
            last_led.off().ok();
 | 
			
		||||
            let led = leds.for_direction(*dir);
 | 
			
		||||
            led.on().ok();
 | 
			
		||||
            *last_dir = *dir;
 | 
			
		||||
        };
 | 
			
		||||
        loop {
 | 
			
		||||
            match curr_dir.next() {
 | 
			
		||||
                Some(dir) => {
 | 
			
		||||
                    toggle_leds(dir);
 | 
			
		||||
                }
 | 
			
		||||
                None => {
 | 
			
		||||
                    *curr_dir = Direction::iter();
 | 
			
		||||
                    toggle_leds(curr_dir.next().unwrap());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
 | 
			
		||||
            Mono::delay(current_blink_freq).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(
 | 
			
		||||
        shared = [tx_shared],
 | 
			
		||||
    )]
 | 
			
		||||
    async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
 | 
			
		||||
        loop {
 | 
			
		||||
            let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
 | 
			
		||||
                if let UartTxState::Idle(_) = tx_shared.state {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                false
 | 
			
		||||
            });
 | 
			
		||||
            if is_idle {
 | 
			
		||||
                let last_completed = cx.shared.tx_shared.lock(|shared| shared.last_completed);
 | 
			
		||||
                if let Some(last_completed) = last_completed {
 | 
			
		||||
                    let elapsed_ms = (Mono::now() - last_completed).to_millis();
 | 
			
		||||
                    if elapsed_ms < MIN_DELAY_BETWEEN_TX_PACKETS_MS {
 | 
			
		||||
                        Mono::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis()).await;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Check for completion after 1 ms
 | 
			
		||||
                Mono::delay(1.millis()).await;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(vec) = TM_REQUESTS.dequeue() {
 | 
			
		||||
                cx.shared
 | 
			
		||||
                    .tx_shared
 | 
			
		||||
                    .lock(|tx_shared| match &mut tx_shared.state {
 | 
			
		||||
                        UartTxState::Idle(tx) => {
 | 
			
		||||
                            let encoded_len;
 | 
			
		||||
                            //debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
 | 
			
		||||
                            // Safety: We only copy the data into the TX DMA buffer in this task.
 | 
			
		||||
                            // If the DMA is active, another branch will be taken.
 | 
			
		||||
                            unsafe {
 | 
			
		||||
                                // 0 sentinel value as start marker
 | 
			
		||||
                                DMA_TX_BUF[0] = 0;
 | 
			
		||||
                                encoded_len =
 | 
			
		||||
                                    cobs::encode(&vec[0..vec.len()], &mut DMA_TX_BUF[1..]);
 | 
			
		||||
                                // Should never panic, we accounted for the overhead.
 | 
			
		||||
                                // Write into transfer buffer directly, no need for intermediate
 | 
			
		||||
                                // encoding buffer.
 | 
			
		||||
                                // 0 end marker
 | 
			
		||||
                                DMA_TX_BUF[encoded_len + 1] = 0;
 | 
			
		||||
                            }
 | 
			
		||||
                            //debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
 | 
			
		||||
                            //debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
 | 
			
		||||
                            let tx_idle = tx.take().unwrap();
 | 
			
		||||
                            // Transfer completion and re-scheduling of new TX transfers will be done
 | 
			
		||||
                            // by the IRQ handler.
 | 
			
		||||
                            // SAFETY: The DMA is the exclusive writer to the DMA buffer now.
 | 
			
		||||
                            let transfer = tx_idle.tx.write_all(
 | 
			
		||||
                                unsafe { &DMA_TX_BUF[0..encoded_len + 2] },
 | 
			
		||||
                                tx_idle.dma_channel,
 | 
			
		||||
                            );
 | 
			
		||||
                            tx_shared.state = UartTxState::Transmitting(Some(transfer));
 | 
			
		||||
                            // The memory block is automatically returned to the pool when it is dropped.
 | 
			
		||||
                        }
 | 
			
		||||
                        UartTxState::Transmitting(_) => (),
 | 
			
		||||
                    });
 | 
			
		||||
                // Check for completion after 1 ms
 | 
			
		||||
                Mono::delay(1.millis()).await;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // Nothing to do, and we are idle.
 | 
			
		||||
            Mono::delay(TX_HANDLER_FREQ_MS.millis()).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(
 | 
			
		||||
        local = [
 | 
			
		||||
            verif_reporter,
 | 
			
		||||
            decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
 | 
			
		||||
            src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
 | 
			
		||||
            timestamp: [u8; 7] = [0; 7],
 | 
			
		||||
        ],
 | 
			
		||||
        shared = [blink_freq]
 | 
			
		||||
    )]
 | 
			
		||||
    async fn serial_rx_handler(
 | 
			
		||||
        mut cx: serial_rx_handler::Context,
 | 
			
		||||
        received_packet: Vec<u8, MAX_TC_LEN>,
 | 
			
		||||
    ) {
 | 
			
		||||
        cx.local.timestamp[0] = P_FIELD_BASE;
 | 
			
		||||
        defmt::info!("Received packet with {} bytes", received_packet.len());
 | 
			
		||||
        let decode_buf = cx.local.decode_buf;
 | 
			
		||||
        let packet = received_packet.as_slice();
 | 
			
		||||
        let mut start_idx = None;
 | 
			
		||||
        for (idx, byte) in packet.iter().enumerate() {
 | 
			
		||||
            if *byte != 0 {
 | 
			
		||||
                start_idx = Some(idx);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if start_idx.is_none() {
 | 
			
		||||
            defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let start_idx = start_idx.unwrap();
 | 
			
		||||
        match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
 | 
			
		||||
            Ok(len) => {
 | 
			
		||||
                defmt::info!("Decoded packet length: {}", len);
 | 
			
		||||
                let pus_tc = PusTcReader::new(decode_buf);
 | 
			
		||||
                match pus_tc {
 | 
			
		||||
                    Ok((tc, _tc_len)) => {
 | 
			
		||||
                        match convert_pus_tc_to_request(
 | 
			
		||||
                            &tc,
 | 
			
		||||
                            cx.local.verif_reporter,
 | 
			
		||||
                            cx.local.src_data_buf,
 | 
			
		||||
                            cx.local.timestamp,
 | 
			
		||||
                        ) {
 | 
			
		||||
                            Ok(request_with_token) => {
 | 
			
		||||
                                let started_token = handle_start_verification(
 | 
			
		||||
                                    request_with_token.token,
 | 
			
		||||
                                    cx.local.verif_reporter,
 | 
			
		||||
                                    cx.local.src_data_buf,
 | 
			
		||||
                                    cx.local.timestamp,
 | 
			
		||||
                                );
 | 
			
		||||
 | 
			
		||||
                                match request_with_token.request {
 | 
			
		||||
                                    Request::Ping => {
 | 
			
		||||
                                        handle_ping_request(cx.local.timestamp);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    Request::ChangeBlinkFrequency(new_freq_ms) => {
 | 
			
		||||
                                        defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
 | 
			
		||||
                                        cx.shared.blink_freq.lock(|blink_freq| {
 | 
			
		||||
                                            *blink_freq =
 | 
			
		||||
                                                MillisDurationU32::from_ticks(new_freq_ms);
 | 
			
		||||
                                        });
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                handle_completion_verification(
 | 
			
		||||
                                    started_token,
 | 
			
		||||
                                    cx.local.verif_reporter,
 | 
			
		||||
                                    cx.local.src_data_buf,
 | 
			
		||||
                                    cx.local.timestamp,
 | 
			
		||||
                                );
 | 
			
		||||
                            }
 | 
			
		||||
                            Err(e) => {
 | 
			
		||||
                                // TODO: Error handling: Send verification failure based on request error.
 | 
			
		||||
                                defmt::warn!("request error {}", e);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        defmt::warn!("Error unpacking PUS TC: {}", e);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                defmt::warn!("decoding error, can only process cobs encoded frames")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_ping_request(timestamp: &[u8]) {
 | 
			
		||||
        defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
 | 
			
		||||
        let sp_header =
 | 
			
		||||
            SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
 | 
			
		||||
        let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
 | 
			
		||||
        let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
 | 
			
		||||
        let mut tm_packet = TmPacket::new();
 | 
			
		||||
        tm_packet
 | 
			
		||||
            .resize(ping_reply.len_written(), 0)
 | 
			
		||||
            .expect("vec resize failed");
 | 
			
		||||
        ping_reply.write_to_bytes(&mut tm_packet).unwrap();
 | 
			
		||||
        if TM_REQUESTS.enqueue(tm_packet).is_err() {
 | 
			
		||||
            defmt::warn!("TC queue full");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_start_verification(
 | 
			
		||||
        accepted_token: VerificationToken<TcStateAccepted>,
 | 
			
		||||
        verif_reporter: &mut VerificationReportCreator,
 | 
			
		||||
        src_data_buf: &mut [u8],
 | 
			
		||||
        timestamp: &[u8],
 | 
			
		||||
    ) -> VerificationToken<TcStateStarted> {
 | 
			
		||||
        let (tm_creator, started_token) = verif_reporter
 | 
			
		||||
            .start_success(
 | 
			
		||||
                src_data_buf,
 | 
			
		||||
                accepted_token,
 | 
			
		||||
                SEQ_COUNT_PROVIDER.get(),
 | 
			
		||||
                0,
 | 
			
		||||
                ×tamp,
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let result = send_tm(tm_creator);
 | 
			
		||||
        if let Err(e) = result {
 | 
			
		||||
            handle_tm_send_error(e);
 | 
			
		||||
        }
 | 
			
		||||
        started_token
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_completion_verification(
 | 
			
		||||
        started_token: VerificationToken<TcStateStarted>,
 | 
			
		||||
        verif_reporter: &mut VerificationReportCreator,
 | 
			
		||||
        src_data_buf: &mut [u8],
 | 
			
		||||
        timestamp: &[u8],
 | 
			
		||||
    ) {
 | 
			
		||||
        let result = send_tm(
 | 
			
		||||
            verif_reporter
 | 
			
		||||
                .completion_success(
 | 
			
		||||
                    src_data_buf,
 | 
			
		||||
                    started_token,
 | 
			
		||||
                    SEQ_COUNT_PROVIDER.get(),
 | 
			
		||||
                    0,
 | 
			
		||||
                    timestamp,
 | 
			
		||||
                )
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        );
 | 
			
		||||
        if let Err(e) = result {
 | 
			
		||||
            handle_tm_send_error(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(binds = DMA1_CH6, shared = [rx_transfer])]
 | 
			
		||||
    fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
 | 
			
		||||
        let mut tc_packet = TcPacket::new();
 | 
			
		||||
        cx.shared.rx_transfer.lock(|rx_transfer| {
 | 
			
		||||
            let rx_ref = rx_transfer.as_ref().unwrap();
 | 
			
		||||
            if rx_ref.is_complete() {
 | 
			
		||||
                let uart_rx_owned = rx_transfer.take().unwrap();
 | 
			
		||||
                let (buf, c, rx) = uart_rx_owned.stop();
 | 
			
		||||
                // The received data is transferred to another task now to avoid any processing overhead
 | 
			
		||||
                // during the interrupt. There are multiple ways to do this, we use a stack allocaed vector here
 | 
			
		||||
                // to do this.
 | 
			
		||||
                tc_packet.resize(buf.len(), 0).expect("vec resize failed");
 | 
			
		||||
                tc_packet.copy_from_slice(buf);
 | 
			
		||||
 | 
			
		||||
                // Start the next transfer as soon as possible.
 | 
			
		||||
                *rx_transfer = Some(rx.read_exact(buf, c));
 | 
			
		||||
 | 
			
		||||
                // Send the vector to a regular task.
 | 
			
		||||
                serial_rx_handler::spawn(tc_packet).expect("spawning rx handler task failed");
 | 
			
		||||
                // If this happens, there is a high chance that the maximum packet length was
 | 
			
		||||
                // exceeded. Circular mode is not used here, so data might be missed.
 | 
			
		||||
                defmt::warn!(
 | 
			
		||||
                    "rx transfer with maximum length {}, might miss data",
 | 
			
		||||
                    TC_BUF_LEN
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
 | 
			
		||||
    fn serial_isr(mut cx: serial_isr::Context) {
 | 
			
		||||
        cx.shared
 | 
			
		||||
            .tx_shared
 | 
			
		||||
            .lock(|tx_shared| match &mut tx_shared.state {
 | 
			
		||||
                UartTxState::Idle(_) => (),
 | 
			
		||||
                UartTxState::Transmitting(transfer) => {
 | 
			
		||||
                    let transfer_ref = transfer.as_ref().unwrap();
 | 
			
		||||
                    if transfer_ref.is_complete() {
 | 
			
		||||
                        let transfer = transfer.take().unwrap();
 | 
			
		||||
                        let (_, dma_channel, mut tx) = transfer.stop();
 | 
			
		||||
                        tx.clear_event(TxEvent::TransmissionComplete);
 | 
			
		||||
                        tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
 | 
			
		||||
                        // We cache the last completed time to ensure that there is a minimum delay between consecutive
 | 
			
		||||
                        // transferred packets.
 | 
			
		||||
                        tx_shared.last_completed = Some(Mono::now());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        let mut tc_packet = TcPacket::new();
 | 
			
		||||
        cx.shared.rx_transfer.lock(|rx_transfer| {
 | 
			
		||||
            let rx_transfer_ref = rx_transfer.as_ref().unwrap();
 | 
			
		||||
            // Received a partial packet.
 | 
			
		||||
            if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
 | 
			
		||||
                let rx_transfer_owned = rx_transfer.take().unwrap();
 | 
			
		||||
                let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
 | 
			
		||||
                // The received data is transferred to another task now to avoid any processing overhead
 | 
			
		||||
                // during the interrupt. There are multiple ways to do this, we use a stack
 | 
			
		||||
                // allocated vector to do this.
 | 
			
		||||
                tc_packet
 | 
			
		||||
                    .resize(rx_len as usize, 0)
 | 
			
		||||
                    .expect("vec resize failed");
 | 
			
		||||
                tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
 | 
			
		||||
                rx.clear_event(RxEvent::Idle);
 | 
			
		||||
                serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
 | 
			
		||||
                *rx_transfer = Some(rx.read_exact(buf, ch));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
	// List of extensions which should be recommended for users of this workspace.
 | 
			
		||||
	"recommendations": [
 | 
			
		||||
		"rust-lang.rust",
 | 
			
		||||
		"marus25.cortex-debug",
 | 
			
		||||
		"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/stm32f3-disco-rtic/vscode/launch.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								embedded-examples/stm32f3-disco-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": "STM32F303VCTx",
 | 
			
		||||
      "coreConfigs": [
 | 
			
		||||
        {
 | 
			
		||||
          "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32f3-disco-rtic",
 | 
			
		||||
          "rttEnabled": true,
 | 
			
		||||
          "svdFile": "STM32F303.svd"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,8 @@ proc CDSWOConfigure { CDCPUFreqHz CDSWOFreqHz CDSWOOutput } {
 | 
			
		||||
    # Alternative option: Pipe ITM output into itm.txt file
 | 
			
		||||
    # tpiu config internal itm.txt uart off $CDCPUFreqHz
 | 
			
		||||
 | 
			
		||||
    # Default option so SWO display of VS code works.
 | 
			
		||||
    # Default option so SWO display of VS code works. Please note that this might not be required
 | 
			
		||||
    # anymore starting at openocd v0.12.0
 | 
			
		||||
    tpiu config internal $CDSWOOutput uart off $CDCPUFreqHz $CDSWOFreqHz
 | 
			
		||||
    itm port 0 on
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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,4 +1,5 @@
 | 
			
		||||
/venv
 | 
			
		||||
/.tmtc-history.txt
 | 
			
		||||
/log
 | 
			
		||||
/.idea/*
 | 
			
		||||
!/.idea/runConfigurations
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
    "com_if": "udp",
 | 
			
		||||
    "tcpip_udp_port": 7301
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										212
									
								
								satrs-example-stm32f3-disco/pyclient/main.py → embedded-examples/stm32h7-nucleo-rtic/pyclient/main.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										212
									
								
								satrs-example-stm32f3-disco/pyclient/main.py → embedded-examples/stm32h7-nucleo-rtic/pyclient/main.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,39 +1,40 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""Example client for the sat-rs example application"""
 | 
			
		||||
import enum
 | 
			
		||||
import struct
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
from typing import Optional, cast
 | 
			
		||||
from typing import Any, Optional, cast
 | 
			
		||||
from prompt_toolkit.history import FileHistory, History
 | 
			
		||||
from spacepackets.ecss.tm import CdsShortTimestamp
 | 
			
		||||
 | 
			
		||||
import tmtccmd
 | 
			
		||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, 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 tmtccmd import CcsdsTmtcBackend, TcHandlerBase, ProcedureParamsWrapper
 | 
			
		||||
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.tm import CcsdsTmHandler, SpecificApidHandlerBase
 | 
			
		||||
from tmtccmd.com_if import ComInterface
 | 
			
		||||
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
 | 
			
		||||
from tmtccmd.com import ComInterface
 | 
			
		||||
from tmtccmd.config import (
 | 
			
		||||
    CmdTreeNode,
 | 
			
		||||
    default_json_path,
 | 
			
		||||
    SetupParams,
 | 
			
		||||
    TmTcCfgHookBase,
 | 
			
		||||
    TmtcDefinitionWrapper,
 | 
			
		||||
    CoreServiceList,
 | 
			
		||||
    OpCodeEntry,
 | 
			
		||||
    HookBase,
 | 
			
		||||
    params_to_procedure_conversion,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.config.com_if import SerialCfgWrapper
 | 
			
		||||
from tmtccmd.config.com import SerialCfgWrapper
 | 
			
		||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
 | 
			
		||||
from tmtccmd.logging import get_console_logger
 | 
			
		||||
from tmtccmd.logging.pus import (
 | 
			
		||||
    RegularTmtcLogWrapper,
 | 
			
		||||
    RawTmtcTimedLogWrapper,
 | 
			
		||||
    TimedLogWhen,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.tc import (
 | 
			
		||||
from tmtccmd.tmtc import (
 | 
			
		||||
    TcQueueEntryType,
 | 
			
		||||
    ProcedureWrapper,
 | 
			
		||||
    TcProcedureType,
 | 
			
		||||
@@ -41,27 +42,26 @@ from tmtccmd.tc import (
 | 
			
		||||
    SendCbParams,
 | 
			
		||||
    DefaultPusQueueHelper,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.tm.pus_5_event import Service5Tm
 | 
			
		||||
from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider
 | 
			
		||||
from tmtccmd.pus.s5_fsfw_event import Service5Tm
 | 
			
		||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
 | 
			
		||||
from tmtccmd.util.obj_id import ObjectIdDictT
 | 
			
		||||
 | 
			
		||||
from tmtccmd.util.tmtc_printer import FsfwTmTcPrinter
 | 
			
		||||
 | 
			
		||||
LOGGER = get_console_logger()
 | 
			
		||||
_LOGGER = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
EXAMPLE_PUS_APID = 0x02
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SatRsConfigHook(TmTcCfgHookBase):
 | 
			
		||||
class SatRsConfigHook(HookBase):
 | 
			
		||||
    def __init__(self, json_cfg_path: str):
 | 
			
		||||
        super().__init__(json_cfg_path=json_cfg_path)
 | 
			
		||||
        super().__init__(json_cfg_path)
 | 
			
		||||
 | 
			
		||||
    def assign_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
 | 
			
		||||
        from tmtccmd.config.com_if import (
 | 
			
		||||
    def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
 | 
			
		||||
        from tmtccmd.config.com import (
 | 
			
		||||
            create_com_interface_default,
 | 
			
		||||
            create_com_interface_cfg_default,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert self.cfg_path is not None
 | 
			
		||||
        cfg = create_com_interface_cfg_default(
 | 
			
		||||
            com_if_key=com_if_key,
 | 
			
		||||
            json_cfg_path=self.cfg_path,
 | 
			
		||||
@@ -76,35 +76,14 @@ class SatRsConfigHook(TmTcCfgHookBase):
 | 
			
		||||
            cfg.serial_cfg.serial_timeout = 0.5
 | 
			
		||||
        return create_com_interface_default(cfg)
 | 
			
		||||
 | 
			
		||||
    def get_tmtc_definitions(self) -> TmtcDefinitionWrapper:
 | 
			
		||||
        from tmtccmd.config.globals import get_default_tmtc_defs
 | 
			
		||||
    def get_command_definitions(self) -> CmdTreeNode:
 | 
			
		||||
        """This function should return the root node of the command definition tree."""
 | 
			
		||||
        return create_cmd_definition_tree()
 | 
			
		||||
 | 
			
		||||
        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("0", "Ping Test")
 | 
			
		||||
        defs.add_service(
 | 
			
		||||
            name=CoreServiceList.SERVICE_17_ALT,
 | 
			
		||||
            info="PUS Service 17 Test",
 | 
			
		||||
            op_code_entry=srv_17,
 | 
			
		||||
        )
 | 
			
		||||
        srv_3 = OpCodeEntry()
 | 
			
		||||
        defs.add_service(
 | 
			
		||||
            name=CoreServiceList.SERVICE_3,
 | 
			
		||||
            info="PUS Service 3 Housekeeping",
 | 
			
		||||
            op_code_entry=srv_3,
 | 
			
		||||
        )
 | 
			
		||||
        return defs
 | 
			
		||||
 | 
			
		||||
    def perform_mode_operation(self, tmtc_backend: CcsdsTmtcBackend, mode: int):
 | 
			
		||||
        LOGGER.info("Mode operation hook was called")
 | 
			
		||||
        pass
 | 
			
		||||
    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
 | 
			
		||||
@@ -112,74 +91,77 @@ class SatRsConfigHook(TmTcCfgHookBase):
 | 
			
		||||
        return get_core_object_ids()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
    root_node = CmdTreeNode.root_node()
 | 
			
		||||
    root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
 | 
			
		||||
    root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
 | 
			
		||||
    return root_node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PusHandler(SpecificApidHandlerBase):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        file_logger: logging.Logger,
 | 
			
		||||
        verif_wrapper: VerificationWrapper,
 | 
			
		||||
        printer: FsfwTmTcPrinter,
 | 
			
		||||
        raw_logger: RawTmtcTimedLogWrapper,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(EXAMPLE_PUS_APID, None)
 | 
			
		||||
        self.printer = printer
 | 
			
		||||
        self.file_logger = file_logger
 | 
			
		||||
        self.raw_logger = raw_logger
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
 | 
			
		||||
    def handle_tm(self, packet: bytes, _user_args: any):
 | 
			
		||||
    def handle_tm(self, packet: bytes, _user_args: Any):
 | 
			
		||||
        try:
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(packet)
 | 
			
		||||
            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}")
 | 
			
		||||
            _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 = tm_packet.service
 | 
			
		||||
        dedicated_handler = False
 | 
			
		||||
        service = pus_tm.service
 | 
			
		||||
        tm_packet = None
 | 
			
		||||
        if service == 1:
 | 
			
		||||
            tm_packet = Service1Tm.unpack(data=packet, params=UnpackParams(1, 2))
 | 
			
		||||
            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(
 | 
			
		||||
                _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(
 | 
			
		||||
                _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)
 | 
			
		||||
            dedicated_handler = True
 | 
			
		||||
        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)
 | 
			
		||||
            _LOGGER.info("No handling for HK packets implemented")
 | 
			
		||||
            _LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
 | 
			
		||||
            pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if pus_tm.subservice == 25:
 | 
			
		||||
                if len(pus_tm.source_data) < 8:
 | 
			
		||||
                    raise ValueError("No addressable ID in HK packet")
 | 
			
		||||
                json_str = pus_tm.source_data[8:]
 | 
			
		||||
            dedicated_handler = True
 | 
			
		||||
                _LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
 | 
			
		||||
        if service == 5:
 | 
			
		||||
            tm_packet = Service5Tm.unpack(packet)
 | 
			
		||||
            tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
        if service == 17:
 | 
			
		||||
            tm_packet = Service17Tm.unpack(packet)
 | 
			
		||||
            dedicated_handler = True
 | 
			
		||||
            tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
 | 
			
		||||
            if tm_packet.subservice == 2:
 | 
			
		||||
                self.printer.file_logger.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
                LOGGER.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
                _LOGGER.info("Received Ping Reply TM[17,2]")
 | 
			
		||||
            else:
 | 
			
		||||
                self.printer.file_logger.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
                LOGGER.info(
 | 
			
		||||
                _LOGGER.info(
 | 
			
		||||
                    f"Received Test Packet with unknown subservice {tm_packet.subservice}"
 | 
			
		||||
                )
 | 
			
		||||
        if tm_packet is None:
 | 
			
		||||
            LOGGER.info(
 | 
			
		||||
            _LOGGER.info(
 | 
			
		||||
                f"The service {service} is not implemented in Telemetry Factory"
 | 
			
		||||
            )
 | 
			
		||||
            tm_packet = PusTelemetry.unpack(packet)
 | 
			
		||||
        self.raw_logger.log_tm(tm_packet)
 | 
			
		||||
        if not dedicated_handler and tm_packet is not None:
 | 
			
		||||
            self.printer.handle_long_tm_print(packet_if=tm_packet, info_if=tm_packet)
 | 
			
		||||
            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:
 | 
			
		||||
@@ -198,8 +180,11 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
        self.seq_count_provider = seq_count_provider
 | 
			
		||||
        self.verif_wrapper = verif_wrapper
 | 
			
		||||
        self.queue_helper = DefaultPusQueueHelper(
 | 
			
		||||
            queue_wrapper=None,
 | 
			
		||||
            queue_wrapper=QueueWrapper.empty(),
 | 
			
		||||
            tc_sched_timestamp_len=7,
 | 
			
		||||
            seq_cnt_provider=seq_count_provider,
 | 
			
		||||
            pus_verificator=verif_wrapper.pus_verificator,
 | 
			
		||||
            default_pus_apid=EXAMPLE_PUS_APID,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_cb(self, send_params: SendCbParams):
 | 
			
		||||
@@ -212,61 +197,74 @@ class TcHandler(TcHandlerBase):
 | 
			
		||||
                )
 | 
			
		||||
                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}")
 | 
			
		||||
                _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)
 | 
			
		||||
            _LOGGER.info(log_entry.log_str)
 | 
			
		||||
 | 
			
		||||
    def queue_finished_cb(self, helper: ProcedureWrapper):
 | 
			
		||||
        if helper.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = helper.to_def_procedure()
 | 
			
		||||
            LOGGER.info(
 | 
			
		||||
                f"Queue handling finished for service {def_proc.service} and "
 | 
			
		||||
                f"op code {def_proc.op_code}"
 | 
			
		||||
            )
 | 
			
		||||
    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, helper: ProcedureWrapper, wrapper: FeedWrapper):
 | 
			
		||||
    def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
 | 
			
		||||
        q = self.queue_helper
 | 
			
		||||
        q.queue_wrapper = wrapper.queue_wrapper
 | 
			
		||||
        if helper.proc_type == TcProcedureType.DEFAULT:
 | 
			
		||||
            def_proc = helper.to_def_procedure()
 | 
			
		||||
            service = def_proc.service
 | 
			
		||||
            op_code = def_proc.op_code
 | 
			
		||||
            if (
 | 
			
		||||
                service == CoreServiceList.SERVICE_17
 | 
			
		||||
                or service == CoreServiceList.SERVICE_17_ALT
 | 
			
		||||
            ):
 | 
			
		||||
        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")
 | 
			
		||||
                return q.add_pus_tc(PusTelecommand(service=17, subservice=1))
 | 
			
		||||
                q.add_pus_tc(PusTelecommand(service=17, subservice=1))
 | 
			
		||||
            if cmd_path == "/change_blink_freq":
 | 
			
		||||
                self.create_change_blink_freq_command(q)
 | 
			
		||||
 | 
			
		||||
    def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
 | 
			
		||||
        q.add_log_cmd("Changing blink frequency")
 | 
			
		||||
        while True:
 | 
			
		||||
            blink_freq = int(
 | 
			
		||||
                input(
 | 
			
		||||
                    "Please specify new blink frequency in ms. Valid Range [2..10000]: "
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            if blink_freq < 2 or blink_freq > 10000:
 | 
			
		||||
                print(
 | 
			
		||||
                    "Invalid blink frequency. Please specify a value between 2 and 10000."
 | 
			
		||||
                )
 | 
			
		||||
                continue
 | 
			
		||||
            break
 | 
			
		||||
        app_data = struct.pack("!I", blink_freq)
 | 
			
		||||
        q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    add_colorlog_console_logger(_LOGGER)
 | 
			
		||||
    tmtccmd.init_printout(False)
 | 
			
		||||
    hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
 | 
			
		||||
    parser_wrapper = PreArgsParsingWrapper()
 | 
			
		||||
    parser_wrapper.create_default_parent_parser()
 | 
			
		||||
    parser_wrapper.create_default_parser()
 | 
			
		||||
    parser_wrapper.add_def_proc_args()
 | 
			
		||||
    post_args_wrapper = parser_wrapper.parse(hook_obj)
 | 
			
		||||
    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(params, proc_wrapper)
 | 
			
		||||
        post_args_wrapper.set_params_without_prompts(proc_wrapper)
 | 
			
		||||
    else:
 | 
			
		||||
        post_args_wrapper.set_params_with_prompts(params, proc_wrapper)
 | 
			
		||||
        post_args_wrapper.set_params_with_prompts(proc_wrapper)
 | 
			
		||||
    params.apid = EXAMPLE_PUS_APID
 | 
			
		||||
    setup_args = SetupWrapper(
 | 
			
		||||
        hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
 | 
			
		||||
    )
 | 
			
		||||
    # Create console logger helper and file loggers
 | 
			
		||||
    tmtc_logger = RegularTmtcLogWrapper()
 | 
			
		||||
    printer = FsfwTmTcPrinter(tmtc_logger.logger)
 | 
			
		||||
    file_logger = tmtc_logger.logger
 | 
			
		||||
    raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
 | 
			
		||||
    verificator = PusVerificator()
 | 
			
		||||
    verification_wrapper = VerificationWrapper(verificator, LOGGER, printer.file_logger)
 | 
			
		||||
    verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
 | 
			
		||||
    # Create primary TM handler and add it to the CCSDS Packet Handler
 | 
			
		||||
    tm_handler = PusHandler(verification_wrapper, printer, raw_logger)
 | 
			
		||||
    tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
 | 
			
		||||
    ccsds_handler = CcsdsTmHandler(generic_handler=None)
 | 
			
		||||
    ccsds_handler.add_apid_handler(tm_handler)
 | 
			
		||||
 | 
			
		||||
@@ -288,7 +286,7 @@ def main():
 | 
			
		||||
            if state.request == BackendRequest.TERMINATION_NO_ERROR:
 | 
			
		||||
                sys.exit(0)
 | 
			
		||||
            elif state.request == BackendRequest.DELAY_IDLE:
 | 
			
		||||
                LOGGER.info("TMTC Client in IDLE mode")
 | 
			
		||||
                _LOGGER.info("TMTC Client in IDLE mode")
 | 
			
		||||
                time.sleep(3.0)
 | 
			
		||||
            elif state.request == BackendRequest.DELAY_LISTENER:
 | 
			
		||||
                time.sleep(0.8)
 | 
			
		||||
@@ -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
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								misc/satrs-logo-v2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								misc/satrs-logo-v2.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 49 KiB  | 
@@ -15,7 +15,7 @@ action commanding could look like.
 | 
			
		||||
2. Target ID and Action String based. The target ID is the same as in the first proposal, but
 | 
			
		||||
   the unique action is identified by a string.
 | 
			
		||||
 | 
			
		||||
The framework provides an `ActionRequest` abstraction to model both of these cases.
 | 
			
		||||
The library provides an `ActionRequest` abstraction to model both of these cases.
 | 
			
		||||
 | 
			
		||||
## Commanding with ECSS PUS 8
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,10 @@ it is still centered around small packets. `sat-rs` provides support for these E
 | 
			
		||||
standards and also attempts to fill the gap to the internet protocol by providing the following
 | 
			
		||||
components.
 | 
			
		||||
 | 
			
		||||
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/host/udp_server/index.html).
 | 
			
		||||
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/index.html).
 | 
			
		||||
   UDP is already packet based which makes it an excellent fit for exchanging space packets.
 | 
			
		||||
2. [TCP TMTC Server Components](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/index.html).
 | 
			
		||||
   TCP is a stream based protocol, so the framework provides building blocks to parse telemetry
 | 
			
		||||
   TCP is a stream based protocol, so the library provides building blocks to parse telemetry
 | 
			
		||||
   from an arbitrary bytestream. Two concrete implementations are provided:
 | 
			
		||||
    - [TCP spacepackets server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
 | 
			
		||||
      to parse tightly packed CCSDS Spacepackets.
 | 
			
		||||
@@ -39,8 +39,12 @@ task might be to store all arriving telemetry persistently. This is especially i
 | 
			
		||||
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
 | 
			
		||||
 | 
			
		||||
The most important task of a TC source is to deliver the telecommands to the correct recipients.
 | 
			
		||||
For modern component oriented software using message passing, this usually includes staged
 | 
			
		||||
demultiplexing components to determine where a command needs to be sent.
 | 
			
		||||
For component oriented software using message passing, this usually includes staged demultiplexing
 | 
			
		||||
components to determine where a command needs to be sent.
 | 
			
		||||
 | 
			
		||||
Using a generic concept of a TC source and a TM sink as part of the software design simplifies
 | 
			
		||||
the flexibility of the TMTC infrastructure: Newly added TM generators and TC receiver only have to
 | 
			
		||||
forward their generated or received packets to those handler objects.
 | 
			
		||||
 | 
			
		||||
# Low-level protocols and the bridge to the communcation subsystem
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,13 +1,14 @@
 | 
			
		||||
# Framework Design
 | 
			
		||||
# Library Design
 | 
			
		||||
 | 
			
		||||
Satellites and space systems in general are complex systems with a wide range of requirements for
 | 
			
		||||
both the hardware and the software. Consequently, the general design of the framework is centered
 | 
			
		||||
both the hardware and the software. Consequently, the general design of the library is centered
 | 
			
		||||
around many light-weight components which try to impose as few restrictions as possible on how to
 | 
			
		||||
solve certain problems.
 | 
			
		||||
solve certain problems. This is also the reason why sat-rs is explicitely called a library
 | 
			
		||||
instead of a framework.
 | 
			
		||||
 | 
			
		||||
There are still a lot of common patterns and architectures across these systems where guidance
 | 
			
		||||
of how to solve a problem and a common structure would still be extremely useful to avoid pitfalls
 | 
			
		||||
which were already solved and to avoid boilerplate code. This framework tries to provide this
 | 
			
		||||
which were already solved and to avoid boilerplate code. This library tries to provide this
 | 
			
		||||
structure and guidance the following way:
 | 
			
		||||
 | 
			
		||||
1. Providing this book which explains the architecture and design patterns in respect to common
 | 
			
		||||
@@ -18,7 +19,7 @@ structure and guidance the following way:
 | 
			
		||||
3. Providing a good test suite. This includes both unittests and integration tests. The integration
 | 
			
		||||
   tests can also serve as smaller usage examples than the large `satrs-example` application.
 | 
			
		||||
 | 
			
		||||
This framework has special support for standards used in the space industry. This especially
 | 
			
		||||
This library has special support for standards used in the space industry. This especially
 | 
			
		||||
includes standards provided by Consultative Committee for Space Data Systems (CCSDS) and European
 | 
			
		||||
Cooperation for Space Standardization (ECSS). It does not enforce using any of those standards,
 | 
			
		||||
but it is always recommended to use some sort of standard for interoperability.
 | 
			
		||||
@@ -30,10 +31,10 @@ Flying Laptop Project by the University of Stuttgart with Airbus Defence and Spa
 | 
			
		||||
It has flight heritage through the 2 mssions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
 | 
			
		||||
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
 | 
			
		||||
Therefore, a lot of the design concepts were ported more or less unchanged to the `sat-rs`
 | 
			
		||||
framework.
 | 
			
		||||
library.
 | 
			
		||||
FLP is a medium-size small satellite with a higher budget and longer development time than EIVE,
 | 
			
		||||
which allowed to build a highly reliable system while EIVE is a smaller 6U+ cubesat which had a
 | 
			
		||||
shorter development cycle and was built using cheaper COTS components. This framework also tries
 | 
			
		||||
shorter development cycle and was built using cheaper COTS components. This library also tries
 | 
			
		||||
to accumulate the knowledge of developing the OBSW and operating the satellite for both these
 | 
			
		||||
different systems and provide a solution for a wider range of small satellite systems.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# sat-rs Example Application
 | 
			
		||||
 | 
			
		||||
The `sat-rs` framework includes a monolithic example application which can be found inside
 | 
			
		||||
The `sat-rs` library includes a monolithic example application which can be found inside
 | 
			
		||||
the [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
 | 
			
		||||
subdirectory of the repository. The primary purpose of this example application is to show how
 | 
			
		||||
the various components of the sat-rs framework could be used as part of a larger on-board
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,24 @@
 | 
			
		||||
The sat-rs book
 | 
			
		||||
======
 | 
			
		||||
 | 
			
		||||
This book is the primary information resource for the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
 | 
			
		||||
This book is the primary information resource for the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
The primary goal of the sat-rs framework is to provide re-usable components
 | 
			
		||||
The primary goal of the sat-rs library 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.
 | 
			
		||||
 | 
			
		||||
It should be noted that sat-rs is early-stage software. Important features are missing. New releases
 | 
			
		||||
with breaking changes are released regularly, with all changes documented inside respective
 | 
			
		||||
changelog files. You should only use this library if your are willing to work in this
 | 
			
		||||
environment.
 | 
			
		||||
 | 
			
		||||
A lot of the architecture and general design considerations are based on the
 | 
			
		||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
 | 
			
		||||
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
 | 
			
		||||
@@ -26,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/).
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
# Modes
 | 
			
		||||
 | 
			
		||||
Modes are an extremely useful concept for complex system in general. They also allow simplified
 | 
			
		||||
system reasoning for both system operators and OBSW developers. They model the behaviour of a
 | 
			
		||||
component and also provide observability of a system. A few examples of how to model
 | 
			
		||||
different components of a space system with modes will be given.
 | 
			
		||||
Modes are an extremely useful concept to model complex systems. They allow simplified
 | 
			
		||||
system reasoning for both system operators and OBSW developers. They also provide a way to alter
 | 
			
		||||
the behaviour of a component and also provide observability of a system. A few examples of how to
 | 
			
		||||
model the mode of different components within a space system with modes will be given.
 | 
			
		||||
 | 
			
		||||
## Modelling a pyhsical devices with modes
 | 
			
		||||
## Pyhsical device component with modes
 | 
			
		||||
 | 
			
		||||
The following simple mode scheme with the following three mode
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +13,8 @@ The following simple mode scheme with the following three mode
 | 
			
		||||
- `ON`
 | 
			
		||||
- `NORMAL`
 | 
			
		||||
 | 
			
		||||
can be applied to a large number of simpler devices of a remote system, for example sensors.
 | 
			
		||||
can be applied to a large number of simpler device controllers of a remote system, for example
 | 
			
		||||
sensors.
 | 
			
		||||
 | 
			
		||||
1. `OFF` means that a device is physically switched off, and the corresponding software component
 | 
			
		||||
does not poll the device regularly.
 | 
			
		||||
@@ -31,7 +32,7 @@ for the majority of devices:
 | 
			
		||||
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
 | 
			
		||||
   before powering off the device.
 | 
			
		||||
 | 
			
		||||
## Modelling a controller with modes
 | 
			
		||||
## Controller components with modes
 | 
			
		||||
 | 
			
		||||
Controller components are not modelling physical devices, but a mode scheme is still the best
 | 
			
		||||
way to model most of these components.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								satrs-example-stm32f3-disco/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								satrs-example-stm32f3-disco/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -1,66 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    /* 
 | 
			
		||||
     * Requires the Rust Language Server (RLS) and Cortex-Debug extensions
 | 
			
		||||
     * https://marketplace.visualstudio.com/items?itemName=rust-lang.rust
 | 
			
		||||
     * https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug
 | 
			
		||||
     */
 | 
			
		||||
    "version": "0.2.0",
 | 
			
		||||
    "configurations": [
 | 
			
		||||
        {
 | 
			
		||||
            /* Launches debug session for currently open example */
 | 
			
		||||
            "type": "cortex-debug",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "name": "Debug",
 | 
			
		||||
            "servertype": "openocd",
 | 
			
		||||
            "cwd": "${workspaceRoot}",
 | 
			
		||||
            "preLaunchTask": "cargo build",
 | 
			
		||||
            "runToEntryPoint": "true",
 | 
			
		||||
            "executable": "./target/thumbv7em-none-eabihf/debug/satrs-example-stm32f3-disco",
 | 
			
		||||
            "preLaunchCommands": ["break rust_begin_unwind"],
 | 
			
		||||
            "device": "STM32F303VCT6",
 | 
			
		||||
            "configFiles": [
 | 
			
		||||
                "${workspaceRoot}/.vscode/openocd-helpers.tcl",
 | 
			
		||||
                "interface/stlink.cfg",
 | 
			
		||||
                "target/stm32f3x.cfg"
 | 
			
		||||
            ],
 | 
			
		||||
            "svdFile": "${env:HOME}/.svd/STM32F303.svd",
 | 
			
		||||
            "swoConfig": {
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "cpuFrequency": 8000000,
 | 
			
		||||
                "swoFrequency": 2000000,
 | 
			
		||||
                "source": "probe",
 | 
			
		||||
                "decoders": [
 | 
			
		||||
                    { "type": "console", "label": "ITM", "port": 0 }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            /* Launches debug session for currently open example */
 | 
			
		||||
            "type": "cortex-debug",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "name": "Release",
 | 
			
		||||
            "servertype": "openocd",
 | 
			
		||||
            "cwd": "${workspaceRoot}",
 | 
			
		||||
            "preLaunchTask": "cargo build",
 | 
			
		||||
            "runToEntryPoint": "true",
 | 
			
		||||
            "executable": "./target/thumbv7em-none-eabihf/release/satrs-example-stm32f3-disco",
 | 
			
		||||
            "preLaunchCommands": ["break rust_begin_unwind"],
 | 
			
		||||
            "device": "STM32F303VCT6",
 | 
			
		||||
            "configFiles": [
 | 
			
		||||
                "${workspaceRoot}/.vscode/openocd-helpers.tcl",
 | 
			
		||||
                "interface/stlink.cfg",
 | 
			
		||||
                "target/stm32f3x.cfg"
 | 
			
		||||
            ],
 | 
			
		||||
            "svdFile": "${env:HOME}/.svd/STM32F303.svd",
 | 
			
		||||
            "swoConfig": {
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "cpuFrequency": 8000000,
 | 
			
		||||
                "swoFrequency": 2000000,
 | 
			
		||||
                "source": "probe",
 | 
			
		||||
                "decoders": [
 | 
			
		||||
                    { "type": "console", "label": "ITM", "port": 0 }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "cortex-debug.gdbPath.linux": "gdb-multiarch"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "satrs-example-stm32f3-disco"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
cortex-m = "0.7"
 | 
			
		||||
cortex-m-rt = "0.7"
 | 
			
		||||
embedded-hal = "0.2.6"
 | 
			
		||||
cortex-m-rtic = "1.0"
 | 
			
		||||
enumset = "1.0"
 | 
			
		||||
heapless = "0.7"
 | 
			
		||||
systick-monotonic = "1.0"
 | 
			
		||||
 | 
			
		||||
[dependencies.cobs]
 | 
			
		||||
git = "https://github.com/robamu/cobs.rs.git"
 | 
			
		||||
branch = "all_features"
 | 
			
		||||
default-features = false
 | 
			
		||||
 | 
			
		||||
[dependencies.panic-itm]
 | 
			
		||||
version = "0.4"
 | 
			
		||||
 | 
			
		||||
[dependencies.itm_logger]
 | 
			
		||||
git = "https://github.com/robamu/itm_logger.rs.git"
 | 
			
		||||
branch = "all_features"
 | 
			
		||||
version = "0.1.3-alpha.0"
 | 
			
		||||
 | 
			
		||||
[dependencies.stm32f3xx-hal]
 | 
			
		||||
git = "https://github.com/robamu/stm32f3xx-hal"
 | 
			
		||||
version = "0.10.0-alpha.0"
 | 
			
		||||
features = ["stm32f303xc", "rt", "enumset"]
 | 
			
		||||
branch = "all_features"
 | 
			
		||||
# Can be used in workspace to develop and update HAL
 | 
			
		||||
# path = "../stm32f3xx-hal"
 | 
			
		||||
 | 
			
		||||
[dependencies.stm32f3-discovery]
 | 
			
		||||
git = "https://github.com/robamu/stm32f3-discovery"
 | 
			
		||||
version = "0.8.0-alpha.0"
 | 
			
		||||
branch = "all_features"
 | 
			
		||||
# Can be used in workspace to develop and update BSP
 | 
			
		||||
# path = "../stm32f3-discovery"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs-core]
 | 
			
		||||
git = "https://egit.irs.uni-stuttgart.de/rust/satrs-core.git"
 | 
			
		||||
version = "0.1.0-alpha.0"
 | 
			
		||||
default-features = false
 | 
			
		||||
 | 
			
		||||
# this lets you use `cargo fix`!
 | 
			
		||||
# [[bin]]
 | 
			
		||||
# name = "stm32f3-blinky"
 | 
			
		||||
# test = false
 | 
			
		||||
# bench = false
 | 
			
		||||
 | 
			
		||||
[profile.release]
 | 
			
		||||
codegen-units = 1 # better optimizations
 | 
			
		||||
debug = true # symbols are nice and they don't increase the size on Flash
 | 
			
		||||
lto = true # better optimizations
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
The STM32F3-Discovery device was picked because it is a cheap Cortex-M4 based device which is also
 | 
			
		||||
used by the [Rust Embedded Book](https://docs.rust-embedded.org/book/intro/hardware.html) and the
 | 
			
		||||
[Rust Discovery](https://docs.rust-embedded.org/discovery/f3discovery/) book as an introduction
 | 
			
		||||
to embedded Rust.
 | 
			
		||||
 | 
			
		||||
## 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 and Debugging from the command line
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
## 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 `openocd`
 | 
			
		||||
and the VS Code [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
If you would like to use a custom GDB application, you can specify the gdb binary in the following
 | 
			
		||||
configuration variables in your `settings.json`:
 | 
			
		||||
 | 
			
		||||
- `"cortex-debug.gdbPath"`
 | 
			
		||||
- `"cortex-debug.gdbPath.linux"`
 | 
			
		||||
- `"cortex-debug.gdbPath.windows"`
 | 
			
		||||
- `"cortex-debug.gdbPath.osx"`
 | 
			
		||||
 | 
			
		||||
## Commanding with Python
 | 
			
		||||
 | 
			
		||||
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
 | 
			
		||||
using COBS encoded CCSDS packets.
 | 
			
		||||
 
 | 
			
		||||
TODO:
 | 
			
		||||
  - How and where to connect serial interface on the MCU
 | 
			
		||||
  - How to set up Python venv (or at least strongly recommend it) and install deps
 | 
			
		||||
  - How to copy `def_tmtc_conf.json` to `tmtc_conf.json` and adapt it for custom serial port
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::Write;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    // Put the linker script somewhere the linker can find it
 | 
			
		||||
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
 | 
			
		||||
    File::create(out.join("memory.x"))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .write_all(include_bytes!("memory.x"))
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    println!("cargo:rustc-link-search={}", out.display());
 | 
			
		||||
 | 
			
		||||
    // Only re-run the build script when memory.x is changed,
 | 
			
		||||
    // instead of when any part of the source code changes.
 | 
			
		||||
    println!("cargo:rerun-if-changed=memory.x");
 | 
			
		||||
}
 | 
			
		||||
@@ -1,604 +0,0 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
extern crate panic_itm;
 | 
			
		||||
 | 
			
		||||
use rtic::app;
 | 
			
		||||
 | 
			
		||||
use heapless::{
 | 
			
		||||
    mpmc::Q16,
 | 
			
		||||
    pool,
 | 
			
		||||
    pool::singleton::{Box, Pool},
 | 
			
		||||
};
 | 
			
		||||
#[allow(unused_imports)]
 | 
			
		||||
use itm_logger::{debug, info, logger_init, warn};
 | 
			
		||||
use satrs_core::spacepackets::{ecss::PusPacket, tm::PusTm};
 | 
			
		||||
use satrs_core::{
 | 
			
		||||
    pus::{EcssTmErrorWithSend, EcssTmSenderCore},
 | 
			
		||||
    seq_count::SequenceCountProviderCore,
 | 
			
		||||
};
 | 
			
		||||
use stm32f3xx_hal::dma::dma1;
 | 
			
		||||
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
 | 
			
		||||
use stm32f3xx_hal::pac::USART2;
 | 
			
		||||
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
 | 
			
		||||
use systick_monotonic::{fugit::Duration, Systick};
 | 
			
		||||
 | 
			
		||||
const UART_BAUD: u32 = 115200;
 | 
			
		||||
const BLINK_FREQ_MS: u64 = 1000;
 | 
			
		||||
const TX_HANDLER_FREQ_MS: u64 = 20;
 | 
			
		||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u16 = 5;
 | 
			
		||||
const MAX_TC_LEN: usize = 200;
 | 
			
		||||
const MAX_TM_LEN: usize = 200;
 | 
			
		||||
pub const PUS_APID: u16 = 0x02;
 | 
			
		||||
 | 
			
		||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
 | 
			
		||||
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
 | 
			
		||||
type MsDuration = Duration<u64, 1, 1000>;
 | 
			
		||||
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
 | 
			
		||||
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
 | 
			
		||||
 | 
			
		||||
// This is the predictable maximum overhead of the COBS encoding scheme.
 | 
			
		||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
 | 
			
		||||
const COBS_TC_OVERHEAD: usize = (MAX_TC_LEN + 254 - 1) / 254;
 | 
			
		||||
const COBS_TM_OVERHEAD: usize = (MAX_TM_LEN + 254 - 1) / 254;
 | 
			
		||||
 | 
			
		||||
const TC_BUF_LEN: usize = MAX_TC_LEN + COBS_TC_OVERHEAD;
 | 
			
		||||
const TM_BUF_LEN: usize = MAX_TC_LEN + COBS_TM_OVERHEAD;
 | 
			
		||||
 | 
			
		||||
// This is a static buffer which should ONLY (!) be used as the TX DMA
 | 
			
		||||
// transfer buffer.
 | 
			
		||||
static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
 | 
			
		||||
// This is a static buffer which should ONLY (!) be used as the RX DMA
 | 
			
		||||
// transfer buffer.
 | 
			
		||||
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
 | 
			
		||||
 | 
			
		||||
static TX_REQUESTS: Q16<(Box<poolmod::TM>, usize)> = Q16::new();
 | 
			
		||||
 | 
			
		||||
const TC_POOL_SLOTS: usize = 12;
 | 
			
		||||
const TM_POOL_SLOTS: usize = 12;
 | 
			
		||||
use core::sync::atomic::{AtomicU16, Ordering};
 | 
			
		||||
 | 
			
		||||
pub struct SeqCountProviderAtomicRef {
 | 
			
		||||
    atomic: AtomicU16,
 | 
			
		||||
    ordering: Ordering,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SeqCountProviderAtomicRef {
 | 
			
		||||
    pub const fn new(ordering: Ordering) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            atomic: AtomicU16::new(0),
 | 
			
		||||
            ordering,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
 | 
			
		||||
    fn get(&self) -> u16 {
 | 
			
		||||
        self.atomic.load(self.ordering)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn increment(&self) {
 | 
			
		||||
        self.atomic.fetch_add(1, self.ordering);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_and_increment(&self) -> u16 {
 | 
			
		||||
        self.atomic.fetch_add(1, self.ordering)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
 | 
			
		||||
    SeqCountProviderAtomicRef::new(Ordering::Relaxed);
 | 
			
		||||
 | 
			
		||||
// Otherwise, warnings because of heapless pool macro.
 | 
			
		||||
#[allow(non_camel_case_types)]
 | 
			
		||||
mod poolmod {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    // Must hold full TC length including COBS overhead.
 | 
			
		||||
    pool!(TC: [u8; TC_BUF_LEN]);
 | 
			
		||||
    // Only encoded at the end, so no need to account for COBS overhead.
 | 
			
		||||
    pool!(TM: [u8; MAX_TM_LEN]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct TxIdle {
 | 
			
		||||
    tx: TxType,
 | 
			
		||||
    dma_channel: dma1::C7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum TmStoreError {
 | 
			
		||||
    StoreFull,
 | 
			
		||||
    StoreSlotsTooSmall,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<TmStoreError> for EcssTmErrorWithSend<TmStoreError> {
 | 
			
		||||
    fn from(value: TmStoreError) -> Self {
 | 
			
		||||
        Self::SendError(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct TmSender {
 | 
			
		||||
    mem_block: Option<Box<poolmod::TM>>,
 | 
			
		||||
    ctx: &'static str,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TmSender {
 | 
			
		||||
    pub fn new(mem_block: Box<poolmod::TM>, ctx: &'static str) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mem_block: Some(mem_block),
 | 
			
		||||
            ctx,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl EcssTmSenderCore for TmSender {
 | 
			
		||||
    type Error = TmStoreError;
 | 
			
		||||
 | 
			
		||||
    fn send_tm(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        tm: PusTm,
 | 
			
		||||
    ) -> Result<(), satrs_core::pus::EcssTmErrorWithSend<Self::Error>> {
 | 
			
		||||
        let mem_block = self.mem_block.take();
 | 
			
		||||
        if mem_block.is_none() {
 | 
			
		||||
            panic!("send_tm should only be called once");
 | 
			
		||||
        }
 | 
			
		||||
        let mut mem_block = mem_block.unwrap();
 | 
			
		||||
        if tm.len_packed() > MAX_TM_LEN {
 | 
			
		||||
            return Err(EcssTmErrorWithSend::SendError(
 | 
			
		||||
                TmStoreError::StoreSlotsTooSmall,
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        tm.write_to_bytes(mem_block.as_mut_slice())
 | 
			
		||||
            .map_err(|e| EcssTmErrorWithSend::EcssTmError(e.into()))?;
 | 
			
		||||
        info!(target: self.ctx, "Sending TM[{},{}] with size {}", tm.service(), tm.subservice(), tm.len_packed());
 | 
			
		||||
        TX_REQUESTS
 | 
			
		||||
            .enqueue((mem_block, tm.len_packed()))
 | 
			
		||||
            .map_err(|_| TmStoreError::StoreFull)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum UartTxState {
 | 
			
		||||
    // Wrapped in an option because we need an owned type later.
 | 
			
		||||
    Idle(Option<TxIdle>),
 | 
			
		||||
    // Same as above
 | 
			
		||||
    Transmitting(Option<TxDmaTransferType>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[app(device = stm32f3xx_hal::pac, peripherals = true, dispatchers = [TIM20_BRK, TIM20_UP, TIM20_TRG_COM])]
 | 
			
		||||
mod app {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use core::slice::Iter;
 | 
			
		||||
    use cortex_m::iprintln;
 | 
			
		||||
    use satrs_core::pus::verification::FailParams;
 | 
			
		||||
    use satrs_core::pus::verification::VerificationReporterCore;
 | 
			
		||||
    use satrs_core::spacepackets::{
 | 
			
		||||
        ecss::EcssEnumU16,
 | 
			
		||||
        tc::PusTc,
 | 
			
		||||
        time::cds::P_FIELD_BASE,
 | 
			
		||||
        tm::{PusTm, PusTmSecondaryHeader},
 | 
			
		||||
        CcsdsPacket, SpHeader,
 | 
			
		||||
    };
 | 
			
		||||
    #[allow(unused_imports)]
 | 
			
		||||
    use stm32f3_discovery::leds::Direction;
 | 
			
		||||
    use stm32f3_discovery::leds::Leds;
 | 
			
		||||
    use stm32f3xx_hal::prelude::*;
 | 
			
		||||
    use stm32f3xx_hal::Toggle;
 | 
			
		||||
 | 
			
		||||
    use stm32f3_discovery::switch_hal::OutputSwitch;
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
 | 
			
		||||
 | 
			
		||||
    #[shared]
 | 
			
		||||
    struct Shared {
 | 
			
		||||
        tx_transfer: UartTxState,
 | 
			
		||||
        rx_transfer: Option<RxDmaTransferType>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[local]
 | 
			
		||||
    struct Local {
 | 
			
		||||
        leds: Leds,
 | 
			
		||||
        last_dir: Direction,
 | 
			
		||||
        verif_reporter: VerificationReporterCore,
 | 
			
		||||
        curr_dir: Iter<'static, Direction>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[monotonic(binds = SysTick, default = true)]
 | 
			
		||||
    type MonoTimer = Systick<1000>;
 | 
			
		||||
 | 
			
		||||
    #[init(local = [
 | 
			
		||||
        tc_pool_mem: [u8; TC_BUF_LEN * TC_POOL_SLOTS] = [0; TC_BUF_LEN * TC_POOL_SLOTS],
 | 
			
		||||
        tm_pool_mem: [u8; MAX_TM_LEN * TM_POOL_SLOTS] = [0; MAX_TM_LEN * TM_POOL_SLOTS]
 | 
			
		||||
    ])]
 | 
			
		||||
    fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) {
 | 
			
		||||
        let mut rcc = cx.device.RCC.constrain();
 | 
			
		||||
 | 
			
		||||
        let mono = Systick::new(cx.core.SYST, 8_000_000);
 | 
			
		||||
        logger_init();
 | 
			
		||||
        let mut flash = cx.device.FLASH.constrain();
 | 
			
		||||
        let clocks = rcc
 | 
			
		||||
            .cfgr
 | 
			
		||||
            .use_hse(8.MHz())
 | 
			
		||||
            .sysclk(8.MHz())
 | 
			
		||||
            .pclk1(8.MHz())
 | 
			
		||||
            .freeze(&mut flash.acr);
 | 
			
		||||
        // setup ITM output
 | 
			
		||||
        iprintln!(
 | 
			
		||||
            &mut cx.core.ITM.stim[0],
 | 
			
		||||
            "Starting sat-rs demo application for the STM32F3-Discovery"
 | 
			
		||||
        );
 | 
			
		||||
        let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
 | 
			
		||||
        // Assign memory to the pools.
 | 
			
		||||
        poolmod::TC::grow(cx.local.tc_pool_mem);
 | 
			
		||||
        poolmod::TM::grow(cx.local.tm_pool_mem);
 | 
			
		||||
 | 
			
		||||
        let verif_reporter = VerificationReporterCore::new(PUS_APID).unwrap();
 | 
			
		||||
 | 
			
		||||
        let leds = Leds::new(
 | 
			
		||||
            gpioe.pe8,
 | 
			
		||||
            gpioe.pe9,
 | 
			
		||||
            gpioe.pe10,
 | 
			
		||||
            gpioe.pe11,
 | 
			
		||||
            gpioe.pe12,
 | 
			
		||||
            gpioe.pe13,
 | 
			
		||||
            gpioe.pe14,
 | 
			
		||||
            gpioe.pe15,
 | 
			
		||||
            &mut gpioe.moder,
 | 
			
		||||
            &mut gpioe.otyper,
 | 
			
		||||
        );
 | 
			
		||||
        let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb);
 | 
			
		||||
        // USART2 pins
 | 
			
		||||
        let mut pins = (
 | 
			
		||||
            // TX pin: PA2
 | 
			
		||||
            gpioa
 | 
			
		||||
                .pa2
 | 
			
		||||
                .into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
 | 
			
		||||
            // RX pin: PA3
 | 
			
		||||
            gpioa
 | 
			
		||||
                .pa3
 | 
			
		||||
                .into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl),
 | 
			
		||||
        );
 | 
			
		||||
        pins.1.internal_pull_up(&mut gpioa.pupdr, true);
 | 
			
		||||
        let mut usart2 = Serial::new(
 | 
			
		||||
            cx.device.USART2,
 | 
			
		||||
            pins,
 | 
			
		||||
            UART_BAUD.Bd(),
 | 
			
		||||
            clocks,
 | 
			
		||||
            &mut rcc.apb1,
 | 
			
		||||
        );
 | 
			
		||||
        usart2.configure_rx_interrupt(RxEvent::Idle, Toggle::On);
 | 
			
		||||
        // This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
 | 
			
		||||
        usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Toggle::On);
 | 
			
		||||
 | 
			
		||||
        let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
 | 
			
		||||
        let (tx_serial, mut rx_serial) = usart2.split();
 | 
			
		||||
 | 
			
		||||
        // This interrupt is immediately triggered, clear it. It will only be reset
 | 
			
		||||
        // by the hardware when data is received on RX (RXNE event)
 | 
			
		||||
        rx_serial.clear_event(RxEvent::Idle);
 | 
			
		||||
        let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
 | 
			
		||||
        info!(target: "init", "Spawning tasks");
 | 
			
		||||
        blink::spawn().unwrap();
 | 
			
		||||
        serial_tx_handler::spawn().unwrap();
 | 
			
		||||
        (
 | 
			
		||||
            Shared {
 | 
			
		||||
                tx_transfer: UartTxState::Idle(Some(TxIdle {
 | 
			
		||||
                    tx: tx_serial,
 | 
			
		||||
                    dma_channel: dma1.ch7,
 | 
			
		||||
                })),
 | 
			
		||||
                rx_transfer: Some(rx_transfer),
 | 
			
		||||
            },
 | 
			
		||||
            Local {
 | 
			
		||||
                leds,
 | 
			
		||||
                last_dir: Direction::North,
 | 
			
		||||
                curr_dir: Direction::iter(),
 | 
			
		||||
                verif_reporter,
 | 
			
		||||
            },
 | 
			
		||||
            init::Monotonics(mono),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(local = [leds, curr_dir, last_dir])]
 | 
			
		||||
    fn blink(cx: blink::Context) {
 | 
			
		||||
        let toggle_leds = |dir: &Direction| {
 | 
			
		||||
            let leds = cx.local.leds;
 | 
			
		||||
            let last_led = leds.for_direction(*cx.local.last_dir);
 | 
			
		||||
            last_led.off().ok();
 | 
			
		||||
            let led = leds.for_direction(*dir);
 | 
			
		||||
            led.on().ok();
 | 
			
		||||
            *cx.local.last_dir = *dir;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match cx.local.curr_dir.next() {
 | 
			
		||||
            Some(dir) => {
 | 
			
		||||
                toggle_leds(dir);
 | 
			
		||||
            }
 | 
			
		||||
            None => {
 | 
			
		||||
                *cx.local.curr_dir = Direction::iter();
 | 
			
		||||
                toggle_leds(cx.local.curr_dir.next().unwrap());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        blink::spawn_after(MsDuration::from_ticks(BLINK_FREQ_MS)).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(
 | 
			
		||||
        shared = [tx_transfer],
 | 
			
		||||
        local = []
 | 
			
		||||
    )]
 | 
			
		||||
    fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
 | 
			
		||||
        if let Some((buf, len)) = TX_REQUESTS.dequeue() {
 | 
			
		||||
            cx.shared.tx_transfer.lock(|tx_state| match tx_state {
 | 
			
		||||
                UartTxState::Idle(tx) => {
 | 
			
		||||
                    //debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
 | 
			
		||||
                    // Safety: We only copy the data into the TX DMA buffer in this task.
 | 
			
		||||
                    // If the DMA is active, another branch will be taken.
 | 
			
		||||
                    let mut_tx_dma_buf = unsafe { &mut DMA_TX_BUF };
 | 
			
		||||
                    // 0 sentinel value as start marker
 | 
			
		||||
                    mut_tx_dma_buf[0] = 0;
 | 
			
		||||
                    // Should never panic, we accounted for the overhead.
 | 
			
		||||
                    // Write into transfer buffer directly, no need for intermediate
 | 
			
		||||
                    // encoding buffer.
 | 
			
		||||
                    let encoded_len = cobs::encode(&buf[0..len], &mut mut_tx_dma_buf[1..]);
 | 
			
		||||
                    // 0 end marker
 | 
			
		||||
                    mut_tx_dma_buf[encoded_len + 1] = 0;
 | 
			
		||||
                    //debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
 | 
			
		||||
                    //debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
 | 
			
		||||
                    let tx_idle = tx.take().unwrap();
 | 
			
		||||
                    // Transfer completion and re-scheduling of new TX transfers will be done
 | 
			
		||||
                    // by the IRQ handler.
 | 
			
		||||
                    let transfer = tx_idle
 | 
			
		||||
                        .tx
 | 
			
		||||
                        .write_all(&mut_tx_dma_buf[0..encoded_len + 2], tx_idle.dma_channel);
 | 
			
		||||
                    *tx_state = UartTxState::Transmitting(Some(transfer));
 | 
			
		||||
                    // The memory block is automatically returned to the pool when it is dropped.
 | 
			
		||||
                }
 | 
			
		||||
                UartTxState::Transmitting(_) => {
 | 
			
		||||
                    // This is a SW configuration error. Only the ISR which
 | 
			
		||||
                    // detects transfer completion should be able to spawn a new
 | 
			
		||||
                    // task, and that ISR should set the state to IDLE.
 | 
			
		||||
                    panic!("invalid internal tx state detected")
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            cx.shared.tx_transfer.lock(|tx_state| {
 | 
			
		||||
                if let UartTxState::Idle(_) = tx_state {
 | 
			
		||||
                    serial_tx_handler::spawn_after(MsDuration::from_ticks(TX_HANDLER_FREQ_MS))
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(
 | 
			
		||||
        local = [
 | 
			
		||||
            stamp_buf: [u8; 7] = [0; 7],
 | 
			
		||||
            decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
 | 
			
		||||
            src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
 | 
			
		||||
            verif_reporter
 | 
			
		||||
        ],
 | 
			
		||||
    )]
 | 
			
		||||
    fn serial_rx_handler(
 | 
			
		||||
        cx: serial_rx_handler::Context,
 | 
			
		||||
        received_packet: Box<poolmod::TC>,
 | 
			
		||||
        rx_len: usize,
 | 
			
		||||
    ) {
 | 
			
		||||
        let tgt: &'static str = "serial_rx_handler";
 | 
			
		||||
        cx.local.stamp_buf[0] = P_FIELD_BASE;
 | 
			
		||||
        info!(target: tgt, "Received packet with {} bytes", rx_len);
 | 
			
		||||
        let decode_buf = cx.local.decode_buf;
 | 
			
		||||
        let packet = received_packet.as_slice();
 | 
			
		||||
        let mut start_idx = None;
 | 
			
		||||
        for (idx, byte) in packet.iter().enumerate() {
 | 
			
		||||
            if *byte != 0 {
 | 
			
		||||
                start_idx = Some(idx);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if start_idx.is_none() {
 | 
			
		||||
            warn!(
 | 
			
		||||
                target: tgt,
 | 
			
		||||
                "decoding error, can only process cobs encoded frames, data is all 0"
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let start_idx = start_idx.unwrap();
 | 
			
		||||
        match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
 | 
			
		||||
            Ok(len) => {
 | 
			
		||||
                info!(target: tgt, "Decoded packet length: {}", len);
 | 
			
		||||
                let pus_tc = PusTc::from_bytes(decode_buf);
 | 
			
		||||
                let verif_reporter = cx.local.verif_reporter;
 | 
			
		||||
                match pus_tc {
 | 
			
		||||
                    Ok((tc, tc_len)) => handle_tc(
 | 
			
		||||
                        tc,
 | 
			
		||||
                        tc_len,
 | 
			
		||||
                        verif_reporter,
 | 
			
		||||
                        cx.local.src_data_buf,
 | 
			
		||||
                        cx.local.stamp_buf,
 | 
			
		||||
                        tgt,
 | 
			
		||||
                    ),
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        warn!(target: tgt, "Error unpacking PUS TC: {}", e);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                warn!(
 | 
			
		||||
                    target: tgt,
 | 
			
		||||
                    "decoding error, can only process cobs encoded frames"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_tc(
 | 
			
		||||
        tc: PusTc,
 | 
			
		||||
        tc_len: usize,
 | 
			
		||||
        verif_reporter: &mut VerificationReporterCore,
 | 
			
		||||
        src_data_buf: &mut [u8; MAX_TM_LEN],
 | 
			
		||||
        stamp_buf: &[u8; 7],
 | 
			
		||||
        tgt: &'static str,
 | 
			
		||||
    ) {
 | 
			
		||||
        info!(
 | 
			
		||||
            target: tgt,
 | 
			
		||||
            "Found PUS TC [{},{}] with length {}",
 | 
			
		||||
            tc.service(),
 | 
			
		||||
            tc.subservice(),
 | 
			
		||||
            tc_len
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let token = verif_reporter.add_tc(&tc);
 | 
			
		||||
        if tc.apid() != PUS_APID {
 | 
			
		||||
            warn!(target: tgt, "Received tc with unknown APID {}", tc.apid());
 | 
			
		||||
            let sendable = verif_reporter
 | 
			
		||||
                .acceptance_failure(
 | 
			
		||||
                    src_data_buf,
 | 
			
		||||
                    token,
 | 
			
		||||
                    &SEQ_COUNT_PROVIDER,
 | 
			
		||||
                    FailParams::new(stamp_buf, &EcssEnumU16::new(0), None),
 | 
			
		||||
                )
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
 | 
			
		||||
            let mut sender = TmSender::new(mem_block, tgt);
 | 
			
		||||
            if let Err(e) =
 | 
			
		||||
                verif_reporter.send_acceptance_failure(sendable, &SEQ_COUNT_PROVIDER, &mut sender)
 | 
			
		||||
            {
 | 
			
		||||
                warn!(target: tgt, "Sending acceptance failure failed: {:?}", e.0);
 | 
			
		||||
            };
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let sendable = verif_reporter
 | 
			
		||||
            .acceptance_success(src_data_buf, token, &SEQ_COUNT_PROVIDER, stamp_buf)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
 | 
			
		||||
        let mut sender = TmSender::new(mem_block, tgt);
 | 
			
		||||
        let accepted_token = match verif_reporter.send_acceptance_success(
 | 
			
		||||
            sendable,
 | 
			
		||||
            &SEQ_COUNT_PROVIDER,
 | 
			
		||||
            &mut sender,
 | 
			
		||||
        ) {
 | 
			
		||||
            Ok(token) => token,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                warn!(target: "serial_rx_handler", "Sending acceptance success failed: {:?}", e.0);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if tc.service() == 17 {
 | 
			
		||||
            if tc.subservice() == 1 {
 | 
			
		||||
                let sendable = verif_reporter
 | 
			
		||||
                    .start_success(src_data_buf, accepted_token, &SEQ_COUNT_PROVIDER, stamp_buf)
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
 | 
			
		||||
                let mut sender = TmSender::new(mem_block, tgt);
 | 
			
		||||
                let started_token = match verif_reporter.send_start_success(
 | 
			
		||||
                    sendable,
 | 
			
		||||
                    &SEQ_COUNT_PROVIDER,
 | 
			
		||||
                    &mut sender,
 | 
			
		||||
                ) {
 | 
			
		||||
                    Ok(token) => token,
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        warn!(target: tgt, "Sending acceptance success failed: {:?}", e.0);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                info!(
 | 
			
		||||
                    target: tgt,
 | 
			
		||||
                    "Received PUS ping telecommand, sending ping reply TM[17,2]"
 | 
			
		||||
                );
 | 
			
		||||
                let mut sp_header =
 | 
			
		||||
                    SpHeader::tc_unseg(PUS_APID, SEQ_COUNT_PROVIDER.get(), 0).unwrap();
 | 
			
		||||
                let sec_header = PusTmSecondaryHeader::new_simple(17, 2, stamp_buf);
 | 
			
		||||
                let ping_reply = PusTm::new(&mut sp_header, sec_header, None, true);
 | 
			
		||||
                let mut mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
 | 
			
		||||
                let reply_len = ping_reply.write_to_bytes(mem_block.as_mut_slice()).unwrap();
 | 
			
		||||
                if TX_REQUESTS.enqueue((mem_block, reply_len)).is_err() {
 | 
			
		||||
                    warn!(target: tgt, "TC queue full");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                SEQ_COUNT_PROVIDER.increment();
 | 
			
		||||
                let sendable = verif_reporter
 | 
			
		||||
                    .completion_success(src_data_buf, started_token, &SEQ_COUNT_PROVIDER, stamp_buf)
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
 | 
			
		||||
                let mut sender = TmSender::new(mem_block, tgt);
 | 
			
		||||
                if let Err(e) = verif_reporter.send_step_or_completion_success(
 | 
			
		||||
                    sendable,
 | 
			
		||||
                    &SEQ_COUNT_PROVIDER,
 | 
			
		||||
                    &mut sender,
 | 
			
		||||
                ) {
 | 
			
		||||
                    warn!(target: tgt, "Sending completion success failed: {:?}", e.0);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // TODO: Invalid subservice
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(binds = DMA1_CH6, shared = [rx_transfer])]
 | 
			
		||||
    fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
 | 
			
		||||
        cx.shared.rx_transfer.lock(|rx_transfer| {
 | 
			
		||||
            let rx_ref = rx_transfer.as_ref().unwrap();
 | 
			
		||||
            if rx_ref.is_complete() {
 | 
			
		||||
                let uart_rx_owned = rx_transfer.take().unwrap();
 | 
			
		||||
                let (buf, c, rx) = uart_rx_owned.stop();
 | 
			
		||||
                // The received data is transferred to another task now to avoid any processing overhead
 | 
			
		||||
                // during the interrupt. There are multiple ways to do this, we use a memory pool here
 | 
			
		||||
                // to do this.
 | 
			
		||||
                let mut mem_block = poolmod::TC::alloc()
 | 
			
		||||
                    .expect("allocating memory block for rx failed")
 | 
			
		||||
                    .init([0u8; TC_BUF_LEN]);
 | 
			
		||||
                // Copy data into memory pool.
 | 
			
		||||
                mem_block.copy_from_slice(buf);
 | 
			
		||||
                *rx_transfer = Some(rx.read_exact(buf, c));
 | 
			
		||||
                // Only send owning pointer to pool memory and the received packet length.
 | 
			
		||||
                serial_rx_handler::spawn(mem_block, TC_BUF_LEN)
 | 
			
		||||
                    .expect("spawning rx handler task failed");
 | 
			
		||||
                // If this happens, there is a high chance that the maximum packet length was
 | 
			
		||||
                // exceeded. Circular mode is not used here, so data might be missed.
 | 
			
		||||
                warn!(
 | 
			
		||||
                    "rx transfer with maximum length {}, might miss data",
 | 
			
		||||
                    TC_BUF_LEN
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_transfer])]
 | 
			
		||||
    fn serial_isr(mut cx: serial_isr::Context) {
 | 
			
		||||
        cx.shared.tx_transfer.lock(|tx_state| match tx_state {
 | 
			
		||||
            UartTxState::Idle(_) => (),
 | 
			
		||||
            UartTxState::Transmitting(transfer) => {
 | 
			
		||||
                let transfer_ref = transfer.as_ref().unwrap();
 | 
			
		||||
                if transfer_ref.is_complete() {
 | 
			
		||||
                    let transfer = transfer.take().unwrap();
 | 
			
		||||
                    let (_, dma_channel, tx) = transfer.stop();
 | 
			
		||||
                    *tx_state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
 | 
			
		||||
                    serial_tx_handler::spawn_after(MsDuration::from_ticks(
 | 
			
		||||
                        MIN_DELAY_BETWEEN_TX_PACKETS_MS.into(),
 | 
			
		||||
                    ))
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        cx.shared.rx_transfer.lock(|rx_transfer| {
 | 
			
		||||
            let rx_transfer_ref = rx_transfer.as_ref().unwrap();
 | 
			
		||||
            // Received a partial packet.
 | 
			
		||||
            if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
 | 
			
		||||
                let rx_transfer_owned = rx_transfer.take().unwrap();
 | 
			
		||||
                let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
 | 
			
		||||
                // The received data is transferred to another task now to avoid any processing overhead
 | 
			
		||||
                // during the interrupt. There are multiple ways to do this, we use a memory pool here
 | 
			
		||||
                // to do this.
 | 
			
		||||
                let mut mem_block = poolmod::TC::alloc()
 | 
			
		||||
                    .expect("allocating memory block for rx failed")
 | 
			
		||||
                    .init([0u8; TC_BUF_LEN]);
 | 
			
		||||
                // Copy data into memory pool.
 | 
			
		||||
                mem_block[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
 | 
			
		||||
                rx.clear_event(RxEvent::Idle);
 | 
			
		||||
                // Only send owning pointer to pool memory and the received packet length.
 | 
			
		||||
                serial_rx_handler::spawn(mem_block, rx_len as usize)
 | 
			
		||||
                    .expect("spawning rx handler task failed");
 | 
			
		||||
                *rx_transfer = Some(rx.read_exact(buf, ch));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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,25 +8,38 @@ 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"
 | 
			
		||||
derive-new = "0.5"
 | 
			
		||||
thiserror = "2"
 | 
			
		||||
lazy_static = "1"
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs]
 | 
			
		||||
version = "0.2.0-rc.0"
 | 
			
		||||
# path = "../satrs"
 | 
			
		||||
path = "../satrs"
 | 
			
		||||
features = ["test_util"]
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs-minisim]
 | 
			
		||||
path = "../satrs-minisim"
 | 
			
		||||
 | 
			
		||||
[dependencies.satrs-mib]
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
# path = "../satrs-mib"
 | 
			
		||||
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
 | 
			
		||||
@@ -4,11 +4,13 @@ import dataclasses
 | 
			
		||||
import enum
 | 
			
		||||
import struct
 | 
			
		||||
 | 
			
		||||
from spacepackets.ecss.tc import PacketId, PacketType
 | 
			
		||||
 | 
			
		||||
EXAMPLE_PUS_APID = 0x02
 | 
			
		||||
EXAMPLE_PUS_PACKET_ID_TM = PacketId(PacketType.TM, True, EXAMPLE_PUS_APID)
 | 
			
		||||
TM_PACKET_IDS = [EXAMPLE_PUS_PACKET_ID_TM]
 | 
			
		||||
class Apid(enum.IntEnum):
 | 
			
		||||
    SCHED = 1
 | 
			
		||||
    GENERIC_PUS = 2
 | 
			
		||||
    ACS = 3
 | 
			
		||||
    CFDP = 4
 | 
			
		||||
    TMTC = 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventSeverity(enum.IntEnum):
 | 
			
		||||
@@ -36,8 +38,11 @@ class EventU32:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestTargetId(enum.IntEnum):
 | 
			
		||||
    ACS = 1
 | 
			
		||||
class AcsId(enum.IntEnum):
 | 
			
		||||
    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))
 | 
			
		||||
							
								
								
									
										143
									
								
								satrs-example/pytmtc/pytmtc/pus_tc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								satrs-example/pytmtc/pytmtc/pus_tc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
import datetime
 | 
			
		||||
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 import VerificationWrapper
 | 
			
		||||
from tmtccmd.tmtc import (
 | 
			
		||||
    DefaultPusQueueHelper,
 | 
			
		||||
    FeedWrapper,
 | 
			
		||||
    QueueWrapper,
 | 
			
		||||
    SendCbParams,
 | 
			
		||||
    TcProcedureType,
 | 
			
		||||
    TcQueueEntryType,
 | 
			
		||||
)
 | 
			
		||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
 | 
			
		||||
 | 
			
		||||
from pytmtc.acs import create_acs_node
 | 
			
		||||
from pytmtc.common import Apid
 | 
			
		||||
from pytmtc.acs.mgms import create_mgm_cmds
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
    hk_node.add_child(CmdTreeNode("one_shot_hk", "Request One Shot HK set"))
 | 
			
		||||
    hk_node.add_child(
 | 
			
		||||
        CmdTreeNode("enable", "Enable periodic housekeeping data generation")
 | 
			
		||||
    )
 | 
			
		||||
    hk_node.add_child(
 | 
			
		||||
        CmdTreeNode("disable", "Disable periodic housekeeping data generation")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    mode_node = CmdTreeNode("mode", "Mode Node", hide_children_for_print=True)
 | 
			
		||||
    set_mode_node = CmdTreeNode(
 | 
			
		||||
        "set_mode", "Set Node", hide_children_which_are_leaves=True
 | 
			
		||||
    )
 | 
			
		||||
    set_mode_node.add_child(CmdTreeNode("off", "Set OFF Mode"))
 | 
			
		||||
    set_mode_node.add_child(CmdTreeNode("on", "Set ON Mode"))
 | 
			
		||||
    set_mode_node.add_child(CmdTreeNode("normal", "Set NORMAL Mode"))
 | 
			
		||||
    mode_node.add_child(set_mode_node)
 | 
			
		||||
    mode_node.add_child(CmdTreeNode("read_mode", "Read Mode"))
 | 
			
		||||
 | 
			
		||||
    test_node = CmdTreeNode("test", "Test Node")
 | 
			
		||||
    test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
 | 
			
		||||
    test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
 | 
			
		||||
    root_node.add_child(test_node)
 | 
			
		||||
 | 
			
		||||
    scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
 | 
			
		||||
    scheduler_node.add_child(
 | 
			
		||||
        CmdTreeNode(
 | 
			
		||||
            "schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    root_node.add_child(scheduler_node)
 | 
			
		||||
    root_node.add_child(create_acs_node(mode_node, hk_node))
 | 
			
		||||
    return root_node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
 | 
			
		||||
    # It should always be at least the root path "/", so we split of the empty portion left of it.
 | 
			
		||||
    cmd_path_list = cmd_path.split("/")[1:]
 | 
			
		||||
    if len(cmd_path_list) == 0:
 | 
			
		||||
        _LOGGER.warning("empty command path")
 | 
			
		||||
        return
 | 
			
		||||
    if cmd_path_list[0] == "test":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "ping":
 | 
			
		||||
            q.add_log_cmd("Sending PUS ping telecommand")
 | 
			
		||||
            return q.add_pus_tc(
 | 
			
		||||
                PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=1)
 | 
			
		||||
            )
 | 
			
		||||
        elif cmd_path_list[1] == "trigger_event":
 | 
			
		||||
            q.add_log_cmd("Triggering test event")
 | 
			
		||||
            return q.add_pus_tc(
 | 
			
		||||
                PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=128)
 | 
			
		||||
            )
 | 
			
		||||
    if cmd_path_list[0] == "scheduler":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
 | 
			
		||||
            q.add_log_cmd("Sending PUS scheduled TC telecommand")
 | 
			
		||||
            crt_time = CdsShortTimestamp.from_now()
 | 
			
		||||
            time_stamp = crt_time + datetime.timedelta(seconds=10)
 | 
			
		||||
            time_stamp = time_stamp.pack()
 | 
			
		||||
            return q.add_pus_tc(
 | 
			
		||||
                create_time_tagged_cmd(
 | 
			
		||||
                    time_stamp,
 | 
			
		||||
                    PusTelecommand(service=17, subservice=1),
 | 
			
		||||
                    apid=Apid.SCHED,
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
    if cmd_path_list[0] == "acs":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "mgms":
 | 
			
		||||
            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,85 +0,0 @@
 | 
			
		||||
import datetime
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from spacepackets.ccsds import CdsShortTimestamp
 | 
			
		||||
from spacepackets.ecss import PusTelecommand
 | 
			
		||||
from tmtccmd.config import CmdTreeNode
 | 
			
		||||
from tmtccmd.tmtc import DefaultPusQueueHelper
 | 
			
		||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
 | 
			
		||||
from tmtccmd.pus.tc.s3_fsfw_hk import create_request_one_hk_command
 | 
			
		||||
 | 
			
		||||
from common import (
 | 
			
		||||
    EXAMPLE_PUS_APID,
 | 
			
		||||
    make_addressable_id,
 | 
			
		||||
    RequestTargetId,
 | 
			
		||||
    AcsHkIds,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_cmd_definition_tree() -> CmdTreeNode:
 | 
			
		||||
 | 
			
		||||
    root_node = CmdTreeNode.root_node()
 | 
			
		||||
 | 
			
		||||
    test_node = CmdTreeNode("test", "Test Node")
 | 
			
		||||
    test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
 | 
			
		||||
    test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
 | 
			
		||||
    root_node.add_child(test_node)
 | 
			
		||||
 | 
			
		||||
    scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
 | 
			
		||||
    scheduler_node.add_child(
 | 
			
		||||
        CmdTreeNode(
 | 
			
		||||
            "schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    root_node.add_child(scheduler_node)
 | 
			
		||||
 | 
			
		||||
    acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
 | 
			
		||||
    mgm_node = CmdTreeNode("mgms", "MGM devices node")
 | 
			
		||||
    mgm_node.add_child(CmdTreeNode("one_shot_hk", "Request one shot HK"))
 | 
			
		||||
    acs_node.add_child(mgm_node)
 | 
			
		||||
    root_node.add_child(acs_node)
 | 
			
		||||
 | 
			
		||||
    return root_node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
 | 
			
		||||
    # It should always be at least the root path "/", so we split of the empty portion left of it.
 | 
			
		||||
    cmd_path_list = cmd_path.split("/")[1:]
 | 
			
		||||
    if len(cmd_path_list) == 0:
 | 
			
		||||
        _LOGGER.warning("empty command path")
 | 
			
		||||
        return
 | 
			
		||||
    if cmd_path_list[0] == "test":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "ping":
 | 
			
		||||
            q.add_log_cmd("Sending PUS ping telecommand")
 | 
			
		||||
            return q.add_pus_tc(PusTelecommand(service=17, subservice=1))
 | 
			
		||||
        elif cmd_path_list[1] == "trigger_event":
 | 
			
		||||
            q.add_log_cmd("Triggering test event")
 | 
			
		||||
            return q.add_pus_tc(PusTelecommand(service=17, subservice=128))
 | 
			
		||||
    if cmd_path_list[0] == "scheduler":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
 | 
			
		||||
            q.add_log_cmd("Sending PUS scheduled TC telecommand")
 | 
			
		||||
            crt_time = CdsShortTimestamp.from_now()
 | 
			
		||||
            time_stamp = crt_time + datetime.timedelta(seconds=10)
 | 
			
		||||
            time_stamp = time_stamp.pack()
 | 
			
		||||
            return q.add_pus_tc(
 | 
			
		||||
                create_time_tagged_cmd(
 | 
			
		||||
                    time_stamp,
 | 
			
		||||
                    PusTelecommand(service=17, subservice=1),
 | 
			
		||||
                    apid=EXAMPLE_PUS_APID,
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
    if cmd_path_list[0] == "acs":
 | 
			
		||||
        assert len(cmd_path_list) >= 2
 | 
			
		||||
        if cmd_path_list[1] == "mgm":
 | 
			
		||||
            assert len(cmd_path_list) >= 3
 | 
			
		||||
            if cmd_path_list[2] == "one_shot_hk":
 | 
			
		||||
                q.add_log_cmd("Sending HK one shot request")
 | 
			
		||||
                q.add_pus_tc(
 | 
			
		||||
                    create_request_one_hk_command(
 | 
			
		||||
                        make_addressable_id(RequestTargetId.ACS, AcsHkIds.MGM_SET)
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user