Compare commits

..
Author SHA1 Message Date
Robin Mueller ce016b1e3f embedded apps now connect to minisim 2026-09-30 12:39:54 +02:00
muellerr 1db716b166 Merge pull request 'removed missing file' (#299) from missing-file-removal into main
Reviewed-on: #299
2026-09-29 15:56:22 +02:00
Robin Mueller 2a5b8f95d3 removed missing file 2026-09-29 15:56:03 +02:00
muellerr 26c0b042c9 Merge pull request 'remove STM32F3 Disco example' (#298) from remove-stm32f3-example into main
Reviewed-on: #298
2026-09-29 15:54:25 +02:00
Robin Mueller e103702184 remove STM32F3 Disco example
- target MCU not large enough for larger example featuring sat-rs and also does not have
  ethernet/networking support
2026-09-29 15:51:08 +02:00
muellerr 233e5abb23 Merge pull request 'unified example types and clients' (#297) from unify-example-types-client into main
Reviewed-on: #297
2026-09-29 15:46:31 +02:00
Robin Mueller f3e5e06937 unified example types and clients 2026-09-29 15:45:03 +02:00
muellerr 7b33855ba1 Merge pull request 'STM32H7 embassy example' (#296) from stm32h7-embassy into main
Reviewed-on: #296
2026-09-29 15:02:30 +02:00
Robin Mueller 8660dbae90 STM32H7 embassy example 2026-09-29 15:01:30 +02:00
muellerr 394ffec6c9 Merge pull request 'Reuse simulator' (#295) from reuse-simulator into main
Reviewed-on: #295
2026-09-29 14:22:52 +02:00
Robin Mueller 7cc2ff3ff9 first step in unifying the examples: one example folder
One example folder for all examples, including std and embedded examples
2026-09-29 13:39:39 +02:00
muellerr bb8decfc74 Merge pull request 'MGT fdir' (#292) from mgt-fdir into main
Reviewed-on: #292
2026-09-29 11:50:39 +02:00
Robin Mueller 23ac7cf0ad MGT fdir 2026-09-29 11:49:37 +02:00
muellerr cb6c8b84bb Merge pull request 'switch to new nucleo H753 board' (#293) from embedded-readmes into main
Reviewed-on: #293
2026-09-24 19:39:25 +02:00
Robin Mueller 390d7aaff5 switch to new nucleo H753 board 2026-09-24 19:37:12 +02:00
muellerr 830df80291 Merge pull request 'typos and design chapter fixes' (#291) from book-design-update into main
Reviewed-on: #291
2026-09-24 19:18:36 +02:00
Robin Mueller 13325056f6 typos and design chapter fixes 2026-09-24 19:17:57 +02:00
muellerr d22ee524ff Merge pull request 'add justfile rule for manual book update command' (#290) from manual-book-update into main
Reviewed-on: #290
2026-09-24 19:17:46 +02:00
Robin Mueller 03fa49341b add justfile rule for manual book update command 2026-09-24 18:38:10 +02:00
muellerr 93fcd44f13 Merge pull request 'update nexosim to v1' (#289) from update-nexosim into main
Reviewed-on: #289
2026-09-24 16:56:42 +02:00
Robin Mueller 7ae61a81e6 update nexosim to v1 2026-09-24 16:54:57 +02:00
muellerr 9ff6600920 Merge pull request 'cleanup' (#288) from cleanup into main
Reviewed-on: #288
2026-09-24 16:32:16 +02:00
Robin Mueller 32834920d1 cleanup 2026-09-24 16:31:14 +02:00
muellerr b4869e0db4 Merge pull request 'Mgt handler' (#287) from mgt-handler into main
Reviewed-on: #287
2026-09-24 16:25:10 +02:00
Robin Mueller db906565c8 MGT handler 2026-09-24 16:23:31 +02:00
muellerr ba9ea3a12c Merge pull request 'periodic HK support for MGMs' (#286) from perioic-hk-support into main
Reviewed-on: #286
2026-09-24 15:42:21 +02:00
Robin Mueller 782f6c3936 periodic HK support for MGMs 2026-09-24 15:41:48 +02:00
muellerr 3fa655da59 Merge pull request 'simplify minisim' (#285) from simplify-minisim into main
Reviewed-on: #285
2026-09-24 15:27:57 +02:00
Robin Mueller 1aee1b8513 simplify minisim 2026-09-24 15:26:48 +02:00
muellerr d791ca2a5f Merge pull request 'Add MGM1 to simulator' (#284) from mgm1-in-sim into main
Reviewed-on: #284
2026-09-24 11:45:49 +02:00
Robin Mueller 7a2a317d58 Add MGM1 to simulator 2026-09-24 11:44:52 +02:00
muellerr d9ead9516e Merge pull request 'MGM FDIR' (#283) from mgm-recovery into main
Reviewed-on: #283
2026-09-23 18:57:30 +02:00
Robin Mueller 7b74bee581 FDIR extensions and improvements for MGM device handler
- try power cycling now instead of going to faulty immediately
- after too many power cycles in a short time frame, go to faulty
- new FDIR/recovery helper which is generic
- new failure variants for fault injection: transient failures to test
  that a power cycles could fix the issue
2026-09-23 18:55:52 +02:00
muellerr 1c6e777d24 Merge pull request 'Granular event generation control' (#282) from granular-event-generation-control into main
Reviewed-on: #282
2026-09-23 15:33:13 +02:00
Robin Mueller 919f1aa65d add granular event management control 2026-09-23 15:31:35 +02:00
muellerr 883998e31d Merge pull request 'more events' (#281) from more-events into main
Reviewed-on: #281
2026-09-22 11:54:28 +02:00
Robin Mueller 8d36fd3761 more events 2026-09-22 11:48:40 +02:00
muellerr 2a7b3796ec Merge pull request 'Try reducing boilerplate' (#280) from try-reducing-boilerplate into main
Reviewed-on: #280
2026-09-16 13:59:53 +02:00
Robin Mueller 1c98f3772f extract switch and mode helper 2026-09-16 13:58:11 +02:00
muellerr 76074d69e4 Merge pull request 'feat: add FDIR fault counter and wire it into the MGM device handler' (#277) from fdir-addition into main
Reviewed-on: #277
2026-09-16 13:57:50 +02:00
Robin Mueller 86d8528d21 feat: add FDIR fault counter and wire it into the MGM device handler
Add satrs::fdir::FaultCounter, an FSFW-style error threshold counter:
counts faults, decrements over time when faults stop, and reports
when a threshold is exceeded. Two variants for now, mirroring the
hk.rs helper pattern:
- FaultCounterStd, backed by std::time::Instant
- FaultCounterEmbassy, backed by embassy_time::Instant (embassy-time
  feature), with an optional defmt::Format impl gated on the defmt
  feature

Add satrs::health::HealthTableMapSync::default() for easy construction
of a shared, global health table.

Wire both into the example app's MGM device handler as the first real
FDIR use case:
- the minisim MGM model gains SpiFaultMode (None/AllZeros/AllOnes) and
  a SetSpiFault request, so a stuck SPI bus can be injected for testing,
  independent of switch state
- MgmHandlerLis3Mdl::poll_sensor checks the SPI transfer result: a
  comm timeout or an all-1s stuck-bus reply (the same pattern the sim
  already uses for "device off") counts as a fault. Above threshold,
  the component is marked Faulty in a HealthTableMapSync shared from
  main.rs. This logic lives in the device handler, not the SPI comm
  layer, since deciding what a failed transfer means for FDIR is a
  handler concern.
- an all-0s reply is deliberately not treated as a fault, since it
  collides with a legitimate zero-field reading
2026-09-16 13:56:47 +02:00
muellerr 7d6e4ca2f8 Merge pull request 'improve system view' (#279) from improve-system-view into main
Reviewed-on: #279
2026-09-16 12:30:57 +02:00
Robin Mueller eeb6fe17cc improve system view 2026-09-16 12:30:25 +02:00
muellerr 9de9ce3131 Merge pull request 'add sat-rs system view' (#278) from satrs-system-view into main
Reviewed-on: #278
2026-09-15 15:47:13 +02:00
Robin Mueller 87fc65fdd4 add sat-rs system view 2026-09-15 15:46:26 +02:00
Robin Mueller 877312d3c3 Merge pull request #4 from 0rlych1kk4/fix/ccsds-packet-length
fix: correct CCSDS packet length calculation
2026-09-01 12:50:12 +02:00
0rlych1kk4 1649306802 fix: correct CCSDS packet length calculation
Signed-off-by: 0rlych1kk4 <orlychikka@gmail.com>
2026-08-31 14:28:24 +08:00
muellerr 12824b2906 Merge pull request 'small documentation fix' (#276) from small-doc-fix into main
Reviewed-on: #276
2026-08-26 15:33:06 +02:00
Robin Mueller cbce7916c2 small documentation fix 2026-08-26 15:32:06 +02:00
muellerr 54971683bf Merge pull request 'continue PUS removal' (#275) from continue-pus-removal into main
Reviewed-on: #275
2026-08-26 15:24:32 +02:00
Robin Mueller 4ba4d3e6a3 try to update more chapters 2026-08-26 15:24:08 +02:00
muellerr 2d9b14e69e Merge pull request 'update satrs book' (#274) from update-satrs-book into main
Reviewed-on: #274
2026-08-25 18:56:14 +02:00
Robin Mueller d2b8bc6afb update satrs book 2026-08-25 18:55:52 +02:00
muellerr 2cf39a0e1c Merge pull request 'add tmtc-utils crate' (#273) from add-tmtc-utils-crate into main
Reviewed-on: #273
2026-08-25 17:54:14 +02:00
Robin Mueller 1e44b2d59f add tmtc-utils crate 2026-08-25 17:53:38 +02:00
muellerr c40b8f32c9 Merge pull request 'continue subsystem integration' (#272) from start-adding-acs-subsys into main
Reviewed-on: #272
2026-08-25 16:58:50 +02:00
Robin Mueller f6a87cff28 continue subsystem integration 2026-08-25 16:57:34 +02:00
muellerr 8195dac587 Merge pull request 'Rename example models crates to types' (#271) from rename-model-crates-to-types into main
Reviewed-on: #271
2026-08-25 14:19:17 +02:00
Robin MuellerandClaude Sonnet 5 5fffcc011f Rename example models crates to types
- satrs-example/models (package "models") -> satrs-example/types ("types")
- embedded-examples/models (package "embedded-models") -> embedded-examples/types ("embedded-types")

Updates all path dependencies and use/models:: references accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 14:13:02 +02:00
muellerr 3e4c3c4e64 Merge pull request 'start adding ACS subsystem' (#266) from start-adding-acs-subsys into main
Reviewed-on: #266
2026-07-28 17:07:25 +02:00
Robin Mueller 3f0937de83 start adding ACS subsystem 2026-07-28 17:00:26 +02:00
muellerr d2db28468f Merge pull request 'ran cargo update' (#270) from dep-update into main
Reviewed-on: #270
2026-07-10 12:05:22 +02:00
muellerr 3550026c00 ran cargo update 2026-07-07 13:27:43 +02:00
muellerr ad4908feef Merge pull request 'update STM32F3 example code' (#268) from update-stm32f3-example-code into main
Reviewed-on: #268
2026-07-07 13:24:51 +02:00
muellerr 20bc6c4fb6 update STM32F3 example code 2026-07-07 13:21:26 +02:00
muellerr e97e6f2a54 Merge pull request 'smaller fixes' (#269) from smaller-fixes into main
Reviewed-on: #269
2026-05-19 11:57:00 +02:00
Robin Mueller 1c9ebe8a75 smaller fixes 2026-05-19 11:56:44 +02:00
muellerr 7a464843ed Merge pull request 'update STM32H7 example code' (#267) from update-stm32h7-example-code into main
Reviewed-on: #267
2026-05-19 11:52:45 +02:00
Robin Mueller d58b1db55b update STM32H7 example code 2026-05-19 11:51:43 +02:00
muellerr b1253eaad4 Merge pull request 'Rework ACS' (#264) from rework-acs into main
Reviewed-on: #264
2026-03-18 11:19:24 +01:00
Robin Mueller ae4d26b8bd re-work ACS 2026-03-17 15:56:25 +01:00
muellerr cfcfabb5e3 Merge pull request 'fixes for switching' (#262) from fix-for-mgm-switching into main
Reviewed-on: #262
2026-03-12 13:48:04 +01:00
Robin Mueller 70f747ad86 fixes for switching 2026-03-12 13:45:19 +01:00
muellerr f44aac6ea2 Merge pull request 'probably need to re-work the mode model..' (#261) from continue-example-update into main
Reviewed-on: #261
2026-03-12 12:01:16 +01:00
Robin Mueller df517af85b probably need to re-work the mode model.. 2026-03-12 12:00:24 +01:00
muellerr ae9edf5888 Merge pull request 'minor clean up' (#260) from minor-cleanup into main
Reviewed-on: #260
2026-03-10 11:57:21 +01:00
Robin Mueller 512384026c minor clean up 2026-03-10 11:56:39 +01:00
muellerr 42c7a3b9ee Merge pull request 'Move to CCSDS + serde, rip out PUS' (#259) from move-to-ccsds-and-serde into main
Reviewed-on: #259
2026-03-10 11:56:16 +01:00
muellerr 1e15e3d501 move to CCSDS + serde for sat-rs example 2026-03-10 11:45:11 +01:00
muellerr d7e6732888 Merge pull request 'CCSDS scheduler' (#258) from ccsds-scheduler into main
Reviewed-on: #258
2025-11-27 16:09:52 +01:00
Robin Mueller c27569a526 new CCSDS packet scheduler 2025-11-27 16:02:39 +01:00
muellerr b2bc87641c Merge pull request 'bumped sat-rs' (#257) from bump-satrs into main
Reviewed-on: #257
2025-11-06 14:54:05 +01:00
Robin Mueller c8245772bb bumped sat-rs 2025-11-06 14:53:49 +01:00
muellerr 56d9cafa59 Merge pull request 'small changelog tweak' (#256) from small-changelog-tweak into main
Reviewed-on: #256
2025-11-06 14:51:07 +01:00
Robin Mueller 7de057aaf2 small changelog tweak 2025-11-06 14:50:38 +01:00
muellerr 2f4a11a0bc Merge pull request 'bump spacepackets version' (#255) from bump-spacepackets-version into main
Reviewed-on: #255
2025-11-06 14:48:06 +01:00
Robin Mueller 0cd929a19c bumped spacepackets version 2025-11-06 14:47:27 +01:00
muellerr d54167837e Merge pull request 'updated STM32F3 RTICv2 example' (#254) from update-embedded-examples into main
Reviewed-on: #254
2025-10-31 14:31:05 +01:00
Robin Mueller ecce07a471 updated STM32F3 RTICv2 example 2025-10-31 14:27:29 +01:00
muellerr 526254851a Merge pull request 'updated dependencies' (#253) from update-dependencies into main
Reviewed-on: #253
2025-10-29 16:41:58 +01:00
Robin Mueller 440e757141 updated dependencies 2025-10-29 16:37:41 +01:00
muellerr 02aa825783 Merge pull request 'Larger update' (#252) from larger-update into main
Reviewed-on: #252
2025-10-21 14:33:31 +02:00
Robin Mueller bc9e0e4a94 Larger update
- ComponentId is u32 now
- Simplified TCP servers
2025-10-21 13:28:24 +02:00
muellerr 06e713a557 Merge pull request 'Rework Event Management Module' (#251) from rework-event-management-module into main
Reviewed-on: #251
2025-10-20 11:21:44 +02:00
Robin Mueller 778512d50e rework event management module 2025-10-20 11:16:28 +02:00
muellerr 5d40638964 Merge pull request 'update spacepackets dependency' (#250) from update-dependencies into main
Reviewed-on: #250
2025-09-26 16:01:40 +02:00
Robin Mueller 1b07b845f2 update spacepackets dependency 2025-09-26 16:01:30 +02:00
muellerr 2e58a311c8 Merge pull request 'CI fix and naming improvement' (#249) from example-ci-fix into main
Reviewed-on: #249
2025-09-06 19:40:17 +02:00
Robin Mueller 62d64e692a CI fix and naming improvement 2025-09-06 19:36:56 +02:00
muellerr 3784f47b66 Merge pull request 'update heapless dependency' (#248) from update-deps into main
Reviewed-on: #248
2025-09-06 19:32:18 +02:00
Robin Mueller e1911f1b6e update heapless dependency 2025-09-06 19:32:06 +02:00
muellerr 2e53ce1871 Merge pull request 'update trait names' (#247) from update-trait-names into main
Reviewed-on: #247
2025-09-06 19:31:46 +02:00
Robin Mueller a6c460129b update trait names 2025-09-06 19:26:39 +02:00
muellerr d5ecefd683 Merge pull request 'small changelog adaption' (#246) from small-changelog-adaption into main
Reviewed-on: #246
2025-08-26 14:29:41 +02:00
muellerr 2f9f3b8183 small changelog adaption 2025-08-26 14:28:22 +02:00
muellerr b28cff85de Merge pull request 'prepare next MIB release' (#245) from satrs-mib-release into main
Reviewed-on: #245
2025-08-26 14:26:47 +02:00
muellerr da3de9f22c prepare next MIB release 2025-08-26 14:24:48 +02:00
muellerr aefdf0987d Merge pull request 'add matrix chat badge' (#243) from add-chat-badge into main
Reviewed-on: #243
2025-08-14 14:17:56 +02:00
Robin Mueller 7ae9e62d66 add matrix chat badge 2025-08-14 14:17:41 +02:00
muellerr fea1a9028b Merge pull request 'edition bump to 2024 ald clippy' (#244) from edition-bump-clippy-fixes into main
Reviewed-on: #244
2025-08-14 14:17:18 +02:00
Robin Mueller abc145fb2d edition bump to 2024 ald clippy 2025-08-14 14:10:00 +02:00
muellerr 490635dfcf Merge pull request 'prepare patch releases' (#242) from prep-patch-releases into main
Reviewed-on: #242
2025-07-23 14:09:59 +02:00
muellerr de3d22a022 prepare patch releases 2025-07-23 14:05:04 +02:00
muellerr cbe211fe8b Merge pull request 'CFDP reference' (#241) from cfdp-ref into main
Reviewed-on: #241
2025-07-22 10:47:08 +02:00
Robin Mueller e379bc3fd7 CFDP 2025-07-22 10:46:56 +02:00
muellerr a61ee85796 Merge pull request 'clippy fixes' (#240) from clippy-fixes into main
Reviewed-on: #240
2025-07-22 10:44:42 +02:00
Robin Mueller 81473b30f9 clippy fixes 2025-07-22 10:44:11 +02:00
muellerr 22675a73f9 Merge pull request 'changelog' (#239) from satrs-changelog into main
Reviewed-on: #239
2025-07-22 10:36:58 +02:00
Robin Mueller c68e3d4f75 changelog 2025-07-22 10:35:38 +02:00
muellerr 3deedfba17 Merge pull request 'bump all dependencies' (#238) from dependency-update into main
Reviewed-on: #238
2025-07-22 10:31:15 +02:00
Robin Mueller 533caea0fe bump all dependencies 2025-07-22 10:29:54 +02:00
muellerr 848fe6f207 Merge pull request 'spacepackets update' (#237) from update-satrs-deps into main
Reviewed-on: #237
2025-07-18 19:24:55 +02:00
Robin Mueller cf64fea7d9 bump cobs-rs dependency 2025-07-18 19:24:40 +02:00
muellerr 4948db3fa5 Merge pull request 'bump cobs-rs dependency' (#236) from update-satrs-deps into main
Reviewed-on: #236
2025-07-18 19:19:58 +02:00
Robin Mueller 1920e4878c bump cobs-rs dependency 2025-07-18 19:19:24 +02:00
muellerr f46bc94b04 Merge pull request 'some test fixes' (#235) from test-fix into main
Reviewed-on: #235
2025-05-19 15:49:02 +02:00
muellerr fb45da1890 some test fixes 2025-05-19 15:46:57 +02:00
muellerr f600ee499a Merge pull request 'add first generator' (#234) from generators into main
Reviewed-on: #234
2025-05-19 15:23:37 +02:00
muellerr 3f28f60c59 add first generator 2025-05-19 15:21:51 +02:00
muellerr 44b1f2b037 Merge pull request 'possible fix for clippy warning' (#233) from possible-fix-for-clippy-warning into main
Reviewed-on: #233
2025-05-19 15:15:24 +02:00
muellerr 4b22958b34 possible fix for clippy warning 2025-05-19 15:14:59 +02:00
muellerr a711c6acd9 Merge pull request 'bump spacepackets' (#232) from bump-spacepackets into main
Reviewed-on: #232
2025-05-19 14:19:00 +02:00
muellerr 99a954a1f5 some tweaks 2025-05-19 14:18:26 +02:00
muellerr 4a4fd7ac2c use git deps 2025-05-16 19:49:43 +02:00
muellerr 9bf08849a2 annoying 2025-05-16 19:43:45 +02:00
muellerr b8f7fefe26 Merge pull request 'should fix tests' (#231) from test-fix into main
Reviewed-on: #231
2025-05-10 16:31:14 +02:00
muellerr a501832698 should fix tests 2025-05-10 16:30:37 +02:00
muellerr c7284d3f1c Merge pull request 'minor wording improvements' (#230) from some-wording-improvements into main
Reviewed-on: #230
2025-05-10 16:27:37 +02:00
muellerr 18263d4568 Merge branch 'main' into some-wording-improvements 2025-05-10 16:27:29 +02:00
muellerr 95519c1363 minor wording improvements 2025-05-10 16:27:04 +02:00
muellerr 2ec32717d0 Merge pull request 'use released dependencies' (#229) from use-relesed-dependencies into main
Reviewed-on: #229
2025-05-10 16:24:37 +02:00
muellerr bfdd777685 use released dependencies 2025-05-10 16:24:06 +02:00
muellerr b54c2b7863 Merge pull request 'spacepackets update' (#227) from spacepackets-update into main
Reviewed-on: #227
2025-05-10 16:19:07 +02:00
muellerr 19f3355283 Merge pull request 'update SIM suite name' (#228) from update-sim-suite-name into main
Reviewed-on: #228
2025-05-10 16:18:16 +02:00
muellerr 4aeb28d2f1 update SIM suite name 2025-05-10 16:14:48 +02:00
muellerr ddc4544456 spacepackets update, clippy fixes 2025-05-10 16:13:45 +02:00
muellerr 9ab36c0362 Merge pull request 'update docs' (#226) from update-docs into main
Reviewed-on: #226
2025-05-06 16:21:17 +02:00
muellerr b95769c177 Merge branch 'main' into update-docs 2025-05-06 16:21:10 +02:00
muellerr bb20533ae1 Merge pull request 'remove token API for verification creator core' (#224) from simplify-verification-reporter-core into main
Reviewed-on: #224
2025-05-05 19:03:34 +02:00
muellerr 27cacd0f43 remove token API for verification creator core 2025-05-05 19:02:37 +02:00
muellerr bd6488e87b update docs 2025-04-01 20:58:18 +02:00
muellerr 52ec0d44aa Merge pull request 'bump README links' (#223) from update-readme-links into main
Reviewed-on: #223
2025-04-01 15:15:14 +02:00
muellerr 4fa9f8d685 bump README links 2025-04-01 15:05:28 +02:00
muellerr 767cf6b1a5 Merge pull request 'prepare alpha release' (#222) from prep-satrs-v0.3.0-alpha.0 into main
Reviewed-on: #222
2025-02-18 16:52:15 +01:00
muellerr 5bf5518591 prepare alpha release 2025-02-18 16:49:33 +01:00
muellerr f3e0609910 Merge pull request 'Mode Sequences and Tables' (#211) from mode-tables-sequence-tables into main
Reviewed-on: #211
2025-02-13 12:44:28 +01:00
muellerr 05106354e3 Mode Tree Feature Update 2025-02-13 12:18:05 +01:00
muellerr 719b70d834 Merge pull request 'use static cell instead of custom struct' (#220) from use-static-cell into main
Reviewed-on: #220
2025-02-07 16:21:23 +01:00
muellerr d4082fa098 use static cell instead of custom struct 2025-02-07 16:15:23 +01:00
muellerr c9c9e4f735 Merge pull request 'update STM32H7 project' (#221) from update-stm32h7-project into main
Reviewed-on: #221
2025-02-07 16:15:08 +01:00
muellerr 2c92a95184 update STM32H7 project 2025-02-07 15:43:03 +01:00
muellerr 1a1d330814 Merge pull request 'update some outdated dependencies' (#219) from update-outdated-deps into main
Reviewed-on: #219
2025-01-31 18:37:49 +01:00
muellerr ef7b5d66fe update some outdated dependencies 2025-01-31 18:37:12 +01:00
muellerr b3497e0592 Merge pull request 'update embedded examples' (#218) from update-embedded-examples into main
Reviewed-on: #218
2025-01-31 18:32:41 +01:00
muellerr 73286e36c2 update embedded examples 2025-01-31 18:20:06 +01:00
muellerr ed266a11f6 Merge pull request 'Avoid static muts for static pool buffer declaration' (#215) from avoid-static-muts-static-pools into main
Reviewed-on: #215
2025-01-31 12:12:57 +01:00
muellerr af972e174f macro fix 2025-01-31 12:04:40 +01:00
muellerr 3d12083c16 use full path to helper type 2025-01-31 11:56:38 +01:00
muellerr e8d2c020fa Merge pull request 'use released nexosim version' (#217) from use-released-nexosim into main
Reviewed-on: #217
2025-01-31 11:33:56 +01:00
muellerr 69e172b633 avoid static muts for static pools 2025-01-31 11:27:09 +01:00
muellerr e1dda751bc use released nexosim version 2025-01-31 11:24:56 +01:00
muellerr b01628d8ef Merge pull request 'fix minisim tests' (#216) from update-minisim into main
Reviewed-on: #216
2025-01-31 11:18:03 +01:00
muellerr 31844e4fe2 fix tests 2025-01-31 11:17:39 +01:00
muellerr 738872f421 Merge pull request 'update mini simulator' (#214) from update-minisim into main
Reviewed-on: #214
2025-01-30 18:57:27 +01:00
muellerr 309e39999f small tweak 2025-01-30 18:56:44 +01:00
muellerr 1c43c3adf9 update mini simulator 2025-01-30 18:55:47 +01:00
muellerr d9e0abffa7 Merge pull request 'clippy and test fix' (#213) from clippy-fixes into main
Reviewed-on: #213
2025-01-23 17:22:46 +01:00
muellerr abec9dd448 clippy and test fix 2025-01-23 17:19:26 +01:00
muellerr c18fbb59ad Merge pull request 're-run formatter' (#212) from run-afmt into main
Reviewed-on: #212
2025-01-23 17:04:58 +01:00
muellerr c91ddcd658 re-run formatter 2025-01-23 17:02:16 +01:00
muellerr c5fa1955d7 Merge pull request 'Scheduling Table for std runtimes' (#210) from scheduling-table into main
Reviewed-on: #210
2024-11-20 17:22:08 +01:00
muellerr c9708810e6 changelog 2024-11-20 17:21:54 +01:00
muellerr 79d0c2e222 Scheduling Table for std runtimes 2024-11-20 17:20:42 +01:00
muellerr 8e87875c0e Merge pull request 'use released satrs-shared' (#209) from use-released-satrs-shared into main
Reviewed-on: #209
2024-11-15 12:15:06 +01:00
muellerr 1ac6c02c06 use released satrs-shared 2024-11-15 12:14:33 +01:00
muellerr 6e7907522e Merge pull request 'changelog satrs-shared' (#208) from satrs-shared-changelog into main
Reviewed-on: #208
2024-11-15 12:08:27 +01:00
muellerr f747a5efdc changelog satrs-shared 2024-11-15 12:07:32 +01:00
muellerr 6ffd55cec2 Merge pull request 'bump satrs-shared' (#207) from satrs-shared-v0.2.1 into main
Reviewed-on: #207
2024-11-15 11:59:00 +01:00
muellerr 5e51b3de42 bump satrs-shared 2024-11-15 11:52:48 +01:00
muellerr bd059a2541 Merge pull request 'Bump dependecies' (#206) from bump-dependencies into main
Reviewed-on: #206
2024-11-15 11:49:19 +01:00
muellerr 1a4d764f25 bump dependencies 2024-11-15 11:37:14 +01:00
muellerr b38c617fae Merge pull request 'clippy fixes' (#205) from small-clippy-fix into main
Reviewed-on: #205
2024-11-04 13:55:27 +01:00
muellerr edcd5491f1 clippy fixes 2024-11-04 13:51:36 +01:00
muellerr b4cb034b73 Merge pull request 'Extract CFDP' (#202) from extract-cfdp into main
Reviewed-on: #202
2024-11-04 12:19:32 +01:00
muellerr 47c86aea5c Extracted CFDP components 2024-11-04 12:18:41 +01:00
muellerr c8bba48e76 Merge pull request 'bump spacepackets' (#204) from satrs-shared-spacepackets-bump into main
Reviewed-on: #204
2024-11-04 12:07:12 +01:00
muellerr 176da4838a bump spacepackets 2024-11-04 12:06:10 +01:00
muellerr 8114195bc6 Merge pull request 'satrs-shared v0.2.0' (#203) from bump-satrs-shared into main
Reviewed-on: #203
2024-11-04 11:51:20 +01:00
muellerr 1726e34fa7 satrs-shared v0.2.0 2024-11-04 11:50:17 +01:00
muellerr 1f2d6c9474 Merge pull request 'another small documentation fix' (#199) from another-small-doc-fix into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #199
2024-06-03 16:09:06 +02:00
muellerr 9960930339 another small documentation fix
Rust/sat-rs/pipeline/head Build queued...
2024-06-03 16:08:38 +02:00
muellerr cb270964a1 Merge pull request 'small README fix' (#198) from small-readme-fix into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #198
2024-06-03 16:02:33 +02:00
muellerr a45e634214 small README fix
Rust/sat-rs/pipeline/head Build started...
2024-06-03 15:52:57 +02:00
muellerr 9e4132706c Merge pull request 'update README and book' (#197) from readme-and-book-updates into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #197
2024-06-03 15:32:24 +02:00
muellerr 36d889a504 update README and book
Rust/sat-rs/pipeline/head Build queued...
2024-06-03 15:31:43 +02:00
muellerr 97a6510af7 Merge pull request 'Integrate Simulator into Example' (#185) from sim-mgm-update into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #185
2024-06-03 15:25:08 +02:00
muellerr 2e5d6a5c41 update example changelog
Rust/sat-rs/pipeline/pr-main Build started...
2024-06-03 15:22:27 +02:00
muellerr 29167736db Integration of the mini simulator into the sat-rs example
Rust/sat-rs/pipeline/pr-main Build started...
2024-06-03 15:18:23 +02:00
muellerr a4c433a7be Merge pull request 'fix sat-rs build by using released asynchronix build' (#196) from fix-minisim-deps into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #196
2024-06-02 00:10:19 +02:00
muellerr 472a8ce0f9 fix sat-rs build by using released asynchronix build
Rust/sat-rs/pipeline/head Build queued...
2024-06-02 00:09:08 +02:00
lkoester e753319dac Merge pull request 'Airbus BA ins Readme' (#195) from lkoester-patch-1 into main
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #195
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2024-05-29 10:11:42 +02:00
lkoester 99dddf36f3 Airbus BA ins Readme
Rust/sat-rs/pipeline/head There was a failure building this commit
Passt das in Flight Heritage? Ist ja nie geflogen aber heritage passt schon
2024-05-29 09:50:55 +02:00
324 changed files with 31677 additions and 167005 deletions

No files matched your search

+14 -3
View File
@@ -11,7 +11,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check --release
- name: Install libudev-dev on Ubuntu
if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt update && sudo apt install -y libudev-dev
- run: cargo check
# Check example with static pool configuration
- run: cargo check -p example-std --no-default-features
test:
name: Run Tests
@@ -21,6 +26,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Install nextest
uses: taiki-e/install-action@nextest
- run: sudo apt update && sudo apt install -y libudev-dev
- run: cargo nextest run --all-features
- run: cargo test --doc --all-features
@@ -37,7 +43,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
- run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
- run: cargo check -p satrs --target=${{matrix.target}} --no-default-features
fmt:
name: Check formatting
@@ -45,6 +51,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
docs:
@@ -53,7 +61,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
- run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc -p satrs --all-features --no-deps
clippy:
name: Clippy
@@ -61,4 +69,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: sudo apt update && sudo apt install -y libudev-dev
- run: cargo clippy -- -D warnings
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Check" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="check" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="false" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="clippy" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="true" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clippy Fix" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="clippy --fix" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Docs" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="doc --all-features" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Doctest" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --doc" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Examples" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --example test" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Format" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="fmt" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run obsw example" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p satrs-example --bin satrs-example" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run obsw simple client" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package fsrc-example --bin client" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
<option name="command" value="test" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="true" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test All" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test -- --include-ignored" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
-19
View File
@@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test satrs-core" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test -p satrs-core --all-features" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="true" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>
+7 -6
View File
@@ -3,14 +3,15 @@ resolver = "2"
members = [
"satrs",
"satrs-mib",
"satrs-example",
"satrs-minisim",
"examples/example-std",
"examples/types",
"examples/client",
"examples/minisim",
"examples/minisim-types",
"satrs-shared",
"tmtc-utils",
]
exclude = [
"embedded-examples/stm32f3-disco-rtic",
"embedded-examples/stm32h7-rtic",
"serialization-prototyping",
"examples/embedded",
]
+33 -18
View File
@@ -1,9 +1,9 @@
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
[![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/book/)
[![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
[![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
[![matrix chat](https://img.shields.io/matrix/sat-rs%3Amatrix.org)](https://matrix.to/#/#sat-rs:matrix.org)
sat-rs
=========
@@ -11,8 +11,8 @@ sat-rs
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
to write on-board software for remote systems like rovers or satellites. It is specifically written
for the special requirements for these systems. You can find an overview of the project and the
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
link to the [more high-level sat-rs book](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/book/)
at the [IRS software projects website](https://documentation.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
@@ -26,29 +26,38 @@ and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-
# Overview
This project currently contains following crates:
This project currently contains the following crates:
* [`satrs-book`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-book):
Primary information resource in addition to the API documentation, hosted
[here](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/). It can be useful to read
[here](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/book/). It can be useful to read
this first before delving into the example application and the API documentation.
* [`satrs`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs):
Primary crate.
* [`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-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-stm32f3-disco-rtic`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/embedded-examples/satrs-stm32f3-disco-rtic):
Example of a simple example using low-level sat-rs components on a bare-metal system
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/satrs-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`.
## Examples
All examples and their helper crates are located inside the
[`examples`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples) folder:
* [`example-std`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/example-std):
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.
* [`minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/minisim):
Mini-Simulator based on [nexosim](https://github.com/asynchronics/nexosim) which
simulates some physical devices for the `example-std` application device handlers.
* [`client`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/client): Ground client to command the `example-std` application and the STM32H7 examples.
* [`types`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/types): Telecommand and telemetry definitions shared by the
`example-std` application, the STM32H7 examples and the `client`.
* [`embedded`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/embedded):
Simple examples using sat-rs components on a bare-metal system with constrained resources,
running on the NUCLEO-H753ZI board. `stm32h7-nucleo-rtic` uses the
[RTIC](https://github.com/rtic-rs/rtic) framework and `stm32h7-nucleo-embassy` uses the
[embassy](https://embassy.dev/) executor.
The library crates and the `example-std` application have their own `CHANGELOG.md`.
# Related projects
@@ -58,6 +67,8 @@ Each project has its own `CHANGELOG.md`.
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
@@ -69,6 +80,10 @@ Currently this library has the following flight heritage:
[flown on the satellite](https://blogs.esa.int/rocketscience/2024/05/21/ops-sat-reentry-tomorrow-final-experiments-continue/).
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
+1 -1
View File
@@ -47,7 +47,7 @@ def main():
parser.add_argument(
"-p",
"--package",
choices=["satrs", "satrs-minisim", "satrs-example"],
choices=["satrs", "minisim", "example-std"],
default="satrs",
help="Choose project to generate coverage for",
)
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open
@@ -1,37 +0,0 @@
[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,4 +0,0 @@
/target
/itm.txt
/.cargo/config*
/.vscode
File diff suppressed because it is too large. Load diff
@@ -1,84 +0,0 @@
[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 = "1"
features = ["cortex-m-systick"]
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
branch = "all_features"
default-features = false
[dependencies.stm32f3xx-hal]
git = "https://github.com/robamu/stm32f3xx-hal"
version = "0.11.0-alpha.0"
features = ["stm32f303xc", "rt", "enumset"]
branch = "complete-dma-update"
# Can be used in workspace to develop and update HAL
# path = "../stm32f3xx-hal"
[dependencies.stm32f3-discovery]
git = "https://github.com/robamu/stm32f3-discovery"
version = "0.8.0-alpha.0"
branch = "complete-dma-update-hal"
# Can be used in workspace to develop and update BSP
# path = "../stm32f3-discovery"
[dependencies.satrs]
# path = "satrs"
version = "0.2"
default-features = false
features = ["defmt"]
[dev-dependencies]
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 # <-
@@ -1,114 +0,0 @@
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.
File diff suppressed because it is too large. Load diff
@@ -1,10 +0,0 @@
target extended-remote localhost:2331
monitor reset
# *try* to stop at the user entry point (it might be gone due to inlining)
break main
load
continue
@@ -1,12 +0,0 @@
# Sample OpenOCD configuration for the STM32F3DISCOVERY development board
# Depending on the hardware revision you got you'll have to pick ONE of these
# interfaces. At any time only one interface should be commented out.
# Revision C (newer revision)
source [find interface/stlink.cfg]
# Revision A and B (older revisions)
# source [find interface/stlink-v2.cfg]
source [find target/stm32f3x.cfg]
@@ -1,42 +0,0 @@
target extended-remote :3333
# print demangled symbols
set print asm-demangle on
# set backtrace limit to not have infinite backtrace loops
set backtrace limit 32
# detect unhandled exceptions, hard faults and panics
break DefaultHandler
break HardFault
break rust_begin_unwind
# # run the next few lines so the panic message is printed immediately
# # the number needs to be adjusted for your panic handler
# commands $bpnum
# next 4
# end
# *try* to stop at the user entry point (it might be gone due to inlining)
break main
# monitor arm semihosting enable
# # send captured ITM to the file itm.fifo
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
# # 8000000 must match the core clock frequency
# # 2000000 is the frequency of the SWO pin. This was added for newer
# openocd versions like v0.12.0.
# monitor tpiu config internal itm.txt uart off 8000000 2000000
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
# # 8000000 must match the core clock frequency
# # 2000000 is the frequency of the SWO pin
# monitor tpiu config external uart off 8000000 2000000
# # enable ITM port 0
# monitor itm port 0 on
load
# start the process but immediately halt the processor
stepi
@@ -1,33 +0,0 @@
/* Linker script for the STM32F303VCT6 */
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 40K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* You may want to use this variable to locate the call stack and static
variables in different memory regions. Below is shown the default value */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
/* You can use this symbol to customize the location of the .text section */
/* If omitted the .text section will be placed right after the .vector_table
section */
/* This is required only on microcontrollers that store some configuration right
after the vector table */
/* _stext = ORIGIN(FLASH) + 0x400; */
/* Example of putting non-initialized variables into custom RAM locations. */
/* This assumes you have defined a region RAM2 above, and in the Rust
sources added the attribute `#[link_section = ".ram2bss"]` to the data
you want to place there. */
/* Note that the section will not be zero-initialized by the runtime! */
/* SECTIONS {
.ram2bss (NOLOAD) : ALIGN(4) {
*(.ram2bss);
. = ALIGN(4);
} > RAM2
} INSERT AFTER .bss;
*/
@@ -1,8 +0,0 @@
/venv
/.tmtc-history.txt
/log
/.idea/*
!/.idea/runConfigurations
/seqcnt.txt
/tmtc_conf.json
@@ -1,4 +0,0 @@
{
"com_if": "serial_cobs",
"serial_baudrate": 115200
}
@@ -1,305 +0,0 @@
#!/usr/bin/env python3
"""Example client for the sat-rs example application"""
import struct
import logging
import sys
import time
from typing import Any, Optional, cast
from prompt_toolkit.history import FileHistory, History
from spacepackets.ecss.tm import CdsShortTimestamp
import tmtccmd
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
from spacepackets.ecss.pus_17_test import Service17Tm
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
from tmtccmd.core.base import BackendRequest
from tmtccmd.core.ccsds_backend import QueueWrapper
from tmtccmd.logging import add_colorlog_console_logger
from tmtccmd.pus import VerificationWrapper
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
from tmtccmd.com import ComInterface
from tmtccmd.config import (
CmdTreeNode,
default_json_path,
SetupParams,
HookBase,
params_to_procedure_conversion,
)
from tmtccmd.config.com import SerialCfgWrapper
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
from tmtccmd.logging.pus import (
RegularTmtcLogWrapper,
RawTmtcTimedLogWrapper,
TimedLogWhen,
)
from tmtccmd.tmtc import (
TcQueueEntryType,
ProcedureWrapper,
TcProcedureType,
FeedWrapper,
SendCbParams,
DefaultPusQueueHelper,
)
from tmtccmd.pus.s5_fsfw_event import Service5Tm
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
from tmtccmd.util.obj_id import ObjectIdDictT
_LOGGER = logging.getLogger()
EXAMPLE_PUS_APID = 0x02
class SatRsConfigHook(HookBase):
def __init__(self, json_cfg_path: str):
super().__init__(json_cfg_path)
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
from tmtccmd.config.com import (
create_com_interface_default,
create_com_interface_cfg_default,
)
assert self.cfg_path is not None
cfg = create_com_interface_cfg_default(
com_if_key=com_if_key,
json_cfg_path=self.cfg_path,
space_packet_ids=None,
)
if cfg is None:
raise ValueError(
f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
)
if cfg.com_if_key == "serial_cobs":
cfg = cast(SerialCfgWrapper, cfg)
cfg.serial_cfg.serial_timeout = 0.5
return create_com_interface_default(cfg)
def get_command_definitions(self) -> CmdTreeNode:
"""This function should return the root node of the command definition tree."""
return create_cmd_definition_tree()
def get_cmd_history(self) -> Optional[History]:
"""Optionlly return a history class for the past command paths which will be used
when prompting a command path from the user in CLI mode."""
return FileHistory(".tmtc-history.txt")
def get_object_ids(self) -> ObjectIdDictT:
from tmtccmd.config.objects import get_core_object_ids
return get_core_object_ids()
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
return root_node
class PusHandler(SpecificApidHandlerBase):
def __init__(
self,
file_logger: logging.Logger,
verif_wrapper: VerificationWrapper,
raw_logger: RawTmtcTimedLogWrapper,
):
super().__init__(EXAMPLE_PUS_APID, None)
self.file_logger = file_logger
self.raw_logger = raw_logger
self.verif_wrapper = verif_wrapper
def handle_tm(self, packet: bytes, _user_args: Any):
try:
pus_tm = PusTm.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
except ValueError as e:
_LOGGER.warning("Could not generate PUS TM object from raw data")
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
raise e
service = pus_tm.service
tm_packet = None
if service == 1:
tm_packet = Service1Tm.unpack(
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
)
res = self.verif_wrapper.add_tm(tm_packet)
if res is None:
_LOGGER.info(
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
)
_LOGGER.warning(
f"No matching telecommand found for {tm_packet.tc_req_id}"
)
else:
self.verif_wrapper.log_to_console(tm_packet, res)
self.verif_wrapper.log_to_file(tm_packet, res)
if service == 3:
_LOGGER.info("No handling for HK packets implemented")
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if pus_tm.subservice == 25:
if len(pus_tm.source_data) < 8:
raise ValueError("No addressable ID in HK packet")
json_str = pus_tm.source_data[8:]
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
if service == 5:
tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if service == 17:
tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if tm_packet.subservice == 2:
_LOGGER.info("Received Ping Reply TM[17,2]")
else:
_LOGGER.info(
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
)
if tm_packet is None:
_LOGGER.info(
f"The service {service} is not implemented in Telemetry Factory"
)
tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
self.raw_logger.log_tm(pus_tm)
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
byte_string = bytearray(struct.pack("!I", target_id))
byte_string.extend(struct.pack("!I", unique_id))
return byte_string
class TcHandler(TcHandlerBase):
def __init__(
self,
seq_count_provider: FileSeqCountProvider,
verif_wrapper: VerificationWrapper,
):
super(TcHandler, self).__init__()
self.seq_count_provider = seq_count_provider
self.verif_wrapper = verif_wrapper
self.queue_helper = DefaultPusQueueHelper(
queue_wrapper=QueueWrapper.empty(),
tc_sched_timestamp_len=7,
seq_cnt_provider=seq_count_provider,
pus_verificator=verif_wrapper.pus_verificator,
default_pus_apid=EXAMPLE_PUS_APID,
)
def send_cb(self, send_params: SendCbParams):
entry_helper = send_params.entry
if entry_helper.is_tc:
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
pus_tc_wrapper.pus_tc.seq_count = (
self.seq_count_provider.get_and_increment()
)
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
raw_tc = pus_tc_wrapper.pus_tc.pack()
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
send_params.com_if.send(raw_tc)
elif entry_helper.entry_type == TcQueueEntryType.LOG:
log_entry = entry_helper.to_log_entry()
_LOGGER.info(log_entry.log_str)
def queue_finished_cb(self, info: ProcedureWrapper):
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
q = self.queue_helper
q.queue_wrapper = wrapper.queue_wrapper
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
cmd_path = def_proc.cmd_path
if cmd_path == "/ping":
q.add_log_cmd("Sending PUS ping telecommand")
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
if cmd_path == "/change_blink_freq":
self.create_change_blink_freq_command(q)
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
q.add_log_cmd("Changing blink frequency")
while True:
blink_freq = int(
input(
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
)
)
if blink_freq < 2 or blink_freq > 10000:
print(
"Invalid blink frequency. Please specify a value between 2 and 10000."
)
continue
break
app_data = struct.pack("!I", blink_freq)
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
def main():
add_colorlog_console_logger(_LOGGER)
tmtccmd.init_printout(False)
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
parser_wrapper = PreArgsParsingWrapper()
parser_wrapper.create_default_parent_parser()
parser_wrapper.create_default_parser()
parser_wrapper.add_def_proc_args()
params = SetupParams()
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
proc_wrapper = ProcedureParamsWrapper()
if post_args_wrapper.use_gui:
post_args_wrapper.set_params_without_prompts(proc_wrapper)
else:
post_args_wrapper.set_params_with_prompts(proc_wrapper)
params.apid = EXAMPLE_PUS_APID
setup_args = SetupWrapper(
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
)
# Create console logger helper and file loggers
tmtc_logger = RegularTmtcLogWrapper()
file_logger = tmtc_logger.logger
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
verificator = PusVerificator()
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
# Create primary TM handler and add it to the CCSDS Packet Handler
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=None)
ccsds_handler.add_apid_handler(tm_handler)
# Create TC handler
seq_count_provider = PusFileSeqCountProvider()
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
tmtccmd.setup(setup_args=setup_args)
init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
tmtc_backend = tmtccmd.create_default_tmtc_backend(
setup_wrapper=setup_args,
tm_handler=ccsds_handler,
tc_handler=tc_handler,
init_procedure=init_proc,
)
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
try:
while True:
state = tmtc_backend.periodic_op(None)
if state.request == BackendRequest.TERMINATION_NO_ERROR:
sys.exit(0)
elif state.request == BackendRequest.DELAY_IDLE:
_LOGGER.info("TMTC Client in IDLE mode")
time.sleep(3.0)
elif state.request == BackendRequest.DELAY_LISTENER:
time.sleep(0.8)
elif state.request == BackendRequest.DELAY_CUSTOM:
if state.next_delay.total_seconds() <= 0.4:
time.sleep(state.next_delay.total_seconds())
else:
time.sleep(0.4)
elif state.request == BackendRequest.CALL_NEXT:
pass
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()
@@ -1,2 +0,0 @@
tmtccmd == 8.0.1
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
@@ -1,76 +0,0 @@
#![no_std]
#![no_main]
use satrs_stm32f3_disco_rtic as _;
use stm32f3_discovery::leds::Leds;
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
#[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();
let mut flash = dp.FLASH.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut delay = Delay::new(cp.SYST, clocks);
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
let mut leds = Leds::new(
gpioe.pe8,
gpioe.pe9,
gpioe.pe10,
gpioe.pe11,
gpioe.pe12,
gpioe.pe13,
gpioe.pe14,
gpioe.pe15,
&mut gpioe.moder,
&mut gpioe.otyper,
);
let delay_ms = 200u16;
loop {
leds.ld3_n.toggle().ok();
delay.delay_ms(delay_ms);
leds.ld3_n.toggle().ok();
delay.delay_ms(delay_ms);
//explicit on/off
leds.ld4_nw.on().ok();
delay.delay_ms(delay_ms);
leds.ld4_nw.off().ok();
delay.delay_ms(delay_ms);
leds.ld5_ne.on().ok();
delay.delay_ms(delay_ms);
leds.ld5_ne.off().ok();
delay.delay_ms(delay_ms);
leds.ld6_w.on().ok();
delay.delay_ms(delay_ms);
leds.ld6_w.off().ok();
delay.delay_ms(delay_ms);
leds.ld7_e.on().ok();
delay.delay_ms(delay_ms);
leds.ld7_e.off().ok();
delay.delay_ms(delay_ms);
leds.ld8_sw.on().ok();
delay.delay_ms(delay_ms);
leds.ld8_sw.off().ok();
delay.delay_ms(delay_ms);
leds.ld9_se.on().ok();
delay.delay_ms(delay_ms);
leds.ld9_se.off().ok();
delay.delay_ms(delay_ms);
leds.ld10_s.on().ok();
delay.delay_ms(delay_ms);
leds.ld10_s.off().ok();
delay.delay_ms(delay_ms);
}
}
@@ -1,684 +0,0 @@
#![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::systick::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::ExtU32;
use satrs::seq_count::SequenceCountProviderCore;
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
use stm32f3xx_hal::dma::dma1;
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
use stm32f3xx_hal::pac::USART2;
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
const UART_BAUD: u32 = 115200;
const 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 rtic_monotonics::systick::Systick;
use rtic_monotonics::Monotonic;
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
#[allow(unused_imports)]
use stm32f3_discovery::leds::Direction;
use stm32f3_discovery::leds::Leds;
use stm32f3xx_hal::prelude::*;
use stm32f3_discovery::switch_hal::OutputSwitch;
use stm32f3xx_hal::Switch;
#[allow(dead_code)]
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
#[shared]
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
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(cx.core.SYST, 8_000_000, systick_mono_token);
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);
Systick::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 = (Systick::now() - last_completed).to_millis();
if elapsed_ms < MIN_DELAY_BETWEEN_TX_PACKETS_MS {
Systick::delay((MIN_DELAY_BETWEEN_TX_PACKETS_MS - elapsed_ms).millis())
.await;
}
}
} else {
// Check for completion after 1 ms
Systick::delay(1.millis()).await;
continue;
}
if let Some(vec) = TM_REQUESTS.dequeue() {
cx.shared
.tx_shared
.lock(|tx_shared| match &mut tx_shared.state {
UartTxState::Idle(tx) => {
let encoded_len;
//debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
// Safety: We only copy the data into the TX DMA buffer in this task.
// If the DMA is active, another branch will be taken.
unsafe {
// 0 sentinel value as start marker
DMA_TX_BUF[0] = 0;
encoded_len =
cobs::encode(&vec[0..vec.len()], &mut DMA_TX_BUF[1..]);
// Should never panic, we accounted for the overhead.
// Write into transfer buffer directly, no need for intermediate
// encoding buffer.
// 0 end marker
DMA_TX_BUF[encoded_len + 1] = 0;
}
//debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
//debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
let tx_idle = tx.take().unwrap();
// Transfer completion and re-scheduling of new TX transfers will be done
// by the IRQ handler.
// SAFETY: The DMA is the exclusive writer to the DMA buffer now.
let transfer = tx_idle.tx.write_all(
unsafe { &DMA_TX_BUF[0..encoded_len + 2] },
tx_idle.dma_channel,
);
tx_shared.state = UartTxState::Transmitting(Some(transfer));
// The memory block is automatically returned to the pool when it is dropped.
}
UartTxState::Transmitting(_) => (),
});
// Check for completion after 1 ms
Systick::delay(1.millis()).await;
continue;
}
// Nothing to do, and we are idle.
Systick::delay(TX_HANDLER_FREQ_MS.millis()).await;
}
}
#[task(
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,
&timestamp,
)
.unwrap();
let result = send_tm(tm_creator);
if let Err(e) = result {
handle_tm_send_error(e);
}
started_token
}
fn handle_completion_verification(
started_token: VerificationToken<TcStateStarted>,
verif_reporter: &mut VerificationReportCreator,
src_data_buf: &mut [u8],
timestamp: &[u8],
) {
let result = send_tm(
verif_reporter
.completion_success(
src_data_buf,
started_token,
SEQ_COUNT_PROVIDER.get(),
0,
timestamp,
)
.unwrap(),
);
if let Err(e) = result {
handle_tm_send_error(e);
}
}
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
let mut tc_packet = TcPacket::new();
cx.shared.rx_transfer.lock(|rx_transfer| {
let rx_ref = rx_transfer.as_ref().unwrap();
if rx_ref.is_complete() {
let uart_rx_owned = rx_transfer.take().unwrap();
let (buf, c, rx) = uart_rx_owned.stop();
// The received data is transferred to another task now to avoid any processing overhead
// during the interrupt. There are multiple ways to do this, we use a stack allocaed vector here
// to do this.
tc_packet.resize(buf.len(), 0).expect("vec resize failed");
tc_packet.copy_from_slice(buf);
// Start the next transfer as soon as possible.
*rx_transfer = Some(rx.read_exact(buf, c));
// Send the vector to a regular task.
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler task failed");
// If this happens, there is a high chance that the maximum packet length was
// exceeded. Circular mode is not used here, so data might be missed.
defmt::warn!(
"rx transfer with maximum length {}, might miss data",
TC_BUF_LEN
);
}
});
}
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
fn serial_isr(mut cx: serial_isr::Context) {
cx.shared
.tx_shared
.lock(|tx_shared| match &mut tx_shared.state {
UartTxState::Idle(_) => (),
UartTxState::Transmitting(transfer) => {
let transfer_ref = transfer.as_ref().unwrap();
if transfer_ref.is_complete() {
let transfer = transfer.take().unwrap();
let (_, dma_channel, mut tx) = transfer.stop();
tx.clear_event(TxEvent::TransmissionComplete);
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
// We cache the last completed time to ensure that there is a minimum delay between consecutive
// transferred packets.
tx_shared.last_completed = Some(Systick::now());
}
}
});
let mut tc_packet = TcPacket::new();
cx.shared.rx_transfer.lock(|rx_transfer| {
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
// Received a partial packet.
if rx_transfer_ref.is_event_triggered(RxEvent::Idle) {
let rx_transfer_owned = rx_transfer.take().unwrap();
let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
// The received data is transferred to another task now to avoid any processing overhead
// during the interrupt. There are multiple ways to do this, we use a stack
// allocated vector to do this.
tc_packet
.resize(rx_len as usize, 0)
.expect("vec resize failed");
tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
rx.clear_event(RxEvent::Idle);
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
*rx_transfer = Some(rx.read_exact(buf, ch));
}
});
}
}
@@ -1,2 +0,0 @@
/settings.json
/.cortex-debug.*
@@ -1,12 +0,0 @@
{
// 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": []
}
@@ -1,22 +0,0 @@
{
"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"
}
]
}
]
}
@@ -1,18 +0,0 @@
#
# Cortex-Debug extension calls this function during initialization. You can copy this
# file, modify it and specifyy it as one of the config files supplied in launch.json
# preferably at the beginning.
#
# Note that this file simply defines a function for use later when it is time to configure
# for SWO.
#
set USE_SWO 0
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. 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
}
@@ -1,20 +0,0 @@
{
// 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
}
},
]
}
@@ -1,4 +0,0 @@
/target
/.cargo/config*
/.vscode
/app.map
-881
View File
@@ -1,881 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version 0.2.3",
]
[[package]]
name = "bare-metal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[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.2.3"
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
[[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.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2722f5b7d6ea8583cffa4d247044e280ccbb9fe501bed56552e2ba48b02d5f3d"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[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.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
[[package]]
name = "defmt"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0"
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.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb"
dependencies = [
"defmt-parser",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.64",
]
[[package]]
name = "defmt-parser"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f"
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.64",
]
[[package]]
name = "delegate"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee5df75c70b95bd3aacc8e2fd098797692fb1d54121019c4de481e42f04c8a1"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "derive-new"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
]
[[package]]
name = "embedded-alloc"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815"
dependencies = [
"critical-section",
"linked_list_allocator",
]
[[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.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
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.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[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.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32 0.2.1",
"rustc_version 0.4.0",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"defmt",
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "managed"
version = "0.8.0"
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.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
]
[[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.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[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.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rtic"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c443db16326376bdd64377da268f6616d5f804aba8ce799bac7d1f7f244e9d51"
dependencies = [
"atomic-polyfill",
"bare-metal 1.0.0",
"cortex-m",
"critical-section",
"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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a"
dependencies = [
"indexmap",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.64",
]
[[package]]
name = "rtic-monotonics"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7"
dependencies = [
"atomic-polyfill",
"cfg-if",
"cortex-m",
"embedded-hal 1.0.0",
"fugit",
"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 0.8.0",
"portable-atomic",
"rtic-common",
]
[[package]]
name = "rtic-time"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19"
dependencies = [
"critical-section",
"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 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.23",
]
[[package]]
name = "satrs"
version = "0.2.1"
dependencies = [
"cobs",
"crc",
"defmt",
"delegate",
"derive-new",
"heapless 0.7.17",
"num-traits",
"num_enum",
"paste",
"satrs-shared",
"smallvec",
"spacepackets",
]
[[package]]
name = "satrs-shared"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6042477018c2d43fffccaaa5099bc299a58485139b4d31c5b276889311e474f1"
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 = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[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"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[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 0.8.0",
"managed",
]
[[package]]
name = "spacepackets"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e85574d113a06312010c0ba51aadccd4ba2806231ebe9a49fc6473d0534d8696"
dependencies = [
"crc",
"defmt",
"delegate",
"num-traits",
"num_enum",
"zerocopy",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "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 = "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.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "void"
version = "1.0.2"
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.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.64",
]
@@ -1,85 +0,0 @@
[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.5"
rtic-sync = { version = "1", features = ["defmt-03"] }
[dependencies.smoltcp]
version = "0.11.0"
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 = "1"
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 # <-
@@ -1,118 +0,0 @@
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 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)
File diff suppressed because it is too large. Load diff
@@ -1,119 +0,0 @@
/* 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,8 +0,0 @@
/venv
/.tmtc-history.txt
/log
/.idea/*
!/.idea/runConfigurations
/seqcnt.txt
/tmtc_conf.json
@@ -1,4 +0,0 @@
{
"com_if": "udp",
"tcpip_udp_port": 7301
}
@@ -1,305 +0,0 @@
#!/usr/bin/env python3
"""Example client for the sat-rs example application"""
import struct
import logging
import sys
import time
from typing import Any, Optional, cast
from prompt_toolkit.history import FileHistory, History
from spacepackets.ecss.tm import CdsShortTimestamp
import tmtccmd
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
from spacepackets.ecss.pus_17_test import Service17Tm
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
from tmtccmd.core.base import BackendRequest
from tmtccmd.core.ccsds_backend import QueueWrapper
from tmtccmd.logging import add_colorlog_console_logger
from tmtccmd.pus import VerificationWrapper
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
from tmtccmd.com import ComInterface
from tmtccmd.config import (
CmdTreeNode,
default_json_path,
SetupParams,
HookBase,
params_to_procedure_conversion,
)
from tmtccmd.config.com import SerialCfgWrapper
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
from tmtccmd.logging.pus import (
RegularTmtcLogWrapper,
RawTmtcTimedLogWrapper,
TimedLogWhen,
)
from tmtccmd.tmtc import (
TcQueueEntryType,
ProcedureWrapper,
TcProcedureType,
FeedWrapper,
SendCbParams,
DefaultPusQueueHelper,
)
from tmtccmd.pus.s5_fsfw_event import Service5Tm
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
from tmtccmd.util.obj_id import ObjectIdDictT
_LOGGER = logging.getLogger()
EXAMPLE_PUS_APID = 0x02
class SatRsConfigHook(HookBase):
def __init__(self, json_cfg_path: str):
super().__init__(json_cfg_path)
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
from tmtccmd.config.com import (
create_com_interface_default,
create_com_interface_cfg_default,
)
assert self.cfg_path is not None
cfg = create_com_interface_cfg_default(
com_if_key=com_if_key,
json_cfg_path=self.cfg_path,
space_packet_ids=None,
)
if cfg is None:
raise ValueError(
f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
)
if cfg.com_if_key == "serial_cobs":
cfg = cast(SerialCfgWrapper, cfg)
cfg.serial_cfg.serial_timeout = 0.5
return create_com_interface_default(cfg)
def get_command_definitions(self) -> CmdTreeNode:
"""This function should return the root node of the command definition tree."""
return create_cmd_definition_tree()
def get_cmd_history(self) -> Optional[History]:
"""Optionlly return a history class for the past command paths which will be used
when prompting a command path from the user in CLI mode."""
return FileHistory(".tmtc-history.txt")
def get_object_ids(self) -> ObjectIdDictT:
from tmtccmd.config.objects import get_core_object_ids
return get_core_object_ids()
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
return root_node
class PusHandler(SpecificApidHandlerBase):
def __init__(
self,
file_logger: logging.Logger,
verif_wrapper: VerificationWrapper,
raw_logger: RawTmtcTimedLogWrapper,
):
super().__init__(EXAMPLE_PUS_APID, None)
self.file_logger = file_logger
self.raw_logger = raw_logger
self.verif_wrapper = verif_wrapper
def handle_tm(self, packet: bytes, _user_args: Any):
try:
pus_tm = PusTm.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
except ValueError as e:
_LOGGER.warning("Could not generate PUS TM object from raw data")
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
raise e
service = pus_tm.service
tm_packet = None
if service == 1:
tm_packet = Service1Tm.unpack(
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
)
res = self.verif_wrapper.add_tm(tm_packet)
if res is None:
_LOGGER.info(
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
)
_LOGGER.warning(
f"No matching telecommand found for {tm_packet.tc_req_id}"
)
else:
self.verif_wrapper.log_to_console(tm_packet, res)
self.verif_wrapper.log_to_file(tm_packet, res)
if service == 3:
_LOGGER.info("No handling for HK packets implemented")
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if pus_tm.subservice == 25:
if len(pus_tm.source_data) < 8:
raise ValueError("No addressable ID in HK packet")
json_str = pus_tm.source_data[8:]
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
if service == 5:
tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if service == 17:
tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
if tm_packet.subservice == 2:
_LOGGER.info("Received Ping Reply TM[17,2]")
else:
_LOGGER.info(
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
)
if tm_packet is None:
_LOGGER.info(
f"The service {service} is not implemented in Telemetry Factory"
)
tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
self.raw_logger.log_tm(pus_tm)
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
byte_string = bytearray(struct.pack("!I", target_id))
byte_string.extend(struct.pack("!I", unique_id))
return byte_string
class TcHandler(TcHandlerBase):
def __init__(
self,
seq_count_provider: FileSeqCountProvider,
verif_wrapper: VerificationWrapper,
):
super(TcHandler, self).__init__()
self.seq_count_provider = seq_count_provider
self.verif_wrapper = verif_wrapper
self.queue_helper = DefaultPusQueueHelper(
queue_wrapper=QueueWrapper.empty(),
tc_sched_timestamp_len=7,
seq_cnt_provider=seq_count_provider,
pus_verificator=verif_wrapper.pus_verificator,
default_pus_apid=EXAMPLE_PUS_APID,
)
def send_cb(self, send_params: SendCbParams):
entry_helper = send_params.entry
if entry_helper.is_tc:
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
pus_tc_wrapper.pus_tc.seq_count = (
self.seq_count_provider.get_and_increment()
)
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
raw_tc = pus_tc_wrapper.pus_tc.pack()
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
send_params.com_if.send(raw_tc)
elif entry_helper.entry_type == TcQueueEntryType.LOG:
log_entry = entry_helper.to_log_entry()
_LOGGER.info(log_entry.log_str)
def queue_finished_cb(self, info: ProcedureWrapper):
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
q = self.queue_helper
q.queue_wrapper = wrapper.queue_wrapper
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
cmd_path = def_proc.cmd_path
if cmd_path == "/ping":
q.add_log_cmd("Sending PUS ping telecommand")
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
if cmd_path == "/change_blink_freq":
self.create_change_blink_freq_command(q)
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
q.add_log_cmd("Changing blink frequency")
while True:
blink_freq = int(
input(
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
)
)
if blink_freq < 2 or blink_freq > 10000:
print(
"Invalid blink frequency. Please specify a value between 2 and 10000."
)
continue
break
app_data = struct.pack("!I", blink_freq)
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
def main():
add_colorlog_console_logger(_LOGGER)
tmtccmd.init_printout(False)
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
parser_wrapper = PreArgsParsingWrapper()
parser_wrapper.create_default_parent_parser()
parser_wrapper.create_default_parser()
parser_wrapper.add_def_proc_args()
params = SetupParams()
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
proc_wrapper = ProcedureParamsWrapper()
if post_args_wrapper.use_gui:
post_args_wrapper.set_params_without_prompts(proc_wrapper)
else:
post_args_wrapper.set_params_with_prompts(proc_wrapper)
params.apid = EXAMPLE_PUS_APID
setup_args = SetupWrapper(
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
)
# Create console logger helper and file loggers
tmtc_logger = RegularTmtcLogWrapper()
file_logger = tmtc_logger.logger
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
verificator = PusVerificator()
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
# Create primary TM handler and add it to the CCSDS Packet Handler
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=None)
ccsds_handler.add_apid_handler(tm_handler)
# Create TC handler
seq_count_provider = PusFileSeqCountProvider()
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
tmtccmd.setup(setup_args=setup_args)
init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
tmtc_backend = tmtccmd.create_default_tmtc_backend(
setup_wrapper=setup_args,
tm_handler=ccsds_handler,
tc_handler=tc_handler,
init_procedure=init_proc,
)
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
try:
while True:
state = tmtc_backend.periodic_op(None)
if state.request == BackendRequest.TERMINATION_NO_ERROR:
sys.exit(0)
elif state.request == BackendRequest.DELAY_IDLE:
_LOGGER.info("TMTC Client in IDLE mode")
time.sleep(3.0)
elif state.request == BackendRequest.DELAY_LISTENER:
time.sleep(0.8)
elif state.request == BackendRequest.DELAY_CUSTOM:
if state.next_delay.total_seconds() <= 0.4:
time.sleep(state.next_delay.total_seconds())
else:
time.sleep(0.4)
elif state.request == BackendRequest.CALL_NEXT:
pass
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()
@@ -1,2 +0,0 @@
tmtccmd == 8.0.1
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
@@ -1,55 +0,0 @@
//! 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();
}
}
@@ -1,11 +0,0 @@
#![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()
}
@@ -1,528 +0,0 @@
#![no_main]
#![no_std]
extern crate alloc;
use rtic::app;
use rtic_monotonics::systick::Systick;
use rtic_monotonics::Monotonic;
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::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();
// 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((Systick::now() - Systick::ZERO).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 = Systick::now() - Systick::ZERO;
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::systick::fugit::MillisDurationU32;
use rtic_monotonics::systick::Systick;
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
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(
cx.core.SYST,
ccdr.clocks.sys_ck().to_Hz(),
systick_mono_token,
);
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
// 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(
unsafe { SUBPOOL_SMALL.assume_init_mut() },
unsafe { SUBPOOL_SMALL_SIZES.assume_init_mut() },
SUBPOOL_SMALL_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
shared_pool
.grow(
unsafe { SUBPOOL_MEDIUM.assume_init_mut() },
unsafe { SUBPOOL_MEDIUM_SIZES.assume_init_mut() },
SUBPOOL_MEDIUM_NUM_BLOCKS,
true,
)
.expect("growing heapless memory pool failed");
shared_pool
.grow(
unsafe { SUBPOOL_LARGE.assume_init_mut() },
unsafe { SUBPOOL_LARGE_SIZES.assume_init_mut() },
SUBPOOL_LARGE_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(HEAP_MEM.as_ptr() 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);
Systick::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");
}
Systick::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);
})
});
Systick::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);
}
};
}
}
}
@@ -1,2 +0,0 @@
/settings.json
/.cortex-debug.*
@@ -1,12 +0,0 @@
{
// 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": []
}
@@ -1,22 +0,0 @@
{
"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"
}
]
}
]
}
@@ -1,20 +0,0 @@
{
// 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
}
},
]
}
+1
View File
@@ -0,0 +1 @@
/config.toml
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4", features = ["derive"] }
log = "0.4"
fern = "0.7"
humantime = "2"
serde = { version = "1", features = ["derive"] }
toml = "1"
satrs = { path = "../../satrs" }
example-std = { path = "../example-std" }
minisim-types = { path = "../minisim-types" }
types = { path = "../types" }
spacepackets = { version = "0.18", default-features = false }
bitbybit = "2"
arbitrary-int = "2"
ctrlc = { version = "3.5" }
postcard = { version = "1", features = ["alloc"] }
anyhow = "1"
+13
View File
@@ -0,0 +1,13 @@
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let config = manifest_dir.join("config.toml");
let config_template = manifest_dir.join("config.toml.template");
if !config.exists() {
fs::copy(&config_template, &config).unwrap();
}
println!("cargo::rerun-if-changed=config.toml.template");
}
+7
View File
@@ -0,0 +1,7 @@
# Copied to config.toml by the build script if config.toml does not exist yet. config.toml is not
# tracked by git, so it can hold local settings like the address of a development board.
[interface]
# Address of the commanded application. Defaults to the example-std OBSW on the local host.
# Can be overridden with the --udp-addr argument.
# udp_addr = "192.168.1.50:7301"
+945
View File
@@ -0,0 +1,945 @@
use anyhow::{Context as _, bail};
use arbitrary_int::u11;
use clap::Parser as _;
use example_std::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use minisim_types::{
SimCtrlReply, SimCtrlRequest, SimReply, SimRequest, SimRequestWithTime, acs::mgm,
udp::SIM_CTRL_PORT,
};
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::{Duration, SystemTime},
};
use types::{Apid, Message as _, MessageType, TcHeader, acs::mgm::request::HkRequest};
#[derive(clap::Parser)]
pub struct Cli {
#[arg(short, long)]
ping: bool,
#[arg(short, long)]
test_event: bool,
/// Address of the commanded application. Overrides the address inside `config.toml`.
#[arg(long, global = true)]
udp_addr: Option<SocketAddr>,
#[command(subcommand)]
commands: Option<Commands>,
}
#[derive(Debug, Default, serde::Deserialize)]
struct Config {
#[serde(default)]
interface: InterfaceConfig,
}
#[derive(Debug, Default, serde::Deserialize)]
struct InterfaceConfig {
/// Defaults to the example-std OBSW on the local host.
udp_addr: Option<SocketAddr>,
}
impl Config {
/// The build script creates `config.toml` from the template. A missing file is still accepted,
/// because all parameters have defaults.
fn load() -> anyhow::Result<Self> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("config.toml");
match std::fs::read_to_string(&path) {
Ok(content) => toml::from_str(&content)
.with_context(|| format!("parsing {} failed", path.display())),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
Err(e) => Err(e).with_context(|| format!("reading {} failed", path.display())),
}
}
}
#[derive(clap::Subcommand)]
enum Commands {
Mgm0(MgmArgs),
Mgm1(MgmArgs),
MgmAssy(MgmAssemblyArgs),
Mgt(MgtArgs),
AcsSubsystem(SubsystemArgs),
EventManager(EventManagerArgs),
/// Blinking LEDs of the embedded examples.
Led(LedArgs),
Sim(SimArgs),
}
#[derive(clap::Parser)]
struct EventManagerArgs {
#[command(subcommand)]
action: EventFilterAction,
}
#[derive(clap::Parser)]
struct SimArgs {
#[command(subcommand)]
action: SimAction,
}
#[derive(clap::Subcommand)]
enum SimAction {
/// Let the OBSW connect to the minisim.
Connect {
/// IP address of the minisim host. Defaults to the address of this client if nothing
/// is specified.
ip: Option<Ipv4Addr>,
},
}
#[derive(clap::Subcommand)]
enum EventFilterAction {
/// Enable event TM generation.
Enable(EventFilterArgs),
/// Disable event TM generation.
Disable(EventFilterArgs),
}
#[derive(clap::Args)]
struct EventFilterArgs {
#[arg(value_enum)]
component: EventSenderSelect,
/// Raw event ID. Without it, the filter applies to all events of the component.
#[arg(short, long)]
event_id: Option<u16>,
}
/// Components which emit events.
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum EventSenderSelect {
Controller,
Mgm0,
Mgm1,
MgmAssy,
Mgt,
Pcdu,
UdpServer,
TcpServer,
Ground,
}
impl From<EventSenderSelect> for types::ComponentId {
fn from(sender: EventSenderSelect) -> Self {
match sender {
EventSenderSelect::Controller => types::ComponentId::Controller,
EventSenderSelect::Mgm0 => types::ComponentId::AcsMgm0,
EventSenderSelect::Mgm1 => types::ComponentId::AcsMgm1,
EventSenderSelect::Mgt => types::ComponentId::AcsMgt,
EventSenderSelect::MgmAssy => types::ComponentId::AcsMgmAssembly,
EventSenderSelect::Pcdu => types::ComponentId::EpsPcdu,
EventSenderSelect::UdpServer => types::ComponentId::UdpServer,
EventSenderSelect::TcpServer => types::ComponentId::TcpServer,
EventSenderSelect::Ground => types::ComponentId::Ground,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum FaultMode {
None,
/// SPI communication is all zeroes, modelling an unconnected sensor.
AllZeros,
/// SPI communication is all ones, modelling a broken sensor.
AllOnes,
}
impl From<FaultMode> for mgm::SpiFaultMode {
fn from(mode: FaultMode) -> Self {
match mode {
FaultMode::None => mgm::SpiFaultMode::None,
FaultMode::AllZeros => mgm::SpiFaultMode::AllZeros,
FaultMode::AllOnes => mgm::SpiFaultMode::AllOnes,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum FaultKind {
/// Cleared when the device is switched off, so a power cycle recovers from it.
Transient,
/// Survives power cycles.
#[default]
Permanent,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum HkSelect {
OneShot,
EnablePeriodic,
DisablePeriodic,
ModifyInterval,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum HealthStateSelect {
Healthy,
Faulty,
PermanentFaulty,
ExternalControl,
NeedsRecovery,
}
impl From<HealthStateSelect> for satrs::health::HealthState {
fn from(state: HealthStateSelect) -> Self {
match state {
HealthStateSelect::Healthy => satrs::health::HealthState::Healthy,
HealthStateSelect::Faulty => satrs::health::HealthState::Faulty,
HealthStateSelect::PermanentFaulty => satrs::health::HealthState::PermanentFaulty,
HealthStateSelect::ExternalControl => satrs::health::HealthState::ExternalControl,
HealthStateSelect::NeedsRecovery => satrs::health::HealthState::NeedsRecovery,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct MgmArgs {
#[arg(short, long)]
ping: bool,
/// Housekeeping request for the sensor data set.
#[arg(long, value_enum)]
hk: Option<HkSelect>,
/// Periodic HK interval. Required for `modify-interval`, optional for `enable-periodic`.
#[arg(long)]
hk_interval_ms: Option<u64>,
#[arg(short, long)]
mode: Option<DeviceModeSelect>,
/// Inject (or clear) an SPI bus failure on the simulated device, bypassing the OBSW.
#[arg(long, value_enum)]
fault: Option<FaultMode>,
/// Whether a power cycle clears the injected SPI fault.
#[arg(long, value_enum, default_value_t)]
fault_kind: FaultKind,
/// Override the device's FDIR health state, for example to clear a `Faulty` state set by
/// the handler after the underlying issue has been fixed or worked around.
#[arg(long, value_enum)]
health: Option<HealthStateSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct MgtArgs {
#[arg(short, long)]
ping: bool,
/// Housekeeping request for the status data set.
#[arg(long, value_enum)]
hk: Option<HkSelect>,
/// Periodic HK interval. Required for `modify-interval`, optional for `enable-periodic`.
#[arg(long)]
hk_interval_ms: Option<u64>,
#[arg(short, long)]
mode: Option<DeviceModeSelect>,
/// Apply a dipole, given as `x,y,z`. Only accepted in normal mode.
#[arg(long, value_name = "X,Y,Z", value_parser = parse_dipole, allow_hyphen_values = true)]
torque: Option<types::acs::mgt::Dipole>,
#[arg(long, default_value_t = 1000)]
torque_duration_ms: u64,
/// Override the device's FDIR health state, for example to clear a `Faulty` state.
#[arg(long, value_enum)]
health: Option<HealthStateSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct LedArgs {
#[arg(short, long)]
ping: bool,
/// Mode of the red and the orange LED.
#[arg(short, long, value_enum)]
mode: Option<LedModeSelect>,
/// Toggle period of the toggle modes.
#[arg(long, default_value_t = 500)]
toggle_period_ms: u64,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum LedModeSelect {
AllOff,
RedOn,
OrangeOn,
AlternatingToggle,
UnifiedToggle,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct MgmAssemblyArgs {
#[arg(short, long)]
ping: bool,
#[arg(short, long)]
mode: Option<AssemblyModeSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::Parser)]
struct SubsystemArgs {
#[arg(short, long)]
ping: bool,
#[arg(short, long)]
mode: Option<SubsystemModeSelect>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
pub enum DeviceModeSelect {
Off,
Normal,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
pub enum AssemblyModeSelect {
NoModeKeeping,
Off,
Normal,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
pub enum SubsystemModeSelect {
Off,
Safe,
}
fn hk_request_type(
hk: HkSelect,
hk_interval_ms: Option<u64>,
) -> anyhow::Result<types::HkRequestType> {
let opt_interval = hk_interval_ms.map(Duration::from_millis);
Ok(match hk {
HkSelect::OneShot => types::HkRequestType::OneShot,
HkSelect::EnablePeriodic => types::HkRequestType::EnablePeriodic(opt_interval),
HkSelect::DisablePeriodic => types::HkRequestType::DisablePeriodic,
HkSelect::ModifyInterval => types::HkRequestType::ModifyInterval(
opt_interval.context("--hk-interval-ms is required for modify-interval")?,
),
})
}
fn parse_dipole(value: &str) -> Result<types::acs::mgt::Dipole, String> {
let axes: Vec<i16> = value
.split(',')
.map(|axis| axis.trim().parse::<i16>().map_err(|e| e.to_string()))
.collect::<Result<_, _>>()?;
let [x, y, z] = axes[..] else {
return Err(format!("expected 3 values, got {}", axes.len()));
};
Ok(types::acs::mgt::Dipole { x, y, z })
}
fn send_mgt_request(
client: &UdpSocket,
addr: SocketAddr,
request: types::acs::mgt::request::Request,
) {
let packet = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(types::ComponentId::AcsMgt, request.message_type()),
request,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
log::info!(
"sending MGT request {:?} with TC ID {:#010x}",
request,
sent_tc_id.raw()
);
client.send_to(&packet.to_vec(), addr).unwrap();
}
fn send_led_request(client: &UdpSocket, addr: SocketAddr, request: types::led::request::Request) {
let packet = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::Led, request.message_type()),
request,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
log::info!(
"sending LED request {:?} with TC ID {:#010x}",
request,
sent_tc_id.raw()
);
client.send_to(&packet.to_vec(), addr).unwrap();
}
fn handle_sim_command(client: &UdpSocket, addr: SocketAddr, args: SimArgs) {
let request = match args.action {
SimAction::Connect { ip } => types::control::request::Request::SimConnect(ip),
};
let packet = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::Controller, MessageType::Action),
request,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
log::info!(
"sending SIM request {:?} with TC ID {:#010x}",
request,
sent_tc_id.raw()
);
client.send_to(&packet.to_vec(), addr).unwrap();
}
fn handle_led_command(client: &UdpSocket, addr: SocketAddr, args: LedArgs) {
use types::led::request::Request;
if args.ping {
send_led_request(client, addr, Request::Ping);
}
if let Some(mode) = args.mode {
let toggle_period = Duration::from_millis(args.toggle_period_ms);
let mode = match mode {
LedModeSelect::AllOff => types::led::Mode::AllOff,
LedModeSelect::RedOn => types::led::Mode::RedOn,
LedModeSelect::OrangeOn => types::led::Mode::OrangeOn,
LedModeSelect::AlternatingToggle => types::led::Mode::AlternatingToggle(toggle_period),
LedModeSelect::UnifiedToggle => types::led::Mode::UnifiedToggle(toggle_period),
};
send_led_request(client, addr, Request::SetMode(mode));
}
}
fn handle_mgt_command(client: &UdpSocket, addr: SocketAddr, args: MgtArgs) -> anyhow::Result<()> {
use types::acs::mgt::request::{ModeRequest, Request};
if args.ping {
send_mgt_request(client, addr, Request::Ping);
}
if let Some(hk) = args.hk {
let req_type = hk_request_type(hk, args.hk_interval_ms)?;
send_mgt_request(client, addr, Request::Hk(req_type));
}
if let Some(mode) = args.mode {
let mode = match mode {
DeviceModeSelect::Off => types::DeviceMode::Off,
DeviceModeSelect::Normal => types::DeviceMode::Normal,
};
send_mgt_request(client, addr, Request::Mode(ModeRequest::SetMode(mode)));
}
if let Some(dipole) = args.torque {
let request = Request::ApplyTorque {
dipole,
duration: Duration::from_millis(args.torque_duration_ms),
};
send_mgt_request(client, addr, request);
}
if let Some(health) = args.health {
send_mgt_request(
client,
addr,
Request::Health(types::HealthRequest::SetHealth(health.into())),
);
}
Ok(())
}
fn handle_mgm_command(
client: &UdpSocket,
addr: SocketAddr,
target_id: types::ComponentId,
args: MgmArgs,
) -> anyhow::Result<()> {
if let Some(mode) = args.fault {
inject_mgm_failure(
target_id,
mgm::SpiFault {
mode: mode.into(),
cleared_by_power_cycle: args.fault_kind == FaultKind::Transient,
},
)?;
}
if args.ping {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Ping),
types::acs::mgm::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} ping request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(hk) = args.hk {
let req_type = hk_request_type(hk, args.hk_interval_ms)?;
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Hk),
types::acs::mgm::request::Request::Hk(HkRequest {
id: types::acs::mgm::request::HkId::Sensor,
req_type,
}),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(mode) = args.mode {
let dev_mode = match mode {
DeviceModeSelect::Off => types::DeviceMode::Off,
DeviceModeSelect::Normal => types::DeviceMode::Normal,
};
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Mode),
types::acs::mgm::request::Request::Mode(
types::acs::mgm::request::ModeRequest::SetMode(dev_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(health) = args.health {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Health),
types::acs::mgm::request::Request::Health(types::HealthRequest::SetHealth(
health.into(),
)),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} set-health request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
Ok(())
}
fn handle_event_manager_command(client: &UdpSocket, addr: SocketAddr, args: EventManagerArgs) {
use types::event_manager::request::Request;
let request = match args.action {
EventFilterAction::Enable(filter) => match filter.event_id {
Some(event_id) => Request::EnableEvent {
sender_id: filter.component.into(),
event_id,
},
None => Request::EnableComponent(filter.component.into()),
},
EventFilterAction::Disable(filter) => match filter.event_id {
Some(event_id) => Request::DisableEvent {
sender_id: filter.component.into(),
event_id,
},
None => Request::DisableComponent(filter.component.into()),
},
};
let request_packet = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::EventManager, MessageType::Event),
request,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request_packet.sp_header);
log::info!(
"sending event manager request {:?} with TC ID {:#010x}",
request,
sent_tc_id.raw()
);
client.send_to(&request_packet.to_vec(), addr).unwrap();
}
fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {} {}] {}",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(),
record.target(),
message
))
})
.level(level)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}
fn main() -> anyhow::Result<()> {
setup_logger(log::LevelFilter::Debug).unwrap();
let kill_signal = Arc::new(AtomicBool::new(false));
let ctrl_kill_signal = kill_signal.clone();
ctrlc::set_handler(move || ctrl_kill_signal.store(true, Ordering::Relaxed)).unwrap();
let cli = Cli::parse();
let config = Config::load()?;
let addr = cli
.udp_addr
.or(config.interface.udp_addr)
.unwrap_or(SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT));
// Bind to all interfaces, so embedded targets inside the local network can be reached too.
let client = UdpSocket::bind("0.0.0.0:7302").expect("Connecting to UDP server failed");
client.set_nonblocking(true)?;
client.set_read_timeout(Some(Duration::from_millis(200)))?;
if cli.ping {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::Controller, types::MessageType::Ping),
types::control::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!("sending ping request with TC ID {:#010x}", sent_tc_id.raw());
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if cli.test_event {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::Controller, types::MessageType::Event),
types::control::request::Request::TestEvent,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending event request with TC ID {:#010x}",
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(cmd) = cli.commands {
match cmd {
Commands::Mgm0(args) => {
handle_mgm_command(&client, addr, types::ComponentId::AcsMgm0, args)?
}
Commands::Mgm1(args) => {
handle_mgm_command(&client, addr, types::ComponentId::AcsMgm1, args)?
}
Commands::Mgt(args) => handle_mgt_command(&client, addr, args)?,
Commands::MgmAssy(mgm_assembly_args) => {
let target_id = types::ComponentId::AcsMgmAssembly;
if mgm_assembly_args.ping {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Ping),
types::acs::mgm::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} ping request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(mode) = mgm_assembly_args.mode {
let assembly_mode = match mode {
AssemblyModeSelect::NoModeKeeping => {
types::acs::mgm_assembly::Mode::NoModeKeeping
}
AssemblyModeSelect::Off => {
types::acs::mgm_assembly::Mode::Device(types::DeviceMode::Off)
}
AssemblyModeSelect::Normal => {
types::acs::mgm_assembly::Mode::Device(types::DeviceMode::Normal)
}
};
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Mode),
types::acs::mgm_assembly::request::Request::Mode(
types::acs::mgm_assembly::request::ModeRequest::SetMode(assembly_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} HK request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
}
Commands::AcsSubsystem(subsystem_args) => {
let target_id = types::ComponentId::AcsSubsystem;
if subsystem_args.ping {
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Ping),
types::acs::subsystem::request::Request::Ping,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} ping request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
if let Some(mode) = subsystem_args.mode {
let subsystem_mode = match mode {
SubsystemModeSelect::Off => types::acs::subsystem::Mode::Off,
SubsystemModeSelect::Safe => types::acs::subsystem::Mode::Safe,
};
let request = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
TcHeader::new(target_id, types::MessageType::Mode),
types::acs::subsystem::request::Request::Mode(
types::acs::subsystem::request::ModeRequest::SetMode(subsystem_mode),
),
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
log::info!(
"sending {:?} mode request with TC ID {:#010x}",
target_id,
sent_tc_id.raw()
);
let request_packet = request.to_vec();
client.send_to(&request_packet, addr).unwrap();
}
}
Commands::EventManager(args) => handle_event_manager_command(&client, addr, args),
Commands::Led(args) => handle_led_command(&client, addr, args),
Commands::Sim(args) => handle_sim_command(&client, addr, args),
}
}
let mut recv_buf: Box<[u8; 2048]> = Box::new([0; 2048]);
log::info!("entering listening loop");
loop {
if kill_signal.load(std::sync::atomic::Ordering::Relaxed) {
log::info!("received kill signal, exiting");
break;
}
match client.recv(recv_buf.as_mut_slice()) {
Ok(received_bytes) => handle_raw_tm_packet(&recv_buf.as_slice()[0..received_bytes])?,
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut
{
continue;
}
log::warn!("UDP reception error: {}", e)
}
}
}
Ok(())
}
/// Injects the given SPI fault directly into minisim's MGM model, bypassing the OBSW.
///
/// Confirms the simulator is actually reachable first (same ping/pong check the OBSW's own
/// internal sim client does, see `SimClientUdp::attempt_connection`), since a fire-and-forget
/// UDP send would otherwise silently do nothing if minisim is not running.
fn inject_mgm_failure(target_id: types::ComponentId, fault: mgm::SpiFault) -> anyhow::Result<()> {
let sim_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), SIM_CTRL_PORT);
let sim_socket = UdpSocket::bind("127.0.0.1:0")?;
sim_socket.set_read_timeout(Some(Duration::from_millis(200)))?;
let mut reply_buf = [0u8; 4096];
let ping = SimRequestWithTime::new_with_epoch_time(SimCtrlRequest::Ping);
sim_socket.send_to(&postcard::to_allocvec(&ping)?, sim_addr)?;
match sim_socket.recv(&mut reply_buf) {
Ok(len) => {
let reply: SimReply = postcard::from_bytes(&reply_buf[..len])?;
if reply != SimReply::SimCtrl(SimCtrlReply::Pong) {
bail!("unexpected reply while checking minisim connectivity: {reply:?}");
}
}
Err(e)
if matches!(
e.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) =>
{
bail!("minisim not reachable at {sim_addr} (ping timed out) - is it running?");
}
Err(e) => return Err(e.into()),
}
let id = match target_id {
types::ComponentId::AcsMgm0 => mgm::Id::Mgm0,
types::ComponentId::AcsMgm1 => mgm::Id::Mgm1,
_ => bail!("SPI fault injection is not supported for {target_id:?}"),
};
let request = SimRequestWithTime::new_with_epoch_time(SimRequest::Mgm {
id,
request: mgm::Request::SetSpiFault(fault),
});
sim_socket.send_to(&postcard::to_allocvec(&request)?, sim_addr)?;
log::info!("injected SPI fault {fault:?} into minisim {target_id:?}");
Ok(())
}
/// Each component has its own event type, so the sender ID determines how to decode the event.
fn handle_event(sender_id: types::ComponentId, data: &[u8]) {
fn log_event<E: serde::de::DeserializeOwned + core::fmt::Debug>(
sender_id: types::ComponentId,
data: &[u8],
) {
match postcard::from_bytes::<E>(data) {
Ok(event) => log::info!("Received event from {:?}: {:?}", sender_id, event),
Err(e) => log::warn!("Failed to deserialize event from {:?}: {}", sender_id, e),
}
}
match sender_id {
types::ComponentId::Controller => log_event::<types::Event>(sender_id, data),
types::ComponentId::AcsMgm0 | types::ComponentId::AcsMgm1 => {
log_event::<types::acs::mgm::Event>(sender_id, data)
}
types::ComponentId::AcsMgmAssembly => {
log_event::<types::acs::mgm_assembly::Event>(sender_id, data)
}
types::ComponentId::AcsMgt => log_event::<types::acs::mgt::Event>(sender_id, data),
types::ComponentId::EpsPcdu => log_event::<types::pcdu::Event>(sender_id, data),
// TC source events are sent with the ID of the packet source.
types::ComponentId::UdpServer
| types::ComponentId::TcpServer
| types::ComponentId::Ground => log_event::<types::tmtc::Event>(sender_id, data),
_ => log::warn!(
"Received event from {:?} with unknown event type",
sender_id
),
}
}
fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
match spacepackets::CcsdsPacketReader::new_with_checksum(data) {
Ok(packet) => {
let tm_header_result = postcard::take_from_bytes::<types::TmHeader>(packet.user_data());
if let Err(e) = tm_header_result {
bail!("Failed to deserialize TM header: {}", e);
}
let (tm_header, remainder) = tm_header_result.unwrap();
if let Some(tc_id) = tm_header.tc_id {
log::info!(
"Received TM with APID {} and from sender {:?} for TC ID {:#010x}",
packet.apid(),
tm_header.sender_id,
tc_id.raw()
);
}
if tm_header.message_type == MessageType::Event {
handle_event(tm_header.sender_id, remainder);
return Ok(());
}
match tm_header.sender_id {
types::ComponentId::EpsPcdu => {
let response =
postcard::from_bytes::<types::pcdu::response::Response>(remainder);
log::info!("Received response from PCDU: {:?}", response.unwrap());
}
types::ComponentId::Controller => {
let response =
postcard::from_bytes::<types::control::response::Response>(remainder);
log::info!("Received response from controller: {:?}", response.unwrap());
}
types::ComponentId::AcsMgmAssembly => {
let response = postcard::from_bytes::<
types::acs::mgm_assembly::response::Response,
>(remainder);
log::info!(
"Received response from MGM Assembly: {:?}",
response.unwrap()
);
}
types::ComponentId::AcsMgm0 => {
let response =
postcard::from_bytes::<types::acs::mgm::response::Response>(remainder);
log::info!("Received response from MGM0: {:?}", response.unwrap());
}
types::ComponentId::AcsMgm1 => {
let response =
postcard::from_bytes::<types::acs::mgm::response::Response>(remainder);
log::info!("Received response from MGM1: {:?}", response.unwrap());
}
types::ComponentId::AcsSubsystem => {
let response = postcard::from_bytes::<types::acs::subsystem::response::Response>(
remainder,
);
log::info!(
"Received response from ACS subsystem: {:?}",
response.unwrap()
);
}
types::ComponentId::EpsSubsystem => todo!(),
types::ComponentId::UdpServer => todo!(),
types::ComponentId::TcpServer => todo!(),
types::ComponentId::Ground => todo!(),
types::ComponentId::EventManager => {
let response =
postcard::from_bytes::<types::event_manager::response::Response>(remainder);
log::info!(
"Received response from event manager: {:?}",
response.unwrap()
);
}
types::ComponentId::AcsController => todo!(),
types::ComponentId::AcsMgt => {
let response =
postcard::from_bytes::<types::acs::mgt::response::Response>(remainder);
log::info!("Received response from MGT: {:?}", response.unwrap());
}
types::ComponentId::Led => {
let response =
postcard::from_bytes::<types::led::response::Response>(remainder);
log::info!("Received response from LED: {:?}", response.unwrap());
}
}
}
Err(_) => todo!(),
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_dipole() {
assert_eq!(
parse_dipole("-200, 200,1000"),
Ok(types::acs::mgt::Dipole {
x: -200,
y: 200,
z: 1000
})
);
assert!(parse_dipole("1,2").is_err());
assert!(parse_dipole("1,2,3,4").is_err());
}
#[test]
fn test_config_template_is_valid() {
let template = include_str!("../config.toml.template");
let config: Config = toml::from_str(template).unwrap();
assert_eq!(config.interface.udp_addr, None);
let config: Config =
toml::from_str("[interface]\nudp_addr = \"192.168.1.50:7301\"").unwrap();
assert_eq!(
config.interface.udp_addr,
Some("192.168.1.50:7301".parse().unwrap())
);
}
#[test]
fn test_negative_torque_argument() {
let cli = Cli::try_parse_from(["client", "mgt", "--torque", "-200,200,1000"]).unwrap();
let Some(Commands::Mgt(args)) = cli.commands else {
panic!("expected mgt subcommand");
};
assert_eq!(args.torque.map(|dipole| dipole.x), Some(-200));
}
}
@@ -1,5 +1,5 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip STM32H743ZITx"
runner = "probe-rs run --chip STM32H753ZITx"
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
rustflags = [
@@ -27,3 +27,6 @@ rrb = "run --release --bin"
[env]
DEFMT_LOG = "info"
# IP address of the host running the minisim. Without it, the firmware does not try to connect
# to a simulator.
# SIM_IP_ADDR = "192.168.1.10"
+3
View File
@@ -0,0 +1,3 @@
/.cargo/config.toml
.vscode/
app.map
+1740
View File
File diff suppressed because it is too large. Load diff
+71
View File
@@ -0,0 +1,71 @@
[workspace]
resolver = "3"
members = [
"shared-embedded",
"stm32h7-nucleo-embassy",
"stm32h7-nucleo-rtic",
]
[workspace.dependencies]
types = { path = "../types" }
minisim-types = { path = "../minisim-types" }
shared-embedded = { path = "shared-embedded" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
arbitrary-int = "2"
defmt = "1"
defmt-rtt = "1"
defmt-test = "0.5"
panic-probe = { version = "1", features = ["print-defmt"] }
embedded-alloc = "0.7"
static_cell = "2"
spacepackets = { version = "0.18", default-features = false, features = ["defmt"] }
postcard = "1"
serde = { version = "1", default-features = false }
rtic = { version = "2", features = ["thumbv7-backend"] }
embassy-stm32 = { version = "0.6", features = ["stm32h753zi", "defmt"] }
embassy-executor = { version = "0.10", features = ["platform-cortex-m", "executor-thread", "defmt"] }
embassy-time = "0.5"
embassy-net = { version = "0.9", features = ["medium-ethernet", "proto-ipv4", "tcp", "udp", "auto-icmp-echo-reply", "dhcpv4", "defmt"] }
embassy-sync = "0.8"
embassy-futures = "0.1"
# 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 # <-
+153
View File
@@ -0,0 +1,153 @@
sat-rs examples for the STM32H753ZI-Nucleo board
=======
These example applications show how the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
can be used on an embedded target.
They also show how a relatively simple OBSW could be built when no standard runtime is available.
Both use the [defmt](https://defmt.ferrous-systems.com/) framework for logging and provide the
same functionality with a different concurrency framework:
- [`stm32h7-nucleo-embassy`](./stm32h7-nucleo-embassy) uses the [embassy](https://embassy.dev/)
executor.
- [`stm32h7-nucleo-rtic`](./stm32h7-nucleo-rtic) uses [RTIC](https://rtic.rs/2/book/en/).
The application code is shared inside the [`shared-embedded`](./shared-embedded) crate.
Each application only initializes the hardware and wraps the shared code inside its own tasks.
The STM32H753ZIT device was picked because it is one of the more powerful Cortex-M based STM32
devices. It has more RAM available and allows commanding via Ethernet. The examples are written
for the NUCLEO-H753ZI board, which uses the MB1364 Nucleo-144 board layout.
## 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
```
All crates in this directory form a separate workspace, because they are built for a different
target than the rest of the repository. They share one `.cargo` config file. A default is
provided as `.cargo/config.toml.template`. The build scripts copy it to `.cargo/config.toml`
if that file does not exist yet. The copy is not tracked by git, so you can change settings like
the runner for your setup.
Cargo reads the configuration before the build script runs, so the very first build on a fresh
checkout does not use it yet and might fail. Simply run the build again, or copy the file
manually beforehand:
```sh
cp .cargo/config.toml.template .cargo/config.toml
```
The configuration file also sets 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 build all applications from this directory with
```sh
cargo build
```
or a single application with `cargo build -p stm32h7-nucleo-embassy`, for example.
## Flashing from the command line
The configuration file sets `probe-rs` as the runner, so you can flash and run an application
with
```sh
cargo run -p stm32h7-nucleo-embassy
```
## Debugging with VS Code
The Nucleo board comes with an on-board ST-Link so all that is required to flash and debug
the board is a 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.
## Commanding the board
The board is commanded via UDP on port 7301. It gets its IP address via DHCP, so it needs to be
connected to a network with a DHCP server. The network configuration including the IP address is
logged after startup. According to the board user manual UM2407, jumper JP6 and solder bridge SB72
must be ON when using Ethernet. The board uses the same TMTC protocol as the
[`example-std`](../example-std) application, which is defined inside the [`types`](../types)
crate.
The [`client`](../client) application is used to command the board. Set the address of the board
inside `client/config.toml`, which is created from `client/config.toml.template` on the first
build:
```toml
[interface]
udp_addr = "192.168.1.50:7301"
```
For example, you can then send a ping to the MCU using
```sh
cargo run -p client -- --ping
```
Like the `example-std` application, the board has a controller component which handles pings and
test events. A test event can be triggered with `--test-event`.
The green LED blinks every 0.5 seconds as a heartbeat. The red and the orange LED are controlled
with a mode. For example, you can let both toggle together every 200 ms using
```sh
cargo run -p client -- led --mode unified-toggle --toggle-period-ms 200
```
Use `cargo run -p client -- led --help` to list all modes.
You can also pass the board address with `--udp-addr` instead of setting it inside the
configuration file.
## Connecting to the mini simulator
The firmware can connect to the [`minisim`](../minisim), which simulates the devices of the OBSW.
The simulator address can be passed with a sim connect request:
```sh
cargo run -p client -- sim connect
```
Without an IP address, the firmware uses the sender address of the request, which fits the
common setup where the client and the simulator run on the same host. Otherwise, pass the IP
address of the simulator host, for example `sim connect 192.168.1.10`.
The simulator address can also be set at build time inside `.cargo/config.toml`, so the firmware
connects on its own after startup:
```toml
[env]
SIM_IP_ADDR = "192.168.1.10"
```
The firmware pings the simulator on UDP port 7303 and logs the result. It reconnects after a
network link loss. A new sim connect request, for example after restarting the simulator,
triggers a new connection attempt.
Like the `example-std` application, the device handlers use dummy interfaces if no simulator
address is known or the simulator does not reply. The simulator sends its replies to the
last client which contacted it, so only one application can use it at a time.
## 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)
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
targets = ["thumbv7em-none-eabihf"]
@@ -0,0 +1,20 @@
[package]
name = "shared-embedded"
version = "0.1.0"
edition = "2024"
[dependencies]
types.workspace = true
minisim-types.workspace = true
arbitrary-int.workspace = true
defmt.workspace = true
spacepackets.workspace = true
postcard.workspace = true
serde.workspace = true
embassy-stm32.workspace = true
embassy-time.workspace = true
embassy-net.workspace = true
embassy-sync.workspace = true
embassy-futures.workspace = true
@@ -0,0 +1,68 @@
use embassy_futures::select::{Either, select};
use embassy_stm32::gpio;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{Duration, Timer};
use types::led;
pub static LED_MODE: Signal<CriticalSectionRawMutex, led::Mode> = Signal::new();
const HEARTBEAT_PERIOD: Duration = Duration::from_millis(500);
const DEFAULT_LED_MODE: led::Mode =
led::Mode::AlternatingToggle(core::time::Duration::from_millis(1000));
pub struct Leds {
pub red: gpio::Output<'static>,
pub orange: gpio::Output<'static>,
}
pub async fn heartbeat(led: &mut gpio::Output<'static>) {
loop {
led.toggle();
Timer::after(HEARTBEAT_PERIOD).await;
}
}
/// Applies the current mode to the red and orange LED. A new mode is applied immediately.
pub async fn led_task(leds: &mut Leds) {
let mut mode = DEFAULT_LED_MODE;
loop {
let toggle_period = match mode {
led::Mode::AllOff => {
leds.red.set_low();
leds.orange.set_low();
None
}
led::Mode::RedOn => {
leds.red.set_high();
leds.orange.set_low();
None
}
led::Mode::OrangeOn => {
leds.red.set_low();
leds.orange.set_high();
None
}
led::Mode::AlternatingToggle(period) => {
leds.red.toggle();
leds.orange.set_level((!leds.red.is_set_high()).into());
Some(period)
}
led::Mode::UnifiedToggle(period) => {
leds.red.toggle();
leds.orange.set_level(leds.red.is_set_high().into());
Some(period)
}
};
mode = match toggle_period {
Some(period) => {
let period = Duration::try_from(period).unwrap_or(Duration::MAX);
match select(Timer::after(period), LED_MODE.wait()).await {
Either::First(()) => mode,
Either::Second(new_mode) => new_mode,
}
}
None => LED_MODE.wait().await,
};
}
}
@@ -0,0 +1,9 @@
//! Application code shared by the STM32H7 Nucleo applications. Each application wraps the
//! async functions inside tasks of its own executor.
#![no_std]
extern crate alloc;
pub mod leds;
pub mod net;
pub mod sim_client;
pub mod tmtc;
@@ -0,0 +1,118 @@
use core::cell::Cell;
use embassy_futures::select::{Either3, select3};
use embassy_net::{
IpEndpoint,
udp::{PacketMetadata, UdpSocket},
};
use embassy_stm32::{eth, peripherals};
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::Timer;
use crate::tmtc::{TcSender, TmReceiver};
const PORT: u16 = 7301;
const MTU: usize = 1500;
/// Locally administered MAC address
pub const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
pub static LAST_SENDER: Mutex<CriticalSectionRawMutex, Cell<Option<core::net::Ipv4Addr>>> =
Mutex::new(Cell::new(None));
pub type Device = eth::Ethernet<
'static,
peripherals::ETH,
eth::GenericPhy<eth::Sma<'static, peripherals::ETH_SMA>>,
>;
pub async fn net_stack_task(runner: &mut embassy_net::Runner<'static, Device>) -> ! {
runner.run().await
}
pub async fn udp_task(stack: embassy_net::Stack<'static>, tc_tx: TcSender, tm_rx: TmReceiver) {
// Task futures are allocated statically, so these buffers do not live on the stack.
let mut rx_udp_meta = [PacketMetadata::EMPTY; 8];
let mut tx_udp_meta = [PacketMetadata::EMPTY; 8];
let mut rx_udp_buf = [0; MTU];
let mut tx_udp_buf = [0; MTU];
let mut rx_buffer = [0; MTU];
loop {
stack.wait_link_up().await;
defmt::info!("Network link is up");
// Ensure DHCP configuration is up before trying connect
stack.wait_config_up().await;
defmt::info!("Network task initialized, config: {}", stack.config_v4());
let mut udp = UdpSocket::new(
stack,
&mut rx_udp_meta,
&mut rx_udp_buf,
&mut tx_udp_meta,
&mut tx_udp_buf,
);
if let Err(e) = udp.bind(PORT) {
defmt::error!("Failed to bind UDP socket: {}", e);
Timer::after_secs(1).await;
continue;
}
defmt::info!("UDP socket bound to port {}", PORT);
let mut remote_endpoint = None;
loop {
match select3(
udp.recv_from(&mut rx_buffer),
tm_rx.receive(),
stack.wait_link_down(),
)
.await
{
Either3::First(Ok((len, meta))) => {
remote_endpoint = Some(meta.endpoint);
let embassy_net::IpAddress::Ipv4(sender_ip) = meta.endpoint.addr;
LAST_SENDER.lock(|val| {
val.set(Some(sender_ip));
});
defmt::debug!("UDP RX {}, Meta: {}", len, meta);
tc_tx.send(rx_buffer[0..len].to_vec()).await;
// Incoming TCs take priority in the select. Draining TM here prevents a burst
// of TCs from overflowing the TM queue. This could lead to a task deadlock
// where each task is waiting on each other.
while let Ok(packet) = tm_rx.try_receive() {
handle_tm(&packet, &mut udp, &remote_endpoint).await;
}
}
Either3::First(Err(e)) => {
defmt::warn!("udp receive error: {}", e);
Timer::after_millis(100).await;
}
// TM is only generated as a response to a TC, so the endpoint is usually known.
Either3::Second(packet) => handle_tm(&packet, &mut udp, &remote_endpoint).await,
Either3::Third(()) => {
defmt::warn!("Network link is down");
break;
}
}
}
}
}
async fn handle_tm(
packet: &[u8],
udp_socket: &mut UdpSocket<'_>,
remote_endpoint: &Option<IpEndpoint>,
) {
match remote_endpoint {
Some(endpoint) => match udp_socket.send_to(packet, *endpoint).await {
Ok(_) => {
defmt::debug!("UDP TX: {} bytes to: {}", packet.len(), endpoint)
}
Err(e) => defmt::warn!("udp send error: {}", e),
},
None => defmt::warn!("dropping TM, no remote endpoint known"),
};
}
@@ -0,0 +1,116 @@
//! UDP client for the minisim, which simulates the devices of the OBSW.
use core::net::{Ipv4Addr, SocketAddrV4};
use defmt::Debug2Format;
use embassy_futures::select::{Either, select};
use embassy_net::udp::{PacketMetadata, UdpSocket};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_sync::watch::Watch;
use embassy_time::{Duration, WithTimeout as _};
use minisim_types::udp::SIM_CTRL_PORT;
use minisim_types::{SimCtrlReply, SimCtrlRequest, SimReply, SimRequestWithTime};
/// Set by a sim connect request. Each new address triggers a connection attempt.
pub static SIM_HOST: Signal<CriticalSectionRawMutex, Ipv4Addr> = Signal::new();
/// IP address of the host running the minisim, set at build time. Without it, no connection
/// to a simulator is attempted until a sim connect request is received.
const SIM_FROM_ENV: Option<&str> = option_env!("SIM_IP_ADDR");
const PING_ATTEMPTS: usize = 3;
const PING_TIMEOUT: Duration = Duration::from_millis(500);
const MAX_PACKET_LEN: usize = 1024;
/// Result of the connection check. Device handlers use it to pick either the simulator or a
/// dummy interface.
pub static SIM_AVAILABLE: Watch<CriticalSectionRawMutex, bool, 4> = Watch::new();
pub async fn sim_client_task(stack: embassy_net::Stack<'static>) {
let mut rx_meta = [PacketMetadata::EMPTY; 4];
let mut tx_meta = [PacketMetadata::EMPTY; 4];
let mut rx_buf = [0; MAX_PACKET_LEN];
let mut tx_buf = [0; MAX_PACKET_LEN];
let sim_available = SIM_AVAILABLE.sender();
sim_available.send(false);
// Bound to an ephemeral port without a fixed local address, so the socket survives a new
// DHCP lease and does not need to be recreated after a link loss.
let mut udp = UdpSocket::new(stack, &mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf);
if let Err(e) = udp.bind(0) {
defmt::error!("Failed to bind simulator UDP socket: {}", e);
return;
}
let mut sim_addr = sim_addr_from_env();
loop {
stack.wait_link_up().await;
stack.wait_config_up().await;
loop {
if let Some(addr) = sim_addr {
let connected = check_connection(&udp, addr).await;
if connected {
defmt::info!("Connected to simulator at {}", Debug2Format(&addr));
} else {
defmt::warn!(
"Simulator at {} not reachable, using dummy interfaces",
Debug2Format(&addr)
);
}
sim_available.send(connected);
}
match select(SIM_HOST.wait(), stack.wait_link_down()).await {
Either::First(ip_addr) => {
sim_addr = Some(SocketAddrV4::new(ip_addr, SIM_CTRL_PORT));
}
Either::Second(()) => {
sim_available.send(false);
break;
}
}
}
}
}
fn sim_addr_from_env() -> Option<SocketAddrV4> {
let ip_addr = SIM_FROM_ENV?;
match ip_addr.parse::<Ipv4Addr>() {
Ok(ip_addr) => Some(SocketAddrV4::new(ip_addr, SIM_CTRL_PORT)),
Err(_) => {
defmt::error!("Invalid simulator IP address {}", ip_addr);
None
}
}
}
/// Uses several attempts, because the first packet can get lost while the MAC address of the
/// simulator host is resolved.
async fn check_connection(udp: &UdpSocket<'_>, sim_addr: SocketAddrV4) -> bool {
let mut tx_buf = [0; MAX_PACKET_LEN];
let mut rx_buf = [0; MAX_PACKET_LEN];
let request = SimRequestWithTime::new_with_epoch_time(SimCtrlRequest::Ping);
let ping = match postcard::to_slice(&request, &mut tx_buf) {
Ok(ping) => ping,
Err(e) => {
defmt::error!("Failed to serialize simulator ping: {}", Debug2Format(&e));
return false;
}
};
for _ in 0..PING_ATTEMPTS {
if let Err(e) = udp.send_to(ping, sim_addr).await {
defmt::warn!("Failed to send simulator ping: {}", e);
continue;
}
if let Ok(Ok((len, _))) = udp.recv_from(&mut rx_buf).with_timeout(PING_TIMEOUT).await
&& matches!(
postcard::from_bytes::<SimReply>(&rx_buf[..len]),
Ok(SimReply::SimCtrl(SimCtrlReply::Pong))
)
{
return true;
}
}
false
}
@@ -0,0 +1,157 @@
use alloc::vec::Vec;
use arbitrary_int::u14;
use defmt::Debug2Format;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::channel::{Channel, Receiver, Sender};
use spacepackets::{CcsdsPacketIdAndPsc, CcsdsPacketReader, SpHeader};
use types::ccsds::{CcsdsCreationError, CcsdsTmPacketOwned};
use types::{Apid, ComponentId, Message, TcHeader, TmHeader, control, led, tmtc};
use crate::leds::LED_MODE;
use crate::net::LAST_SENDER;
use crate::sim_client;
pub const TC_QUEUE_DEPTH: usize = 32;
pub const TM_QUEUE_DEPTH: usize = 32;
pub type TcChannel = Channel<NoopRawMutex, Vec<u8>, TC_QUEUE_DEPTH>;
pub type TmChannel = Channel<NoopRawMutex, Vec<u8>, TM_QUEUE_DEPTH>;
pub type TcSender = Sender<'static, NoopRawMutex, Vec<u8>, TC_QUEUE_DEPTH>;
pub type TcReceiver = Receiver<'static, NoopRawMutex, Vec<u8>, TC_QUEUE_DEPTH>;
pub type TmSender = Sender<'static, NoopRawMutex, Vec<u8>, TM_QUEUE_DEPTH>;
pub type TmReceiver = Receiver<'static, NoopRawMutex, Vec<u8>, TM_QUEUE_DEPTH>;
pub async fn tc_handler(tc_rx: TcReceiver, telemetry: &mut Telemetry) {
loop {
let tc = tc_rx.receive().await;
let packet = match CcsdsPacketReader::new_with_checksum(&tc) {
Ok(packet) => packet,
Err(e) => {
defmt::warn!("Failed to parse received TC packet: {}", e);
send_tmtc_event(telemetry, tmtc::Event::InvalidTcPacket).await;
continue;
}
};
let tc_id = CcsdsPacketIdAndPsc {
packet_id: packet.packet_id(),
psc: packet.psc(),
};
let Ok((tc_header, payload)) = postcard::take_from_bytes::<TcHeader>(packet.user_data())
else {
defmt::warn!("Failed to deserialize TC header");
send_tmtc_event(telemetry, tmtc::Event::InvalidTcHeader).await;
continue;
};
match tc_header.target_id {
ComponentId::Controller => handle_controller_tc(payload, tc_id, telemetry).await,
ComponentId::Led => handle_led_tc(payload, tc_id, telemetry).await,
target_id => {
defmt::warn!("No TC handler for target ID {}", Debug2Format(&target_id));
send_tmtc_event(telemetry, tmtc::Event::UnknownTargetId(target_id)).await;
}
}
}
}
/// All TCs are received via UDP, so the UDP server is the sender of TMTC events.
async fn send_tmtc_event(telemetry: &mut Telemetry, event: tmtc::Event) {
telemetry.send(ComponentId::UdpServer, None, &event).await;
}
/// The controller does not control anything yet, but handles generic requests like pings.
async fn handle_controller_tc(
payload: &[u8],
tc_id: CcsdsPacketIdAndPsc,
telemetry: &mut Telemetry,
) {
let Ok(request) = postcard::from_bytes::<control::request::Request>(payload) else {
defmt::warn!("Failed to deserialize controller request");
return;
};
match request {
control::request::Request::Ping => defmt::info!("Received controller ping request"),
control::request::Request::TestEvent => {
defmt::info!("Received test event request");
let event = types::Event::ControllerEvent(control::Event::TestEvent);
telemetry.send(ComponentId::Controller, None, &event).await;
}
control::request::Request::SimConnect(opt_ipv4_addr) => match opt_ipv4_addr {
Some(ipv4_addr) => sim_client::SIM_HOST.signal(ipv4_addr),
None => {
// If the UDP socket has received anything, the last sender should be stored and
// we use that IP address.
let opt_last_sender = LAST_SENDER.lock(|val| val.clone());
match opt_last_sender.get() {
Some(last_sender) => sim_client::SIM_HOST.signal(last_sender),
None => defmt::warn!("SimConnect without IP and no known sender"),
}
}
},
}
telemetry
.send(
ComponentId::Controller,
Some(tc_id),
&control::response::Response::Ok,
)
.await;
}
async fn handle_led_tc(payload: &[u8], tc_id: CcsdsPacketIdAndPsc, telemetry: &mut Telemetry) {
let Ok(request) = postcard::from_bytes::<led::request::Request>(payload) else {
defmt::warn!("Failed to deserialize LED request");
return;
};
match request {
led::request::Request::Ping => defmt::info!("Received LED ping request"),
led::request::Request::SetMode(mode) => {
defmt::info!("Received LED mode request: {}", Debug2Format(&mode));
LED_MODE.signal(mode);
}
}
telemetry
.send(ComponentId::Led, Some(tc_id), &led::response::Response::Ok)
.await;
}
/// Packs TM and passes it to the UDP task.
pub struct Telemetry {
tx: TmSender,
sequence_count: u14,
}
impl Telemetry {
pub fn new(tx: TmSender) -> Self {
Self {
tx,
sequence_count: u14::new(0),
}
}
/// TM without a TC ID is sent unsolicited, for example events.
async fn send(
&mut self,
sender_id: ComponentId,
tc_id: Option<CcsdsPacketIdAndPsc>,
payload: &(impl serde::Serialize + Message),
) {
let sp_header = SpHeader::new_for_unseg_tm(Apid::Tmtc.raw_value(), self.sequence_count, 0);
let tm_header = TmHeader::new_without_timestamp(
sender_id,
ComponentId::Ground,
payload.message_type(),
tc_id,
);
match CcsdsTmPacketOwned::new_with_serde_payload(sp_header, &tm_header, payload)
.map_err(CcsdsCreationError::from)
.and_then(|packet| packet.try_to_vec())
{
Ok(raw_packet) => {
self.tx.send(raw_packet).await;
self.sequence_count = self.sequence_count.wrapping_add(u14::new(1));
}
Err(e) => defmt::warn!("Failed to create TM packet: {}", Debug2Format(&e)),
}
}
}
@@ -0,0 +1,32 @@
[package]
name = "stm32h7-nucleo-embassy"
edition = "2024"
version = "0.1.0"
default-run = "stm32h7-nucleo-embassy"
[lib]
harness = false
# needed for each integration test
[[test]]
name = "integration"
harness = false
[dependencies]
shared-embedded.workspace = true
cortex-m.workspace = true
cortex-m-rt.workspace = true
defmt.workspace = true
defmt-rtt.workspace = true
panic-probe.workspace = true
embedded-alloc.workspace = true
static_cell.workspace = true
embassy-stm32 = { workspace = true, features = ["memory-x", "time-driver-any"] }
embassy-executor.workspace = true
embassy-time = { workspace = true, features = ["defmt-timestamp-uptime-ms"] }
embassy-net.workspace = true
[dev-dependencies]
defmt-test.workspace = true
@@ -0,0 +1,15 @@
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
// The configuration is shared by all applications of the embedded workspace.
let cargo_dir = manifest_dir.parent().unwrap().join(".cargo");
let config = cargo_dir.join("config.toml");
let config_template = cargo_dir.join("config.toml.template");
if !config.exists() && config_template.exists() {
fs::create_dir_all(&cargo_dir).unwrap();
fs::copy(&config_template, &config).unwrap();
}
}
@@ -0,0 +1,43 @@
//! Blinks an LED
//!
//! This assumes that LD1 (green) is connected to PB0, LD2 (yellow) to PE1 and LD3 (red) to
//! PB14. This assumption is true for the MB1364 Nucleo-144 board, for example the
//! NUCLEO-H753ZI.
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_stm32::gpio;
use stm32h7_nucleo_embassy as _;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
defmt::info!("Hello World!");
let ld1 = gpio::Output::new(p.PB0, gpio::Level::High, gpio::Speed::Low);
let ld2 = gpio::Output::new(p.PE1, gpio::Level::High, gpio::Speed::Low);
let ld3 = gpio::Output::new(p.PB14, gpio::Level::High, gpio::Speed::Low);
spawner.spawn(blink(ld1, ld2, ld3).expect("spawning blink task failed"));
}
#[embassy_executor::task]
async fn blink(
mut ld1: gpio::Output<'static>,
mut ld2: gpio::Output<'static>,
mut ld3: gpio::Output<'static>,
) {
loop {
defmt::info!("high");
ld1.set_high();
ld2.set_high();
ld3.set_high();
embassy_time::Timer::after_millis(500).await;
defmt::info!("low");
ld1.set_low();
ld2.set_low();
ld3.set_low();
embassy_time::Timer::after_millis(500).await;
}
}
@@ -0,0 +1,13 @@
#![no_main]
#![no_std]
// global logger + panicking-behavior + memory layout
use stm32h7_nucleo_embassy as _;
#[cortex_m_rt::entry]
fn main() -> ! {
loop {
defmt::println!("Hello, world!");
cortex_m::asm::delay(100_000_000);
}
}
@@ -1,15 +1,27 @@
#![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 defmt_rtt as _;
use embassy_stm32 as _;
use panic_probe as _;
use core::mem::MaybeUninit;
use embedded_alloc::LlffHeap as Heap;
const HEAP_SIZE: usize = 131_072;
// Part of the library, because all binaries depend on crates which require an allocator.
#[global_allocator]
static HEAP: Heap = Heap::empty();
/// # Safety
///
/// Must be called exactly once, before the first allocation.
pub unsafe fn init_heap() {
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
}
// 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]
@@ -17,14 +29,6 @@ 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
@@ -32,14 +36,9 @@ pub fn exit() -> ! {
/// loop.
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
loop {
debug::exit(debug::EXIT_FAILURE);
}
panic!("unexpected hard fault");
}
// 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 {
@@ -0,0 +1,150 @@
#![no_main]
#![no_std]
extern crate alloc;
use embassy_executor::Spawner;
use embassy_net::StackResources;
use embassy_stm32::{bind_interrupts, eth, gpio, peripherals, rng};
use static_cell::{ConstStaticCell, StaticCell};
bind_interrupts!(struct Irqs {
ETH => eth::InterruptHandler;
HASH_RNG => rng::InterruptHandler<peripherals::RNG>;
});
#[embassy_executor::main]
async fn main(spawner: Spawner) {
defmt::println!("Starting sat-rs demo application for the STM32H753ZIT");
// Safety: Called once, before the first allocation.
unsafe { stm32h7_nucleo_embassy::init_heap() };
let mut config = embassy_stm32::Config::default();
{
use embassy_stm32::rcc::*;
config.rcc.hsi = Some(HSIPrescaler::DIV1);
config.rcc.csi = true;
config.rcc.hsi48 = Some(Default::default()); // needed for RNG
config.rcc.pll1 = Some(Pll {
source: PllSource::HSI,
prediv: PllPreDiv::DIV4,
mul: PllMul::MUL50,
divp: Some(PllDiv::DIV2),
divq: None,
divr: None,
});
config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz
config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz
config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.voltage_scale = VoltageScale::Scale1;
}
let periphs = embassy_stm32::init(config);
let green_led = gpio::Output::new(periphs.PB0, gpio::Level::Low, gpio::Speed::Medium);
let leds = shared_embedded::leds::Leds {
red: gpio::Output::new(periphs.PB14, gpio::Level::Low, gpio::Speed::Medium),
orange: gpio::Output::new(periphs.PE1, gpio::Level::Low, gpio::Speed::Medium),
};
static PACKETS: StaticCell<eth::PacketQueue<4, 4>> = StaticCell::new();
// warning: Not all STM32H7 devices have the exact same pins here
// for STM32H747XIH, replace p.PB13 for PG12
let device = eth::Ethernet::new(
PACKETS.init(eth::PacketQueue::<4, 4>::new()),
periphs.ETH,
Irqs,
periphs.PA1, // ref_clk
periphs.PA7, // CRS_DV: Carrier Sense
periphs.PC4, // RX_D0: Received Bit 0
periphs.PC5, // RX_D1: Received Bit 1
periphs.PG13, // TX_D0: Transmit Bit 0
periphs.PB13, // TX_D1: Transmit Bit 1
periphs.PG11, // TX_EN: Transmit Enable
shared_embedded::net::MAC_ADDRESS,
periphs.ETH_SMA,
periphs.PA2, // mdio
periphs.PC1, // mdc
);
let net_config = embassy_net::Config::dhcpv4(embassy_net::DhcpConfig::default());
// Generate random seed.
let mut rng = rng::Rng::new(periphs.RNG, Irqs);
let mut seed = [0; 8];
rng.fill_bytes(&mut seed);
let seed = u64::from_le_bytes(seed);
// DHCP, TMTC and simulator socket.
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let (stack, runner) = embassy_net::new(
device,
net_config,
RESOURCES.init(StackResources::new()),
seed,
);
static TC_CHANNEL: ConstStaticCell<shared_embedded::tmtc::TcChannel> =
ConstStaticCell::new(shared_embedded::tmtc::TcChannel::new());
let tc_channel = TC_CHANNEL.take();
static TM_CHANNEL: ConstStaticCell<shared_embedded::tmtc::TmChannel> =
ConstStaticCell::new(shared_embedded::tmtc::TmChannel::new());
let tm_channel = TM_CHANNEL.take();
spawner.spawn(net_stack_task(runner).expect("spawning net stack task failed"));
spawner.spawn(sim_client_task(stack).expect("spawning sim client task failed"));
spawner.spawn(
udp_task(stack, tc_channel.sender(), tm_channel.receiver())
.expect("spawning UDP task failed"),
);
spawner.spawn(heartbeat(green_led).expect("spawning heartbeat task failed"));
spawner.spawn(led_task(leds).expect("spawning LED task failed"));
spawner.spawn(
tc_handler(
tc_channel.receiver(),
shared_embedded::tmtc::Telemetry::new(tm_channel.sender()),
)
.expect("spawning TC handler task failed"),
);
}
#[embassy_executor::task]
async fn net_stack_task(
mut runner: embassy_net::Runner<'static, shared_embedded::net::Device>,
) -> ! {
shared_embedded::net::net_stack_task(&mut runner).await
}
#[embassy_executor::task]
async fn udp_task(
stack: embassy_net::Stack<'static>,
tc_tx: shared_embedded::tmtc::TcSender,
tm_rx: shared_embedded::tmtc::TmReceiver,
) {
shared_embedded::net::udp_task(stack, tc_tx, tm_rx).await
}
#[embassy_executor::task]
async fn sim_client_task(stack: embassy_net::Stack<'static>) {
shared_embedded::sim_client::sim_client_task(stack).await
}
#[embassy_executor::task]
async fn heartbeat(mut led: gpio::Output<'static>) {
shared_embedded::leds::heartbeat(&mut led).await
}
#[embassy_executor::task]
async fn led_task(mut leds: shared_embedded::leds::Leds) {
shared_embedded::leds::led_task(&mut leds).await
}
#[embassy_executor::task]
async fn tc_handler(
tc_rx: shared_embedded::tmtc::TcReceiver,
mut telemetry: shared_embedded::tmtc::Telemetry,
) {
shared_embedded::tmtc::tc_handler(tc_rx, &mut telemetry).await
}
@@ -0,0 +1,16 @@
#![no_std]
#![no_main]
use stm32h7_nucleo_embassy 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)
}
}
@@ -0,0 +1,32 @@
[package]
name = "stm32h7-nucleo-rtic"
edition = "2024"
version = "0.1.0"
default-run = "stm32h7-nucleo-rtic"
[lib]
harness = false
# needed for each integration test
[[test]]
name = "integration"
harness = false
[dependencies]
shared-embedded.workspace = true
cortex-m.workspace = true
cortex-m-rt.workspace = true
defmt.workspace = true
defmt-rtt.workspace = true
panic-probe.workspace = true
embedded-alloc.workspace = true
static_cell.workspace = true
rtic.workspace = true
embassy-stm32 = { workspace = true, features = ["memory-x", "time-driver-any"] }
embassy-time = { workspace = true, features = ["defmt-timestamp-uptime-ms", "generic-queue-16"] }
embassy-net.workspace = true
[dev-dependencies]
defmt-test.workspace = true
@@ -0,0 +1,15 @@
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
// The configuration is shared by all applications of the embedded workspace.
let cargo_dir = manifest_dir.parent().unwrap().join(".cargo");
let config = cargo_dir.join("config.toml");
let config_template = cargo_dir.join("config.toml.template");
if !config.exists() && config_template.exists() {
fs::create_dir_all(&cargo_dir).unwrap();
fs::copy(&config_template, &config).unwrap();
}
}
@@ -0,0 +1,58 @@
//! Blinks an LED
//!
//! This assumes that LD1 (green) is connected to PB0, LD2 (yellow) to PE1 and LD3 (red) to
//! PB14. This assumption is true for the MB1364 Nucleo-144 board, for example the
//! NUCLEO-H753ZI.
#![no_std]
#![no_main]
use rtic::app;
use stm32h7_nucleo_rtic as _;
#[app(device = embassy_stm32, peripherals = false, dispatchers = [SPI1])]
mod app {
use embassy_stm32::gpio;
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
let p = embassy_stm32::init(Default::default());
defmt::info!("Hello World!");
// Configure gpio B pin 0 as a push-pull output.
let ld1 = gpio::Output::new(p.PB0, gpio::Level::High, gpio::Speed::Low);
let ld2 = gpio::Output::new(p.PE1, gpio::Level::High, gpio::Speed::Low);
let ld3 = gpio::Output::new(p.PB14, gpio::Level::High, gpio::Speed::Low);
// Schedule the blinking task
blink::spawn(ld1, ld2, ld3).ok();
(Shared {}, Local {})
}
#[task()]
async fn blink(
_cx: blink::Context,
mut ld1: gpio::Output<'static>,
mut ld2: gpio::Output<'static>,
mut ld3: gpio::Output<'static>,
) {
loop {
defmt::info!("high");
ld1.set_high();
ld2.set_high();
ld3.set_high();
embassy_time::Timer::after_millis(500).await;
defmt::info!("low");
ld1.set_low();
ld2.set_low();
ld3.set_low();
embassy_time::Timer::after_millis(500).await;
}
}
}
@@ -0,0 +1,13 @@
#![no_main]
#![no_std]
// global logger + panicking-behavior + memory layout
use stm32h7_nucleo_rtic as _;
#[cortex_m_rt::entry]
fn main() -> ! {
loop {
defmt::println!("Hello, world!");
cortex_m::asm::delay(100_000_000);
}
}
@@ -1,14 +1,27 @@
#![no_main]
#![no_std]
use cortex_m_semihosting::debug;
use defmt_brtt as _; // global logger
use stm32f3xx_hal as _; // memory layout
use defmt_rtt as _;
use embassy_stm32 as _;
use panic_probe as _;
use core::mem::MaybeUninit;
use embedded_alloc::LlffHeap as Heap;
const HEAP_SIZE: usize = 131_072;
// Part of the library, because all binaries depend on crates which require an allocator.
#[global_allocator]
static HEAP: Heap = Heap::empty();
/// # Safety
///
/// Must be called exactly once, before the first allocation.
pub unsafe fn init_heap() {
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
}
// 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]
@@ -16,14 +29,6 @@ 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
@@ -31,9 +36,7 @@ pub fn exit() -> ! {
/// loop.
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
loop {
debug::exit(debug::EXIT_FAILURE);
}
panic!("unexpected hard fault");
}
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
@@ -0,0 +1,163 @@
#![no_main]
#![no_std]
extern crate alloc;
use rtic::app;
#[app(device = embassy_stm32, peripherals = false)]
mod app {
use embassy_net::StackResources;
use embassy_stm32::{bind_interrupts, eth, gpio, peripherals, rng};
use static_cell::{ConstStaticCell, StaticCell};
bind_interrupts!(struct Irqs {
ETH => eth::InterruptHandler;
HASH_RNG => rng::InterruptHandler<peripherals::RNG>;
});
#[local]
struct Local {
net_runner: embassy_net::Runner<'static, shared_embedded::net::Device>,
net_stack: embassy_net::Stack<'static>,
sim_net_stack: embassy_net::Stack<'static>,
leds: shared_embedded::leds::Leds,
green_led: gpio::Output<'static>,
tc_rx: shared_embedded::tmtc::TcReceiver,
tc_tx: shared_embedded::tmtc::TcSender,
tm_rx: shared_embedded::tmtc::TmReceiver,
telemetry: shared_embedded::tmtc::Telemetry,
}
#[shared]
struct Shared {}
#[init]
fn init(_cx: init::Context) -> (Shared, Local) {
defmt::println!("Starting sat-rs demo application for the STM32H753ZIT");
// Safety: Called once, before the first allocation.
unsafe { stm32h7_nucleo_rtic::init_heap() };
let mut config = embassy_stm32::Config::default();
{
use embassy_stm32::rcc::*;
config.rcc.hsi = Some(HSIPrescaler::DIV1);
config.rcc.csi = true;
config.rcc.hsi48 = Some(Default::default()); // needed for RNG
config.rcc.pll1 = Some(Pll {
source: PllSource::HSI,
prediv: PllPreDiv::DIV4,
mul: PllMul::MUL50,
divp: Some(PllDiv::DIV2),
divq: None,
divr: None,
});
config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz
config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz
config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz
config.rcc.voltage_scale = VoltageScale::Scale1;
}
let periphs = embassy_stm32::init(config);
let green_led = gpio::Output::new(periphs.PB0, gpio::Level::Low, gpio::Speed::Medium);
let leds = shared_embedded::leds::Leds {
red: gpio::Output::new(periphs.PB14, gpio::Level::Low, gpio::Speed::Medium),
orange: gpio::Output::new(periphs.PE1, gpio::Level::Low, gpio::Speed::Medium),
};
static PACKETS: StaticCell<eth::PacketQueue<4, 4>> = StaticCell::new();
// warning: Not all STM32H7 devices have the exact same pins here
// for STM32H747XIH, replace p.PB13 for PG12
let device = eth::Ethernet::new(
PACKETS.init(eth::PacketQueue::<4, 4>::new()),
periphs.ETH,
Irqs,
periphs.PA1, // ref_clk
periphs.PA7, // CRS_DV: Carrier Sense
periphs.PC4, // RX_D0: Received Bit 0
periphs.PC5, // RX_D1: Received Bit 1
periphs.PG13, // TX_D0: Transmit Bit 0
periphs.PB13, // TX_D1: Transmit Bit 1
periphs.PG11, // TX_EN: Transmit Enable
shared_embedded::net::MAC_ADDRESS,
periphs.ETH_SMA,
periphs.PA2, // mdio
periphs.PC1, // mdc
);
let config = embassy_net::Config::dhcpv4(embassy_net::DhcpConfig::default());
// Generate random seed.
let mut rng = rng::Rng::new(periphs.RNG, Irqs);
let mut seed = [0; 8];
rng.fill_bytes(&mut seed);
let seed = u64::from_le_bytes(seed);
// DHCP, TMTC and simulator socket.
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let (stack, runner) =
embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
static TC_CHANNEL: ConstStaticCell<shared_embedded::tmtc::TcChannel> =
ConstStaticCell::new(shared_embedded::tmtc::TcChannel::new());
let tc_channel = TC_CHANNEL.take();
static TM_CHANNEL: ConstStaticCell<shared_embedded::tmtc::TmChannel> =
ConstStaticCell::new(shared_embedded::tmtc::TmChannel::new());
let tm_channel = TM_CHANNEL.take();
net_lib_task::spawn().expect("spawning net library task failed");
udp_task::spawn().expect("spawning UDP task failed");
sim_client_task::spawn().expect("spawning sim client task failed");
heartbeat::spawn().expect("spawning heartbeat task failed");
led_task::spawn().expect("spawning LED task failed");
tc_handler::spawn().expect("spawning TC handler task failed");
(
Shared {},
Local {
green_led,
leds,
net_runner: runner,
net_stack: stack,
sim_net_stack: stack,
tc_tx: tc_channel.sender(),
tc_rx: tc_channel.receiver(),
telemetry: shared_embedded::tmtc::Telemetry::new(tm_channel.sender()),
tm_rx: tm_channel.receiver(),
},
)
}
#[task(local = [green_led])]
async fn heartbeat(cx: heartbeat::Context) {
shared_embedded::leds::heartbeat(cx.local.green_led).await;
}
#[task(local = [leds])]
async fn led_task(cx: led_task::Context) {
shared_embedded::leds::led_task(cx.local.leds).await;
}
#[task(local = [net_runner])]
async fn net_lib_task(cx: net_lib_task::Context) {
shared_embedded::net::net_stack_task(cx.local.net_runner).await;
}
#[task(local = [net_stack, tc_tx, tm_rx])]
async fn udp_task(cx: udp_task::Context) {
shared_embedded::net::udp_task(*cx.local.net_stack, *cx.local.tc_tx, *cx.local.tm_rx).await;
}
#[task(local = [sim_net_stack])]
async fn sim_client_task(cx: sim_client_task::Context) {
shared_embedded::sim_client::sim_client_task(*cx.local.sim_net_stack).await;
}
#[task(local = [tc_rx, telemetry])]
async fn tc_handler(cx: tc_handler::Context) {
shared_embedded::tmtc::tc_handler(*cx.local.tc_rx, cx.local.telemetry).await;
}
}
@@ -1,7 +1,7 @@
#![no_std]
#![no_main]
use stm32h7_testapp as _; // memory layout + panic handler
use stm32h7_nucleo_rtic as _; // memory layout + panic handler
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
// feature)
File renamed without changes.
@@ -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
+38
View File
@@ -0,0 +1,38 @@
[package]
name = "example-std"
version = "0.1.1"
edition = "2024"
default-run = "example-std"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
[dependencies]
fern = "0.7"
chrono = "0.4"
log = "0.4"
crossbeam-channel = "0.5"
delegate = "0.13"
zerocopy = "0.8"
csv = "1"
num_enum = "0.7"
thiserror = "2"
lazy_static = "1"
strum = { version = "0.28", features = ["derive"] }
derive-new = "0.7"
cfg-if = "1"
arbitrary-int = "2"
bitbybit = "2"
postcard = { version = "1", features = ["alloc"] }
ctrlc = "3"
serde = { version = "1", features = ["derive"] }
satrs = { path = "../../satrs", features = ["test_util"] }
types = { path = "../types" }
minisim-types = { path = "../minisim-types" }
satrs-mib = { path = "../../satrs-mib" }
[features]
# default = ["heap_tmtc"]
# heap_tmtc = []
[dev-dependencies]
env_logger = "0.11"
File renamed without changes.
File renamed without changes.
+57
View File
@@ -0,0 +1,57 @@
sat-rs example
======
This crate contains an example application which simulates an on-board software.
It uses various components provided by the sat-rs framework to do this. As such, it shows how
a more complex real on-board software could be built from these components. It is recommended to
read the dedicated
[example chapters](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/book/example.html) inside
the sat-rs book.
The application opens a UDP and a TCP server on port 7301 to receive telecommands.
You can run the application using `cargo run`.
# Features
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.
You can run the application without this feature using
```sh
cargo run --no-default-features
```
# Interacting with the sat-rs example
The `client` crate is a command line client which sends telecommands to the example application
and prints the received telemetry. For example, you can ping the application or switch MGM 0
to normal mode like this:
```sh
cargo run -p client -- --ping
cargo run -p client -- mgm0 -m normal
```
Use `cargo run -p client -- --help` to list all available commands.
## Adding the mini simulator application
This example application features a few device handlers. The
[`minisim`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/examples/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 minisim
```
and then start the example using `cargo run -p example-std`.
+118
View File
@@ -0,0 +1,118 @@
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
use types::acs::ctrl::{Mode, request::ModeRequest, response::ModeReport};
/// Helper component for communication with a parent component, which is usually an assembly
/// or a subsystem.
pub struct ModeLeafHelper {
pub request_rx: Receiver<ModeRequest>,
pub report_tx: SyncSender<ModeReport>,
}
/// Dummy ACS controller. It has no actual control law and reaches any commanded mode
/// immediately, which is sufficient to exercise the mode commanding of its parent subsystem.
pub struct Controller {
mode: Mode,
mode_leaf_helper: ModeLeafHelper,
}
impl Controller {
pub fn new(mode_leaf_helper: ModeLeafHelper) -> Self {
Self {
mode: Mode::Passive,
mode_leaf_helper,
}
}
#[allow(dead_code)]
#[inline]
pub fn mode(&self) -> Mode {
self.mode
}
pub fn periodic_operation(&mut self) {
self.handle_mode_leaf_handling();
}
fn handle_mode_leaf_handling(&mut self) {
loop {
match self.mode_leaf_helper.request_rx.try_recv() {
Ok(request) => match request {
ModeRequest::SetMode(mode) => {
log::info!("ACS controller: transitioning to mode {:?}", mode);
self.mode = mode;
self.report_mode();
}
ModeRequest::ReadMode => self.report_mode(),
},
Err(e) => match e {
TryRecvError::Empty => break,
TryRecvError::Disconnected => log::warn!("packet sender disconnected"),
},
}
}
}
fn report_mode(&self) {
self.mode_leaf_helper
.report_tx
.send(ModeReport::Mode(self.mode))
.expect("failed to send mode report to parent");
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use super::*;
struct ControllerTestbench {
request_tx: SyncSender<ModeRequest>,
report_rx: Receiver<ModeReport>,
controller: Controller,
}
impl ControllerTestbench {
fn new() -> Self {
let (request_tx, request_rx) = mpsc::sync_channel(5);
let (report_tx, report_rx) = mpsc::sync_channel(5);
Self {
request_tx,
report_rx,
controller: Controller::new(ModeLeafHelper {
request_rx,
report_tx,
}),
}
}
}
#[test]
fn test_initial_mode() {
let testbench = ControllerTestbench::new();
assert_eq!(testbench.controller.mode(), Mode::Passive);
}
#[test]
fn test_set_mode() {
let mut testbench = ControllerTestbench::new();
testbench
.request_tx
.send(ModeRequest::SetMode(Mode::Safe))
.unwrap();
testbench.controller.periodic_operation();
assert_eq!(testbench.controller.mode(), Mode::Safe);
let report = testbench.report_rx.try_recv().expect("no mode report sent");
assert_eq!(report, ModeReport::Mode(Mode::Safe));
}
#[test]
fn test_read_mode() {
let mut testbench = ControllerTestbench::new();
testbench.request_tx.send(ModeRequest::ReadMode).unwrap();
testbench.controller.periodic_operation();
let report = testbench.report_rx.try_recv().expect("no mode report sent");
assert_eq!(report, ModeReport::Mode(Mode::Passive));
}
}
File diff suppressed because it is too large. Load diff
Loaded 100 of 324 files, more files were not shown because too many files have changed in this diff. Show more