Compare commits
127 Commits
satrs-mib-
...
satrs-v0.2
Author | SHA1 | Date | |
---|---|---|---|
b2e77fbc09 | |||
5371928496
|
|||
31cddbd99b | |||
7c00e13e70 | |||
aa72063454 | |||
7b37b76695 | |||
ea5d95c12d | |||
c62adbb300 | |||
9242b8a607 | |||
4a27d2605d
|
|||
8195245481
|
|||
f6f7519625 | |||
0f0fbc1a18
|
|||
6e55e2ac95 | |||
2f96bfe992
|
|||
52aafb3aab
|
|||
6ce9cb5ead | |||
273f79d1e6
|
|||
622221835e | |||
e396ad2e7a
|
|||
772927d50b
|
|||
be9a45e55f | |||
eee8a69550
|
|||
f7a6d3ce47 | |||
df97a3a93e
|
|||
42750e08c0
|
|||
786671bbd7 | |||
63f37f0917 | |||
8cfe3b81e7 | |||
de50bec562
|
|||
39ab9fa27b | |||
1dbc81a8f5
|
|||
1ad74ee1d5 | |||
f96fe6bdc0
|
|||
d43a8eb571 | |||
0bbada90ef | |||
3375780e00 | |||
de028ed827
|
|||
d27ac5dfc9
|
|||
c67b7cb93a
|
|||
f71ba3e8d8 | |||
3cc9dd3c48 | |||
0fec994028 | |||
226a134aff
|
|||
aac59ec7c1 | |||
ce7eb8650f | |||
df2733a176
|
|||
344fe6a4c0 | |||
a5941751d7
|
|||
977e29894b | |||
dd1417a368
|
|||
3195cf5111
|
|||
8280c70682 | |||
19c5aa9b83
|
|||
713b4e097b
|
|||
746b31ec5d | |||
2318cd4293
|
|||
a6b57d3eb9 | |||
bddd3132d4 | |||
6a6ffba754 | |||
d27a41e4de | |||
128df9a813 | |||
7387be3bc3
|
|||
d3fb504545
|
|||
ae8e39f626
|
|||
ab3d907d4e | |||
3de5954898
|
|||
5600aa576c | |||
88793cfa87
|
|||
223b637eb8
|
|||
cf9b115e1e | |||
eea9b11b39
|
|||
f21ab0017e | |||
a7ca00317f
|
|||
75fda42f4f
|
|||
faf0f6f6c6 | |||
a690c7720d | |||
b48b5b8caa | |||
238c3a8d43 | |||
de8c0bc13e | |||
012eb82f42
|
|||
d26f6cbe27 | |||
82d3215761
|
|||
2b80244636
|
|||
f1611cd5b8 | |||
808126ee41 | |||
05df24447b | |||
b229360233 | |||
52be26829b | |||
ca2c8aa359 | |||
ba03150178 | |||
4e45bfa7e6 | |||
93c01c8c22
|
|||
2d062f3010 | |||
01d9a85976 | |||
fa7cd39f3e | |||
5bb37d8e87 | |||
813e221030 | |||
18cec8bcf0 | |||
d4a122e462 | |||
7af327d077 | |||
3a6cd6712d | |||
c4eba03043 | |||
e9e5c999ec | |||
f14a85cb84 | |||
c3902c2c06 | |||
24f82d4c5e | |||
44ff62e947 | |||
9cf80b44ea | |||
f4bc33aefa | |||
445fdfe066 | |||
7c3879fbce | |||
c3e9d4441f | |||
bbcad18dfa | |||
2607252be2 | |||
8b79e967bb | |||
c1e1c10f2d | |||
eef6f42d3b | |||
6bfd37ba24 | |||
94fbb50e11 | |||
0caab60d74 | |||
e2e6605f50 | |||
0b99a40c6f | |||
c635c7eed3 | |||
de4e6183b3 | |||
f58a4eaee5 | |||
544488eaa3 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,9 @@
|
|||||||
/target
|
target/
|
||||||
|
|
||||||
|
output.log
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
|
|
||||||
|
output.log
|
||||||
|
|
||||||
/.idea/*
|
/.idea/*
|
||||||
!/.idea/runConfigurations
|
!/.idea/runConfigurations
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"satrs-core",
|
"satrs",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
"satrs-example",
|
"satrs-example",
|
||||||
|
"satrs-minisim",
|
||||||
|
"satrs-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
28
README.md
28
README.md
@ -1,13 +1,23 @@
|
|||||||
<p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p>
|
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
|
||||||
|
|
||||||
|
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
||||||
|
[](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
|
||||||
|
[](https://crates.io/crates/satrs)
|
||||||
|
[](https://docs.rs/satrs)
|
||||||
|
|
||||||
sat-rs
|
sat-rs
|
||||||
=========
|
=========
|
||||||
|
|
||||||
This is the repository of the sat-rs framework. Its primary goal is to provide re-usable components
|
This is the repository of the sat-rs library. Its primary goal is to provide re-usable components
|
||||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||||
for the special requirements for these systems. You can find an overview of the project and the
|
for the special requirements for these systems. You can find an overview of the project and the
|
||||||
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
||||||
at the [IRS documentation website](https://absatsw.irs.uni-stuttgart.de/sat-rs.html).
|
at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
|
||||||
|
|
||||||
|
This is early-stage software. Important features are missing. New releases
|
||||||
|
with breaking changes are released regularly, with all changes documented inside respective
|
||||||
|
changelog files. You should only use this library if your are willing to work in this
|
||||||
|
environment.
|
||||||
|
|
||||||
A lot of the architecture and general design considerations are based on the
|
A lot of the architecture and general design considerations are based on the
|
||||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
||||||
@ -22,15 +32,15 @@ This project currently contains following crates:
|
|||||||
Primary information resource in addition to the API documentation, hosted
|
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/). It can be useful to read
|
||||||
this first before delving into the example application and the API documentation.
|
this first before delving into the example application and the API documentation.
|
||||||
* [`satrs-core`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-core):
|
* [`satrs`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs):
|
||||||
Core components of sat-rs.
|
Primary crate.
|
||||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-example):
|
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
|
||||||
Example of a simple example on-board software using various sat-rs components which can be run
|
Example of a simple example on-board software using various sat-rs components which can be run
|
||||||
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
||||||
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-mib):
|
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-mib):
|
||||||
Components to build a mission information base from the on-board software directly.
|
Components to build a mission information base from the on-board software directly.
|
||||||
* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco):
|
* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco):
|
||||||
Example of a simple example on-board software using sat-rs components on a bare-metal system
|
Example of a simple example using low-level sat-rs components on a bare-metal system
|
||||||
with constrained resources.
|
with constrained resources.
|
||||||
|
|
||||||
Each project has its own `CHANGELOG.md`.
|
Each project has its own `CHANGELOG.md`.
|
||||||
@ -41,7 +51,7 @@ Each project has its own `CHANGELOG.md`.
|
|||||||
|
|
||||||
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
|
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
|
||||||
packet protocol implementations. This repository is re-exported in the
|
packet protocol implementations. This repository is re-exported in the
|
||||||
[`satrs-core`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-core)
|
[`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
|
||||||
crate.
|
crate.
|
||||||
|
|
||||||
# Coverage
|
# Coverage
|
||||||
|
@ -15,7 +15,9 @@ RUN rustup install nightly && \
|
|||||||
rustup component add rustfmt clippy
|
rustup component add rustfmt clippy
|
||||||
|
|
||||||
WORKDIR "/tmp"
|
WORKDIR "/tmp"
|
||||||
# RUN cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
# Install cargo-nextest
|
||||||
|
RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
|
||||||
|
# Install mdbook and mdbook-linkcheck
|
||||||
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
|
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
|
||||||
RUN curl -sSL https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && \
|
RUN curl -sSL https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && \
|
||||||
unzip mdbook-linkcheck.zip && \
|
unzip mdbook-linkcheck.zip && \
|
||||||
|
4
automation/Jenkinsfile
vendored
4
automation/Jenkinsfile
vendored
@ -32,7 +32,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Test') {
|
stage('Test') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo test --all-features'
|
sh 'cargo nextest r --all-features'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check with all features') {
|
stage('Check with all features') {
|
||||||
@ -47,7 +47,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Check Cross Embedded Bare Metal') {
|
stage('Check Cross Embedded Bare Metal') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo check -p satrs-core --target thumbv7em-none-eabihf --no-default-features'
|
sh 'cargo check -p satrs --target thumbv7em-none-eabihf --no-default-features'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check Cross Embedded Linux') {
|
stage('Check Cross Embedded Linux') {
|
||||||
|
12
coverage.py
12
coverage.py
@ -18,15 +18,19 @@ def generate_cov_report(open_report: bool, format: str, package: str):
|
|||||||
out_path = "./target/debug/coverage"
|
out_path = "./target/debug/coverage"
|
||||||
if format == "lcov":
|
if format == "lcov":
|
||||||
out_path = "./target/debug/lcov.info"
|
out_path = "./target/debug/lcov.info"
|
||||||
os.system(
|
grcov_cmd = (
|
||||||
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
|
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
|
||||||
f"-o {out_path}"
|
f"-o {out_path}"
|
||||||
)
|
)
|
||||||
|
print(f"Running: {grcov_cmd}")
|
||||||
|
os.system(grcov_cmd)
|
||||||
if format == "lcov":
|
if format == "lcov":
|
||||||
os.system(
|
lcov_cmd = (
|
||||||
"genhtml -o ./target/debug/coverage/ --show-details --highlight --ignore-errors source "
|
"genhtml -o ./target/debug/coverage/ --show-details --highlight --ignore-errors source "
|
||||||
"--legend ./target/debug/lcov.info"
|
"--legend ./target/debug/lcov.info"
|
||||||
)
|
)
|
||||||
|
print(f"Running: {lcov_cmd}")
|
||||||
|
os.system(lcov_cmd)
|
||||||
if open_report:
|
if open_report:
|
||||||
coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
|
coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
|
||||||
webbrowser.open_new_tab(coverage_report_path)
|
webbrowser.open_new_tab(coverage_report_path)
|
||||||
@ -43,8 +47,8 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-p",
|
"-p",
|
||||||
"--package",
|
"--package",
|
||||||
choices=["satrs-core"],
|
choices=["satrs", "satrs-minisim", "satrs-example"],
|
||||||
default="satrs-core",
|
default="satrs",
|
||||||
help="Choose project to generate coverage for",
|
help="Choose project to generate coverage for",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
650
images/mode-tree/mode-tree.graphml
Normal file
650
images/mode-tree/mode-tree.graphml
Normal file
@ -0,0 +1,650 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
|
<!--Created by yEd 3.23.2-->
|
||||||
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
|
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||||
|
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||||
|
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||||
|
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||||
|
<graph edgedefault="directed" id="G">
|
||||||
|
<data key="d0"/>
|
||||||
|
<node id="n0">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="157.79999999999998" width="452.0" x="959.3461887999997" y="585.7236400000005"/>
|
||||||
|
<y:Fill color2="#000000" hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="145.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="434.0" x="9.0" xml:space="preserve" y="6.399999999999977"><html>
|
||||||
|
<center>
|
||||||
|
<h4>ACS Mode Tree</h4>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Mode</th>
|
||||||
|
<th>MGMs</th>
|
||||||
|
<th>SUSs</th>
|
||||||
|
<th>STR</th>
|
||||||
|
<th>MGT</th>
|
||||||
|
<th>RWs</th>
|
||||||
|
<th>ACS CTRL</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>SAFE</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>OFF</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>SAFE</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>IDLE</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>IDLE</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</html><y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="-1.1102230246251565E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="134.60000000000014" width="452.0" x="959.3461887999997" y="757.2703199999999"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="120.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="428.0" x="12.0" xml:space="preserve" y="7.300000000000068"><html>
|
||||||
|
<center>
|
||||||
|
<h4>ACS IDLE Sequence</h4>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Step</th>
|
||||||
|
<th>MGMs</th>
|
||||||
|
<th>SUS</th>
|
||||||
|
<th>STR</th>
|
||||||
|
<th>MGT</th>
|
||||||
|
<th>RWs</th>
|
||||||
|
<th>ACS CTRL</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td>NORMAL</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td>SAFE</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</html><y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="32.400000000000034" width="146.79999999999995" x="1128.4" y="313.94999999999993"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="113.94921875" x="16.42539062500009" xml:space="preserve" y="6.0515625000000455">ACS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n3">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="80.39999999999998" x="910.7200000000004" y="407.20000000000005"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="9.281054687499932" xml:space="preserve" y="4.03125">MGM
|
||||||
|
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n4">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="32.400000000000034" width="146.79999999999995" x="1133.6000000000001" y="243.3579999999999"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.99609375" x="14.401953124999864" xml:space="preserve" y="6.051562499999989">Satellite System<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n5">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="72.40000000000009" x="1021.1200000000003" y="404.99560000000014"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="5.2810546875000455" xml:space="preserve" y="4.03125">SUS
|
||||||
|
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n6">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="67.59999999999991" x="1124.92" y="404.99560000000014"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.599609375" x="5.5001953124999545" xml:space="preserve" y="4.03125">STR
|
||||||
|
Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n7">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="67.59999999999991" x="1208.74" y="407.20000000000005"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.599609375" x="5.5001953124999545" xml:space="preserve" y="4.03125">MGT
|
||||||
|
Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n8">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="72.40000000000009" x="1292.56" y="407.20000000000005"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.837890625" x="5.2810546875000455" xml:space="preserve" y="4.03125">RW
|
||||||
|
Assembly<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n9">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="67.59999999999991" x="1381.18" y="407.20000000000005"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="34.732421875" x="16.433789062499955" xml:space="preserve" y="4.03125">ACS
|
||||||
|
CTRL<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n10">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="463.3000000000002"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.0156250000000568">MGM0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n11">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="491.32480000000066"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.015625">MGM1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n12">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="946.4000000000004" y="519.3496000000011"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.640625" x="1.5396874999999" xml:space="preserve" y="1.015625">MGM2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n13">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="463.3000000000002"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.65234375" x="4.5338281249999" xml:space="preserve" y="1.015625">SUS0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n14">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="491.32480000000066"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="15.443359375" x="14.6383203124999" xml:space="preserve" y="1.015625">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n15">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1055.2600000000007" y="519.3496000000011"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="43.287109375" x="0.7164453124999" xml:space="preserve" y="1.015625">SUS12<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n16">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="50.0" x="1129.9800000000005" y="482.79120000000023"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.0390625" x="2.48046875" xml:space="preserve" y="4.03125">STR
|
||||||
|
Device<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n17">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="50.0" x="1216.4400000000005" y="482.7912000000002"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.0390625" x="2.48046875" xml:space="preserve" y="4.031249999999943">MGT
|
||||||
|
Device<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n18">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="454.7884800000007"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n19">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="482.3769600000013"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n20">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="512.3769600000013"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n21">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="20.0" width="44.719999999999914" x="1341.6661887999999" y="542.3769600000013"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.837890625" x="6.4410546874999" xml:space="preserve" y="1.015625">RW3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n22">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="32.400000000000034" width="59.75999999999999" x="1305.2" y="313.94999999999993"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="17.3505859375" x="21.20470703125011" xml:space="preserve" y="6.051562499999989">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<edge id="e0" source="n4" target="n2">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-5.199999999999818" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e1" source="n2" target="n3">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="950.9200000000003" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e2" source="n2" target="n5">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="1057.3200000000004" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e3" source="n2" target="n6">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="1158.72" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e4" source="n2" target="n7">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="-11.29999999999859" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="1231.2400000000014" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e5" source="n2" target="n8">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="1328.76" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e6" source="n2" target="n9">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-1.1700000000000728" sy="0.0" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1200.63" y="379.7"/>
|
||||||
|
<y:Point x="1414.98" y="379.7"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e7" source="n3" target="n10">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-28.009798234586242" sy="0.0" tx="13.11999999999989" ty="0.0">
|
||||||
|
<y:Point x="922.9102017654141" y="473.3000000000002"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e8" source="n3" target="n11">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-28.009798234586242" sy="17.706497359551577" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="922.9102017654141" y="501.32480000000066"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e9" source="n3" target="n12">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-28.009798234586242" sy="12.053248679775777" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="922.9102017654141" y="529.3496000000011"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e10" source="n5" target="n13">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-24.18711646917177" sy="11.599696230174459" tx="-20.855199999999513" ty="-1.9751999999995178">
|
||||||
|
<y:Point x="1033.1328835308286" y="471.32480000000066"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e11" source="n5" target="n14">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-24.18711646917177" sy="0.0" tx="-20.855199999999513" ty="-3.024800000000596">
|
||||||
|
<y:Point x="1033.1328835308286" y="498.30000000000007"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e12" source="n6" target="n16">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="3.7399999999995543" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e13" source="n7" target="n17">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="1.0999999999994543" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e14" source="n8" target="n18">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-23.248152938343992" sy="14.026599270174415" tx="-13.026188800000227" ty="0.33167708069714763">
|
||||||
|
<y:Point x="1305.511847061656" y="465.1201570806978"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e15" source="n8" target="n19">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="0.0">
|
||||||
|
<y:Point x="1305.511847061656" y="492.3769600000013"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e16" source="n8" target="n20">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="0.0">
|
||||||
|
<y:Point x="1305.511847061656" y="522.3769600000013"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e17" source="n8" target="n21">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-23.248152938343992" sy="0.0" tx="-13.026188800000227" ty="-1.1368683772161603E-13">
|
||||||
|
<y:Point x="1305.511847061656" y="552.3769600000012"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e18" source="n4" target="n22">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-5.199999999999818" sy="16.200000000000045" tx="0.0" ty="0.0">
|
||||||
|
<y:Point x="1201.8000000000002" y="290.43031999999994"/>
|
||||||
|
<y:Point x="1335.08" y="290.43031999999994"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e19" source="n5" target="n15">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="-24.18711646917177" sy="0.0" tx="-20.855199999999513" ty="0.0">
|
||||||
|
<y:Point x="1033.1328835308286" y="529.3496000000011"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
</graph>
|
||||||
|
<data key="d7">
|
||||||
|
<y:Resources/>
|
||||||
|
</data>
|
||||||
|
</graphml>
|
1062
images/mode-tree/mode-tree.pdf
Normal file
1062
images/mode-tree/mode-tree.pdf
Normal file
File diff suppressed because it is too large
Load Diff
285
images/satrs-example-goal/satrs-example-goal.graphml
Normal file
285
images/satrs-example-goal/satrs-example-goal.graphml
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
|
<!--Created by yEd 3.23.2-->
|
||||||
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
|
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||||
|
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||||
|
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||||
|
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||||
|
<graph edgedefault="directed" id="G">
|
||||||
|
<data key="d0" xml:space="preserve"/>
|
||||||
|
<node id="n0">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="261.7582999999998" width="631.1152000000001" x="810.8848" y="142.00000000000003"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="296.09375" x="26.51035059931519" xml:space="preserve" y="9.42072533356739">satrs-example Component Structure<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4579944349315068" nodeRatioY="-0.46400983146067415" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1" yfiles.foldertype="group">
|
||||||
|
<data key="d4" xml:space="preserve"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ProxyAutoBoundsNode>
|
||||||
|
<y:Realizers active="0">
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="217.68500000000003" width="166.9147999999998" x="819.9999999999999" y="180.434265625"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="166.9147999999998" x="13.181757989188668" xml:space="preserve" y="6.728692946816665">Application
|
||||||
|
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||||
|
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="39" topF="38.90500000000003"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||||
|
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
|
||||||
|
<y:Shape type="roundrectangle"/>
|
||||||
|
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||||
|
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
</y:Realizers>
|
||||||
|
</y:ProxyAutoBoundsNode>
|
||||||
|
</data>
|
||||||
|
<graph edgedefault="directed" id="n1:">
|
||||||
|
<node id="n1::n0">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="234.33926562500002"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="113.94921875" x="11.482790624999893" xml:space="preserve" y="4.851562500000028">ACS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1::n1">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="270.5687968750001"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.884765625" x="12.515017187499893" xml:space="preserve" y="4.8515625">EPS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1::n2">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="136.9147999999998" x="834.9999999999999" y="306.84403125000006"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.923828125" x="11.995485937499893" xml:space="preserve" y="4.8515625">TCS Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1::n3">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="136.9147999999998" x="834.9999999999999" y="343.119265625"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="81.259765625" x="27.827517187499893" xml:space="preserve" y="1.703125">Payload
|
||||||
|
Subsystem<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
</graph>
|
||||||
|
</node>
|
||||||
|
<node id="n2" yfiles.foldertype="group">
|
||||||
|
<data key="d4" xml:space="preserve"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ProxyAutoBoundsNode>
|
||||||
|
<y:Realizers active="0">
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="261.7582999999998" width="498.25116669136776" x="971.9147999999997" y="151.36096562500023"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="right" autoSizePolicy="content" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="4.0" x="247.12558334568382" y="0.0"/>
|
||||||
|
<y:Shape type="roundrectangle"/>
|
||||||
|
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||||
|
<y:BorderInsets bottom="0" bottomF="0.0" left="7" leftF="7.00529999999992" right="3" rightF="3.313607121059931" top="14" topF="14.073299999999762"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||||
|
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 4</y:NodeLabel>
|
||||||
|
<y:Shape type="roundrectangle"/>
|
||||||
|
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||||
|
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
</y:Realizers>
|
||||||
|
</y:ProxyAutoBoundsNode>
|
||||||
|
</data>
|
||||||
|
<graph edgedefault="directed" id="n2:">
|
||||||
|
<node id="n2::n0" yfiles.foldertype="group">
|
||||||
|
<data key="d4" xml:space="preserve"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ProxyAutoBoundsNode>
|
||||||
|
<y:Realizers active="0">
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="217.68500000000003" width="441.0158999999999" x="993.9200999999996" y="180.434265625"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="441.0158999999999" x="16.916359570308032" xml:space="preserve" y="5.969557999968089">Generic Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.46164229096885634" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4725770815629552" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
|
||||||
|
<y:BorderInsets bottom="22" bottomF="22.006468749999954" left="0" leftF="0.0" right="0" rightF="0.0" top="28" topF="27.68500000000006"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
<y:GroupNode>
|
||||||
|
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
|
||||||
|
<y:Fill color="#F5F5F5" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
|
||||||
|
<y:Shape type="roundrectangle"/>
|
||||||
|
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
|
||||||
|
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
|
||||||
|
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
|
||||||
|
</y:GroupNode>
|
||||||
|
</y:Realizers>
|
||||||
|
</y:ProxyAutoBoundsNode>
|
||||||
|
</data>
|
||||||
|
<graph edgedefault="directed" id="n2::n0:">
|
||||||
|
<node id="n2::n0::n0">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1151.9280499999995" y="281.84403125000006"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.255859375" x="24.3720703125" xml:space="preserve" y="4.8515625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n1">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1151.9280499999995" y="330.5687968750001"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.7119140625" x="9.64404296875" xml:space="preserve" y="4.8515625">TCP/IP Servers<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n2">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1294.9359999999995" y="281.84403125000006"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.1689453125" x="24.91552734375" xml:space="preserve" y="4.8515625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n3">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1008.9200999999996" y="223.11926562500005"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="7.53857421875" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n4">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1008.9200999999996" y="267.1160312500001"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.7021484375" x="3.64892578125" xml:space="preserve" y="4.8515625">PUS Distribution<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n5">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="40.0" width="125.0" x="1151.9280499999995" y="223.11926562500005"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="20.41748046875" xml:space="preserve" y="1.7031249999999716">Shared
|
||||||
|
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n6">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="50.0" width="125.0" x="1008.9200999999996" y="311.1127968750001"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.12890625" x="23.435546875" xml:space="preserve" y="6.703125">Satellite
|
||||||
|
Mode Tree<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2::n0::n7">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="125.0" x="1294.9359999999995" y="223.11926562500005"/>
|
||||||
|
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="74.7861328125" x="25.10693359375" xml:space="preserve" y="4.8515625"> PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
</graph>
|
||||||
|
</node>
|
||||||
|
</graph>
|
||||||
|
</node>
|
||||||
|
<node id="n3">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="57.265600000000006" width="631.1152" x="810.8847999999999" y="411.39428125"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="261.8125" x="166.89412267941418" xml:space="preserve" y="3.144146301369915">satrs-satellite
|
||||||
|
Simulator based on asynchronix<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n4">
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="50.0" width="631.1152000000002" x="810.8847999999998" y="476.2958625000002"/>
|
||||||
|
<y:Fill hasColor="false" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="374.8359375" x="110.3824039294143" xml:space="preserve" y="0.12842465753431043">satrs-tmtc
|
||||||
|
Command-line interface based TMTC handling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-0.028136269449041573" nodeRatioY="-0.08493150684931505" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
</graph>
|
||||||
|
<data key="d7">
|
||||||
|
<y:Resources/>
|
||||||
|
</data>
|
||||||
|
</graphml>
|
1051
images/satrs-example-goal/satrs-example-goal.pdf
Normal file
1051
images/satrs-example-goal/satrs-example-goal.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
misc/satrs-logo-v2.png
Normal file
BIN
misc/satrs-logo-v2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
@ -15,7 +15,7 @@ action commanding could look like.
|
|||||||
2. Target ID and Action String based. The target ID is the same as in the first proposal, but
|
2. Target ID and Action String based. The target ID is the same as in the first proposal, but
|
||||||
the unique action is identified by a string.
|
the unique action is identified by a string.
|
||||||
|
|
||||||
The framework provides an `ActionRequest` abstraction to model both of these cases.
|
The library provides an `ActionRequest` abstraction to model both of these cases.
|
||||||
|
|
||||||
## Commanding with ECSS PUS 8
|
## Commanding with ECSS PUS 8
|
||||||
|
|
||||||
|
@ -17,14 +17,14 @@ it is still centered around small packets. `sat-rs` provides support for these E
|
|||||||
standards and also attempts to fill the gap to the internet protocol by providing the following
|
standards and also attempts to fill the gap to the internet protocol by providing the following
|
||||||
components.
|
components.
|
||||||
|
|
||||||
1. [UDP TMTC Server](https://docs.rs/satrs-core/0.1.0-alpha.0/satrs_core/hal/host/udp_server/index.html).
|
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/index.html).
|
||||||
UDP is already packet based which makes it an excellent fit for exchanging space packets.
|
UDP is already packet based which makes it an excellent fit for exchanging space packets.
|
||||||
2. [TCP TMTC Server Components](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/index.html).
|
2. [TCP TMTC Server Components](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/index.html).
|
||||||
TCP is a stream based protocol, so the framework provides building blocks to parse telemetry
|
TCP is a stream based protocol, so the library provides building blocks to parse telemetry
|
||||||
from an arbitrary bytestream. Two concrete implementations are provided:
|
from an arbitrary bytestream. Two concrete implementations are provided:
|
||||||
- [TCP spacepackets server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
- [TCP spacepackets server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
||||||
to parse tightly packed CCSDS Spacepackets.
|
to parse tightly packed CCSDS Spacepackets.
|
||||||
- [TCP COBS server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html)
|
- [TCP COBS server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html)
|
||||||
to parse generic frames wrapped with the
|
to parse generic frames wrapped with the
|
||||||
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
||||||
|
|
||||||
@ -39,8 +39,12 @@ task might be to store all arriving telemetry persistently. This is especially i
|
|||||||
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
|
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
|
||||||
|
|
||||||
The most important task of a TC source is to deliver the telecommands to the correct recipients.
|
The most important task of a TC source is to deliver the telecommands to the correct recipients.
|
||||||
For modern component oriented software using message passing, this usually includes staged
|
For component oriented software using message passing, this usually includes staged demultiplexing
|
||||||
demultiplexing components to determine where a command needs to be sent.
|
components to determine where a command needs to be sent.
|
||||||
|
|
||||||
|
Using a generic concept of a TC source and a TM sink as part of the software design simplifies
|
||||||
|
the flexibility of the TMTC infrastructure: Newly added TM generators and TC receiver only have to
|
||||||
|
forward their generated or received packets to those handler objects.
|
||||||
|
|
||||||
# Low-level protocols and the bridge to the communcation subsystem
|
# Low-level protocols and the bridge to the communcation subsystem
|
||||||
|
|
||||||
|
@ -26,15 +26,17 @@ For example, a very small telecommand (TC) pool might look like this:
|
|||||||

|

|
||||||
|
|
||||||
The core of the pool abstractions is the
|
The core of the pool abstractions is the
|
||||||
[PoolProvider trait](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/trait.PoolProvider.html).
|
[PoolProvider trait](https://docs.rs/satrs/latest/satrs/pool/trait.PoolProvider.html).
|
||||||
This trait specifies the general API a pool structure should have without making assumption
|
This trait specifies the general API a pool structure should have without making assumption
|
||||||
of how the data is stored.
|
of how the data is stored.
|
||||||
|
|
||||||
This trait is implemented by a static memory pool implementation.
|
This trait is implemented by a static memory pool implementation.
|
||||||
The code to generate this static pool would look like this:
|
The code to generate this static pool would look like this:
|
||||||
|
|
||||||
```rust
|
<!-- Would be nice to test this code sample, but need to wait
|
||||||
use satrs_core::pool::{StaticMemoryPool, StaticPoolConfig};
|
for https://github.com/rust-lang/mdBook/issues/706 to be merged.. -->
|
||||||
|
```rust, ignore
|
||||||
|
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
||||||
|
|
||||||
let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec
|
- [`StaticPoolConfig` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticPoolConfig.html)
|
||||||
- [`StaticMemoryPool` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticMemoryPool.html)
|
- [`StaticMemoryPool` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticMemoryPool.html)
|
||||||
|
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
# Framework Design
|
# Library Design
|
||||||
|
|
||||||
Satellites and space systems in general are complex systems with a wide range of requirements for
|
Satellites and space systems in general are complex systems with a wide range of requirements for
|
||||||
both the hardware and the software. Consequently, the general design of the framework is centered
|
both the hardware and the software. Consequently, the general design of the library is centered
|
||||||
around many light-weight components which try to impose as few restrictions as possible on how to
|
around many light-weight components which try to impose as few restrictions as possible on how to
|
||||||
solve certain problems.
|
solve certain problems. This is also the reason why sat-rs is explicitely called a library
|
||||||
|
instead of a framework.
|
||||||
|
|
||||||
There are still a lot of common patterns and architectures across these systems where guidance
|
There are still a lot of common patterns and architectures across these systems where guidance
|
||||||
of how to solve a problem and a common structure would still be extremely useful to avoid pitfalls
|
of how to solve a problem and a common structure would still be extremely useful to avoid pitfalls
|
||||||
which were already solved and to avoid boilerplate code. This framework tries to provide this
|
which were already solved and to avoid boilerplate code. This library tries to provide this
|
||||||
structure and guidance the following way:
|
structure and guidance the following way:
|
||||||
|
|
||||||
1. Providing this book which explains the architecture and design patterns in respect to common
|
1. Providing this book which explains the architecture and design patterns in respect to common
|
||||||
@ -18,7 +19,7 @@ structure and guidance the following way:
|
|||||||
3. Providing a good test suite. This includes both unittests and integration tests. The integration
|
3. Providing a good test suite. This includes both unittests and integration tests. The integration
|
||||||
tests can also serve as smaller usage examples than the large `satrs-example` application.
|
tests can also serve as smaller usage examples than the large `satrs-example` application.
|
||||||
|
|
||||||
This framework has special support for standards used in the space industry. This especially
|
This library has special support for standards used in the space industry. This especially
|
||||||
includes standards provided by Consultative Committee for Space Data Systems (CCSDS) and European
|
includes standards provided by Consultative Committee for Space Data Systems (CCSDS) and European
|
||||||
Cooperation for Space Standardization (ECSS). It does not enforce using any of those standards,
|
Cooperation for Space Standardization (ECSS). It does not enforce using any of those standards,
|
||||||
but it is always recommended to use some sort of standard for interoperability.
|
but it is always recommended to use some sort of standard for interoperability.
|
||||||
@ -30,10 +31,10 @@ Flying Laptop Project by the University of Stuttgart with Airbus Defence and Spa
|
|||||||
It has flight heritage through the 2 mssions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
It has flight heritage through the 2 mssions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
||||||
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
and [EIVE](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/EIVE/).
|
||||||
Therefore, a lot of the design concepts were ported more or less unchanged to the `sat-rs`
|
Therefore, a lot of the design concepts were ported more or less unchanged to the `sat-rs`
|
||||||
framework.
|
library.
|
||||||
FLP is a medium-size small satellite with a higher budget and longer development time than EIVE,
|
FLP is a medium-size small satellite with a higher budget and longer development time than EIVE,
|
||||||
which allowed to build a highly reliable system while EIVE is a smaller 6U+ cubesat which had a
|
which allowed to build a highly reliable system while EIVE is a smaller 6U+ cubesat which had a
|
||||||
shorter development cycle and was built using cheaper COTS components. This framework also tries
|
shorter development cycle and was built using cheaper COTS components. This library also tries
|
||||||
to accumulate the knowledge of developing the OBSW and operating the satellite for both these
|
to accumulate the knowledge of developing the OBSW and operating the satellite for both these
|
||||||
different systems and provide a solution for a wider range of small satellite systems.
|
different systems and provide a solution for a wider range of small satellite systems.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# sat-rs Example Application
|
# sat-rs Example Application
|
||||||
|
|
||||||
The `sat-rs` framework includes a monolithic example application which can be found inside
|
The `sat-rs` library includes a monolithic example application which can be found inside
|
||||||
the [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
the [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example)
|
||||||
subdirectory of the repository. The primary purpose of this example application is to show how
|
subdirectory of the repository. The primary purpose of this example application is to show how
|
||||||
the various components of the sat-rs framework could be used as part of a larger on-board
|
the various components of the sat-rs framework could be used as part of a larger on-board
|
||||||
@ -23,11 +23,11 @@ Some additional explanation is provided for the various components.
|
|||||||
The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This
|
The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This
|
||||||
might be an optional component for an OBSW which is only used during the development phase on
|
might be an optional component for an OBSW which is only used during the development phase on
|
||||||
ground. The UDP server is strongly based on the
|
ground. The UDP server is strongly based on the
|
||||||
[UDP TC server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/udp_server/struct.UdpTcServer.html).
|
[UDP TC server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/struct.UdpTcServer.html).
|
||||||
This server component is wrapped by a TMTC server which handles all telemetry to the last connected
|
This server component is wrapped by a TMTC server which handles all telemetry to the last connected
|
||||||
client.
|
client.
|
||||||
|
|
||||||
The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
|
||||||
class. It parses space packets by using the CCSDS space packet ID as the packet
|
class. It parses space packets by using the CCSDS space packet ID as the packet
|
||||||
start delimiter. All available telemetry will be sent back to a client after having read all
|
start delimiter. All available telemetry will be sent back to a client after having read all
|
||||||
telecommands from the client.
|
telecommands from the client.
|
||||||
@ -51,13 +51,13 @@ services. This currently includes the following services:
|
|||||||
|
|
||||||
- Service 1 for telecommand verification. The verification handling is handled locally: Each
|
- Service 1 for telecommand verification. The verification handling is handled locally: Each
|
||||||
component which generates verification telemetry in some shape or form receives a
|
component which generates verification telemetry in some shape or form receives a
|
||||||
[reporter](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/verification/struct.VerificationReporterWithSender.html)
|
[reporter](https://docs.rs/satrs/latest/satrs/pus/verification/struct.VerificationReporterWithSender.html)
|
||||||
object which can be used to send PUS 1 verification telemetry to the TM funnel.
|
object which can be used to send PUS 1 verification telemetry to the TM funnel.
|
||||||
- Service 3 for housekeeping telemetry handling.
|
- Service 3 for housekeeping telemetry handling.
|
||||||
- Service 5 for management and downlink of on-board events.
|
- Service 5 for management and downlink of on-board events.
|
||||||
- Service 8 for handling on-board actions.
|
- Service 8 for handling on-board actions.
|
||||||
- Service 11 for scheduling telecommands to be released at a specific time. This component
|
- Service 11 for scheduling telecommands to be released at a specific time. This component
|
||||||
uses the [PUS scheduler class](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/scheduler/alloc_mod/struct.PusScheduler.html)
|
uses the [PUS scheduler class](https://docs.rs/satrs/latest/satrs/pus/scheduler/alloc_mod/struct.PusScheduler.html)
|
||||||
which performs the core logic of scheduling telecommands. All telecommands released by the
|
which performs the core logic of scheduling telecommands. All telecommands released by the
|
||||||
scheduler are sent to the central TC source using a message.
|
scheduler are sent to the central TC source using a message.
|
||||||
- Service 17 for test purposes like pings.
|
- Service 17 for test purposes like pings.
|
||||||
@ -65,10 +65,10 @@ services. This currently includes the following services:
|
|||||||
### Event Management Component
|
### Event Management Component
|
||||||
|
|
||||||
An event manager based on the sat-rs
|
An event manager based on the sat-rs
|
||||||
[event manager component](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/event_man/index.html)
|
[event manager component](https://docs.rs/satrs/latest/satrs/event_man/index.html)
|
||||||
is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5
|
is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5
|
||||||
telemetry by the
|
telemetry by the
|
||||||
[PUS event dispatcher](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/event_man/alloc_mod/struct.PusEventDispatcher.html).
|
[PUS event dispatcher](https://docs.rs/satrs/latest/satrs/pus/event_man/alloc_mod/struct.PusEventDispatcher.html).
|
||||||
|
|
||||||
You can read the [events](./events.md) chapter for more in-depth information about event management.
|
You can read the [events](./events.md) chapter for more in-depth information about event management.
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
The sat-rs book
|
The sat-rs book
|
||||||
======
|
======
|
||||||
|
|
||||||
This book is the primary information resource for the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
This book is the primary information resource for the [sat-rs library](https://egit.irs.uni-stuttgart.de/rust/sat-rs)
|
||||||
in addition to the regular API documentation. It contains the following resources:
|
in addition to the regular API documentation. It contains the following resources:
|
||||||
|
|
||||||
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
1. Architecture informations and consideration which would exceeds the scope of the regular API.
|
||||||
@ -12,10 +12,15 @@ in addition to the regular API documentation. It contains the following resource
|
|||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
The primary goal of the sat-rs framework is to provide re-usable components
|
The primary goal of the sat-rs library is to provide re-usable components
|
||||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||||
for the special requirements for these systems.
|
for the special requirements for these systems.
|
||||||
|
|
||||||
|
It should be noted that sat-rs is early-stage software. Important features are missing. New releases
|
||||||
|
with breaking changes are released regularly, with all changes documented inside respective
|
||||||
|
changelog files. You should only use this library if your are willing to work in this
|
||||||
|
environment.
|
||||||
|
|
||||||
A lot of the architecture and general design considerations are based on the
|
A lot of the architecture and general design considerations are based on the
|
||||||
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
|
||||||
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
through the 2 missions [FLP](https://www.irs.uni-stuttgart.de/en/research/satellitetechnology-and-instruments/smallsatelliteprogram/flying-laptop/)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# Modes
|
# Modes
|
||||||
|
|
||||||
Modes are an extremely useful concept for complex system in general. They also allow simplified
|
Modes are an extremely useful concept to model complex systems. They allow simplified
|
||||||
system reasoning for both system operators and OBSW developers. They model the behaviour of a
|
system reasoning for both system operators and OBSW developers. They also provide a way to alter
|
||||||
component and also provide observability of a system. A few examples of how to model
|
the behaviour of a component and also provide observability of a system. A few examples of how to
|
||||||
different components of a space system with modes will be given.
|
model the mode of different components within a space system with modes will be given.
|
||||||
|
|
||||||
## Modelling a pyhsical devices with modes
|
## Pyhsical device component with modes
|
||||||
|
|
||||||
The following simple mode scheme with the following three mode
|
The following simple mode scheme with the following three mode
|
||||||
|
|
||||||
@ -13,7 +13,8 @@ The following simple mode scheme with the following three mode
|
|||||||
- `ON`
|
- `ON`
|
||||||
- `NORMAL`
|
- `NORMAL`
|
||||||
|
|
||||||
can be applied to a large number of simpler devices of a remote system, for example sensors.
|
can be applied to a large number of simpler device controllers of a remote system, for example
|
||||||
|
sensors.
|
||||||
|
|
||||||
1. `OFF` means that a device is physically switched off, and the corresponding software component
|
1. `OFF` means that a device is physically switched off, and the corresponding software component
|
||||||
does not poll the device regularly.
|
does not poll the device regularly.
|
||||||
@ -31,7 +32,7 @@ for the majority of devices:
|
|||||||
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
|
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
|
||||||
before powering off the device.
|
before powering off the device.
|
||||||
|
|
||||||
## Modelling a controller with modes
|
## Controller components with modes
|
||||||
|
|
||||||
Controller components are not modelling physical devices, but a mode scheme is still the best
|
Controller components are not modelling physical devices, but a mode scheme is still the best
|
||||||
way to model most of these components.
|
way to model most of these components.
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
Change Log
|
|
||||||
=======
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
# [unreleased]
|
|
@ -1,9 +0,0 @@
|
|||||||
[](https://crates.io/crates/satrs-core)
|
|
||||||
[](https://docs.rs/satrs-core)
|
|
||||||
|
|
||||||
satrs-core
|
|
||||||
======
|
|
||||||
|
|
||||||
This crate contains the core components of the sat-rs framework.
|
|
||||||
You can find more information on [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
|||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
use spacepackets::PacketId;
|
|
||||||
|
|
||||||
use crate::tmtc::ReceivesTcCore;
|
|
||||||
|
|
||||||
pub trait PacketIdLookup {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl PacketIdLookup for Vec<u16> {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.contains(&packet_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl PacketIdLookup for HashSet<u16> {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.contains(&packet_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketIdLookup for [u16] {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.binary_search(&packet_id).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketIdLookup for &[u16] {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.binary_search(&packet_id).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl PacketIdLookup for Vec<PacketId> {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.contains(&PacketId::from(packet_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl PacketIdLookup for HashSet<PacketId> {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.contains(&PacketId::from(packet_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketIdLookup for [PacketId] {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.binary_search(&PacketId::from(packet_id)).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketIdLookup for &[PacketId] {
|
|
||||||
fn validate(&self, packet_id: u16) -> bool {
|
|
||||||
self.binary_search(&PacketId::from(packet_id)).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the
|
|
||||||
/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then
|
|
||||||
/// uses the length field of the packet to extract CCSDS packets.
|
|
||||||
///
|
|
||||||
/// This function is also able to deal with broken tail packets at the end as long a the parser
|
|
||||||
/// can read the full 7 bytes which constitue a space packet header plus one byte minimal size.
|
|
||||||
/// If broken tail packets are detected, they are moved to the front of the buffer, and the write
|
|
||||||
/// index for future write operations will be written to the `next_write_idx` argument.
|
|
||||||
///
|
|
||||||
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`
|
|
||||||
/// and return the number of packets found. If the [ReceivesTcCore::pass_tc] calls fails, the
|
|
||||||
/// error will be returned.
|
|
||||||
pub fn parse_buffer_for_ccsds_space_packets<E>(
|
|
||||||
buf: &mut [u8],
|
|
||||||
packet_id_lookup: &(impl PacketIdLookup + ?Sized),
|
|
||||||
tc_receiver: &mut (impl ReceivesTcCore<Error = E> + ?Sized),
|
|
||||||
next_write_idx: &mut usize,
|
|
||||||
) -> Result<u32, E> {
|
|
||||||
*next_write_idx = 0;
|
|
||||||
let mut packets_found = 0;
|
|
||||||
let mut current_idx = 0;
|
|
||||||
let buf_len = buf.len();
|
|
||||||
loop {
|
|
||||||
if current_idx + 7 >= buf.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap());
|
|
||||||
if packet_id_lookup.validate(packet_id) {
|
|
||||||
let length_field =
|
|
||||||
u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap());
|
|
||||||
let packet_size = length_field + 7;
|
|
||||||
if (current_idx + packet_size as usize) <= buf_len {
|
|
||||||
tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?;
|
|
||||||
packets_found += 1;
|
|
||||||
} else {
|
|
||||||
// Move packet to start of buffer if applicable.
|
|
||||||
if current_idx > 0 {
|
|
||||||
buf.copy_within(current_idx.., 0);
|
|
||||||
*next_write_idx = buf.len() - current_idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
current_idx += packet_size as usize;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
current_idx += 1;
|
|
||||||
}
|
|
||||||
Ok(packets_found)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use spacepackets::{
|
|
||||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
|
||||||
PacketId, SpHeader,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::encoding::tests::TcCacher;
|
|
||||||
|
|
||||||
use super::parse_buffer_for_ccsds_space_packets;
|
|
||||||
|
|
||||||
const TEST_APID_0: u16 = 0x02;
|
|
||||||
const TEST_APID_1: u16 = 0x10;
|
|
||||||
const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0);
|
|
||||||
const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic() {
|
|
||||||
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let mut buffer: [u8; 32] = [0; 32];
|
|
||||||
let packet_len = ping_tc
|
|
||||||
.write_to_bytes(&mut buffer)
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let valid_packet_ids = [TEST_PACKET_ID_0];
|
|
||||||
let mut tc_cacher = TcCacher::default();
|
|
||||||
let mut next_write_idx = 0;
|
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
|
||||||
&mut buffer,
|
|
||||||
valid_packet_ids.as_slice(),
|
|
||||||
&mut tc_cacher,
|
|
||||||
&mut next_write_idx,
|
|
||||||
);
|
|
||||||
assert!(parse_result.is_ok());
|
|
||||||
let parsed_packets = parse_result.unwrap();
|
|
||||||
assert_eq!(parsed_packets, 1);
|
|
||||||
assert_eq!(tc_cacher.tc_queue.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
tc_cacher.tc_queue.pop_front().unwrap(),
|
|
||||||
buffer[..packet_len]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multi_packet() {
|
|
||||||
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
|
||||||
let mut buffer: [u8; 32] = [0; 32];
|
|
||||||
let packet_len_ping = ping_tc
|
|
||||||
.write_to_bytes(&mut buffer)
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let packet_len_action = action_tc
|
|
||||||
.write_to_bytes(&mut buffer[packet_len_ping..])
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let valid_packet_ids = [TEST_PACKET_ID_0];
|
|
||||||
let mut tc_cacher = TcCacher::default();
|
|
||||||
let mut next_write_idx = 0;
|
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
|
||||||
&mut buffer,
|
|
||||||
valid_packet_ids.as_slice(),
|
|
||||||
&mut tc_cacher,
|
|
||||||
&mut next_write_idx,
|
|
||||||
);
|
|
||||||
assert!(parse_result.is_ok());
|
|
||||||
let parsed_packets = parse_result.unwrap();
|
|
||||||
assert_eq!(parsed_packets, 2);
|
|
||||||
assert_eq!(tc_cacher.tc_queue.len(), 2);
|
|
||||||
assert_eq!(
|
|
||||||
tc_cacher.tc_queue.pop_front().unwrap(),
|
|
||||||
buffer[..packet_len_ping]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tc_cacher.tc_queue.pop_front().unwrap(),
|
|
||||||
buffer[packet_len_ping..packet_len_ping + packet_len_action]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multi_apid() {
|
|
||||||
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
|
||||||
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
|
||||||
let mut buffer: [u8; 32] = [0; 32];
|
|
||||||
let packet_len_ping = ping_tc
|
|
||||||
.write_to_bytes(&mut buffer)
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let packet_len_action = action_tc
|
|
||||||
.write_to_bytes(&mut buffer[packet_len_ping..])
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
|
||||||
let mut tc_cacher = TcCacher::default();
|
|
||||||
let mut next_write_idx = 0;
|
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
|
||||||
&mut buffer,
|
|
||||||
valid_packet_ids.as_slice(),
|
|
||||||
&mut tc_cacher,
|
|
||||||
&mut next_write_idx,
|
|
||||||
);
|
|
||||||
assert!(parse_result.is_ok());
|
|
||||||
let parsed_packets = parse_result.unwrap();
|
|
||||||
assert_eq!(parsed_packets, 2);
|
|
||||||
assert_eq!(tc_cacher.tc_queue.len(), 2);
|
|
||||||
assert_eq!(
|
|
||||||
tc_cacher.tc_queue.pop_front().unwrap(),
|
|
||||||
buffer[..packet_len_ping]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tc_cacher.tc_queue.pop_front().unwrap(),
|
|
||||||
buffer[packet_len_ping..packet_len_ping + packet_len_action]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_packet_multi() {
|
|
||||||
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
|
|
||||||
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
|
|
||||||
let mut buffer: [u8; 32] = [0; 32];
|
|
||||||
let packet_len_ping = ping_tc
|
|
||||||
.write_to_bytes(&mut buffer)
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let packet_len_action = action_tc
|
|
||||||
.write_to_bytes(&mut buffer[packet_len_ping..])
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
|
||||||
let mut tc_cacher = TcCacher::default();
|
|
||||||
let mut next_write_idx = 0;
|
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
|
||||||
&mut buffer[..packet_len_ping + packet_len_action - 4],
|
|
||||||
valid_packet_ids.as_slice(),
|
|
||||||
&mut tc_cacher,
|
|
||||||
&mut next_write_idx,
|
|
||||||
);
|
|
||||||
assert!(parse_result.is_ok());
|
|
||||||
let parsed_packets = parse_result.unwrap();
|
|
||||||
assert_eq!(parsed_packets, 1);
|
|
||||||
assert_eq!(tc_cacher.tc_queue.len(), 1);
|
|
||||||
// The broken packet was moved to the start, so the next write index should be after the
|
|
||||||
// last segment missing 4 bytes.
|
|
||||||
assert_eq!(next_write_idx, packet_len_action - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_one_split_packet() {
|
|
||||||
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
|
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let mut buffer: [u8; 32] = [0; 32];
|
|
||||||
let packet_len_ping = ping_tc
|
|
||||||
.write_to_bytes(&mut buffer)
|
|
||||||
.expect("writing packet failed");
|
|
||||||
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
|
|
||||||
let mut tc_cacher = TcCacher::default();
|
|
||||||
let mut next_write_idx = 0;
|
|
||||||
let parse_result = parse_buffer_for_ccsds_space_packets(
|
|
||||||
&mut buffer[..packet_len_ping - 4],
|
|
||||||
valid_packet_ids.as_slice(),
|
|
||||||
&mut tc_cacher,
|
|
||||||
&mut next_write_idx,
|
|
||||||
);
|
|
||||||
assert_eq!(next_write_idx, 0);
|
|
||||||
assert!(parse_result.is_ok());
|
|
||||||
let parsed_packets = parse_result.unwrap();
|
|
||||||
assert_eq!(parsed_packets, 0);
|
|
||||||
assert_eq!(tc_cacher.tc_queue.len(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,710 +0,0 @@
|
|||||||
//! Event management and forwarding
|
|
||||||
//!
|
|
||||||
//! This module provides components to perform event routing. The most important component for this
|
|
||||||
//! task is the [EventManager]. It receives all events and then routes them to event subscribers
|
|
||||||
//! where appropriate. One common use case for satellite systems is to offer a light-weight
|
|
||||||
//! publish-subscribe mechanism and IPC mechanism for software and hardware events which are also
|
|
||||||
//! packaged as telemetry (TM) or can trigger a system response.
|
|
||||||
//!
|
|
||||||
//! It is recommended to read the
|
|
||||||
//! [sat-rs book chapter](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/events.html)
|
|
||||||
//! about events first:
|
|
||||||
//!
|
|
||||||
//! The event manager has a listener table abstracted by the [ListenerTable], which maps
|
|
||||||
//! listener groups identified by [ListenerKey]s to a [sender ID][ChannelId].
|
|
||||||
//! It also contains a sender table abstracted by the [SenderTable] which maps these sender IDs
|
|
||||||
//! to a concrete [SendEventProvider]s. A simple approach would be to use one send event provider
|
|
||||||
//! for each OBSW thread and then subscribe for all interesting events for a particular thread
|
|
||||||
//! using the send event provider ID.
|
|
||||||
//!
|
|
||||||
//! This can be done with the [EventManager] like this:
|
|
||||||
//!
|
|
||||||
//! 1. Provide a concrete [EventReceiver] implementation. This abstraction allow to use different
|
|
||||||
//! message queue backends. A straightforward implementation where dynamic memory allocation is
|
|
||||||
//! not a big concern could use [std::sync::mpsc::channel] to do this and is provided in
|
|
||||||
//! form of the [MpscEventReceiver].
|
|
||||||
//! 2. To set up event creators, create channel pairs using some message queue implementation.
|
|
||||||
//! Each event creator gets a (cloned) sender component which allows it to send events to the
|
|
||||||
//! manager.
|
|
||||||
//! 3. The event manager receives the receiver component as part of a [EventReceiver]
|
|
||||||
//! implementation so all events are routed to the manager.
|
|
||||||
//! 4. Create the [send event providers][SendEventProvider]s which allow routing events to
|
|
||||||
//! subscribers. You can now use their [sender IDs][SendEventProvider::id] to subscribe for
|
|
||||||
//! event groups, for example by using the [EventManager::subscribe_single] method.
|
|
||||||
//! 5. Add the send provider as well using the [EventManager::add_sender] call so the event
|
|
||||||
//! manager can route listener groups to a the send provider.
|
|
||||||
//!
|
|
||||||
//! Some components like a PUS Event Service or PUS Event Action Service might require all
|
|
||||||
//! events to package them as telemetry or start actions where applicable.
|
|
||||||
//! Other components might only be interested in certain events. For example, a thermal system
|
|
||||||
//! handler might only be interested in temperature events generated by a thermal sensor component.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! You can check [integration test](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/pus_events.rs)
|
|
||||||
//! for a concrete example using multi-threading where events are routed to
|
|
||||||
//! different threads.
|
|
||||||
use crate::events::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
|
|
||||||
use crate::params::{Params, ParamsHeapless};
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::vec;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::slice::Iter;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
|
|
||||||
use crate::ChannelId;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub use stdmod::*;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
|
||||||
pub enum ListenerKey {
|
|
||||||
Single(LargestEventRaw),
|
|
||||||
Group(LargestGroupIdRaw),
|
|
||||||
All,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type EventWithHeaplessAuxData<Event> = (Event, Option<ParamsHeapless>);
|
|
||||||
pub type EventU32WithHeaplessAuxData = EventWithHeaplessAuxData<EventU32>;
|
|
||||||
pub type EventU16WithHeaplessAuxData = EventWithHeaplessAuxData<EventU16>;
|
|
||||||
|
|
||||||
pub type EventWithAuxData<Event> = (Event, Option<Params>);
|
|
||||||
pub type EventU32WithAuxData = EventWithAuxData<EventU32>;
|
|
||||||
pub type EventU16WithAuxData = EventWithAuxData<EventU16>;
|
|
||||||
|
|
||||||
pub trait SendEventProvider<Provider: GenericEvent, AuxDataProvider = Params> {
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn id(&self) -> ChannelId;
|
|
||||||
fn send_no_data(&self, event: Provider) -> Result<(), Self::Error> {
|
|
||||||
self.send(event, None)
|
|
||||||
}
|
|
||||||
fn send(&self, event: Provider, aux_data: Option<AuxDataProvider>) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic abstraction for an event receiver.
|
|
||||||
pub trait EventReceiver<Event: GenericEvent, AuxDataProvider = Params> {
|
|
||||||
/// This function has to be provided by any event receiver. A receive call may or may not return
|
|
||||||
/// an event.
|
|
||||||
///
|
|
||||||
/// To allow returning arbitrary additional auxiliary data, a mutable slice is passed to the
|
|
||||||
/// [Self::receive] call as well. Receivers can write data to this slice, but care must be taken
|
|
||||||
/// to avoid panics due to size missmatches or out of bound writes.
|
|
||||||
fn receive(&self) -> Option<(Event, Option<AuxDataProvider>)>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ListenerTable {
|
|
||||||
fn get_listeners(&self) -> Vec<ListenerKey>;
|
|
||||||
fn contains_listener(&self, key: &ListenerKey) -> bool;
|
|
||||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>>;
|
|
||||||
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool;
|
|
||||||
fn remove_duplicates(&mut self, key: &ListenerKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SenderTable<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params> {
|
|
||||||
fn contains_send_event_provider(&self, id: &ChannelId) -> bool;
|
|
||||||
fn get_send_event_provider(
|
|
||||||
&self,
|
|
||||||
id: &ChannelId,
|
|
||||||
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>;
|
|
||||||
fn add_send_event_provider(
|
|
||||||
&mut self,
|
|
||||||
send_provider: Box<
|
|
||||||
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
|
||||||
>,
|
|
||||||
) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic event manager implementation.
|
|
||||||
///
|
|
||||||
/// # Generics
|
|
||||||
///
|
|
||||||
/// * `SendProviderError`: [SendEventProvider] error type
|
|
||||||
/// * `Event`: Concrete event provider, currently either [EventU32] or [EventU16]
|
|
||||||
/// * `AuxDataProvider`: Concrete auxiliary data provider, currently either [Params] or
|
|
||||||
/// [ParamsHeapless]
|
|
||||||
pub struct EventManager<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params>
|
|
||||||
{
|
|
||||||
listener_table: Box<dyn ListenerTable>,
|
|
||||||
sender_table: Box<dyn SenderTable<SendProviderError, Event, AuxDataProvider>>,
|
|
||||||
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Safety: It is safe to implement [Send] because all fields in the [EventManager] are [Send]
|
|
||||||
/// as well
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
unsafe impl<E, Event: GenericEvent + Send, AuxDataProvider: Send> Send
|
|
||||||
for EventManager<E, Event, AuxDataProvider>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub type EventManagerWithMpscQueue<Event, AuxDataProvider> = EventManager<
|
|
||||||
std::sync::mpsc::SendError<(Event, Option<AuxDataProvider>)>,
|
|
||||||
Event,
|
|
||||||
AuxDataProvider,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventRoutingResult<Event: GenericEvent, AuxDataProvider> {
|
|
||||||
/// No event was received
|
|
||||||
Empty,
|
|
||||||
/// An event was received and routed.
|
|
||||||
/// The first tuple entry will contain the number of recipients.
|
|
||||||
Handled(u32, Event, Option<AuxDataProvider>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventRoutingError<E> {
|
|
||||||
SendError(E),
|
|
||||||
NoSendersForKey(ListenerKey),
|
|
||||||
NoSenderForId(ChannelId),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EventRoutingErrorsWithResult<Event: GenericEvent, AuxDataProvider, E> {
|
|
||||||
pub result: EventRoutingResult<Event, AuxDataProvider>,
|
|
||||||
pub errors: [Option<EventRoutingError<E>>; 3],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E, Event: GenericEvent + Copy> EventManager<E, Event> {
|
|
||||||
pub fn remove_duplicates(&mut self, key: &ListenerKey) {
|
|
||||||
self.listener_table.remove_duplicates(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe for a unique event.
|
|
||||||
pub fn subscribe_single(&mut self, event: &Event, sender_id: ChannelId) {
|
|
||||||
self.update_listeners(ListenerKey::Single(event.raw_as_largest_type()), sender_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe for an event group.
|
|
||||||
pub fn subscribe_group(&mut self, group_id: LargestGroupIdRaw, sender_id: ChannelId) {
|
|
||||||
self.update_listeners(ListenerKey::Group(group_id), sender_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe for all events received by the manager.
|
|
||||||
///
|
|
||||||
/// For example, this can be useful for a handler component which sends every event as
|
|
||||||
/// a telemetry packet.
|
|
||||||
pub fn subscribe_all(&mut self, sender_id: ChannelId) {
|
|
||||||
self.update_listeners(ListenerKey::All, sender_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static, Event: GenericEvent + Copy + 'static, AuxDataProvider: Clone + 'static>
|
|
||||||
EventManager<E, Event, AuxDataProvider>
|
|
||||||
{
|
|
||||||
/// Create an event manager where the sender table will be the [DefaultSenderTableProvider]
|
|
||||||
/// and the listener table will be the [DefaultListenerTableProvider].
|
|
||||||
pub fn new(event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>) -> Self {
|
|
||||||
let listener_table: Box<DefaultListenerTableProvider> = Box::default();
|
|
||||||
let sender_table: Box<DefaultSenderTableProvider<E, Event, AuxDataProvider>> =
|
|
||||||
Box::default();
|
|
||||||
Self::new_custom_tables(listener_table, sender_table, event_receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
|
|
||||||
EventManager<E, Event, AuxDataProvider>
|
|
||||||
{
|
|
||||||
pub fn new_custom_tables(
|
|
||||||
listener_table: Box<dyn ListenerTable>,
|
|
||||||
sender_table: Box<dyn SenderTable<E, Event, AuxDataProvider>>,
|
|
||||||
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
|
||||||
) -> Self {
|
|
||||||
EventManager {
|
|
||||||
listener_table,
|
|
||||||
sender_table,
|
|
||||||
event_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_sender(
|
|
||||||
&mut self,
|
|
||||||
send_provider: impl SendEventProvider<Event, AuxDataProvider, Error = E> + 'static,
|
|
||||||
) {
|
|
||||||
if !self
|
|
||||||
.sender_table
|
|
||||||
.contains_send_event_provider(&send_provider.id())
|
|
||||||
{
|
|
||||||
self.sender_table
|
|
||||||
.add_send_event_provider(Box::new(send_provider));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_listeners(&mut self, key: ListenerKey, sender_id: ChannelId) {
|
|
||||||
self.listener_table.add_listener(key, sender_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function will use the cached event receiver and try to receive one event.
|
|
||||||
/// If an event was received, it will try to route that event to all subscribed event listeners.
|
|
||||||
/// If this works without any issues, the [EventRoutingResult] will contain context information
|
|
||||||
/// about the routed event.
|
|
||||||
///
|
|
||||||
/// This function will track up to 3 errors returned as part of the
|
|
||||||
/// [EventRoutingErrorsWithResult] error struct.
|
|
||||||
pub fn try_event_handling(
|
|
||||||
&self,
|
|
||||||
) -> Result<
|
|
||||||
EventRoutingResult<Event, AuxDataProvider>,
|
|
||||||
EventRoutingErrorsWithResult<Event, AuxDataProvider, E>,
|
|
||||||
> {
|
|
||||||
let mut err_idx = 0;
|
|
||||||
let mut err_slice = [None, None, None];
|
|
||||||
let mut num_recipients = 0;
|
|
||||||
let mut add_error = |error: EventRoutingError<E>| {
|
|
||||||
if err_idx < 3 {
|
|
||||||
err_slice[err_idx] = Some(error);
|
|
||||||
err_idx += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut send_handler =
|
|
||||||
|key: &ListenerKey, event: Event, aux_data: &Option<AuxDataProvider>| {
|
|
||||||
if self.listener_table.contains_listener(key) {
|
|
||||||
if let Some(ids) = self.listener_table.get_listener_ids(key) {
|
|
||||||
for id in ids {
|
|
||||||
if let Some(sender) = self.sender_table.get_send_event_provider(id) {
|
|
||||||
if let Err(e) = sender.send(event, aux_data.clone()) {
|
|
||||||
add_error(EventRoutingError::SendError(e));
|
|
||||||
} else {
|
|
||||||
num_recipients += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
add_error(EventRoutingError::NoSenderForId(*id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
add_error(EventRoutingError::NoSendersForKey(*key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some((event, aux_data)) = self.event_receiver.receive() {
|
|
||||||
let single_key = ListenerKey::Single(event.raw_as_largest_type());
|
|
||||||
send_handler(&single_key, event, &aux_data);
|
|
||||||
let group_key = ListenerKey::Group(event.group_id_as_largest_type());
|
|
||||||
send_handler(&group_key, event, &aux_data);
|
|
||||||
send_handler(&ListenerKey::All, event, &aux_data);
|
|
||||||
if err_idx > 0 {
|
|
||||||
return Err(EventRoutingErrorsWithResult {
|
|
||||||
result: EventRoutingResult::Handled(num_recipients, event, aux_data),
|
|
||||||
errors: err_slice,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Ok(EventRoutingResult::Handled(num_recipients, event, aux_data));
|
|
||||||
}
|
|
||||||
Ok(EventRoutingResult::Empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DefaultListenerTableProvider {
|
|
||||||
listeners: HashMap<ListenerKey, Vec<ChannelId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DefaultSenderTableProvider<
|
|
||||||
SendProviderError,
|
|
||||||
Event: GenericEvent = EventU32,
|
|
||||||
AuxDataProvider = Params,
|
|
||||||
> {
|
|
||||||
senders: HashMap<
|
|
||||||
ChannelId,
|
|
||||||
Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SendProviderError, Event: GenericEvent, AuxDataProvider> Default
|
|
||||||
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
senders: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListenerTable for DefaultListenerTableProvider {
|
|
||||||
fn get_listeners(&self) -> Vec<ListenerKey> {
|
|
||||||
let mut key_list = Vec::new();
|
|
||||||
for key in self.listeners.keys() {
|
|
||||||
key_list.push(*key);
|
|
||||||
}
|
|
||||||
key_list
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_listener(&self, key: &ListenerKey) -> bool {
|
|
||||||
self.listeners.contains_key(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>> {
|
|
||||||
self.listeners.get(key).map(|vec| vec.iter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool {
|
|
||||||
if let Some(existing_list) = self.listeners.get_mut(&key) {
|
|
||||||
existing_list.push(sender_id);
|
|
||||||
} else {
|
|
||||||
let new_list = vec![sender_id];
|
|
||||||
self.listeners.insert(key, new_list);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_duplicates(&mut self, key: &ListenerKey) {
|
|
||||||
if let Some(list) = self.listeners.get_mut(key) {
|
|
||||||
list.sort_unstable();
|
|
||||||
list.dedup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SendProviderError, Event: GenericEvent, AuxDataProvider>
|
|
||||||
SenderTable<SendProviderError, Event, AuxDataProvider>
|
|
||||||
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
|
||||||
{
|
|
||||||
fn contains_send_event_provider(&self, id: &ChannelId) -> bool {
|
|
||||||
self.senders.contains_key(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_send_event_provider(
|
|
||||||
&self,
|
|
||||||
id: &ChannelId,
|
|
||||||
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>> {
|
|
||||||
self.senders
|
|
||||||
.get(id)
|
|
||||||
.filter(|sender| sender.id() == *id)
|
|
||||||
.map(|v| v.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_send_event_provider(
|
|
||||||
&mut self,
|
|
||||||
send_provider: Box<
|
|
||||||
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
|
||||||
>,
|
|
||||||
) -> bool {
|
|
||||||
let id = send_provider.id();
|
|
||||||
if self.senders.contains_key(&id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.senders.insert(id, send_provider).is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub mod stdmod {
|
|
||||||
use super::*;
|
|
||||||
use crate::event_man::{EventReceiver, EventWithAuxData};
|
|
||||||
use crate::events::{EventU16, EventU32, GenericEvent};
|
|
||||||
use crate::params::Params;
|
|
||||||
use std::sync::mpsc::{Receiver, SendError, Sender};
|
|
||||||
|
|
||||||
pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> {
|
|
||||||
mpsc_receiver: Receiver<(Event, Option<Params>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent + Send> MpscEventReceiver<Event> {
|
|
||||||
pub fn new(receiver: Receiver<(Event, Option<Params>)>) -> Self {
|
|
||||||
Self {
|
|
||||||
mpsc_receiver: receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Event: GenericEvent + Send> EventReceiver<Event> for MpscEventReceiver<Event> {
|
|
||||||
fn receive(&self) -> Option<EventWithAuxData<Event>> {
|
|
||||||
if let Ok(event_and_data) = self.mpsc_receiver.try_recv() {
|
|
||||||
return Some(event_and_data);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type MpscEventU32Receiver = MpscEventReceiver<EventU32>;
|
|
||||||
pub type MpscEventU16Receiver = MpscEventReceiver<EventU16>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MpscEventSendProvider<Event: GenericEvent + Send> {
|
|
||||||
id: u32,
|
|
||||||
sender: Sender<(Event, Option<Params>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent + Send> MpscEventSendProvider<Event> {
|
|
||||||
pub fn new(id: u32, sender: Sender<(Event, Option<Params>)>) -> Self {
|
|
||||||
Self { id, sender }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Event: GenericEvent + Send> SendEventProvider<Event> for MpscEventSendProvider<Event> {
|
|
||||||
type Error = SendError<(Event, Option<Params>)>;
|
|
||||||
|
|
||||||
fn id(&self) -> u32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
|
||||||
self.sender.send((event, aux_data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type MpscEventU32SendProvider = MpscEventSendProvider<EventU32>;
|
|
||||||
pub type MpscEventU16SendProvider = MpscEventSendProvider<EventU16>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::event_man::EventManager;
|
|
||||||
use crate::events::{EventU32, GenericEvent, Severity};
|
|
||||||
use crate::params::ParamsRaw;
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use std::format;
|
|
||||||
use std::sync::mpsc::{channel, Receiver, SendError, Sender};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct MpscEventSenderQueue {
|
|
||||||
id: u32,
|
|
||||||
mpsc_sender: Sender<EventU32WithAuxData>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MpscEventSenderQueue {
|
|
||||||
fn new(id: u32, mpsc_sender: Sender<EventU32WithAuxData>) -> Self {
|
|
||||||
Self { id, mpsc_sender }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SendEventProvider<EventU32> for MpscEventSenderQueue {
|
|
||||||
type Error = SendError<EventU32WithAuxData>;
|
|
||||||
|
|
||||||
fn id(&self) -> u32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
fn send(&self, event: EventU32, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
|
||||||
self.mpsc_sender.send((event, aux_data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_next_event(
|
|
||||||
expected: EventU32,
|
|
||||||
receiver: &Receiver<EventU32WithAuxData>,
|
|
||||||
) -> Option<Params> {
|
|
||||||
if let Ok(event) = receiver.try_recv() {
|
|
||||||
assert_eq!(event.0, expected);
|
|
||||||
return event.1;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_handled_event(
|
|
||||||
res: EventRoutingResult<EventU32, Params>,
|
|
||||||
expected: EventU32,
|
|
||||||
expected_num_sent: u32,
|
|
||||||
) {
|
|
||||||
assert!(matches!(res, EventRoutingResult::Handled { .. }));
|
|
||||||
if let EventRoutingResult::Handled(num_recipients, event, _aux_data) = res {
|
|
||||||
assert_eq!(event, expected);
|
|
||||||
assert_eq!(num_recipients, expected_num_sent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generic_event_man() -> (
|
|
||||||
Sender<EventU32WithAuxData>,
|
|
||||||
EventManager<SendError<EventU32WithAuxData>>,
|
|
||||||
) {
|
|
||||||
let (event_sender, manager_queue) = channel();
|
|
||||||
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
|
||||||
(
|
|
||||||
event_sender,
|
|
||||||
EventManager::new(Box::new(event_man_receiver)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic() {
|
|
||||||
let (event_sender, mut event_man) = generic_event_man();
|
|
||||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
|
||||||
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
|
||||||
let (single_event_sender, single_event_receiver) = channel();
|
|
||||||
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
|
||||||
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
|
||||||
event_man.add_sender(single_event_listener);
|
|
||||||
let (group_event_sender_0, group_event_receiver_0) = channel();
|
|
||||||
let group_event_listener = MpscEventSenderQueue {
|
|
||||||
id: 1,
|
|
||||||
mpsc_sender: group_event_sender_0,
|
|
||||||
};
|
|
||||||
event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.id());
|
|
||||||
event_man.add_sender(group_event_listener);
|
|
||||||
|
|
||||||
// Test event with one listener
|
|
||||||
event_sender
|
|
||||||
.send((event_grp_0, None))
|
|
||||||
.expect("Sending single error failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
|
||||||
check_next_event(event_grp_0, &single_event_receiver);
|
|
||||||
|
|
||||||
// Test event which is sent to all group listeners
|
|
||||||
event_sender
|
|
||||||
.send((event_grp_1_0, None))
|
|
||||||
.expect("Sending group error failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
|
||||||
check_next_event(event_grp_1_0, &group_event_receiver_0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_with_basic_aux_data() {
|
|
||||||
let (event_sender, mut event_man) = generic_event_man();
|
|
||||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
|
||||||
let (single_event_sender, single_event_receiver) = channel();
|
|
||||||
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
|
||||||
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
|
||||||
event_man.add_sender(single_event_listener);
|
|
||||||
event_sender
|
|
||||||
.send((event_grp_0, Some(Params::Heapless((2_u32, 3_u32).into()))))
|
|
||||||
.expect("Sending group error failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
|
||||||
let aux = check_next_event(event_grp_0, &single_event_receiver);
|
|
||||||
assert!(aux.is_some());
|
|
||||||
let aux = aux.unwrap();
|
|
||||||
if let Params::Heapless(ParamsHeapless::Raw(ParamsRaw::U32Pair(pair))) = aux {
|
|
||||||
assert_eq!(pair.0, 2);
|
|
||||||
assert_eq!(pair.1, 3);
|
|
||||||
} else {
|
|
||||||
panic!("{}", format!("Unexpected auxiliary value type {:?}", aux));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test listening for multiple groups
|
|
||||||
#[test]
|
|
||||||
fn test_multi_group() {
|
|
||||||
let (event_sender, mut event_man) = generic_event_man();
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let hres = res.unwrap();
|
|
||||||
assert!(matches!(hres, EventRoutingResult::Empty));
|
|
||||||
|
|
||||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
|
||||||
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
|
||||||
let (event_grp_0_sender, event_grp_0_receiver) = channel();
|
|
||||||
let event_grp_0_and_1_listener = MpscEventSenderQueue {
|
|
||||||
id: 0,
|
|
||||||
mpsc_sender: event_grp_0_sender,
|
|
||||||
};
|
|
||||||
event_man.subscribe_group(event_grp_0.group_id(), event_grp_0_and_1_listener.id());
|
|
||||||
event_man.subscribe_group(event_grp_1_0.group_id(), event_grp_0_and_1_listener.id());
|
|
||||||
event_man.add_sender(event_grp_0_and_1_listener);
|
|
||||||
|
|
||||||
event_sender
|
|
||||||
.send((event_grp_0, None))
|
|
||||||
.expect("Sending Event Group 0 failed");
|
|
||||||
event_sender
|
|
||||||
.send((event_grp_1_0, None))
|
|
||||||
.expect("Sendign Event Group 1 failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
|
||||||
|
|
||||||
check_next_event(event_grp_0, &event_grp_0_receiver);
|
|
||||||
check_next_event(event_grp_1_0, &event_grp_0_receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test listening to the same event from multiple listeners. Also test listening
|
|
||||||
/// to both group and single events from one listener
|
|
||||||
#[test]
|
|
||||||
fn test_listening_to_same_event_and_multi_type() {
|
|
||||||
let (event_sender, mut event_man) = generic_event_man();
|
|
||||||
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
|
||||||
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
|
||||||
let (event_0_tx_0, event_0_rx_0) = channel();
|
|
||||||
let (event_0_tx_1, event_0_rx_1) = channel();
|
|
||||||
let event_listener_0 = MpscEventSenderQueue {
|
|
||||||
id: 0,
|
|
||||||
mpsc_sender: event_0_tx_0,
|
|
||||||
};
|
|
||||||
let event_listener_1 = MpscEventSenderQueue {
|
|
||||||
id: 1,
|
|
||||||
mpsc_sender: event_0_tx_1,
|
|
||||||
};
|
|
||||||
let event_listener_0_sender_id = event_listener_0.id();
|
|
||||||
event_man.subscribe_single(&event_0, event_listener_0_sender_id);
|
|
||||||
event_man.add_sender(event_listener_0);
|
|
||||||
let event_listener_1_sender_id = event_listener_1.id();
|
|
||||||
event_man.subscribe_single(&event_0, event_listener_1_sender_id);
|
|
||||||
event_man.add_sender(event_listener_1);
|
|
||||||
event_sender
|
|
||||||
.send((event_0, None))
|
|
||||||
.expect("Triggering Event 0 failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_0, 2);
|
|
||||||
check_next_event(event_0, &event_0_rx_0);
|
|
||||||
check_next_event(event_0, &event_0_rx_1);
|
|
||||||
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
|
||||||
event_sender
|
|
||||||
.send((event_0, None))
|
|
||||||
.expect("Triggering Event 0 failed");
|
|
||||||
event_sender
|
|
||||||
.send((event_1, None))
|
|
||||||
.expect("Triggering Event 1 failed");
|
|
||||||
|
|
||||||
// 3 Events messages will be sent now
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_0, 2);
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_1, 1);
|
|
||||||
// Both the single event and the group event should arrive now
|
|
||||||
check_next_event(event_0, &event_0_rx_0);
|
|
||||||
check_next_event(event_1, &event_0_rx_0);
|
|
||||||
|
|
||||||
// Do double insertion and then remove duplicates
|
|
||||||
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
|
||||||
event_man.remove_duplicates(&ListenerKey::Group(event_1.group_id()));
|
|
||||||
event_sender
|
|
||||||
.send((event_1, None))
|
|
||||||
.expect("Triggering Event 1 failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_all_events_listener() {
|
|
||||||
let (event_sender, manager_queue) = channel();
|
|
||||||
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
|
||||||
let mut event_man: EventManager<SendError<EventU32WithAuxData>> =
|
|
||||||
EventManager::new(Box::new(event_man_receiver));
|
|
||||||
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
|
||||||
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
|
||||||
let (event_0_tx_0, all_events_rx) = channel();
|
|
||||||
let all_events_listener = MpscEventSenderQueue {
|
|
||||||
id: 0,
|
|
||||||
mpsc_sender: event_0_tx_0,
|
|
||||||
};
|
|
||||||
event_man.subscribe_all(all_events_listener.id());
|
|
||||||
event_man.add_sender(all_events_listener);
|
|
||||||
event_sender
|
|
||||||
.send((event_0, None))
|
|
||||||
.expect("Triggering event 0 failed");
|
|
||||||
event_sender
|
|
||||||
.send((event_1, None))
|
|
||||||
.expect("Triggering event 1 failed");
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_0, 1);
|
|
||||||
let res = event_man.try_event_handling();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
check_handled_event(res.unwrap(), event_1, 1);
|
|
||||||
check_next_event(event_0, &all_events_rx);
|
|
||||||
check_next_event(event_1, &all_events_rx);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
pub type CollectionIntervalFactor = u32;
|
|
||||||
pub type UniqueId = u32;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum HkRequest {
|
|
||||||
OneShot(UniqueId),
|
|
||||||
Enable(UniqueId),
|
|
||||||
Disable(UniqueId),
|
|
||||||
ModifyCollectionInterval(UniqueId, CollectionIntervalFactor),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct TargetedHkRequest {
|
|
||||||
target: u32,
|
|
||||||
hk_request: HkRequest,
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
//! # Core components of the sat-rs framework
|
|
||||||
//!
|
|
||||||
//! You can find more information about the sat-rs framework on the
|
|
||||||
//! [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
|
||||||
//!
|
|
||||||
//! ## Overview
|
|
||||||
//!
|
|
||||||
//! The core modules of this crate include
|
|
||||||
//!
|
|
||||||
//! - The [event manager][event_man] module which provides a publish and
|
|
||||||
//! and subscribe to route events.
|
|
||||||
//! - The [pus] module which provides special support for projects using
|
|
||||||
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
|
||||||
#![no_std]
|
|
||||||
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
extern crate alloc;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
extern crate downcast_rs;
|
|
||||||
#[cfg(any(feature = "std", test))]
|
|
||||||
extern crate std;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
|
||||||
pub mod cfdp;
|
|
||||||
pub mod encoding;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
|
||||||
pub mod event_man;
|
|
||||||
pub mod events;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
|
||||||
pub mod executable;
|
|
||||||
pub mod hal;
|
|
||||||
pub mod hk;
|
|
||||||
pub mod mode;
|
|
||||||
pub mod objects;
|
|
||||||
pub mod params;
|
|
||||||
pub mod pool;
|
|
||||||
pub mod power;
|
|
||||||
pub mod pus;
|
|
||||||
pub mod request;
|
|
||||||
pub mod res_code;
|
|
||||||
pub mod seq_count;
|
|
||||||
pub mod tmtc;
|
|
||||||
|
|
||||||
pub use spacepackets;
|
|
||||||
|
|
||||||
// Generic channel ID type.
|
|
||||||
pub type ChannelId = u32;
|
|
@ -1,94 +0,0 @@
|
|||||||
use crate::tmtc::TargetId;
|
|
||||||
use core::mem::size_of;
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use spacepackets::ByteConversionError;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct ModeAndSubmode {
|
|
||||||
mode: u32,
|
|
||||||
submode: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModeAndSubmode {
|
|
||||||
pub const fn new_mode_only(mode: u32) -> Self {
|
|
||||||
Self { mode, submode: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn new(mode: u32, submode: u16) -> Self {
|
|
||||||
Self { mode, submode }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_len() -> usize {
|
|
||||||
size_of::<u32>() + size_of::<u16>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_be_bytes(buf: &[u8]) -> Result<Self, ByteConversionError> {
|
|
||||||
if buf.len() < 6 {
|
|
||||||
return Err(ByteConversionError::FromSliceTooSmall {
|
|
||||||
expected: 6,
|
|
||||||
found: buf.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
mode: u32::from_be_bytes(buf[0..4].try_into().unwrap()),
|
|
||||||
submode: u16::from_be_bytes(buf[4..6].try_into().unwrap()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mode(&self) -> u32 {
|
|
||||||
self.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submode(&self) -> u16 {
|
|
||||||
self.submode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct ModeCommand {
|
|
||||||
pub address: TargetId,
|
|
||||||
pub mode_submode: ModeAndSubmode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModeCommand {
|
|
||||||
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
|
|
||||||
Self {
|
|
||||||
address,
|
|
||||||
mode_submode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn address(&self) -> TargetId {
|
|
||||||
self.address
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mode_submode(&self) -> ModeAndSubmode {
|
|
||||||
self.mode_submode
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mode(&self) -> u32 {
|
|
||||||
self.mode_submode.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submode(&self) -> u16 {
|
|
||||||
self.mode_submode.submode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum ModeRequest {
|
|
||||||
SetMode(ModeAndSubmode),
|
|
||||||
ReadMode,
|
|
||||||
AnnounceMode,
|
|
||||||
AnnounceModeRecursive,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct TargetedModeRequest {
|
|
||||||
target_id: TargetId,
|
|
||||||
mode_request: ModeRequest,
|
|
||||||
}
|
|
@ -1,307 +0,0 @@
|
|||||||
//! # Module providing addressable object support and a manager for them
|
|
||||||
//!
|
|
||||||
//! Each addressable object can be identified using an [object ID][ObjectId].
|
|
||||||
//! The [system object][ManagedSystemObject] trait also allows storing these objects into the
|
|
||||||
//! [object manager][ObjectManager]. They can then be retrieved and casted back to a known type
|
|
||||||
//! using the object ID.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use std::any::Any;
|
|
||||||
//! use std::error::Error;
|
|
||||||
//! use satrs_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
|
||||||
//!
|
|
||||||
//! struct ExampleSysObj {
|
|
||||||
//! id: ObjectId,
|
|
||||||
//! dummy: u32,
|
|
||||||
//! was_initialized: bool,
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl ExampleSysObj {
|
|
||||||
//! fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
|
||||||
//! ExampleSysObj {
|
|
||||||
//! id,
|
|
||||||
//! dummy,
|
|
||||||
//! was_initialized: false,
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl SystemObject for ExampleSysObj {
|
|
||||||
//! type Error = ();
|
|
||||||
//! fn get_object_id(&self) -> &ObjectId {
|
|
||||||
//! &self.id
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn initialize(&mut self) -> Result<(), Self::Error> {
|
|
||||||
//! self.was_initialized = true;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl ManagedSystemObject for ExampleSysObj {}
|
|
||||||
//!
|
|
||||||
//! let mut obj_manager = ObjectManager::default();
|
|
||||||
//! let obj_id = ObjectId { id: 0, name: "Example 0"};
|
|
||||||
//! let example_obj = ExampleSysObj::new(obj_id, 42);
|
|
||||||
//! obj_manager.insert(Box::new(example_obj));
|
|
||||||
//! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&obj_id);
|
|
||||||
//! let example_obj = obj_back_casted.unwrap();
|
|
||||||
//! assert_eq!(example_obj.id, obj_id);
|
|
||||||
//! assert_eq!(example_obj.dummy, 42);
|
|
||||||
//! ```
|
|
||||||
use crate::tmtc::TargetId;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use alloc_mod::*;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use downcast_rs::Downcast;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
|
||||||
pub struct ObjectId {
|
|
||||||
pub id: TargetId,
|
|
||||||
pub name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub mod alloc_mod {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Each object which is stored inside the [object manager][ObjectManager] needs to implemented
|
|
||||||
/// this trait
|
|
||||||
pub trait SystemObject: Downcast {
|
|
||||||
type Error;
|
|
||||||
fn get_object_id(&self) -> &ObjectId;
|
|
||||||
fn initialize(&mut self) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
downcast_rs::impl_downcast!(SystemObject assoc Error);
|
|
||||||
|
|
||||||
pub trait ManagedSystemObject: SystemObject + Send {}
|
|
||||||
downcast_rs::impl_downcast!(ManagedSystemObject assoc Error);
|
|
||||||
|
|
||||||
/// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them
|
|
||||||
/// using an [object ID][ObjectId]
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub struct ObjectManager<E> {
|
|
||||||
obj_map: HashMap<ObjectId, Box<dyn ManagedSystemObject<Error = E>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl<E: 'static> Default for ObjectManager<E> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl<E: 'static> ObjectManager<E> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
ObjectManager {
|
|
||||||
obj_map: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn insert(&mut self, sys_obj: Box<dyn ManagedSystemObject<Error = E>>) -> bool {
|
|
||||||
let obj_id = sys_obj.get_object_id();
|
|
||||||
if self.obj_map.contains_key(obj_id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.obj_map.insert(*obj_id, sys_obj).is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes all System Objects in the hash map and returns the number of successful
|
|
||||||
/// initializations
|
|
||||||
pub fn initialize(&mut self) -> Result<u32, Box<dyn Error>> {
|
|
||||||
let mut init_success = 0;
|
|
||||||
for val in self.obj_map.values_mut() {
|
|
||||||
if val.initialize().is_ok() {
|
|
||||||
init_success += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(init_success)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a reference to an object stored inside the manager. The type to retrieve needs to
|
|
||||||
/// be explicitly passed as a generic parameter or specified on the left hand side of the
|
|
||||||
/// expression.
|
|
||||||
pub fn get_ref<T: ManagedSystemObject<Error = E>>(&self, key: &ObjectId) -> Option<&T> {
|
|
||||||
self.obj_map.get(key).and_then(|o| o.downcast_ref::<T>())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a mutable reference to an object stored inside the manager. The type to retrieve
|
|
||||||
/// needs to be explicitly passed as a generic parameter or specified on the left hand side
|
|
||||||
/// of the expression.
|
|
||||||
pub fn get_mut<T: ManagedSystemObject<Error = E>>(
|
|
||||||
&mut self,
|
|
||||||
key: &ObjectId,
|
|
||||||
) -> Option<&mut T> {
|
|
||||||
self.obj_map
|
|
||||||
.get_mut(key)
|
|
||||||
.and_then(|o| o.downcast_mut::<T>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
|
||||||
use std::boxed::Box;
|
|
||||||
use std::string::String;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
struct ExampleSysObj {
|
|
||||||
id: ObjectId,
|
|
||||||
dummy: u32,
|
|
||||||
was_initialized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExampleSysObj {
|
|
||||||
fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
|
||||||
ExampleSysObj {
|
|
||||||
id,
|
|
||||||
dummy,
|
|
||||||
was_initialized: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemObject for ExampleSysObj {
|
|
||||||
type Error = ();
|
|
||||||
fn get_object_id(&self) -> &ObjectId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize(&mut self) -> Result<(), Self::Error> {
|
|
||||||
self.was_initialized = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ManagedSystemObject for ExampleSysObj {}
|
|
||||||
|
|
||||||
struct OtherExampleObject {
|
|
||||||
id: ObjectId,
|
|
||||||
string: String,
|
|
||||||
was_initialized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemObject for OtherExampleObject {
|
|
||||||
type Error = ();
|
|
||||||
fn get_object_id(&self) -> &ObjectId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize(&mut self) -> Result<(), Self::Error> {
|
|
||||||
self.was_initialized = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ManagedSystemObject for OtherExampleObject {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_obj_manager_simple() {
|
|
||||||
let mut obj_manager = ObjectManager::default();
|
|
||||||
let expl_obj_id = ObjectId {
|
|
||||||
id: 0,
|
|
||||||
name: "Example 0",
|
|
||||||
};
|
|
||||||
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
|
||||||
assert!(obj_manager.insert(Box::new(example_obj)));
|
|
||||||
let res = obj_manager.initialize();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert_eq!(res.unwrap(), 1);
|
|
||||||
let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&expl_obj_id);
|
|
||||||
assert!(obj_back_casted.is_some());
|
|
||||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
|
||||||
assert_eq!(expl_obj_back_casted.dummy, 42);
|
|
||||||
assert!(expl_obj_back_casted.was_initialized);
|
|
||||||
|
|
||||||
let second_obj_id = ObjectId {
|
|
||||||
id: 12,
|
|
||||||
name: "Example 1",
|
|
||||||
};
|
|
||||||
let second_example_obj = OtherExampleObject {
|
|
||||||
id: second_obj_id,
|
|
||||||
string: String::from("Hello Test"),
|
|
||||||
was_initialized: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(obj_manager.insert(Box::new(second_example_obj)));
|
|
||||||
let res = obj_manager.initialize();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert_eq!(res.unwrap(), 2);
|
|
||||||
let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get_ref(&second_obj_id);
|
|
||||||
assert!(obj_back_casted.is_some());
|
|
||||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
|
||||||
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
|
||||||
assert!(expl_obj_back_casted.was_initialized);
|
|
||||||
|
|
||||||
let existing_obj_id = ObjectId {
|
|
||||||
id: 12,
|
|
||||||
name: "Example 1",
|
|
||||||
};
|
|
||||||
let invalid_obj = OtherExampleObject {
|
|
||||||
id: existing_obj_id,
|
|
||||||
string: String::from("Hello Test"),
|
|
||||||
was_initialized: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(!obj_manager.insert(Box::new(invalid_obj)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn object_man_threaded() {
|
|
||||||
let obj_manager = Arc::new(Mutex::new(ObjectManager::new()));
|
|
||||||
let expl_obj_id = ObjectId {
|
|
||||||
id: 0,
|
|
||||||
name: "Example 0",
|
|
||||||
};
|
|
||||||
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
|
||||||
let second_obj_id = ObjectId {
|
|
||||||
id: 12,
|
|
||||||
name: "Example 1",
|
|
||||||
};
|
|
||||||
let second_example_obj = OtherExampleObject {
|
|
||||||
id: second_obj_id,
|
|
||||||
string: String::from("Hello Test"),
|
|
||||||
was_initialized: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut obj_man_handle = obj_manager.lock().expect("Mutex lock failed");
|
|
||||||
assert!(obj_man_handle.insert(Box::new(example_obj)));
|
|
||||||
assert!(obj_man_handle.insert(Box::new(second_example_obj)));
|
|
||||||
let res = obj_man_handle.initialize();
|
|
||||||
std::mem::drop(obj_man_handle);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
assert_eq!(res.unwrap(), 2);
|
|
||||||
let obj_man_0 = obj_manager.clone();
|
|
||||||
let jh0 = thread::spawn(move || {
|
|
||||||
let locked_man = obj_man_0.lock().expect("Mutex lock failed");
|
|
||||||
let obj_back_casted: Option<&ExampleSysObj> = locked_man.get_ref(&expl_obj_id);
|
|
||||||
assert!(obj_back_casted.is_some());
|
|
||||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
|
||||||
assert_eq!(expl_obj_back_casted.dummy, 42);
|
|
||||||
assert!(expl_obj_back_casted.was_initialized);
|
|
||||||
std::mem::drop(locked_man)
|
|
||||||
});
|
|
||||||
|
|
||||||
let jh1 = thread::spawn(move || {
|
|
||||||
let locked_man = obj_manager.lock().expect("Mutex lock failed");
|
|
||||||
let obj_back_casted: Option<&OtherExampleObject> = locked_man.get_ref(&second_obj_id);
|
|
||||||
assert!(obj_back_casted.is_some());
|
|
||||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
|
||||||
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
|
||||||
assert!(expl_obj_back_casted.was_initialized);
|
|
||||||
std::mem::drop(locked_man)
|
|
||||||
});
|
|
||||||
jh0.join().expect("Joining thread 0 failed");
|
|
||||||
jh1.join().expect("Joining thread 1 failed");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,679 +0,0 @@
|
|||||||
//! Parameter types and enums.
|
|
||||||
//!
|
|
||||||
//! This module contains various helper types.
|
|
||||||
//!
|
|
||||||
//! # Primtive Parameter Wrappers and Enumeration
|
|
||||||
//!
|
|
||||||
//! This module includes wrapper for primitive rust types using the newtype pattern.
|
|
||||||
//! This was also done for pairs and triplets of these primitive types.
|
|
||||||
//! The [WritableToBeBytes] was implemented for all those types as well, which allows to easily
|
|
||||||
//! convert them into a network friendly raw byte format. The [ParamsRaw] enumeration groups
|
|
||||||
//! all newtypes and implements the [WritableToBeBytes] trait itself.
|
|
||||||
//!
|
|
||||||
//! ## Example for primitive type wrapper
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! use satrs_core::params::{ParamsRaw, ToBeBytes, U32Pair, WritableToBeBytes};
|
|
||||||
//!
|
|
||||||
//! let u32_pair = U32Pair(0x1010, 25);
|
|
||||||
//! assert_eq!(u32_pair.0, 0x1010);
|
|
||||||
//! assert_eq!(u32_pair.1, 25);
|
|
||||||
//! // Convert to raw stream
|
|
||||||
//! let raw_buf = u32_pair.to_be_bytes();
|
|
||||||
//! assert_eq!(raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
|
||||||
//!
|
|
||||||
//! // Convert to enum variant
|
|
||||||
//! let params_raw: ParamsRaw = u32_pair.into();
|
|
||||||
//! assert_eq!(params_raw, (0x1010_u32, 25_u32).into());
|
|
||||||
//!
|
|
||||||
//! // Convert to stream using the enum variant
|
|
||||||
//! let mut other_raw_buf: [u8; 8] = [0; 8];
|
|
||||||
//! params_raw.write_to_be_bytes(&mut other_raw_buf).expect("Writing parameter to buffer failed");
|
|
||||||
//! assert_eq!(other_raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
|
||||||
//!
|
|
||||||
//! // Create a pair from a raw stream
|
|
||||||
//! let u32_pair_from_stream: U32Pair = raw_buf.as_slice().try_into().unwrap();
|
|
||||||
//! assert_eq!(u32_pair_from_stream.0, 0x1010);
|
|
||||||
//! assert_eq!(u32_pair_from_stream.1, 25);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Generic Parameter Enumeration
|
|
||||||
//!
|
|
||||||
//! The module also contains generic parameter enumerations.
|
|
||||||
//! This includes the [ParamsHeapless] enumeration for contained values which do not require heap
|
|
||||||
//! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which
|
|
||||||
//! require [alloc] support but allow for more flexbility.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use crate::pool::StoreAddr;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::string::{String, ToString};
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::fmt::Debug;
|
|
||||||
use core::mem::size_of;
|
|
||||||
use paste::paste;
|
|
||||||
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8};
|
|
||||||
use spacepackets::util::UnsignedEnum;
|
|
||||||
use spacepackets::ByteConversionError;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use alloc_mod::*;
|
|
||||||
pub use spacepackets::util::ToBeBytes;
|
|
||||||
|
|
||||||
/// Generic trait which is used for objects which can be converted into a raw network (big) endian
|
|
||||||
/// byte format.
|
|
||||||
pub trait WritableToBeBytes {
|
|
||||||
fn raw_len(&self) -> usize;
|
|
||||||
/// Writes the object to a raw buffer in network endianness (big)
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! param_to_be_bytes_impl {
|
|
||||||
($Newtype: ident) => {
|
|
||||||
impl WritableToBeBytes for $Newtype {
|
|
||||||
#[inline]
|
|
||||||
fn raw_len(&self) -> usize {
|
|
||||||
size_of::<<Self as ToBeBytes>::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
let raw_len = self.raw_len();
|
|
||||||
if buf.len() < raw_len {
|
|
||||||
return Err(ByteConversionError::ToSliceTooSmall {
|
|
||||||
found: buf.len(),
|
|
||||||
expected: raw_len,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
buf[0..raw_len].copy_from_slice(&self.to_be_bytes());
|
|
||||||
Ok(raw_len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitive_newtypes_with_eq {
|
|
||||||
($($ty: ty,)+) => {
|
|
||||||
$(
|
|
||||||
paste! {
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct [<$ty:upper>](pub $ty);
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
|
||||||
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper>]);
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
|
||||||
|
|
||||||
impl From<$ty> for [<$ty:upper>] {
|
|
||||||
fn from(v: $ty) -> Self {
|
|
||||||
Self(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
|
||||||
fn from(v: ($ty, $ty)) -> Self {
|
|
||||||
Self(v.0, v.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
|
||||||
fn from(v: ($ty, $ty, $ty)) -> Self {
|
|
||||||
Self(v.0, v.1, v.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitive_newtypes {
|
|
||||||
($($ty: ty,)+) => {
|
|
||||||
$(
|
|
||||||
paste! {
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct [<$ty:upper>](pub $ty);
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
|
||||||
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper>]);
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
|
||||||
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
|
||||||
|
|
||||||
impl From<$ty> for [<$ty:upper>] {
|
|
||||||
fn from(v: $ty) -> Self {
|
|
||||||
Self(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
|
||||||
fn from(v: ($ty, $ty)) -> Self {
|
|
||||||
Self(v.0, v.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
|
||||||
fn from(v: ($ty, $ty, $ty)) -> Self {
|
|
||||||
Self(v.0, v.1, v.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive_newtypes_with_eq!(u8, u16, u32, u64, i8, i16, i32, i64,);
|
|
||||||
primitive_newtypes!(f32, f64,);
|
|
||||||
|
|
||||||
macro_rules! scalar_byte_conversions_impl {
|
|
||||||
($($ty: ty,)+) => {
|
|
||||||
$(
|
|
||||||
paste! {
|
|
||||||
impl ToBeBytes for [<$ty:upper>] {
|
|
||||||
type ByteArray = [u8; size_of::<$ty>()];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
self.0.to_be_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for [<$ty:upper>] {
|
|
||||||
type Error = ByteConversionError;
|
|
||||||
|
|
||||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
if v.len() < size_of::<$ty>() {
|
|
||||||
return Err(ByteConversionError::FromSliceTooSmall{
|
|
||||||
expected: size_of::<$ty>(),
|
|
||||||
found: v.len()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok([<$ty:upper>]($ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! pair_byte_conversions_impl {
|
|
||||||
($($ty: ty,)+) => {
|
|
||||||
$(
|
|
||||||
paste! {
|
|
||||||
impl ToBeBytes for [<$ty:upper Pair>] {
|
|
||||||
type ByteArray = [u8; size_of::<$ty>() * 2];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; size_of::<$ty>() * 2];
|
|
||||||
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
|
||||||
array[
|
|
||||||
size_of::<$ty>()..2 * size_of::<$ty>()
|
|
||||||
].copy_from_slice(&self.1.to_be_bytes());
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for [<$ty:upper Pair>] {
|
|
||||||
type Error = ByteConversionError;
|
|
||||||
|
|
||||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
if v.len() < 2 * size_of::<$ty>() {
|
|
||||||
return Err(ByteConversionError::FromSliceTooSmall{
|
|
||||||
expected: 2 * size_of::<$ty>(),
|
|
||||||
found: v.len()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok([<$ty:upper Pair>](
|
|
||||||
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
|
||||||
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap())
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! triplet_to_be_bytes_impl {
|
|
||||||
($($ty: ty,)+) => {
|
|
||||||
$(
|
|
||||||
paste! {
|
|
||||||
impl ToBeBytes for [<$ty:upper Triplet>] {
|
|
||||||
type ByteArray = [u8; size_of::<$ty>() * 3];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; size_of::<$ty>() * 3];
|
|
||||||
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
|
||||||
array[
|
|
||||||
size_of::<$ty>()..2 * size_of::<$ty>()
|
|
||||||
].copy_from_slice(&self.1.to_be_bytes());
|
|
||||||
array[
|
|
||||||
2 * size_of::<$ty>()..3 * size_of::<$ty>()
|
|
||||||
].copy_from_slice(&self.2.to_be_bytes());
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TryFrom<&[u8]> for [<$ty:upper Triplet>] {
|
|
||||||
type Error = ByteConversionError;
|
|
||||||
|
|
||||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
if v.len() < 3 * size_of::<$ty>() {
|
|
||||||
return Err(ByteConversionError::FromSliceTooSmall{
|
|
||||||
expected: 3 * size_of::<$ty>(),
|
|
||||||
found: v.len()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok([<$ty:upper Triplet>](
|
|
||||||
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
|
||||||
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap()),
|
|
||||||
$ty::from_be_bytes(v[2 * size_of::<$ty>()..3 * size_of::<$ty>()].try_into().unwrap())
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scalar_byte_conversions_impl!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64,);
|
|
||||||
|
|
||||||
impl ToBeBytes for U8Pair {
|
|
||||||
type ByteArray = [u8; 2];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; 2];
|
|
||||||
array[0] = self.0;
|
|
||||||
array[1] = self.1;
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToBeBytes for I8Pair {
|
|
||||||
type ByteArray = [u8; 2];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; 2];
|
|
||||||
array[0] = self.0 as u8;
|
|
||||||
array[1] = self.1 as u8;
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToBeBytes for U8Triplet {
|
|
||||||
type ByteArray = [u8; 3];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; 3];
|
|
||||||
array[0] = self.0;
|
|
||||||
array[1] = self.1;
|
|
||||||
array[2] = self.2;
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToBeBytes for I8Triplet {
|
|
||||||
type ByteArray = [u8; 3];
|
|
||||||
|
|
||||||
fn written_len(&self) -> usize {
|
|
||||||
size_of::<Self::ByteArray>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
|
||||||
let mut array = [0; 3];
|
|
||||||
array[0] = self.0 as u8;
|
|
||||||
array[1] = self.1 as u8;
|
|
||||||
array[2] = self.2 as u8;
|
|
||||||
array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pair_byte_conversions_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
|
||||||
triplet_to_be_bytes_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
|
||||||
|
|
||||||
/// Generic enumeration for additonal parameters only consisting of primitive data types.
|
|
||||||
///
|
|
||||||
/// All contained variants and the enum itself implement the [WritableToBeBytes] trait, which
|
|
||||||
/// allows to easily convert them into a network-friendly format.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub enum ParamsRaw {
|
|
||||||
U8(U8),
|
|
||||||
U8Pair(U8Pair),
|
|
||||||
U8Triplet(U8Triplet),
|
|
||||||
I8(I8),
|
|
||||||
I8Pair(I8Pair),
|
|
||||||
I8Triplet(I8Triplet),
|
|
||||||
U16(U16),
|
|
||||||
U16Pair(U16Pair),
|
|
||||||
U16Triplet(U16Triplet),
|
|
||||||
I16(I16),
|
|
||||||
I16Pair(I16Pair),
|
|
||||||
I16Triplet(I16Triplet),
|
|
||||||
U32(U32),
|
|
||||||
U32Pair(U32Pair),
|
|
||||||
U32Triplet(U32Triplet),
|
|
||||||
I32(I32),
|
|
||||||
I32Pair(I32Pair),
|
|
||||||
I32Triplet(I32Triplet),
|
|
||||||
F32(F32),
|
|
||||||
F32Pair(F32Pair),
|
|
||||||
F32Triplet(F32Triplet),
|
|
||||||
U64(U64),
|
|
||||||
I64(I64),
|
|
||||||
F64(F64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WritableToBeBytes for ParamsRaw {
|
|
||||||
fn raw_len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
ParamsRaw::U8(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U8Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U8Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I8(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I8Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I8Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U16(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U16Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U16Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I16(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I16Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I16Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U32(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U32Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U32Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I32(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I32Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I32Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::F32(v) => v.raw_len(),
|
|
||||||
ParamsRaw::F32Pair(v) => v.raw_len(),
|
|
||||||
ParamsRaw::F32Triplet(v) => v.raw_len(),
|
|
||||||
ParamsRaw::U64(v) => v.raw_len(),
|
|
||||||
ParamsRaw::I64(v) => v.raw_len(),
|
|
||||||
ParamsRaw::F64(v) => v.raw_len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
match self {
|
|
||||||
ParamsRaw::U8(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U8Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U8Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I8(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I8Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I8Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U16(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U16Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U16Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I16(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I16Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I16Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U32(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U32Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U32Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I32(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I32Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I32Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::F32(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::F32Pair(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::F32Triplet(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::U64(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::I64(v) => v.write_to_be_bytes(buf),
|
|
||||||
ParamsRaw::F64(v) => v.write_to_be_bytes(buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! params_raw_from_newtype {
|
|
||||||
($($newtype: ident,)+) => {
|
|
||||||
$(
|
|
||||||
impl From<$newtype> for ParamsRaw {
|
|
||||||
fn from(v: $newtype) -> Self {
|
|
||||||
Self::$newtype(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params_raw_from_newtype!(
|
|
||||||
U8, U8Pair, U8Triplet, U16, U16Pair, U16Triplet, U32, U32Pair, U32Triplet, I8, I8Pair,
|
|
||||||
I8Triplet, I16, I16Pair, I16Triplet, I32, I32Pair, I32Triplet, F32, F32Pair, F32Triplet, U64,
|
|
||||||
I64, F64,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum EcssEnumParams {
|
|
||||||
U8(EcssEnumU8),
|
|
||||||
U16(EcssEnumU16),
|
|
||||||
U32(EcssEnumU32),
|
|
||||||
U64(EcssEnumU64),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! writable_as_be_bytes_ecss_enum_impl {
|
|
||||||
($EnumIdent: ident) => {
|
|
||||||
impl WritableToBeBytes for $EnumIdent {
|
|
||||||
fn raw_len(&self) -> usize {
|
|
||||||
self.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
<Self as UnsignedEnum>::write_to_be_bytes(self, buf).map(|_| self.raw_len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU8);
|
|
||||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU16);
|
|
||||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU32);
|
|
||||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU64);
|
|
||||||
|
|
||||||
impl WritableToBeBytes for EcssEnumParams {
|
|
||||||
fn raw_len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
EcssEnumParams::U8(e) => e.raw_len(),
|
|
||||||
EcssEnumParams::U16(e) => e.raw_len(),
|
|
||||||
EcssEnumParams::U32(e) => e.raw_len(),
|
|
||||||
EcssEnumParams::U64(e) => e.raw_len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
|
||||||
match self {
|
|
||||||
EcssEnumParams::U8(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
|
||||||
EcssEnumParams::U16(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
|
||||||
EcssEnumParams::U32(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
|
||||||
EcssEnumParams::U64(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic enumeration for parameters which do not rely on heap allocations.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub enum ParamsHeapless {
|
|
||||||
Raw(ParamsRaw),
|
|
||||||
EcssEnum(EcssEnumParams),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! from_conversions_for_raw {
|
|
||||||
($(($raw_ty: ty, $TargetPath: path),)+) => {
|
|
||||||
$(
|
|
||||||
impl From<$raw_ty> for ParamsRaw {
|
|
||||||
fn from(val: $raw_ty) -> Self {
|
|
||||||
$TargetPath(val.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<$raw_ty> for ParamsHeapless {
|
|
||||||
fn from(val: $raw_ty) -> Self {
|
|
||||||
ParamsHeapless::Raw(val.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
from_conversions_for_raw!(
|
|
||||||
(u8, Self::U8),
|
|
||||||
((u8, u8), Self::U8Pair),
|
|
||||||
((u8, u8, u8), Self::U8Triplet),
|
|
||||||
(i8, Self::I8),
|
|
||||||
((i8, i8), Self::I8Pair),
|
|
||||||
((i8, i8, i8), Self::I8Triplet),
|
|
||||||
(u16, Self::U16),
|
|
||||||
((u16, u16), Self::U16Pair),
|
|
||||||
((u16, u16, u16), Self::U16Triplet),
|
|
||||||
(i16, Self::I16),
|
|
||||||
((i16, i16), Self::I16Pair),
|
|
||||||
((i16, i16, i16), Self::I16Triplet),
|
|
||||||
(u32, Self::U32),
|
|
||||||
((u32, u32), Self::U32Pair),
|
|
||||||
((u32, u32, u32), Self::U32Triplet),
|
|
||||||
(i32, Self::I32),
|
|
||||||
((i32, i32), Self::I32Pair),
|
|
||||||
((i32, i32, i32), Self::I32Triplet),
|
|
||||||
(f32, Self::F32),
|
|
||||||
((f32, f32), Self::F32Pair),
|
|
||||||
((f32, f32, f32), Self::F32Triplet),
|
|
||||||
(u64, Self::U64),
|
|
||||||
(f64, Self::F64),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
mod alloc_mod {
|
|
||||||
use super::*;
|
|
||||||
/// Generic enumeration for additional parameters, including parameters which rely on heap
|
|
||||||
/// allocations.
|
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Params {
|
|
||||||
Heapless(ParamsHeapless),
|
|
||||||
Store(StoreAddr),
|
|
||||||
Vec(Vec<u8>),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StoreAddr> for Params {
|
|
||||||
fn from(x: StoreAddr) -> Self {
|
|
||||||
Self::Store(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ParamsHeapless> for Params {
|
|
||||||
fn from(x: ParamsHeapless) -> Self {
|
|
||||||
Self::Heapless(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<u8>> for Params {
|
|
||||||
fn from(val: Vec<u8>) -> Self {
|
|
||||||
Self::Vec(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a byte slice into the [Params::Vec] variant
|
|
||||||
impl From<&[u8]> for Params {
|
|
||||||
fn from(val: &[u8]) -> Self {
|
|
||||||
Self::Vec(val.to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Params {
|
|
||||||
fn from(val: String) -> Self {
|
|
||||||
Self::String(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a string slice into the [Params::String] variant
|
|
||||||
impl From<&str> for Params {
|
|
||||||
fn from(val: &str) -> Self {
|
|
||||||
Self::String(val.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_u32_pair() {
|
|
||||||
let u32_pair = U32Pair(4, 8);
|
|
||||||
assert_eq!(u32_pair.0, 4);
|
|
||||||
assert_eq!(u32_pair.1, 8);
|
|
||||||
let raw = u32_pair.to_be_bytes();
|
|
||||||
let mut u32_conv_back = u32::from_be_bytes(raw[0..4].try_into().unwrap());
|
|
||||||
assert_eq!(u32_conv_back, 4);
|
|
||||||
u32_conv_back = u32::from_be_bytes(raw[4..8].try_into().unwrap());
|
|
||||||
assert_eq!(u32_conv_back, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_signed_test_pair() {
|
|
||||||
let i8_pair = I8Pair(-3, -16);
|
|
||||||
assert_eq!(i8_pair.0, -3);
|
|
||||||
assert_eq!(i8_pair.1, -16);
|
|
||||||
let raw = i8_pair.to_be_bytes();
|
|
||||||
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
|
||||||
assert_eq!(i8_conv_back, -3);
|
|
||||||
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
|
||||||
assert_eq!(i8_conv_back, -16);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_signed_test_triplet() {
|
|
||||||
let i8_triplet = I8Triplet(-3, -16, -126);
|
|
||||||
assert_eq!(i8_triplet.0, -3);
|
|
||||||
assert_eq!(i8_triplet.1, -16);
|
|
||||||
assert_eq!(i8_triplet.2, -126);
|
|
||||||
let raw = i8_triplet.to_be_bytes();
|
|
||||||
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
|
||||||
assert_eq!(i8_conv_back, -3);
|
|
||||||
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
|
||||||
assert_eq!(i8_conv_back, -16);
|
|
||||||
i8_conv_back = i8::from_be_bytes(raw[2..3].try_into().unwrap());
|
|
||||||
assert_eq!(i8_conv_back, -126);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn conversion_test_string() {
|
|
||||||
let param: Params = "Test String".into();
|
|
||||||
if let Params::String(str) = param {
|
|
||||||
assert_eq!(str, String::from("Test String"));
|
|
||||||
} else {
|
|
||||||
panic!("Params type is not String")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn conversion_from_slice() {
|
|
||||||
let test_slice: [u8; 5] = [0; 5];
|
|
||||||
let vec_param: Params = test_slice.as_slice().into();
|
|
||||||
if let Params::Vec(vec) = vec_param {
|
|
||||||
assert_eq!(vec, test_slice.to_vec());
|
|
||||||
} else {
|
|
||||||
panic!("Params type is not a vector")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub use spacepackets::ecss::hk::*;
|
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
|||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Subservice {
|
|
||||||
TcSetMode = 1,
|
|
||||||
TcReadMode = 3,
|
|
||||||
TcAnnounceMode = 4,
|
|
||||||
TcAnnounceModeRecursive = 5,
|
|
||||||
TmModeReply = 6,
|
|
||||||
TmCantReachMode = 7,
|
|
||||||
TmWrongModeReply = 8,
|
|
||||||
}
|
|
@ -1,272 +0,0 @@
|
|||||||
use crate::pus::{
|
|
||||||
PartialPusHandlingError, PusPacketHandlerResult, PusPacketHandlingError, PusTmWrapper,
|
|
||||||
};
|
|
||||||
use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
|
||||||
use spacepackets::ecss::PusPacket;
|
|
||||||
use spacepackets::SpHeader;
|
|
||||||
|
|
||||||
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
|
|
||||||
|
|
||||||
/// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets.
|
|
||||||
/// This handler only processes ping requests and generates a ping reply for them accordingly.
|
|
||||||
pub struct PusService17TestHandler<TcInMemConverter: EcssTcInMemConverter> {
|
|
||||||
pub service_helper: PusServiceHelper<TcInMemConverter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> PusService17TestHandler<TcInMemConverter> {
|
|
||||||
pub fn new(service_helper: PusServiceHelper<TcInMemConverter>) -> Self {
|
|
||||||
Self { service_helper }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
|
|
||||||
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
|
|
||||||
if possible_packet.is_none() {
|
|
||||||
return Ok(PusPacketHandlerResult::Empty);
|
|
||||||
}
|
|
||||||
let ecss_tc_and_token = possible_packet.unwrap();
|
|
||||||
let tc = self
|
|
||||||
.service_helper
|
|
||||||
.tc_in_mem_converter
|
|
||||||
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
|
|
||||||
if tc.service() != 17 {
|
|
||||||
return Err(PusPacketHandlingError::WrongService(tc.service()));
|
|
||||||
}
|
|
||||||
if tc.subservice() == 1 {
|
|
||||||
let mut partial_error = None;
|
|
||||||
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
|
|
||||||
let result = self
|
|
||||||
.service_helper
|
|
||||||
.common
|
|
||||||
.verification_handler
|
|
||||||
.get_mut()
|
|
||||||
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
|
|
||||||
.map_err(|_| PartialPusHandlingError::Verification);
|
|
||||||
let start_token = if let Ok(result) = result {
|
|
||||||
Some(result)
|
|
||||||
} else {
|
|
||||||
partial_error = Some(result.unwrap_err());
|
|
||||||
None
|
|
||||||
};
|
|
||||||
// Sequence count will be handled centrally in TM funnel.
|
|
||||||
let mut reply_header =
|
|
||||||
SpHeader::tm_unseg(self.service_helper.common.tm_apid, 0, 0).unwrap();
|
|
||||||
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
|
|
||||||
let ping_reply = PusTmCreator::new(&mut reply_header, tc_header, &[], true);
|
|
||||||
let result = self
|
|
||||||
.service_helper
|
|
||||||
.common
|
|
||||||
.tm_sender
|
|
||||||
.send_tm(PusTmWrapper::Direct(ping_reply))
|
|
||||||
.map_err(PartialPusHandlingError::TmSend);
|
|
||||||
if let Err(err) = result {
|
|
||||||
partial_error = Some(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(start_token) = start_token {
|
|
||||||
if self
|
|
||||||
.service_helper
|
|
||||||
.common
|
|
||||||
.verification_handler
|
|
||||||
.get_mut()
|
|
||||||
.completion_success(start_token, Some(&time_stamp))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
partial_error = Some(PartialPusHandlingError::Verification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(partial_error) = partial_error {
|
|
||||||
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
|
|
||||||
partial_error,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return Ok(PusPacketHandlerResult::CustomSubservice(
|
|
||||||
tc.subservice(),
|
|
||||||
ecss_tc_and_token.token,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(PusPacketHandlerResult::RequestHandled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::pus::tests::{
|
|
||||||
PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness,
|
|
||||||
SimplePusPacketHandler, TEST_APID,
|
|
||||||
};
|
|
||||||
use crate::pus::verification::RequestId;
|
|
||||||
use crate::pus::verification::{TcStateAccepted, VerificationToken};
|
|
||||||
use crate::pus::{
|
|
||||||
EcssTcInSharedStoreConverter, EcssTcInVecConverter, PusPacketHandlerResult,
|
|
||||||
PusPacketHandlingError,
|
|
||||||
};
|
|
||||||
use delegate::delegate;
|
|
||||||
use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader};
|
|
||||||
use spacepackets::ecss::tm::PusTmReader;
|
|
||||||
use spacepackets::ecss::PusPacket;
|
|
||||||
use spacepackets::{SequenceFlags, SpHeader};
|
|
||||||
|
|
||||||
use super::PusService17TestHandler;
|
|
||||||
|
|
||||||
struct Pus17HandlerWithStoreTester {
|
|
||||||
common: PusServiceHandlerWithSharedStoreCommon,
|
|
||||||
handler: PusService17TestHandler<EcssTcInSharedStoreConverter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pus17HandlerWithStoreTester {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new();
|
|
||||||
let pus_17_handler = PusService17TestHandler::new(srv_handler);
|
|
||||||
Self {
|
|
||||||
common,
|
|
||||||
handler: pus_17_handler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusTestHarness for Pus17HandlerWithStoreTester {
|
|
||||||
delegate! {
|
|
||||||
to self.common {
|
|
||||||
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
|
|
||||||
fn read_next_tm(&mut self) -> PusTmReader<'_>;
|
|
||||||
fn check_no_tm_available(&self) -> bool;
|
|
||||||
fn check_next_verification_tm(
|
|
||||||
&self,
|
|
||||||
subservice: u8,
|
|
||||||
expected_request_id: RequestId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SimplePusPacketHandler for Pus17HandlerWithStoreTester {
|
|
||||||
delegate! {
|
|
||||||
to self.handler {
|
|
||||||
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Pus17HandlerWithVecTester {
|
|
||||||
common: PusServiceHandlerWithVecCommon,
|
|
||||||
handler: PusService17TestHandler<EcssTcInVecConverter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pus17HandlerWithVecTester {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (common, srv_handler) = PusServiceHandlerWithVecCommon::new();
|
|
||||||
Self {
|
|
||||||
common,
|
|
||||||
handler: PusService17TestHandler::new(srv_handler),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusTestHarness for Pus17HandlerWithVecTester {
|
|
||||||
delegate! {
|
|
||||||
to self.common {
|
|
||||||
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
|
|
||||||
fn read_next_tm(&mut self) -> PusTmReader<'_>;
|
|
||||||
fn check_no_tm_available(&self) -> bool;
|
|
||||||
fn check_next_verification_tm(
|
|
||||||
&self,
|
|
||||||
subservice: u8,
|
|
||||||
expected_request_id: RequestId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SimplePusPacketHandler for Pus17HandlerWithVecTester {
|
|
||||||
delegate! {
|
|
||||||
to self.handler {
|
|
||||||
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ping_test(test_harness: &mut (impl PusTestHarness + SimplePusPacketHandler)) {
|
|
||||||
// Create a ping TC, verify acceptance.
|
|
||||||
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
|
|
||||||
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
|
|
||||||
let token = test_harness.send_tc(&ping_tc);
|
|
||||||
let request_id = token.req_id();
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
// We should see 4 replies in the TM queue now: Acceptance TM, Start TM, ping reply and
|
|
||||||
// Completion TM
|
|
||||||
|
|
||||||
// Acceptance TM
|
|
||||||
test_harness.check_next_verification_tm(1, request_id);
|
|
||||||
|
|
||||||
// Start TM
|
|
||||||
test_harness.check_next_verification_tm(3, request_id);
|
|
||||||
|
|
||||||
// Ping reply
|
|
||||||
let tm = test_harness.read_next_tm();
|
|
||||||
assert_eq!(tm.service(), 17);
|
|
||||||
assert_eq!(tm.subservice(), 2);
|
|
||||||
assert!(tm.user_data().is_empty());
|
|
||||||
|
|
||||||
// TM completion
|
|
||||||
test_harness.check_next_verification_tm(7, request_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_ping_processing_using_store() {
|
|
||||||
let mut test_harness = Pus17HandlerWithStoreTester::new();
|
|
||||||
ping_test(&mut test_harness);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_ping_processing_using_vec() {
|
|
||||||
let mut test_harness = Pus17HandlerWithVecTester::new();
|
|
||||||
ping_test(&mut test_harness);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_tc_queue() {
|
|
||||||
let mut test_harness = Pus17HandlerWithStoreTester::new();
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let result = result.unwrap();
|
|
||||||
if let PusPacketHandlerResult::Empty = result {
|
|
||||||
} else {
|
|
||||||
panic!("unexpected result type {result:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sending_unsupported_service() {
|
|
||||||
let mut test_harness = Pus17HandlerWithStoreTester::new();
|
|
||||||
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(3, 1);
|
|
||||||
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
|
|
||||||
test_harness.send_tc(&ping_tc);
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_err());
|
|
||||||
let error = result.unwrap_err();
|
|
||||||
if let PusPacketHandlingError::WrongService(num) = error {
|
|
||||||
assert_eq!(num, 3);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected error type {error}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sending_custom_subservice() {
|
|
||||||
let mut test_harness = Pus17HandlerWithStoreTester::new();
|
|
||||||
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
|
||||||
let sec_header = PusTcSecondaryHeader::new_simple(17, 200);
|
|
||||||
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
|
|
||||||
test_harness.send_tc(&ping_tc);
|
|
||||||
let result = test_harness.handle_one_tc();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let result = result.unwrap();
|
|
||||||
if let PusPacketHandlerResult::CustomSubservice(subservice, _) = result {
|
|
||||||
assert_eq!(subservice, 200);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected result type {result:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
|||||||
|
|
@ -1,367 +0,0 @@
|
|||||||
//! CCSDS packet routing components.
|
|
||||||
//!
|
|
||||||
//! The routing components consist of two core components:
|
|
||||||
//! 1. [CcsdsDistributor] component which dispatches received packets to a user-provided handler
|
|
||||||
//! 2. [CcsdsPacketHandler] trait which should be implemented by the user-provided packet handler.
|
|
||||||
//!
|
|
||||||
//! The [CcsdsDistributor] implements the [ReceivesCcsdsTc] and [ReceivesTcCore] trait which allows to
|
|
||||||
//! pass raw or CCSDS packets to it. Upon receiving a packet, it performs the following steps:
|
|
||||||
//!
|
|
||||||
//! 1. It tries to identify the target Application Process Identifier (APID) based on the
|
|
||||||
//! respective CCSDS space packet header field. If that process fails, a [ByteConversionError] is
|
|
||||||
//! returned to the user
|
|
||||||
//! 2. If a valid APID is found and matches one of the APIDs provided by
|
|
||||||
//! [CcsdsPacketHandler::valid_apids], it will pass the packet to the user provided
|
|
||||||
//! [CcsdsPacketHandler::handle_known_apid] function. If no valid APID is found, the packet
|
|
||||||
//! will be passed to the [CcsdsPacketHandler::handle_unknown_apid] function.
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use satrs_core::tmtc::ccsds_distrib::{CcsdsPacketHandler, CcsdsDistributor};
|
|
||||||
//! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
|
||||||
//! use spacepackets::{CcsdsPacket, SpHeader};
|
|
||||||
//! use spacepackets::ecss::WritablePusPacket;
|
|
||||||
//! use spacepackets::ecss::tc::{PusTc, PusTcCreator};
|
|
||||||
//!
|
|
||||||
//! #[derive (Default)]
|
|
||||||
//! struct ConcreteApidHandler {
|
|
||||||
//! known_call_count: u32,
|
|
||||||
//! unknown_call_count: u32
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl ConcreteApidHandler {
|
|
||||||
//! fn mutable_foo(&mut self) {}
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl CcsdsPacketHandler for ConcreteApidHandler {
|
|
||||||
//! type Error = ();
|
|
||||||
//! fn valid_apids(&self) -> &'static [u16] { &[0x002] }
|
|
||||||
//! fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
//! assert_eq!(sp_header.apid(), 0x002);
|
|
||||||
//! assert_eq!(tc_raw.len(), 13);
|
|
||||||
//! self.known_call_count += 1;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! fn handle_unknown_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
//! assert_eq!(sp_header.apid(), 0x003);
|
|
||||||
//! assert_eq!(tc_raw.len(), 13);
|
|
||||||
//! self.unknown_call_count += 1;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! let apid_handler = ConcreteApidHandler::default();
|
|
||||||
//! let mut ccsds_distributor = CcsdsDistributor::new(Box::new(apid_handler));
|
|
||||||
//!
|
|
||||||
//! // Create and pass PUS telecommand with a valid APID
|
|
||||||
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
|
||||||
//! let mut pus_tc = PusTcCreator::new_simple(&mut space_packet_header, 17, 1, None, true);
|
|
||||||
//! let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
//! let mut size = pus_tc
|
|
||||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
|
||||||
//! .expect("Error writing TC to buffer");
|
|
||||||
//! let tc_slice = &test_buf[0..size];
|
|
||||||
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
|
|
||||||
//!
|
|
||||||
//! // Now pass a packet with an unknown APID to the distributor
|
|
||||||
//! pus_tc.set_apid(0x003);
|
|
||||||
//! size = pus_tc
|
|
||||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
|
||||||
//! .expect("Error writing TC to buffer");
|
|
||||||
//! let tc_slice = &test_buf[0..size];
|
|
||||||
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
|
|
||||||
//!
|
|
||||||
//! // User helper function to retrieve concrete class
|
|
||||||
//! let concrete_handler_ref: &ConcreteApidHandler = ccsds_distributor
|
|
||||||
//! .apid_handler_ref()
|
|
||||||
//! .expect("Casting back to concrete type failed");
|
|
||||||
//! assert_eq!(concrete_handler_ref.known_call_count, 1);
|
|
||||||
//! assert_eq!(concrete_handler_ref.unknown_call_count, 1);
|
|
||||||
//!
|
|
||||||
//! // It's also possible to retrieve a mutable reference
|
|
||||||
//! let mutable_ref: &mut ConcreteApidHandler = ccsds_distributor
|
|
||||||
//! .apid_handler_mut()
|
|
||||||
//! .expect("Casting back to concrete type failed");
|
|
||||||
//! mutable_ref.mutable_foo();
|
|
||||||
//! ```
|
|
||||||
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTcCore};
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use core::fmt::{Display, Formatter};
|
|
||||||
use downcast_rs::Downcast;
|
|
||||||
use spacepackets::{ByteConversionError, CcsdsPacket, SpHeader};
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
/// Generic trait for a handler or dispatcher object handling CCSDS packets.
|
|
||||||
///
|
|
||||||
/// Users should implement this trait on their custom CCSDS packet handler and then pass a boxed
|
|
||||||
/// instance of this handler to the [CcsdsDistributor]. The distributor will use the trait
|
|
||||||
/// interface to dispatch received packets to the user based on the Application Process Identifier
|
|
||||||
/// (APID) field of the CCSDS packet.
|
|
||||||
///
|
|
||||||
/// This trait automatically implements the [downcast_rs::Downcast] to allow a more convenient API
|
|
||||||
/// to cast trait objects back to their concrete type after the handler was passed to the
|
|
||||||
/// distributor.
|
|
||||||
pub trait CcsdsPacketHandler: Downcast {
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn valid_apids(&self) -> &'static [u16];
|
|
||||||
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8])
|
|
||||||
-> Result<(), Self::Error>;
|
|
||||||
fn handle_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(CcsdsPacketHandler assoc Error);
|
|
||||||
|
|
||||||
pub trait SendableCcsdsPacketHandler: CcsdsPacketHandler + Send {}
|
|
||||||
|
|
||||||
impl<T: CcsdsPacketHandler + Send> SendableCcsdsPacketHandler for T {}
|
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(SendableCcsdsPacketHandler assoc Error);
|
|
||||||
|
|
||||||
/// The CCSDS distributor dispatches received CCSDS packets to a user provided packet handler.
|
|
||||||
///
|
|
||||||
/// The passed APID handler is required to be [Send]able to allow more ergonomic usage with
|
|
||||||
/// threads.
|
|
||||||
pub struct CcsdsDistributor<E> {
|
|
||||||
/// User provided APID handler stored as a generic trait object.
|
|
||||||
/// It can be cast back to the original concrete type using the [Self::apid_handler_ref] or
|
|
||||||
/// the [Self::apid_handler_mut] method.
|
|
||||||
pub apid_handler: Box<dyn SendableCcsdsPacketHandler<Error = E>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum CcsdsError<E> {
|
|
||||||
CustomError(E),
|
|
||||||
ByteConversionError(ByteConversionError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Display> Display for CcsdsError<E> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::CustomError(e) => write!(f, "{e}"),
|
|
||||||
Self::ByteConversionError(e) => write!(f, "{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl<E: Error> Error for CcsdsError<E> {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::CustomError(e) => e.source(),
|
|
||||||
Self::ByteConversionError(e) => e.source(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> ReceivesCcsdsTc for CcsdsDistributor<E> {
|
|
||||||
type Error = CcsdsError<E>;
|
|
||||||
|
|
||||||
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
self.dispatch_ccsds(header, tc_raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> ReceivesTcCore for CcsdsDistributor<E> {
|
|
||||||
type Error = CcsdsError<E>;
|
|
||||||
|
|
||||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
if tc_raw.len() < 7 {
|
|
||||||
return Err(CcsdsError::ByteConversionError(
|
|
||||||
ByteConversionError::FromSliceTooSmall {
|
|
||||||
found: tc_raw.len(),
|
|
||||||
expected: 7,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let (sp_header, _) =
|
|
||||||
SpHeader::from_be_bytes(tc_raw).map_err(|e| CcsdsError::ByteConversionError(e))?;
|
|
||||||
self.dispatch_ccsds(&sp_header, tc_raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> CcsdsDistributor<E> {
|
|
||||||
pub fn new(apid_handler: Box<dyn SendableCcsdsPacketHandler<Error = E>>) -> Self {
|
|
||||||
CcsdsDistributor { apid_handler }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function can be used to retrieve a reference to the concrete instance of the APID
|
|
||||||
/// handler after it was passed to the distributor. See the
|
|
||||||
/// [module documentation][crate::tmtc::ccsds_distrib] for an fsrc-example.
|
|
||||||
pub fn apid_handler_ref<T: SendableCcsdsPacketHandler<Error = E>>(&self) -> Option<&T> {
|
|
||||||
self.apid_handler.downcast_ref::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function can be used to retrieve a mutable reference to the concrete instance of the
|
|
||||||
/// APID handler after it was passed to the distributor.
|
|
||||||
pub fn apid_handler_mut<T: SendableCcsdsPacketHandler<Error = E>>(&mut self) -> Option<&mut T> {
|
|
||||||
self.apid_handler.downcast_mut::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispatch_ccsds(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), CcsdsError<E>> {
|
|
||||||
let apid = sp_header.apid();
|
|
||||||
let valid_apids = self.apid_handler.valid_apids();
|
|
||||||
for &valid_apid in valid_apids {
|
|
||||||
if valid_apid == apid {
|
|
||||||
return self
|
|
||||||
.apid_handler
|
|
||||||
.handle_known_apid(sp_header, tc_raw)
|
|
||||||
.map_err(|e| CcsdsError::CustomError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.apid_handler
|
|
||||||
.handle_unknown_apid(sp_header, tc_raw)
|
|
||||||
.map_err(|e| CcsdsError::CustomError(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
|
|
||||||
use spacepackets::ecss::tc::PusTcCreator;
|
|
||||||
use spacepackets::ecss::WritablePusPacket;
|
|
||||||
use spacepackets::CcsdsPacket;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
fn is_send<T: Send>(_: &T) {}
|
|
||||||
|
|
||||||
pub fn generate_ping_tc(buf: &mut [u8]) -> &[u8] {
|
|
||||||
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
|
||||||
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let size = pus_tc
|
|
||||||
.write_to_bytes(buf)
|
|
||||||
.expect("Error writing TC to buffer");
|
|
||||||
assert_eq!(size, 13);
|
|
||||||
&buf[0..size]
|
|
||||||
}
|
|
||||||
|
|
||||||
type SharedPacketQueue = Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>;
|
|
||||||
pub struct BasicApidHandlerSharedQueue {
|
|
||||||
pub known_packet_queue: SharedPacketQueue,
|
|
||||||
pub unknown_packet_queue: SharedPacketQueue,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BasicApidHandlerOwnedQueue {
|
|
||||||
pub known_packet_queue: VecDeque<(u16, Vec<u8>)>,
|
|
||||||
pub unknown_packet_queue: VecDeque<(u16, Vec<u8>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CcsdsPacketHandler for BasicApidHandlerSharedQueue {
|
|
||||||
type Error = ();
|
|
||||||
fn valid_apids(&self) -> &'static [u16] {
|
|
||||||
&[0x000, 0x002]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_known_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
vec.extend_from_slice(tc_raw);
|
|
||||||
self.known_packet_queue
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push_back((sp_header.apid(), vec));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
vec.extend_from_slice(tc_raw);
|
|
||||||
self.unknown_packet_queue
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push_back((sp_header.apid(), vec));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CcsdsPacketHandler for BasicApidHandlerOwnedQueue {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn valid_apids(&self) -> &'static [u16] {
|
|
||||||
&[0x000, 0x002]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_known_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
vec.extend_from_slice(tc_raw);
|
|
||||||
Ok(self.known_packet_queue.push_back((sp_header.apid(), vec)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
vec.extend_from_slice(tc_raw);
|
|
||||||
Ok(self.unknown_packet_queue.push_back((sp_header.apid(), vec)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_distribs_known_apid() {
|
|
||||||
let known_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let apid_handler = BasicApidHandlerSharedQueue {
|
|
||||||
known_packet_queue: known_packet_queue.clone(),
|
|
||||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
|
||||||
};
|
|
||||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
|
||||||
is_send(&ccsds_distrib);
|
|
||||||
let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
|
||||||
|
|
||||||
ccsds_distrib.pass_tc(tc_slice).expect("Passing TC failed");
|
|
||||||
let recvd = known_packet_queue.lock().unwrap().pop_front();
|
|
||||||
assert!(unknown_packet_queue.lock().unwrap().is_empty());
|
|
||||||
assert!(recvd.is_some());
|
|
||||||
let (apid, packet) = recvd.unwrap();
|
|
||||||
assert_eq!(apid, 0x002);
|
|
||||||
assert_eq!(packet, tc_slice);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_distribs_unknown_apid() {
|
|
||||||
let known_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let apid_handler = BasicApidHandlerSharedQueue {
|
|
||||||
known_packet_queue: known_packet_queue.clone(),
|
|
||||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
|
||||||
};
|
|
||||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
|
||||||
let mut sph = SpHeader::tc_unseg(0x004, 0x34, 0).unwrap();
|
|
||||||
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
pus_tc
|
|
||||||
.write_to_bytes(test_buf.as_mut_slice())
|
|
||||||
.expect("Error writing TC to buffer");
|
|
||||||
ccsds_distrib.pass_tc(&test_buf).expect("Passing TC failed");
|
|
||||||
let recvd = unknown_packet_queue.lock().unwrap().pop_front();
|
|
||||||
assert!(known_packet_queue.lock().unwrap().is_empty());
|
|
||||||
assert!(recvd.is_some());
|
|
||||||
let (apid, packet) = recvd.unwrap();
|
|
||||||
assert_eq!(apid, 0x004);
|
|
||||||
assert_eq!(packet.as_slice(), test_buf);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
//! Telemetry and Telecommanding (TMTC) module. Contains packet routing components with special
|
|
||||||
//! support for CCSDS and ECSS packets.
|
|
||||||
//!
|
|
||||||
//! The distributor modules provided by this module use trait objects provided by the user to
|
|
||||||
//! directly dispatch received packets to packet listeners based on packet fields like the CCSDS
|
|
||||||
//! Application Process ID (APID) or the ECSS PUS service type. This allows for fast packet
|
|
||||||
//! routing without the overhead and complication of using message queues. However, it also requires
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
|
||||||
use spacepackets::SpHeader;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub mod ccsds_distrib;
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub mod pus_distrib;
|
|
||||||
pub mod tm_helper;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler};
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub use pus_distrib::{PusDistributor, PusServiceProvider};
|
|
||||||
|
|
||||||
pub type TargetId = u32;
|
|
||||||
|
|
||||||
/// Generic trait for object which can receive any telecommands in form of a raw bytestream, with
|
|
||||||
/// no assumptions about the received protocol.
|
|
||||||
///
|
|
||||||
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
|
|
||||||
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows to pass the respective packets in
|
|
||||||
/// raw byte format into them.
|
|
||||||
pub trait ReceivesTcCore {
|
|
||||||
type Error;
|
|
||||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extension trait of [ReceivesTcCore] which allows downcasting by implementing [Downcast] and
|
|
||||||
/// is also sendable.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub trait ReceivesTc: ReceivesTcCore + Downcast + Send {
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast(&self) -> &dyn ReceivesTcCore<Error = Self::Error>;
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast_mut(&mut self) -> &mut dyn ReceivesTcCore<Error = Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature
|
|
||||||
/// is enabled.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl<T> ReceivesTc for T
|
|
||||||
where
|
|
||||||
T: ReceivesTcCore + Send + 'static,
|
|
||||||
{
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast(&self) -> &dyn ReceivesTcCore<Error = Self::Error> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast_mut(&mut self) -> &mut dyn ReceivesTcCore<Error = Self::Error> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl_downcast!(ReceivesTc assoc Error);
|
|
||||||
|
|
||||||
/// Generic trait for object which can receive CCSDS space packets, for example ECSS PUS packets
|
|
||||||
/// for CCSDS File Delivery Protocol (CFDP) packets.
|
|
||||||
///
|
|
||||||
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
|
|
||||||
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows
|
|
||||||
/// to pass the respective packets in raw byte format or in CCSDS format into them.
|
|
||||||
pub trait ReceivesCcsdsTc {
|
|
||||||
type Error;
|
|
||||||
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic trait for a TM packet source, with no restrictions on the type of TM.
|
|
||||||
/// Implementors write the telemetry into the provided buffer and return the size of the telemetry.
|
|
||||||
pub trait TmPacketSourceCore {
|
|
||||||
type Error;
|
|
||||||
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extension trait of [TmPacketSourceCore] which allows downcasting by implementing [Downcast] and
|
|
||||||
/// is also sendable.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub trait TmPacketSource: TmPacketSourceCore + Downcast + Send {
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast(&self) -> &dyn TmPacketSourceCore<Error = Self::Error>;
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast_mut(&mut self) -> &mut dyn TmPacketSourceCore<Error = Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature
|
|
||||||
/// is enabled.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
impl<T> TmPacketSource for T
|
|
||||||
where
|
|
||||||
T: TmPacketSourceCore + Send + 'static,
|
|
||||||
{
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast(&self) -> &dyn TmPacketSourceCore<Error = Self::Error> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
// Remove this once trait upcasting coercion has been implemented.
|
|
||||||
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
|
|
||||||
fn upcast_mut(&mut self) -> &mut dyn TmPacketSourceCore<Error = Self::Error> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,369 +0,0 @@
|
|||||||
//! ECSS PUS packet routing components.
|
|
||||||
//!
|
|
||||||
//! The routing components consist of two core components:
|
|
||||||
//! 1. [PusDistributor] component which dispatches received packets to a user-provided handler.
|
|
||||||
//! 2. [PusServiceProvider] trait which should be implemented by the user-provided PUS packet
|
|
||||||
//! handler.
|
|
||||||
//!
|
|
||||||
//! The [PusDistributor] implements the [ReceivesEcssPusTc], [ReceivesCcsdsTc] and the
|
|
||||||
//! [ReceivesTcCore] trait which allows to pass raw packets, CCSDS packets and PUS TC packets into
|
|
||||||
//! it. Upon receiving a packet, it performs the following steps:
|
|
||||||
//!
|
|
||||||
//! 1. It tries to extract the [SpHeader] and [spacepackets::ecss::tc::PusTcReader] objects from
|
|
||||||
//! the raw bytestream. If this process fails, a [PusDistribError::PusError] is returned to the
|
|
||||||
//! user.
|
|
||||||
//! 2. If it was possible to extract both components, the packet will be passed to the
|
|
||||||
//! [PusServiceProvider::handle_pus_tc_packet] method provided by the user.
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use spacepackets::ecss::WritablePusPacket;
|
|
||||||
//! use satrs_core::tmtc::pus_distrib::{PusDistributor, PusServiceProvider};
|
|
||||||
//! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
|
||||||
//! use spacepackets::SpHeader;
|
|
||||||
//! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader};
|
|
||||||
//! struct ConcretePusHandler {
|
|
||||||
//! handler_call_count: u32
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // This is a very simple possible service provider. It increments an internal call count field,
|
|
||||||
//! // which is used to verify the handler was called
|
|
||||||
//! impl PusServiceProvider for ConcretePusHandler {
|
|
||||||
//! type Error = ();
|
|
||||||
//! fn handle_pus_tc_packet(&mut self, service: u8, header: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
|
||||||
//! assert_eq!(service, 17);
|
|
||||||
//! assert_eq!(pus_tc.len_packed(), 13);
|
|
||||||
//! self.handler_call_count += 1;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! let service_handler = ConcretePusHandler {
|
|
||||||
//! handler_call_count: 0
|
|
||||||
//! };
|
|
||||||
//! let mut pus_distributor = PusDistributor::new(Box::new(service_handler));
|
|
||||||
//!
|
|
||||||
//! // Create and pass PUS ping telecommand with a valid APID
|
|
||||||
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
|
||||||
//! let mut pus_tc = PusTcCreator::new_simple(&mut space_packet_header, 17, 1, None, true);
|
|
||||||
//! let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
//! let mut size = pus_tc
|
|
||||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
|
||||||
//! .expect("Error writing TC to buffer");
|
|
||||||
//! let tc_slice = &test_buf[0..size];
|
|
||||||
//!
|
|
||||||
//! pus_distributor.pass_tc(tc_slice).expect("Passing PUS telecommand failed");
|
|
||||||
//!
|
|
||||||
//! // User helper function to retrieve concrete class. We check the call count here to verify
|
|
||||||
//! // that the PUS ping telecommand was routed successfully.
|
|
||||||
//! let concrete_handler_ref: &ConcretePusHandler = pus_distributor
|
|
||||||
//! .service_provider_ref()
|
|
||||||
//! .expect("Casting back to concrete type failed");
|
|
||||||
//! assert_eq!(concrete_handler_ref.handler_call_count, 1);
|
|
||||||
//! ```
|
|
||||||
use crate::pus::ReceivesEcssPusTc;
|
|
||||||
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTcCore};
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use core::fmt::{Display, Formatter};
|
|
||||||
use downcast_rs::Downcast;
|
|
||||||
use spacepackets::ecss::tc::PusTcReader;
|
|
||||||
use spacepackets::ecss::{PusError, PusPacket};
|
|
||||||
use spacepackets::SpHeader;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub trait PusServiceProvider: Downcast {
|
|
||||||
type Error;
|
|
||||||
fn handle_pus_tc_packet(
|
|
||||||
&mut self,
|
|
||||||
service: u8,
|
|
||||||
header: &SpHeader,
|
|
||||||
pus_tc: &PusTcReader,
|
|
||||||
) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
downcast_rs::impl_downcast!(PusServiceProvider assoc Error);
|
|
||||||
|
|
||||||
pub trait SendablePusServiceProvider: PusServiceProvider + Send {}
|
|
||||||
|
|
||||||
impl<T: Send + PusServiceProvider> SendablePusServiceProvider for T {}
|
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(SendablePusServiceProvider assoc Error);
|
|
||||||
|
|
||||||
/// Generic distributor object which dispatches received packets to a user provided handler.
|
|
||||||
///
|
|
||||||
/// This distributor expects the passed trait object to be [Send]able to allow more ergonomic
|
|
||||||
/// usage with threads.
|
|
||||||
pub struct PusDistributor<E> {
|
|
||||||
pub service_provider: Box<dyn SendablePusServiceProvider<Error = E>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> PusDistributor<E> {
|
|
||||||
pub fn new(service_provider: Box<dyn SendablePusServiceProvider<Error = E>>) -> Self {
|
|
||||||
PusDistributor { service_provider }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum PusDistribError<E> {
|
|
||||||
CustomError(E),
|
|
||||||
PusError(PusError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Display> Display for PusDistribError<E> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
|
||||||
match self {
|
|
||||||
PusDistribError::CustomError(e) => write!(f, "{e}"),
|
|
||||||
PusDistribError::PusError(e) => write!(f, "{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl<E: Error> Error for PusDistribError<E> {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::CustomError(e) => e.source(),
|
|
||||||
Self::PusError(e) => e.source(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> ReceivesTcCore for PusDistributor<E> {
|
|
||||||
type Error = PusDistribError<E>;
|
|
||||||
fn pass_tc(&mut self, tm_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
// Convert to ccsds and call pass_ccsds
|
|
||||||
let (sp_header, _) = SpHeader::from_be_bytes(tm_raw)
|
|
||||||
.map_err(|e| PusDistribError::PusError(PusError::ByteConversion(e)))?;
|
|
||||||
self.pass_ccsds(&sp_header, tm_raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> ReceivesCcsdsTc for PusDistributor<E> {
|
|
||||||
type Error = PusDistribError<E>;
|
|
||||||
fn pass_ccsds(&mut self, header: &SpHeader, tm_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
let (tc, _) = PusTcReader::new(tm_raw).map_err(|e| PusDistribError::PusError(e))?;
|
|
||||||
self.pass_pus_tc(header, &tc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> ReceivesEcssPusTc for PusDistributor<E> {
|
|
||||||
type Error = PusDistribError<E>;
|
|
||||||
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
|
||||||
self.service_provider
|
|
||||||
.handle_pus_tc_packet(pus_tc.service(), header, pus_tc)
|
|
||||||
.map_err(|e| PusDistribError::CustomError(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: 'static> PusDistributor<E> {
|
|
||||||
pub fn service_provider_ref<T: SendablePusServiceProvider<Error = E>>(&self) -> Option<&T> {
|
|
||||||
self.service_provider.downcast_ref::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn service_provider_mut<T: SendablePusServiceProvider<Error = E>>(
|
|
||||||
&mut self,
|
|
||||||
) -> Option<&mut T> {
|
|
||||||
self.service_provider.downcast_mut::<T>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::tmtc::ccsds_distrib::tests::{
|
|
||||||
generate_ping_tc, BasicApidHandlerOwnedQueue, BasicApidHandlerSharedQueue,
|
|
||||||
};
|
|
||||||
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use spacepackets::ecss::PusError;
|
|
||||||
use spacepackets::CcsdsPacket;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
fn is_send<T: Send>(_: &T) {}
|
|
||||||
|
|
||||||
struct PusHandlerSharedQueue {
|
|
||||||
pub pus_queue: Arc<Mutex<VecDeque<(u8, u16, Vec<u8>)>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct PusHandlerOwnedQueue {
|
|
||||||
pub pus_queue: VecDeque<(u8, u16, Vec<u8>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusServiceProvider for PusHandlerSharedQueue {
|
|
||||||
type Error = PusError;
|
|
||||||
fn handle_pus_tc_packet(
|
|
||||||
&mut self,
|
|
||||||
service: u8,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
pus_tc: &PusTcReader,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec: Vec<u8> = Vec::new();
|
|
||||||
vec.extend_from_slice(pus_tc.raw_data());
|
|
||||||
Ok(self
|
|
||||||
.pus_queue
|
|
||||||
.lock()
|
|
||||||
.expect("Mutex lock failed")
|
|
||||||
.push_back((service, sp_header.apid(), vec)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusServiceProvider for PusHandlerOwnedQueue {
|
|
||||||
type Error = PusError;
|
|
||||||
fn handle_pus_tc_packet(
|
|
||||||
&mut self,
|
|
||||||
service: u8,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
pus_tc: &PusTcReader,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
let mut vec: Vec<u8> = Vec::new();
|
|
||||||
vec.extend_from_slice(pus_tc.raw_data());
|
|
||||||
Ok(self.pus_queue.push_back((service, sp_header.apid(), vec)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ApidHandlerShared {
|
|
||||||
pub pus_distrib: PusDistributor<PusError>,
|
|
||||||
pub handler_base: BasicApidHandlerSharedQueue,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ApidHandlerOwned {
|
|
||||||
pub pus_distrib: PusDistributor<PusError>,
|
|
||||||
handler_base: BasicApidHandlerOwnedQueue,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! apid_handler_impl {
|
|
||||||
() => {
|
|
||||||
type Error = PusError;
|
|
||||||
|
|
||||||
fn valid_apids(&self) -> &'static [u16] {
|
|
||||||
&[0x000, 0x002]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_known_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
self.handler_base
|
|
||||||
.handle_known_apid(&sp_header, tc_raw)
|
|
||||||
.ok()
|
|
||||||
.expect("Unexpected error");
|
|
||||||
match self.pus_distrib.pass_ccsds(&sp_header, tc_raw) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => match e {
|
|
||||||
PusDistribError::CustomError(_) => Ok(()),
|
|
||||||
PusDistribError::PusError(e) => Err(e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
self.handler_base
|
|
||||||
.handle_unknown_apid(&sp_header, tc_raw)
|
|
||||||
.ok()
|
|
||||||
.expect("Unexpected error");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CcsdsPacketHandler for ApidHandlerOwned {
|
|
||||||
apid_handler_impl!();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CcsdsPacketHandler for ApidHandlerShared {
|
|
||||||
apid_handler_impl!();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
fn test_pus_distribution() {
|
|
||||||
let known_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
|
||||||
let pus_queue = Arc::new(Mutex::default());
|
|
||||||
let pus_handler = PusHandlerSharedQueue {
|
|
||||||
pus_queue: pus_queue.clone(),
|
|
||||||
};
|
|
||||||
let handler_base = BasicApidHandlerSharedQueue {
|
|
||||||
known_packet_queue: known_packet_queue.clone(),
|
|
||||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pus_distrib = PusDistributor {
|
|
||||||
service_provider: Box::new(pus_handler),
|
|
||||||
};
|
|
||||||
is_send(&pus_distrib);
|
|
||||||
let apid_handler = ApidHandlerShared {
|
|
||||||
pus_distrib,
|
|
||||||
handler_base,
|
|
||||||
};
|
|
||||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
|
||||||
let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
|
||||||
|
|
||||||
// Pass packet to distributor
|
|
||||||
ccsds_distrib
|
|
||||||
.pass_tc(tc_slice)
|
|
||||||
.expect("Passing TC slice failed");
|
|
||||||
let recvd_ccsds = known_packet_queue.lock().unwrap().pop_front();
|
|
||||||
assert!(unknown_packet_queue.lock().unwrap().is_empty());
|
|
||||||
assert!(recvd_ccsds.is_some());
|
|
||||||
let (apid, packet) = recvd_ccsds.unwrap();
|
|
||||||
assert_eq!(apid, 0x002);
|
|
||||||
assert_eq!(packet.as_slice(), tc_slice);
|
|
||||||
let recvd_pus = pus_queue.lock().unwrap().pop_front();
|
|
||||||
assert!(recvd_pus.is_some());
|
|
||||||
let (service, apid, tc_raw) = recvd_pus.unwrap();
|
|
||||||
assert_eq!(service, 17);
|
|
||||||
assert_eq!(apid, 0x002);
|
|
||||||
assert_eq!(tc_raw, tc_slice);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_as_any_cast() {
|
|
||||||
let pus_handler = PusHandlerOwnedQueue::default();
|
|
||||||
let handler_base = BasicApidHandlerOwnedQueue::default();
|
|
||||||
let pus_distrib = PusDistributor {
|
|
||||||
service_provider: Box::new(pus_handler),
|
|
||||||
};
|
|
||||||
|
|
||||||
let apid_handler = ApidHandlerOwned {
|
|
||||||
pus_distrib,
|
|
||||||
handler_base,
|
|
||||||
};
|
|
||||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
|
||||||
|
|
||||||
let mut test_buf: [u8; 32] = [0; 32];
|
|
||||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
|
||||||
|
|
||||||
ccsds_distrib
|
|
||||||
.pass_tc(tc_slice)
|
|
||||||
.expect("Passing TC slice failed");
|
|
||||||
|
|
||||||
let apid_handler_casted_back: &mut ApidHandlerOwned = ccsds_distrib
|
|
||||||
.apid_handler_mut()
|
|
||||||
.expect("Cast to concrete type ApidHandler failed");
|
|
||||||
assert!(!apid_handler_casted_back
|
|
||||||
.handler_base
|
|
||||||
.known_packet_queue
|
|
||||||
.is_empty());
|
|
||||||
let handler_casted_back: &mut PusHandlerOwnedQueue = apid_handler_casted_back
|
|
||||||
.pus_distrib
|
|
||||||
.service_provider_mut()
|
|
||||||
.expect("Cast to concrete type PusHandlerOwnedQueue failed");
|
|
||||||
assert!(!handler_casted_back.pus_queue.is_empty());
|
|
||||||
let (service, apid, packet_raw) = handler_casted_back.pus_queue.pop_front().unwrap();
|
|
||||||
assert_eq!(service, 17);
|
|
||||||
assert_eq!(apid, 0x002);
|
|
||||||
assert_eq!(packet_raw.as_slice(), tc_slice);
|
|
||||||
}
|
|
||||||
}
|
|
37
satrs-example-stm32f3-disco/.cargo/def_config.toml
Normal file
37
satrs-example-stm32f3-disco/.cargo/def_config.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
# uncomment ONE of these three option to make `cargo run` start a GDB session
|
||||||
|
# which option to pick depends on your system
|
||||||
|
# You can also replace openocd.gdb by jlink.gdb when using a J-Link.
|
||||||
|
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
||||||
|
# runner = "gdb-multiarch -q -x openocd.gdb"
|
||||||
|
# runner = "gdb -q -x openocd.gdb"
|
||||||
|
runner = "probe-rs run --chip STM32F303VCTx"
|
||||||
|
|
||||||
|
rustflags = [
|
||||||
|
"-C", "linker=flip-link",
|
||||||
|
# LLD (shipped with the Rust toolchain) is used as the default linker
|
||||||
|
"-C", "link-arg=-Tlink.x",
|
||||||
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
|
||||||
|
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||||
|
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||||
|
"-C", "link-arg=--nmagic",
|
||||||
|
|
||||||
|
# if you run into problems with LLD switch to the GNU linker by commenting out
|
||||||
|
# this line
|
||||||
|
# "-C", "linker=arm-none-eabi-ld",
|
||||||
|
|
||||||
|
# if you need to link to pre-compiled C libraries provided by a C toolchain
|
||||||
|
# use GCC as the linker by commenting out both lines above and then
|
||||||
|
# uncommenting the three lines below
|
||||||
|
# "-C", "linker=arm-none-eabi-gcc",
|
||||||
|
# "-C", "link-arg=-Wl,-Tlink.x",
|
||||||
|
# "-C", "link-arg=-nostartfiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
# comment out the following line if you intend to run unit tests on host machine
|
||||||
|
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||||
|
|
||||||
|
[env]
|
||||||
|
DEFMT_LOG = "info"
|
1
satrs-example-stm32f3-disco/.gitignore
vendored
1
satrs-example-stm32f3-disco/.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/itm.txt
|
/itm.txt
|
||||||
/.cargo/config*
|
/.cargo/config*
|
||||||
|
/.vscode
|
||||||
|
66
satrs-example-stm32f3-disco/.vscode/launch.json
vendored
66
satrs-example-stm32f3-disco/.vscode/launch.json
vendored
@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
/*
|
|
||||||
* Requires the Rust Language Server (RLS) and Cortex-Debug extensions
|
|
||||||
* https://marketplace.visualstudio.com/items?itemName=rust-lang.rust
|
|
||||||
* https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug
|
|
||||||
*/
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
/* Launches debug session for currently open example */
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug",
|
|
||||||
"servertype": "openocd",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"preLaunchTask": "cargo build",
|
|
||||||
"runToEntryPoint": "true",
|
|
||||||
"executable": "./target/thumbv7em-none-eabihf/debug/satrs-example-stm32f3-disco",
|
|
||||||
"preLaunchCommands": ["break rust_begin_unwind"],
|
|
||||||
"device": "STM32F303VCT6",
|
|
||||||
"configFiles": [
|
|
||||||
"${workspaceRoot}/.vscode/openocd-helpers.tcl",
|
|
||||||
"interface/stlink.cfg",
|
|
||||||
"target/stm32f3x.cfg"
|
|
||||||
],
|
|
||||||
"svdFile": "${env:HOME}/.svd/STM32F303.svd",
|
|
||||||
"swoConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"cpuFrequency": 8000000,
|
|
||||||
"swoFrequency": 2000000,
|
|
||||||
"source": "probe",
|
|
||||||
"decoders": [
|
|
||||||
{ "type": "console", "label": "ITM", "port": 0 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/* Launches debug session for currently open example */
|
|
||||||
"type": "cortex-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Release",
|
|
||||||
"servertype": "openocd",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"preLaunchTask": "cargo build",
|
|
||||||
"runToEntryPoint": "true",
|
|
||||||
"executable": "./target/thumbv7em-none-eabihf/release/satrs-example-stm32f3-disco",
|
|
||||||
"preLaunchCommands": ["break rust_begin_unwind"],
|
|
||||||
"device": "STM32F303VCT6",
|
|
||||||
"configFiles": [
|
|
||||||
"${workspaceRoot}/.vscode/openocd-helpers.tcl",
|
|
||||||
"interface/stlink.cfg",
|
|
||||||
"target/stm32f3x.cfg"
|
|
||||||
],
|
|
||||||
"svdFile": "${env:HOME}/.svd/STM32F303.svd",
|
|
||||||
"swoConfig": {
|
|
||||||
"enabled": true,
|
|
||||||
"cpuFrequency": 8000000,
|
|
||||||
"swoFrequency": 2000000,
|
|
||||||
"source": "probe",
|
|
||||||
"decoders": [
|
|
||||||
{ "type": "console", "label": "ITM", "port": 0 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"cortex-debug.gdbPath.linux": "gdb-multiarch"
|
|
||||||
}
|
|
579
satrs-example-stm32f3-disco/Cargo.lock
generated
579
satrs-example-stm32f3-disco/Cargo.lock
generated
@ -13,18 +13,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-polyfill"
|
name = "atomic-polyfill"
|
||||||
version = "0.1.11"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28"
|
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"critical-section",
|
"critical-section",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bare-metal"
|
name = "bare-metal"
|
||||||
@ -55,20 +55,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bxcan"
|
name = "bxcan"
|
||||||
version = "0.6.2"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b13b4b2ea9ab2ba924063ebb86ad895cb79f4a79bf90f27949eb20c335b30f9"
|
checksum = "40ac3d0c0a542d0ab5521211f873f62706a7136df415676f676d347e5a41dd80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"nb 1.0.0",
|
"embedded-hal 0.2.7",
|
||||||
|
"nb 1.1.0",
|
||||||
"vcell",
|
"vcell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
@ -87,31 +88,36 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.23"
|
version = "0.4.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "git+https://github.com/robamu/cobs.rs.git?branch=all_features#c70a7f30fd00a7cbdb7666dec12b437977385d40"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m"
|
name = "cortex-m"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70858629a458fdfd39f9675c4dc309411f2a3f83bede76988d81bf1a0ecee9e0"
|
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 0.2.5",
|
"bare-metal 0.2.5",
|
||||||
"bitfield",
|
"bitfield",
|
||||||
"embedded-hal",
|
"critical-section",
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
"volatile-register",
|
"volatile-register",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m-rt"
|
name = "cortex-m-rt"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6d3328b8b5534f0c90acd66b68950f2763b37e0173cac4d8b4937c4a80761f9"
|
checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m-rt-macros",
|
"cortex-m-rt-macros",
|
||||||
]
|
]
|
||||||
@ -124,48 +130,44 @@ checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m-rtic"
|
name = "cortex-m-semihosting"
|
||||||
version = "1.1.3"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6b82f1c39acd6c3a35c2013b6110c20f5bc534522791fabadeed49ccada2dce"
|
checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rtic-macros",
|
|
||||||
"heapless",
|
|
||||||
"rtic-core",
|
|
||||||
"rtic-monotonic",
|
|
||||||
"version_check",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m-rtic-macros"
|
name = "crc"
|
||||||
version = "1.1.5"
|
version = "3.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e8e9645ef54bec1cf70ac33e9bf9566e6507ab5b41ae6baf3735662194e8607"
|
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error",
|
"crc-catalog",
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"rtic-syntax",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc-catalog"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "critical-section"
|
name = "critical-section"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52"
|
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.14.2"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
|
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@ -173,26 +175,102 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.14.2"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.58",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.14.2"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3939552907426de152b3c2c6f51ed53f98f448babd26f28694c95f5906194595"
|
||||||
|
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.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18bdc7a7b92ac413e19e95240e75d3a73a8d8e78aa24a594c22cbb4d44b4bbda"
|
||||||
|
dependencies = [
|
||||||
|
"defmt-parser",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
@ -214,6 +292,12 @@ dependencies = [
|
|||||||
"void",
|
"void",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-time"
|
name = "embedded-time"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -225,25 +309,31 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumset"
|
name = "enumset"
|
||||||
version = "1.0.12"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753"
|
checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"enumset_derive",
|
"enumset_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumset_derive"
|
name = "enumset_derive"
|
||||||
version = "0.6.1"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0"
|
checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.58",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@ -252,18 +342,42 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fugit"
|
name = "fugit"
|
||||||
version = "0.3.6"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ab17bb279def6720d058cb6c052249938e7f99260ab534879281a95367a87e5"
|
checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gcd",
|
"gcd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcd"
|
name = "futures-core"
|
||||||
version = "2.2.0"
|
version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4b1b088ad0a967aa29540456b82fc8903f854775d33f71e9709c4efb3dfbfd2"
|
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]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
@ -276,9 +390,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.6"
|
version = "0.14.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
@ -286,29 +400,26 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hash32"
|
name = "hash32"
|
||||||
version = "0.2.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heapless"
|
name = "heapless"
|
||||||
version = "0.7.16"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743"
|
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-polyfill",
|
|
||||||
"hash32",
|
"hash32",
|
||||||
"rustc_version 0.4.0",
|
|
||||||
"spin",
|
|
||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -320,41 +431,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.2"
|
version = "2.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itm_logger"
|
|
||||||
version = "0.1.3-pre.0"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsm303dlhc"
|
name = "lsm303dlhc"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -362,7 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9e5d1a5c290951321d1b0d4a40edd828537de9889134a0e67c5146542ae57706"
|
checksum = "9e5d1a5c290951321d1b0d4a40edd828537de9889134a0e67c5146542ae57706"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"cast",
|
||||||
"embedded-hal",
|
"embedded-hal 0.2.7",
|
||||||
"generic-array 0.11.2",
|
"generic-array 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -372,7 +456,7 @@ version = "1.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94"
|
checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.6",
|
"generic-array 0.14.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -381,14 +465,14 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nb 1.0.0",
|
"nb 1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
|
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
@ -414,19 +498,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-iter"
|
name = "num-iter"
|
||||||
version = "0.1.43"
|
version = "0.1.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
@ -446,27 +529,60 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "panic-itm"
|
name = "num_enum"
|
||||||
version = "0.4.2"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d577d97d1b31268087b6dddf2470e6794ef5eee87d9dca7fcd0481695391a4c"
|
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.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "panic-probe"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
|
"defmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.11"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
@ -477,7 +593,7 @@ dependencies = [
|
|||||||
"proc-macro-error-attr",
|
"proc-macro-error-attr",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -494,31 +610,54 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.49"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.23"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rtcc"
|
name = "rtcc"
|
||||||
version = "0.3.0"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3623619ce77c09a7d87cf7c61c5c887b9c7dee8805f66af6c4aa5824be4d9930"
|
checksum = "95973c3a0274adc4f3c5b70d2b5b85618d6de9559a6737d3293ecae9a2fc0839"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "rtic-core"
|
name = "rtic-core"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -526,21 +665,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
|
checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rtic-monotonic"
|
name = "rtic-macros"
|
||||||
version = "1.0.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb8b0b822d1a366470b9cea83a1d4e788392db763539dc4ba022bcc787fece82"
|
checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtic-syntax"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3ad3ae243dd8d0a1b064615f664d4fa7e63929939074c564cbe5efdc4c503065"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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-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]]
|
[[package]]
|
||||||
@ -558,31 +717,52 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver 1.0.16",
|
"semver 1.0.22",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sat-rs-example-stm32f-disco"
|
name = "satrs"
|
||||||
|
version = "0.2.0-rc.0"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"crc",
|
||||||
|
"delegate",
|
||||||
|
"num-traits",
|
||||||
|
"num_enum",
|
||||||
|
"paste",
|
||||||
|
"satrs-shared",
|
||||||
|
"smallvec",
|
||||||
|
"spacepackets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "satrs-example-stm32f3-disco"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"cortex-m-rtic",
|
"cortex-m-semihosting",
|
||||||
"embedded-hal",
|
"defmt",
|
||||||
|
"defmt-brtt",
|
||||||
|
"defmt-test",
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
"enumset",
|
"enumset",
|
||||||
"heapless",
|
"heapless",
|
||||||
"itm_logger",
|
"panic-probe",
|
||||||
"panic-itm",
|
"rtic",
|
||||||
|
"rtic-monotonics",
|
||||||
|
"satrs",
|
||||||
"stm32f3-discovery",
|
"stm32f3-discovery",
|
||||||
"stm32f3xx-hal",
|
"stm32f3xx-hal",
|
||||||
"systick-monotonic",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "satrs-shared"
|
||||||
version = "1.1.0"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
dependencies = [
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
"spacepackets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
@ -595,9 +775,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.16"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
@ -607,17 +787,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slice-group-by"
|
name = "slice-group-by"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec"
|
checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "smallvec"
|
||||||
version = "0.9.4"
|
version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09"
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spacepackets"
|
||||||
|
version = "0.11.0-rc.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2cfd5f9a4c7f10714d21f9bc61f2d176cb7ae092cdd687e7ade2d4e6f7d7125"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"crc",
|
||||||
|
"defmt",
|
||||||
|
"delegate",
|
||||||
|
"num-traits",
|
||||||
|
"num_enum",
|
||||||
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -639,9 +830,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f3"
|
name = "stm32f3"
|
||||||
version = "0.14.0"
|
version = "0.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "265cda62ac13307414de4aca58dbbbd8038ddba85cffbb335823aa216f2e3200"
|
checksum = "b28b37228ef3fa47956af38c6abd756e912f244c1657f14e66d42fc8d74ea96f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
"bare-metal 1.0.0",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
@ -651,7 +842,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f3-discovery"
|
name = "stm32f3-discovery"
|
||||||
version = "0.8.0-pre.0"
|
version = "0.8.0-alpha.0"
|
||||||
|
source = "git+https://github.com/robamu/stm32f3-discovery?branch=complete-dma-update-hal#5ccacae07ceff02d7d3649df67a6a0ba2a144752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accelerometer",
|
"accelerometer",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
@ -663,20 +855,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f3xx-hal"
|
name = "stm32f3xx-hal"
|
||||||
version = "0.9.1"
|
version = "0.11.0-alpha.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/robamu/stm32f3xx-hal?branch=complete-dma-update#04fc76b7912649c84b57bd0ab803ea3ccf2aadae"
|
||||||
checksum = "4e422c5c044e8f3a068b1e14b83c071449e27c9d4bc0e24f972b552d79f2be03"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
|
||||||
"bxcan",
|
"bxcan",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
|
"critical-section",
|
||||||
"embedded-dma",
|
"embedded-dma",
|
||||||
"embedded-hal",
|
"embedded-hal 0.2.7",
|
||||||
"embedded-time",
|
"embedded-time",
|
||||||
"enumset",
|
"enumset",
|
||||||
"nb 1.0.0",
|
"nb 1.1.0",
|
||||||
|
"num-traits",
|
||||||
"paste",
|
"paste",
|
||||||
"rtcc",
|
"rtcc",
|
||||||
"slice-group-by",
|
"slice-group-by",
|
||||||
@ -691,14 +883,14 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90a4adc8cbd1726249b161898e48e0f3f1ce74d34dc784cbbc98fba4ed283fbf"
|
checksum = "90a4adc8cbd1726249b161898e48e0f3f1ce74d34dc784cbbc98fba4ed283fbf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-hal",
|
"embedded-hal 0.2.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.107"
|
version = "1.0.109"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -706,27 +898,47 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "systick-monotonic"
|
name = "syn"
|
||||||
version = "1.0.1"
|
version = "2.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67fb822d5c615a0ae3a4795ee5b1d06381c7faf488d861c0a4fa8e6a88d5ff84"
|
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"proc-macro2",
|
||||||
"fugit",
|
"quote",
|
||||||
"rtic-monotonic",
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.6"
|
version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usb-device"
|
name = "usb-device"
|
||||||
@ -754,9 +966,30 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "volatile-register"
|
name = "volatile-register"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6"
|
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"vcell",
|
"vcell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
]
|
||||||
|
@ -2,58 +2,82 @@
|
|||||||
name = "satrs-example-stm32f3-disco"
|
name = "satrs-example-stm32f3-disco"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "satrs-example-stm32f3-disco"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embedded-hal = "0.2.6"
|
defmt = "0.3"
|
||||||
cortex-m-rtic = "1.0"
|
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||||
enumset = "1.0"
|
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||||
heapless = "0.7"
|
embedded-hal = "0.2.7"
|
||||||
systick-monotonic = "1.0"
|
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]
|
[dependencies.cobs]
|
||||||
git = "https://github.com/robamu/cobs.rs.git"
|
git = "https://github.com/robamu/cobs.rs.git"
|
||||||
branch = "all_features"
|
branch = "all_features"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.panic-itm]
|
|
||||||
version = "0.4"
|
|
||||||
|
|
||||||
[dependencies.itm_logger]
|
|
||||||
git = "https://github.com/robamu/itm_logger.rs.git"
|
|
||||||
branch = "all_features"
|
|
||||||
version = "0.1.3-alpha.0"
|
|
||||||
|
|
||||||
[dependencies.stm32f3xx-hal]
|
[dependencies.stm32f3xx-hal]
|
||||||
git = "https://github.com/robamu/stm32f3xx-hal"
|
git = "https://github.com/robamu/stm32f3xx-hal"
|
||||||
version = "0.10.0-alpha.0"
|
version = "0.11.0-alpha.0"
|
||||||
features = ["stm32f303xc", "rt", "enumset"]
|
features = ["stm32f303xc", "rt", "enumset"]
|
||||||
branch = "all_features"
|
branch = "complete-dma-update"
|
||||||
# Can be used in workspace to develop and update HAL
|
# Can be used in workspace to develop and update HAL
|
||||||
# path = "../stm32f3xx-hal"
|
# path = "../stm32f3xx-hal"
|
||||||
|
|
||||||
[dependencies.stm32f3-discovery]
|
[dependencies.stm32f3-discovery]
|
||||||
git = "https://github.com/robamu/stm32f3-discovery"
|
git = "https://github.com/robamu/stm32f3-discovery"
|
||||||
version = "0.8.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
branch = "all_features"
|
branch = "complete-dma-update-hal"
|
||||||
# Can be used in workspace to develop and update BSP
|
# Can be used in workspace to develop and update BSP
|
||||||
# path = "../stm32f3-discovery"
|
# path = "../stm32f3-discovery"
|
||||||
|
|
||||||
[dependencies.satrs-core]
|
[dependencies.satrs]
|
||||||
git = "https://egit.irs.uni-stuttgart.de/rust/satrs-core.git"
|
path = "../satrs"
|
||||||
version = "0.1.0-alpha.0"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
|
features = ["defmt"]
|
||||||
|
|
||||||
# this lets you use `cargo fix`!
|
[dev-dependencies]
|
||||||
# [[bin]]
|
defmt-test = "0.3"
|
||||||
# name = "stm32f3-blinky"
|
|
||||||
# test = false
|
|
||||||
# bench = false
|
|
||||||
|
|
||||||
|
# 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]
|
[profile.release]
|
||||||
codegen-units = 1 # better optimizations
|
codegen-units = 1
|
||||||
debug = true # symbols are nice and they don't increase the size on Flash
|
debug = 2
|
||||||
lto = true # better optimizations
|
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 # <-
|
||||||
|
@ -2,15 +2,25 @@ sat-rs example for the STM32F3-Discovery board
|
|||||||
=======
|
=======
|
||||||
|
|
||||||
This example application shows how the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad)
|
This example application shows how the [sat-rs framework](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad)
|
||||||
can be used on an embedded target. It also shows how a relatively simple OBSW could be built when no
|
can be used on an embedded target.
|
||||||
standard runtime is available. It uses [RTIC](https://rtic.rs/1/book/en/) as the concurrency
|
It also shows how a relatively simple OBSW could be built when no standard runtime is available.
|
||||||
framework.
|
It uses [RTIC](https://rtic.rs/1/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
|
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
|
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
|
[Rust Discovery](https://docs.rust-embedded.org/discovery/f3discovery/) book as an introduction
|
||||||
to embedded Rust.
|
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
|
## Preparing Rust and the repository
|
||||||
|
|
||||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
||||||
@ -40,15 +50,23 @@ you can simply build the application with
|
|||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flashing and Debugging from the command line
|
## Flashing from the command line
|
||||||
|
|
||||||
TODO
|
You can flash the application from the command line using `probe-rs`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
probe-rs run --chip STM32F303VCTx
|
||||||
|
```
|
||||||
|
|
||||||
## Debugging with VS Code
|
## Debugging with VS Code
|
||||||
|
|
||||||
The STM32F3-Discovery comes with an on-board ST-Link so all that is required to flash and debug
|
The STM32F3-Discovery comes with an on-board ST-Link so all that is required to flash and debug
|
||||||
the board is a Mini-USB cable. The code in this repository was debugged using `openocd`
|
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 [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
|
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`
|
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.
|
to automatically rebuild and flash your application.
|
||||||
@ -56,20 +74,41 @@ 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 `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.
|
the folder in VS code or adding it to a workspace.
|
||||||
|
|
||||||
If you would like to use a custom GDB application, you can specify the gdb binary in the following
|
|
||||||
configuration variables in your `settings.json`:
|
|
||||||
|
|
||||||
- `"cortex-debug.gdbPath"`
|
|
||||||
- `"cortex-debug.gdbPath.linux"`
|
|
||||||
- `"cortex-debug.gdbPath.windows"`
|
|
||||||
- `"cortex-debug.gdbPath.osx"`
|
|
||||||
|
|
||||||
## Commanding with Python
|
## Commanding with Python
|
||||||
|
|
||||||
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
|
When the SW is running on the Discovery board, you can command the MCU via a serial interface,
|
||||||
using COBS encoded CCSDS packets.
|
using COBS encoded PUS packets.
|
||||||
|
|
||||||
TODO:
|
It is recommended to use a virtual environment to do this. To set up one in the command line,
|
||||||
- How and where to connect serial interface on the MCU
|
you can use `python3 -m venv venv` on Unix systems or `py -m venv venv` on Windows systems.
|
||||||
- How to set up Python venv (or at least strongly recommend it) and install deps
|
After doing this, you can check the [venv tutorial](https://docs.python.org/3/tutorial/venv.html)
|
||||||
- How to copy `def_tmtc_conf.json` to `tmtc_conf.json` and adapt it for custom serial port
|
on how to activate the environment and then use the following command to install the required
|
||||||
|
dependency:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The packets are exchanged using a dedicated serial interface. You can use any generic USB-to-UART
|
||||||
|
converter device with the TX pin connected to the PA3 pin and the RX pin connected to the PA2 pin.
|
||||||
|
|
||||||
|
A default configuration file for the python application is provided and can be used by running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp def_tmtc_conf.json tmtc_conf.json
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, you can for example send a ping to the MCU using the following command
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./main.py -p /ping
|
||||||
|
```
|
||||||
|
|
||||||
|
You can configure the blinky frequency using
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./main.py -p /change_blink_freq
|
||||||
|
```
|
||||||
|
|
||||||
|
All these commands will package a PUS telecommand which will be sent to the MCU using the COBS
|
||||||
|
format as the packet framing format.
|
||||||
|
38601
satrs-example-stm32f3-disco/STM32F303.svd
Normal file
38601
satrs-example-stm32f3-disco/STM32F303.svd
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Put the linker script somewhere the linker can find it
|
|
||||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
|
||||||
File::create(out.join("memory.x"))
|
|
||||||
.unwrap()
|
|
||||||
.write_all(include_bytes!("memory.x"))
|
|
||||||
.unwrap();
|
|
||||||
println!("cargo:rustc-link-search={}", out.display());
|
|
||||||
|
|
||||||
// Only re-run the build script when memory.x is changed,
|
|
||||||
// instead of when any part of the source code changes.
|
|
||||||
println!("cargo:rerun-if-changed=memory.x");
|
|
||||||
}
|
|
@ -24,7 +24,9 @@ break main
|
|||||||
# # send captured ITM to the file itm.fifo
|
# # send captured ITM to the file itm.fifo
|
||||||
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
|
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
|
||||||
# # 8000000 must match the core clock frequency
|
# # 8000000 must match the core clock frequency
|
||||||
monitor tpiu config internal itm.txt uart off 8000000
|
# # 2000000 is the frequency of the SWO pin. This was added for newer
|
||||||
|
# openocd versions like v0.12.0.
|
||||||
|
# monitor tpiu config internal itm.txt uart off 8000000 2000000
|
||||||
|
|
||||||
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
|
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
|
||||||
# # 8000000 must match the core clock frequency
|
# # 8000000 must match the core clock frequency
|
||||||
@ -32,7 +34,7 @@ monitor tpiu config internal itm.txt uart off 8000000
|
|||||||
# monitor tpiu config external uart off 8000000 2000000
|
# monitor tpiu config external uart off 8000000 2000000
|
||||||
|
|
||||||
# # enable ITM port 0
|
# # enable ITM port 0
|
||||||
monitor itm port 0 on
|
# monitor itm port 0 on
|
||||||
|
|
||||||
load
|
load
|
||||||
|
|
@ -1,4 +1,5 @@
|
|||||||
/venv
|
/venv
|
||||||
|
/.tmtc-history.txt
|
||||||
/log
|
/log
|
||||||
/.idea/*
|
/.idea/*
|
||||||
!/.idea/runConfigurations
|
!/.idea/runConfigurations
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"com_if": "serial_cobs",
|
"com_if": "serial_cobs",
|
||||||
"serial_baudrate": 115200
|
"serial_baudrate": 115200
|
||||||
}
|
}
|
||||||
|
205
satrs-example-stm32f3-disco/pyclient/main.py
Normal file → Executable file
205
satrs-example-stm32f3-disco/pyclient/main.py
Normal file → Executable file
@ -1,39 +1,40 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Example client for the sat-rs example application"""
|
"""Example client for the sat-rs example application"""
|
||||||
import enum
|
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Optional, cast
|
from typing import Any, Optional, cast
|
||||||
|
from prompt_toolkit.history import FileHistory, History
|
||||||
|
from spacepackets.ecss.tm import CdsShortTimestamp
|
||||||
|
|
||||||
import tmtccmd
|
import tmtccmd
|
||||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusVerificator
|
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusVerificator
|
||||||
from spacepackets.ecss.pus_17_test import Service17Tm
|
from spacepackets.ecss.pus_17_test import Service17Tm
|
||||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
|
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
|
||||||
|
|
||||||
from tmtccmd import CcsdsTmtcBackend, TcHandlerBase, ProcedureParamsWrapper
|
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
||||||
from tmtccmd.core.base import BackendRequest
|
from tmtccmd.core.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.pus import VerificationWrapper
|
||||||
from tmtccmd.tm import CcsdsTmHandler, SpecificApidHandlerBase
|
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
|
||||||
from tmtccmd.com_if import ComInterface
|
from tmtccmd.com import ComInterface
|
||||||
from tmtccmd.config import (
|
from tmtccmd.config import (
|
||||||
|
CmdTreeNode,
|
||||||
default_json_path,
|
default_json_path,
|
||||||
SetupParams,
|
SetupParams,
|
||||||
TmTcCfgHookBase,
|
HookBase,
|
||||||
TmtcDefinitionWrapper,
|
|
||||||
CoreServiceList,
|
|
||||||
OpCodeEntry,
|
|
||||||
params_to_procedure_conversion,
|
params_to_procedure_conversion,
|
||||||
)
|
)
|
||||||
from tmtccmd.config.com_if import SerialCfgWrapper
|
from tmtccmd.config.com import SerialCfgWrapper
|
||||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
||||||
from tmtccmd.logging import get_console_logger
|
|
||||||
from tmtccmd.logging.pus import (
|
from tmtccmd.logging.pus import (
|
||||||
RegularTmtcLogWrapper,
|
RegularTmtcLogWrapper,
|
||||||
RawTmtcTimedLogWrapper,
|
RawTmtcTimedLogWrapper,
|
||||||
TimedLogWhen,
|
TimedLogWhen,
|
||||||
)
|
)
|
||||||
from tmtccmd.tc import (
|
from tmtccmd.tmtc import (
|
||||||
TcQueueEntryType,
|
TcQueueEntryType,
|
||||||
ProcedureWrapper,
|
ProcedureWrapper,
|
||||||
TcProcedureType,
|
TcProcedureType,
|
||||||
@ -41,27 +42,26 @@ from tmtccmd.tc import (
|
|||||||
SendCbParams,
|
SendCbParams,
|
||||||
DefaultPusQueueHelper,
|
DefaultPusQueueHelper,
|
||||||
)
|
)
|
||||||
from tmtccmd.tm.pus_5_event import Service5Tm
|
from tmtccmd.pus.s5_fsfw_event import Service5Tm
|
||||||
from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider
|
from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider
|
||||||
from tmtccmd.util.obj_id import ObjectIdDictT
|
from tmtccmd.util.obj_id import ObjectIdDictT
|
||||||
|
|
||||||
from tmtccmd.util.tmtc_printer import FsfwTmTcPrinter
|
_LOGGER = logging.getLogger()
|
||||||
|
|
||||||
LOGGER = get_console_logger()
|
|
||||||
|
|
||||||
EXAMPLE_PUS_APID = 0x02
|
EXAMPLE_PUS_APID = 0x02
|
||||||
|
|
||||||
|
|
||||||
class SatRsConfigHook(TmTcCfgHookBase):
|
class SatRsConfigHook(HookBase):
|
||||||
def __init__(self, json_cfg_path: str):
|
def __init__(self, json_cfg_path: str):
|
||||||
super().__init__(json_cfg_path=json_cfg_path)
|
super().__init__(json_cfg_path=json_cfg_path)
|
||||||
|
|
||||||
def assign_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
||||||
from tmtccmd.config.com_if import (
|
from tmtccmd.config.com import (
|
||||||
create_com_interface_default,
|
create_com_interface_default,
|
||||||
create_com_interface_cfg_default,
|
create_com_interface_cfg_default,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert self.cfg_path is not None
|
||||||
cfg = create_com_interface_cfg_default(
|
cfg = create_com_interface_cfg_default(
|
||||||
com_if_key=com_if_key,
|
com_if_key=com_if_key,
|
||||||
json_cfg_path=self.cfg_path,
|
json_cfg_path=self.cfg_path,
|
||||||
@ -76,35 +76,14 @@ class SatRsConfigHook(TmTcCfgHookBase):
|
|||||||
cfg.serial_cfg.serial_timeout = 0.5
|
cfg.serial_cfg.serial_timeout = 0.5
|
||||||
return create_com_interface_default(cfg)
|
return create_com_interface_default(cfg)
|
||||||
|
|
||||||
def get_tmtc_definitions(self) -> TmtcDefinitionWrapper:
|
def get_command_definitions(self) -> CmdTreeNode:
|
||||||
from tmtccmd.config.globals import get_default_tmtc_defs
|
"""This function should return the root node of the command definition tree."""
|
||||||
|
return create_cmd_definition_tree()
|
||||||
|
|
||||||
defs = get_default_tmtc_defs()
|
def get_cmd_history(self) -> Optional[History]:
|
||||||
srv_5 = OpCodeEntry()
|
"""Optionlly return a history class for the past command paths which will be used
|
||||||
srv_5.add("0", "Event Test")
|
when prompting a command path from the user in CLI mode."""
|
||||||
defs.add_service(
|
return FileHistory(".tmtc-history.txt")
|
||||||
name=CoreServiceList.SERVICE_5.value,
|
|
||||||
info="PUS Service 5 Event",
|
|
||||||
op_code_entry=srv_5,
|
|
||||||
)
|
|
||||||
srv_17 = OpCodeEntry()
|
|
||||||
srv_17.add("0", "Ping Test")
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_17_ALT,
|
|
||||||
info="PUS Service 17 Test",
|
|
||||||
op_code_entry=srv_17,
|
|
||||||
)
|
|
||||||
srv_3 = OpCodeEntry()
|
|
||||||
defs.add_service(
|
|
||||||
name=CoreServiceList.SERVICE_3,
|
|
||||||
info="PUS Service 3 Housekeeping",
|
|
||||||
op_code_entry=srv_3,
|
|
||||||
)
|
|
||||||
return defs
|
|
||||||
|
|
||||||
def perform_mode_operation(self, tmtc_backend: CcsdsTmtcBackend, mode: int):
|
|
||||||
LOGGER.info("Mode operation hook was called")
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_object_ids(self) -> ObjectIdDictT:
|
def get_object_ids(self) -> ObjectIdDictT:
|
||||||
from tmtccmd.config.objects import get_core_object_ids
|
from tmtccmd.config.objects import get_core_object_ids
|
||||||
@ -112,74 +91,76 @@ class SatRsConfigHook(TmTcCfgHookBase):
|
|||||||
return 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):
|
class PusHandler(SpecificApidHandlerBase):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
file_logger: logging.Logger,
|
||||||
verif_wrapper: VerificationWrapper,
|
verif_wrapper: VerificationWrapper,
|
||||||
printer: FsfwTmTcPrinter,
|
|
||||||
raw_logger: RawTmtcTimedLogWrapper,
|
raw_logger: RawTmtcTimedLogWrapper,
|
||||||
):
|
):
|
||||||
super().__init__(EXAMPLE_PUS_APID, None)
|
super().__init__(EXAMPLE_PUS_APID, None)
|
||||||
self.printer = printer
|
self.file_logger = file_logger
|
||||||
self.raw_logger = raw_logger
|
self.raw_logger = raw_logger
|
||||||
self.verif_wrapper = verif_wrapper
|
self.verif_wrapper = verif_wrapper
|
||||||
|
|
||||||
def handle_tm(self, packet: bytes, _user_args: any):
|
def handle_tm(self, packet: bytes, _user_args: Any):
|
||||||
|
time_reader = CdsShortTimestamp.empty()
|
||||||
try:
|
try:
|
||||||
tm_packet = PusTelemetry.unpack(packet)
|
pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
LOGGER.warning("Could not generate PUS TM object from raw data")
|
_LOGGER.warning("Could not generate PUS TM object from raw data")
|
||||||
LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
||||||
raise e
|
raise e
|
||||||
service = tm_packet.service
|
service = pus_tm.service
|
||||||
dedicated_handler = False
|
tm_packet = None
|
||||||
if service == 1:
|
if service == 1:
|
||||||
tm_packet = Service1Tm.unpack(data=packet, params=UnpackParams(1, 2))
|
tm_packet = Service1Tm.unpack(
|
||||||
|
data=packet, params=UnpackParams(time_reader, 1, 2)
|
||||||
|
)
|
||||||
res = self.verif_wrapper.add_tm(tm_packet)
|
res = self.verif_wrapper.add_tm(tm_packet)
|
||||||
if res is None:
|
if res is None:
|
||||||
LOGGER.info(
|
_LOGGER.info(
|
||||||
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
||||||
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
||||||
)
|
)
|
||||||
LOGGER.warning(
|
_LOGGER.warning(
|
||||||
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.verif_wrapper.log_to_console(tm_packet, res)
|
self.verif_wrapper.log_to_console(tm_packet, res)
|
||||||
self.verif_wrapper.log_to_file(tm_packet, res)
|
self.verif_wrapper.log_to_file(tm_packet, res)
|
||||||
dedicated_handler = True
|
|
||||||
if service == 3:
|
if service == 3:
|
||||||
LOGGER.info("No handling for HK packets implemented")
|
_LOGGER.info("No handling for HK packets implemented")
|
||||||
LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
|
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
|
||||||
pus_tm = PusTelemetry.unpack(packet)
|
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.empty())
|
||||||
if pus_tm.subservice == 25:
|
if pus_tm.subservice == 25:
|
||||||
if len(pus_tm.source_data) < 8:
|
if len(pus_tm.source_data) < 8:
|
||||||
raise ValueError("No addressable ID in HK packet")
|
raise ValueError("No addressable ID in HK packet")
|
||||||
json_str = pus_tm.source_data[8:]
|
json_str = pus_tm.source_data[8:]
|
||||||
dedicated_handler = True
|
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
|
||||||
if service == 5:
|
if service == 5:
|
||||||
tm_packet = Service5Tm.unpack(packet)
|
tm_packet = Service5Tm.unpack(packet, time_reader)
|
||||||
if service == 17:
|
if service == 17:
|
||||||
tm_packet = Service17Tm.unpack(packet)
|
tm_packet = Service17Tm.unpack(packet, time_reader)
|
||||||
dedicated_handler = True
|
|
||||||
if tm_packet.subservice == 2:
|
if tm_packet.subservice == 2:
|
||||||
self.printer.file_logger.info("Received Ping Reply TM[17,2]")
|
_LOGGER.info("Received Ping Reply TM[17,2]")
|
||||||
LOGGER.info("Received Ping Reply TM[17,2]")
|
|
||||||
else:
|
else:
|
||||||
self.printer.file_logger.info(
|
_LOGGER.info(
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
|
||||||
)
|
|
||||||
LOGGER.info(
|
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
||||||
)
|
)
|
||||||
if tm_packet is None:
|
if tm_packet is None:
|
||||||
LOGGER.info(
|
_LOGGER.info(
|
||||||
f"The service {service} is not implemented in Telemetry Factory"
|
f"The service {service} is not implemented in Telemetry Factory"
|
||||||
)
|
)
|
||||||
tm_packet = PusTelemetry.unpack(packet)
|
tm_packet = PusTelemetry.unpack(packet, time_reader)
|
||||||
self.raw_logger.log_tm(tm_packet)
|
self.raw_logger.log_tm(pus_tm)
|
||||||
if not dedicated_handler and tm_packet is not None:
|
|
||||||
self.printer.handle_long_tm_print(packet_if=tm_packet, info_if=tm_packet)
|
|
||||||
|
|
||||||
|
|
||||||
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
|
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
|
||||||
@ -198,8 +179,11 @@ class TcHandler(TcHandlerBase):
|
|||||||
self.seq_count_provider = seq_count_provider
|
self.seq_count_provider = seq_count_provider
|
||||||
self.verif_wrapper = verif_wrapper
|
self.verif_wrapper = verif_wrapper
|
||||||
self.queue_helper = DefaultPusQueueHelper(
|
self.queue_helper = DefaultPusQueueHelper(
|
||||||
queue_wrapper=None,
|
queue_wrapper=QueueWrapper.empty(),
|
||||||
|
tc_sched_timestamp_len=7,
|
||||||
seq_cnt_provider=seq_count_provider,
|
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):
|
def send_cb(self, send_params: SendCbParams):
|
||||||
@ -212,61 +196,74 @@ class TcHandler(TcHandlerBase):
|
|||||||
)
|
)
|
||||||
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
|
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
|
||||||
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
||||||
LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
||||||
send_params.com_if.send(raw_tc)
|
send_params.com_if.send(raw_tc)
|
||||||
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
||||||
log_entry = entry_helper.to_log_entry()
|
log_entry = entry_helper.to_log_entry()
|
||||||
LOGGER.info(log_entry.log_str)
|
_LOGGER.info(log_entry.log_str)
|
||||||
|
|
||||||
def queue_finished_cb(self, helper: ProcedureWrapper):
|
def queue_finished_cb(self, info: ProcedureWrapper):
|
||||||
if helper.proc_type == TcProcedureType.DEFAULT:
|
if info.proc_type == TcProcedureType.DEFAULT:
|
||||||
def_proc = helper.to_def_procedure()
|
def_proc = info.to_def_procedure()
|
||||||
LOGGER.info(
|
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
|
||||||
f"Queue handling finished for service {def_proc.service} and "
|
|
||||||
f"op code {def_proc.op_code}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def feed_cb(self, helper: ProcedureWrapper, wrapper: FeedWrapper):
|
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
|
||||||
q = self.queue_helper
|
q = self.queue_helper
|
||||||
q.queue_wrapper = wrapper.queue_wrapper
|
q.queue_wrapper = wrapper.queue_wrapper
|
||||||
if helper.proc_type == TcProcedureType.DEFAULT:
|
if info.proc_type == TcProcedureType.DEFAULT:
|
||||||
def_proc = helper.to_def_procedure()
|
def_proc = info.to_def_procedure()
|
||||||
service = def_proc.service
|
cmd_path = def_proc.cmd_path
|
||||||
op_code = def_proc.op_code
|
if cmd_path == "/ping":
|
||||||
if (
|
|
||||||
service == CoreServiceList.SERVICE_17
|
|
||||||
or service == CoreServiceList.SERVICE_17_ALT
|
|
||||||
):
|
|
||||||
q.add_log_cmd("Sending PUS ping telecommand")
|
q.add_log_cmd("Sending PUS ping telecommand")
|
||||||
return q.add_pus_tc(PusTelecommand(service=17, subservice=1))
|
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
|
||||||
|
if cmd_path == "/change_blink_freq":
|
||||||
|
self.create_change_blink_freq_command(q)
|
||||||
|
|
||||||
|
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
|
||||||
|
q.add_log_cmd("Changing blink frequency")
|
||||||
|
while True:
|
||||||
|
blink_freq = int(
|
||||||
|
input(
|
||||||
|
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if blink_freq < 2 or blink_freq > 10000:
|
||||||
|
print(
|
||||||
|
"Invalid blink frequency. Please specify a value between 2 and 10000."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
app_data = struct.pack("!I", blink_freq)
|
||||||
|
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
add_colorlog_console_logger(_LOGGER)
|
||||||
tmtccmd.init_printout(False)
|
tmtccmd.init_printout(False)
|
||||||
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
|
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
|
||||||
parser_wrapper = PreArgsParsingWrapper()
|
parser_wrapper = PreArgsParsingWrapper()
|
||||||
parser_wrapper.create_default_parent_parser()
|
parser_wrapper.create_default_parent_parser()
|
||||||
parser_wrapper.create_default_parser()
|
parser_wrapper.create_default_parser()
|
||||||
parser_wrapper.add_def_proc_args()
|
parser_wrapper.add_def_proc_args()
|
||||||
post_args_wrapper = parser_wrapper.parse(hook_obj)
|
|
||||||
params = SetupParams()
|
params = SetupParams()
|
||||||
|
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
|
||||||
proc_wrapper = ProcedureParamsWrapper()
|
proc_wrapper = ProcedureParamsWrapper()
|
||||||
if post_args_wrapper.use_gui:
|
if post_args_wrapper.use_gui:
|
||||||
post_args_wrapper.set_params_without_prompts(params, proc_wrapper)
|
post_args_wrapper.set_params_without_prompts(proc_wrapper)
|
||||||
else:
|
else:
|
||||||
post_args_wrapper.set_params_with_prompts(params, proc_wrapper)
|
post_args_wrapper.set_params_with_prompts(proc_wrapper)
|
||||||
params.apid = EXAMPLE_PUS_APID
|
params.apid = EXAMPLE_PUS_APID
|
||||||
setup_args = SetupWrapper(
|
setup_args = SetupWrapper(
|
||||||
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
||||||
)
|
)
|
||||||
# Create console logger helper and file loggers
|
# Create console logger helper and file loggers
|
||||||
tmtc_logger = RegularTmtcLogWrapper()
|
tmtc_logger = RegularTmtcLogWrapper()
|
||||||
printer = FsfwTmTcPrinter(tmtc_logger.logger)
|
file_logger = tmtc_logger.logger
|
||||||
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
|
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
|
||||||
verificator = PusVerificator()
|
verificator = PusVerificator()
|
||||||
verification_wrapper = VerificationWrapper(verificator, LOGGER, printer.file_logger)
|
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
|
||||||
# Create primary TM handler and add it to the CCSDS Packet Handler
|
# Create primary TM handler and add it to the CCSDS Packet Handler
|
||||||
tm_handler = PusHandler(verification_wrapper, printer, raw_logger)
|
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
|
||||||
ccsds_handler = CcsdsTmHandler(generic_handler=None)
|
ccsds_handler = CcsdsTmHandler(generic_handler=None)
|
||||||
ccsds_handler.add_apid_handler(tm_handler)
|
ccsds_handler.add_apid_handler(tm_handler)
|
||||||
|
|
||||||
@ -288,7 +285,7 @@ def main():
|
|||||||
if state.request == BackendRequest.TERMINATION_NO_ERROR:
|
if state.request == BackendRequest.TERMINATION_NO_ERROR:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif state.request == BackendRequest.DELAY_IDLE:
|
elif state.request == BackendRequest.DELAY_IDLE:
|
||||||
LOGGER.info("TMTC Client in IDLE mode")
|
_LOGGER.info("TMTC Client in IDLE mode")
|
||||||
time.sleep(3.0)
|
time.sleep(3.0)
|
||||||
elif state.request == BackendRequest.DELAY_LISTENER:
|
elif state.request == BackendRequest.DELAY_LISTENER:
|
||||||
time.sleep(0.8)
|
time.sleep(0.8)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
tmtccmd == 4.0.0a0
|
tmtccmd == 8.0.0rc.0
|
||||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
|
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
use satrs_example_stm32f3_disco as _;
|
||||||
|
|
||||||
extern crate panic_itm;
|
use stm32f3_discovery::leds::Leds;
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
|
|
||||||
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
|
use stm32f3_discovery::stm32f3xx_hal::delay::Delay;
|
||||||
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
|
use stm32f3_discovery::stm32f3xx_hal::{pac, prelude::*};
|
||||||
use stm32f3_discovery::leds::Leds;
|
|
||||||
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
|
use stm32f3_discovery::switch_hal::{OutputSwitch, ToggleableOutputSwitch};
|
||||||
|
|
||||||
#[entry]
|
#[cortex_m_rt::entry]
|
||||||
fn main()-> ! {
|
fn main() -> ! {
|
||||||
|
defmt::println!("STM32F3 Discovery Blinky");
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut rcc = dp.RCC.constrain();
|
let mut rcc = dp.RCC.constrain();
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
let cp = cortex_m::Peripherals::take().unwrap();
|
||||||
@ -30,49 +28,49 @@ fn main()-> ! {
|
|||||||
gpioe.pe14,
|
gpioe.pe14,
|
||||||
gpioe.pe15,
|
gpioe.pe15,
|
||||||
&mut gpioe.moder,
|
&mut gpioe.moder,
|
||||||
&mut gpioe.otyper
|
&mut gpioe.otyper,
|
||||||
);
|
);
|
||||||
let delay_ms = 200u16;
|
let delay_ms = 200u16;
|
||||||
loop {
|
loop {
|
||||||
leds.ld3.toggle().ok();
|
leds.ld3_n.toggle().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
leds.ld3.toggle().ok();
|
leds.ld3_n.toggle().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
//explicit on/off
|
//explicit on/off
|
||||||
leds.ld4.on().ok();
|
leds.ld4_nw.on().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
leds.ld4.off().ok();
|
leds.ld4_nw.off().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
leds.ld5.on().ok();
|
leds.ld5_ne.on().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
leds.ld5.off().ok();
|
leds.ld5_ne.off().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
leds.ld6.on().ok();
|
leds.ld6_w.on().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
leds.ld6.off().ok();
|
leds.ld6_w.off().ok();
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld7.on().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld7.off().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld8.on().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld8.off().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
|
|
||||||
leds.ld9.on().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
|
||||||
leds.ld9.off().ok();
|
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
leds.ld10.on().ok();
|
leds.ld7_e.on().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
leds.ld10.off().ok();
|
leds.ld7_e.off().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
|
leds.ld8_sw.on().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
leds.ld8_sw.off().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
|
leds.ld9_se.on().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
leds.ld9_se.off().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
|
||||||
|
leds.ld10_s.on().ok();
|
||||||
|
delay.delay_ms(delay_ms);
|
||||||
|
leds.ld10_s.off().ok();
|
||||||
delay.delay_ms(delay_ms);
|
delay.delay_ms(delay_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
51
satrs-example-stm32f3-disco/src/lib.rs
Normal file
51
satrs-example-stm32f3-disco/src/lib.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use cortex_m_semihosting::debug;
|
||||||
|
|
||||||
|
use defmt_brtt as _; // global logger
|
||||||
|
|
||||||
|
use stm32f3xx_hal as _; // memory layout
|
||||||
|
|
||||||
|
use panic_probe as _;
|
||||||
|
|
||||||
|
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
|
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||||
|
#[defmt::panic_handler]
|
||||||
|
fn panic() -> ! {
|
||||||
|
cortex_m::asm::udf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
/// with status code 0.
|
||||||
|
pub fn exit() -> ! {
|
||||||
|
loop {
|
||||||
|
debug::exit(debug::EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hardfault handler.
|
||||||
|
///
|
||||||
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
/// with an error. This seems better than the default, which is to spin in a
|
||||||
|
/// loop.
|
||||||
|
#[cortex_m_rt::exception]
|
||||||
|
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||||
|
loop {
|
||||||
|
debug::exit(debug::EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||||
|
// once within a crate. the module can be in any file but there can only be at most
|
||||||
|
// one `#[tests]` module in this library crate
|
||||||
|
#[cfg(test)]
|
||||||
|
#[defmt_test::tests]
|
||||||
|
mod unit_tests {
|
||||||
|
use defmt::assert;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(true)
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,40 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
extern crate panic_itm;
|
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_example_stm32f3_disco as _;
|
||||||
|
|
||||||
use rtic::app;
|
use rtic::app;
|
||||||
|
|
||||||
use heapless::{
|
use heapless::{mpmc::Q8, Vec};
|
||||||
mpmc::Q16,
|
|
||||||
pool,
|
|
||||||
pool::singleton::{Box, Pool},
|
|
||||||
};
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use itm_logger::{debug, info, logger_init, warn};
|
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
|
||||||
use satrs_core::spacepackets::{ecss::PusPacket, tm::PusTm};
|
use rtic_monotonics::systick::ExtU32;
|
||||||
use satrs_core::{
|
use satrs::seq_count::SequenceCountProviderCore;
|
||||||
pus::{EcssTmErrorWithSend, EcssTmSenderCore},
|
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
|
||||||
seq_count::SequenceCountProviderCore,
|
|
||||||
};
|
|
||||||
use stm32f3xx_hal::dma::dma1;
|
use stm32f3xx_hal::dma::dma1;
|
||||||
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
|
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
|
||||||
use stm32f3xx_hal::pac::USART2;
|
use stm32f3xx_hal::pac::USART2;
|
||||||
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
|
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
|
||||||
use systick_monotonic::{fugit::Duration, Systick};
|
|
||||||
|
|
||||||
const UART_BAUD: u32 = 115200;
|
const UART_BAUD: u32 = 115200;
|
||||||
const BLINK_FREQ_MS: u64 = 1000;
|
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
||||||
const TX_HANDLER_FREQ_MS: u64 = 20;
|
const TX_HANDLER_FREQ_MS: u32 = 20;
|
||||||
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u16 = 5;
|
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
|
||||||
const MAX_TC_LEN: usize = 200;
|
const MAX_TC_LEN: usize = 128;
|
||||||
const MAX_TM_LEN: usize = 200;
|
const MAX_TM_LEN: usize = 128;
|
||||||
pub const PUS_APID: u16 = 0x02;
|
pub const PUS_APID: u16 = 0x02;
|
||||||
|
|
||||||
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
|
type TxType = Tx<USART2, PA2<AF7<PushPull>>>;
|
||||||
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
|
type RxType = Rx<USART2, PA3<AF7<PushPull>>>;
|
||||||
type MsDuration = Duration<u64, 1, 1000>;
|
type InstantFugit = TimerInstantU32<1000>;
|
||||||
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
|
type TxDmaTransferType = SerialDmaTx<&'static [u8], dma1::C7, TxType>;
|
||||||
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
|
type RxDmaTransferType = SerialDmaRx<&'static mut [u8], dma1::C6, RxType>;
|
||||||
|
|
||||||
@ -51,10 +53,11 @@ static mut DMA_TX_BUF: [u8; TM_BUF_LEN] = [0; TM_BUF_LEN];
|
|||||||
// transfer buffer.
|
// transfer buffer.
|
||||||
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
|
static mut DMA_RX_BUF: [u8; TC_BUF_LEN] = [0; TC_BUF_LEN];
|
||||||
|
|
||||||
static TX_REQUESTS: Q16<(Box<poolmod::TM>, usize)> = Q16::new();
|
type TmPacket = Vec<u8, MAX_TM_LEN>;
|
||||||
|
type TcPacket = Vec<u8, MAX_TC_LEN>;
|
||||||
|
|
||||||
|
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
|
||||||
|
|
||||||
const TC_POOL_SLOTS: usize = 12;
|
|
||||||
const TM_POOL_SLOTS: usize = 12;
|
|
||||||
use core::sync::atomic::{AtomicU16, Ordering};
|
use core::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
|
||||||
pub struct SeqCountProviderAtomicRef {
|
pub struct SeqCountProviderAtomicRef {
|
||||||
@ -88,72 +91,50 @@ impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
|
|||||||
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
|
static SEQ_COUNT_PROVIDER: SeqCountProviderAtomicRef =
|
||||||
SeqCountProviderAtomicRef::new(Ordering::Relaxed);
|
SeqCountProviderAtomicRef::new(Ordering::Relaxed);
|
||||||
|
|
||||||
// Otherwise, warnings because of heapless pool macro.
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
mod poolmod {
|
|
||||||
use super::*;
|
|
||||||
// Must hold full TC length including COBS overhead.
|
|
||||||
pool!(TC: [u8; TC_BUF_LEN]);
|
|
||||||
// Only encoded at the end, so no need to account for COBS overhead.
|
|
||||||
pool!(TM: [u8; MAX_TM_LEN]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TxIdle {
|
pub struct TxIdle {
|
||||||
tx: TxType,
|
tx: TxType,
|
||||||
dma_channel: dma1::C7,
|
dma_channel: dma1::C7,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, defmt::Format)]
|
||||||
pub enum TmStoreError {
|
pub enum TmSendError {
|
||||||
StoreFull,
|
ByteConversion(ByteConversionError),
|
||||||
StoreSlotsTooSmall,
|
Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TmStoreError> for EcssTmErrorWithSend<TmStoreError> {
|
impl From<ByteConversionError> for TmSendError {
|
||||||
fn from(value: TmStoreError) -> Self {
|
fn from(value: ByteConversionError) -> Self {
|
||||||
Self::SendError(value)
|
Self::ByteConversion(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TmSender {
|
fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
|
||||||
mem_block: Option<Box<poolmod::TM>>,
|
if tm_creator.len_written() > MAX_TM_LEN {
|
||||||
ctx: &'static str,
|
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TmSender {
|
fn handle_tm_send_error(error: TmSendError) {
|
||||||
pub fn new(mem_block: Box<poolmod::TM>, ctx: &'static str) -> Self {
|
defmt::warn!("sending tm failed with error {}", error);
|
||||||
Self {
|
|
||||||
mem_block: Some(mem_block),
|
|
||||||
ctx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EcssTmSenderCore for TmSender {
|
|
||||||
type Error = TmStoreError;
|
|
||||||
|
|
||||||
fn send_tm(
|
|
||||||
&mut self,
|
|
||||||
tm: PusTm,
|
|
||||||
) -> Result<(), satrs_core::pus::EcssTmErrorWithSend<Self::Error>> {
|
|
||||||
let mem_block = self.mem_block.take();
|
|
||||||
if mem_block.is_none() {
|
|
||||||
panic!("send_tm should only be called once");
|
|
||||||
}
|
|
||||||
let mut mem_block = mem_block.unwrap();
|
|
||||||
if tm.len_packed() > MAX_TM_LEN {
|
|
||||||
return Err(EcssTmErrorWithSend::SendError(
|
|
||||||
TmStoreError::StoreSlotsTooSmall,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
tm.write_to_bytes(mem_block.as_mut_slice())
|
|
||||||
.map_err(|e| EcssTmErrorWithSend::EcssTmError(e.into()))?;
|
|
||||||
info!(target: self.ctx, "Sending TM[{},{}] with size {}", tm.service(), tm.subservice(), tm.len_packed());
|
|
||||||
TX_REQUESTS
|
|
||||||
.enqueue((mem_block, tm.len_packed()))
|
|
||||||
.map_err(|_| TmStoreError::StoreFull)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum UartTxState {
|
pub enum UartTxState {
|
||||||
@ -163,56 +144,144 @@ pub enum UartTxState {
|
|||||||
Transmitting(Option<TxDmaTransferType>),
|
Transmitting(Option<TxDmaTransferType>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[app(device = stm32f3xx_hal::pac, peripherals = true, dispatchers = [TIM20_BRK, TIM20_UP, TIM20_TRG_COM])]
|
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 {
|
mod app {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core::slice::Iter;
|
use core::slice::Iter;
|
||||||
use cortex_m::iprintln;
|
use rtic_monotonics::systick::Systick;
|
||||||
use satrs_core::pus::verification::FailParams;
|
use rtic_monotonics::Monotonic;
|
||||||
use satrs_core::pus::verification::VerificationReporterCore;
|
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
|
||||||
use satrs_core::spacepackets::{
|
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
|
||||||
ecss::EcssEnumU16,
|
|
||||||
tc::PusTc,
|
|
||||||
time::cds::P_FIELD_BASE,
|
|
||||||
tm::{PusTm, PusTmSecondaryHeader},
|
|
||||||
CcsdsPacket, SpHeader,
|
|
||||||
};
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use stm32f3_discovery::leds::Direction;
|
use stm32f3_discovery::leds::Direction;
|
||||||
use stm32f3_discovery::leds::Leds;
|
use stm32f3_discovery::leds::Leds;
|
||||||
use stm32f3xx_hal::prelude::*;
|
use stm32f3xx_hal::prelude::*;
|
||||||
use stm32f3xx_hal::Toggle;
|
|
||||||
|
|
||||||
use stm32f3_discovery::switch_hal::OutputSwitch;
|
use stm32f3_discovery::switch_hal::OutputSwitch;
|
||||||
|
use stm32f3xx_hal::Switch;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
|
type SerialType = Serial<USART2, (PA2<AF7<PushPull>>, PA3<AF7<PushPull>>)>;
|
||||||
|
|
||||||
#[shared]
|
#[shared]
|
||||||
struct Shared {
|
struct Shared {
|
||||||
tx_transfer: UartTxState,
|
blink_freq: MillisDurationU32,
|
||||||
|
tx_shared: UartTxShared,
|
||||||
rx_transfer: Option<RxDmaTransferType>,
|
rx_transfer: Option<RxDmaTransferType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[local]
|
#[local]
|
||||||
struct Local {
|
struct Local {
|
||||||
|
verif_reporter: VerificationReportCreator,
|
||||||
leds: Leds,
|
leds: Leds,
|
||||||
last_dir: Direction,
|
last_dir: Direction,
|
||||||
verif_reporter: VerificationReporterCore,
|
|
||||||
curr_dir: Iter<'static, Direction>,
|
curr_dir: Iter<'static, Direction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[monotonic(binds = SysTick, default = true)]
|
#[init]
|
||||||
type MonoTimer = Systick<1000>;
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
|
|
||||||
#[init(local = [
|
|
||||||
tc_pool_mem: [u8; TC_BUF_LEN * TC_POOL_SLOTS] = [0; TC_BUF_LEN * TC_POOL_SLOTS],
|
|
||||||
tm_pool_mem: [u8; MAX_TM_LEN * TM_POOL_SLOTS] = [0; MAX_TM_LEN * TM_POOL_SLOTS]
|
|
||||||
])]
|
|
||||||
fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
|
||||||
let mut rcc = cx.device.RCC.constrain();
|
let mut rcc = cx.device.RCC.constrain();
|
||||||
|
|
||||||
let mono = Systick::new(cx.core.SYST, 8_000_000);
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
logger_init();
|
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 mut flash = cx.device.FLASH.constrain();
|
||||||
let clocks = rcc
|
let clocks = rcc
|
||||||
.cfgr
|
.cfgr
|
||||||
@ -220,17 +289,12 @@ mod app {
|
|||||||
.sysclk(8.MHz())
|
.sysclk(8.MHz())
|
||||||
.pclk1(8.MHz())
|
.pclk1(8.MHz())
|
||||||
.freeze(&mut flash.acr);
|
.freeze(&mut flash.acr);
|
||||||
// setup ITM output
|
|
||||||
iprintln!(
|
|
||||||
&mut cx.core.ITM.stim[0],
|
|
||||||
"Starting sat-rs demo application for the STM32F3-Discovery"
|
|
||||||
);
|
|
||||||
let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
|
|
||||||
// Assign memory to the pools.
|
|
||||||
poolmod::TC::grow(cx.local.tc_pool_mem);
|
|
||||||
poolmod::TM::grow(cx.local.tm_pool_mem);
|
|
||||||
|
|
||||||
let verif_reporter = VerificationReporterCore::new(PUS_APID).unwrap();
|
// 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(
|
let leds = Leds::new(
|
||||||
gpioe.pe8,
|
gpioe.pe8,
|
||||||
@ -264,125 +328,160 @@ mod app {
|
|||||||
clocks,
|
clocks,
|
||||||
&mut rcc.apb1,
|
&mut rcc.apb1,
|
||||||
);
|
);
|
||||||
usart2.configure_rx_interrupt(RxEvent::Idle, Toggle::On);
|
usart2.configure_rx_interrupt(RxEvent::Idle, Switch::On);
|
||||||
// This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
|
// This interrupt is enabled to re-schedule new transfers in the interrupt handler immediately.
|
||||||
usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Toggle::On);
|
usart2.configure_tx_interrupt(TxEvent::TransmissionComplete, Switch::On);
|
||||||
|
|
||||||
let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
|
let dma1 = cx.device.DMA1.split(&mut rcc.ahb);
|
||||||
let (tx_serial, mut rx_serial) = usart2.split();
|
let (mut tx_serial, mut rx_serial) = usart2.split();
|
||||||
|
|
||||||
// This interrupt is immediately triggered, clear it. It will only be reset
|
// This interrupt is immediately triggered, clear it. It will only be reset
|
||||||
// by the hardware when data is received on RX (RXNE event)
|
// by the hardware when data is received on RX (RXNE event)
|
||||||
rx_serial.clear_event(RxEvent::Idle);
|
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);
|
let rx_transfer = rx_serial.read_exact(unsafe { DMA_RX_BUF.as_mut_slice() }, dma1.ch6);
|
||||||
info!(target: "init", "Spawning tasks");
|
defmt::info!("Spawning tasks");
|
||||||
blink::spawn().unwrap();
|
blink::spawn().unwrap();
|
||||||
serial_tx_handler::spawn().unwrap();
|
serial_tx_handler::spawn().unwrap();
|
||||||
|
|
||||||
|
let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
|
||||||
|
|
||||||
(
|
(
|
||||||
Shared {
|
Shared {
|
||||||
tx_transfer: UartTxState::Idle(Some(TxIdle {
|
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
||||||
tx: tx_serial,
|
tx_shared: UartTxShared {
|
||||||
dma_channel: dma1.ch7,
|
last_completed: None,
|
||||||
})),
|
state: UartTxState::Idle(Some(TxIdle {
|
||||||
|
tx: tx_serial,
|
||||||
|
dma_channel: dma1.ch7,
|
||||||
|
})),
|
||||||
|
},
|
||||||
rx_transfer: Some(rx_transfer),
|
rx_transfer: Some(rx_transfer),
|
||||||
},
|
},
|
||||||
Local {
|
Local {
|
||||||
|
verif_reporter,
|
||||||
leds,
|
leds,
|
||||||
last_dir: Direction::North,
|
last_dir: Direction::North,
|
||||||
curr_dir: Direction::iter(),
|
curr_dir: Direction::iter(),
|
||||||
verif_reporter,
|
|
||||||
},
|
},
|
||||||
init::Monotonics(mono),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(local = [leds, curr_dir, last_dir])]
|
#[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
|
||||||
fn blink(cx: blink::Context) {
|
async fn blink(mut cx: blink::Context) {
|
||||||
let toggle_leds = |dir: &Direction| {
|
let blink::LocalResources {
|
||||||
let leds = cx.local.leds;
|
leds,
|
||||||
let last_led = leds.for_direction(*cx.local.last_dir);
|
curr_dir,
|
||||||
|
last_dir,
|
||||||
|
..
|
||||||
|
} = cx.local;
|
||||||
|
let mut toggle_leds = |dir: &Direction| {
|
||||||
|
let last_led = leds.for_direction(*last_dir);
|
||||||
last_led.off().ok();
|
last_led.off().ok();
|
||||||
let led = leds.for_direction(*dir);
|
let led = leds.for_direction(*dir);
|
||||||
led.on().ok();
|
led.on().ok();
|
||||||
*cx.local.last_dir = *dir;
|
*last_dir = *dir;
|
||||||
};
|
};
|
||||||
|
loop {
|
||||||
match cx.local.curr_dir.next() {
|
match curr_dir.next() {
|
||||||
Some(dir) => {
|
Some(dir) => {
|
||||||
toggle_leds(dir);
|
toggle_leds(dir);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
*cx.local.curr_dir = Direction::iter();
|
*curr_dir = Direction::iter();
|
||||||
toggle_leds(cx.local.curr_dir.next().unwrap());
|
toggle_leds(curr_dir.next().unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
||||||
|
Systick::delay(current_blink_freq).await;
|
||||||
}
|
}
|
||||||
blink::spawn_after(MsDuration::from_ticks(BLINK_FREQ_MS)).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(
|
#[task(
|
||||||
shared = [tx_transfer],
|
shared = [tx_shared],
|
||||||
local = []
|
|
||||||
)]
|
)]
|
||||||
fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
|
async fn serial_tx_handler(mut cx: serial_tx_handler::Context) {
|
||||||
if let Some((buf, len)) = TX_REQUESTS.dequeue() {
|
loop {
|
||||||
cx.shared.tx_transfer.lock(|tx_state| match tx_state {
|
let is_idle = cx.shared.tx_shared.lock(|tx_shared| {
|
||||||
UartTxState::Idle(tx) => {
|
if let UartTxState::Idle(_) = tx_shared.state {
|
||||||
//debug!(target: "serial_tx_handler", "bytes: {:x?}", &buf[0..len]);
|
return true;
|
||||||
// Safety: We only copy the data into the TX DMA buffer in this task.
|
|
||||||
// If the DMA is active, another branch will be taken.
|
|
||||||
let mut_tx_dma_buf = unsafe { &mut DMA_TX_BUF };
|
|
||||||
// 0 sentinel value as start marker
|
|
||||||
mut_tx_dma_buf[0] = 0;
|
|
||||||
// Should never panic, we accounted for the overhead.
|
|
||||||
// Write into transfer buffer directly, no need for intermediate
|
|
||||||
// encoding buffer.
|
|
||||||
let encoded_len = cobs::encode(&buf[0..len], &mut mut_tx_dma_buf[1..]);
|
|
||||||
// 0 end marker
|
|
||||||
mut_tx_dma_buf[encoded_len + 1] = 0;
|
|
||||||
//debug!(target: "serial_tx_handler", "Sending {} bytes", encoded_len + 2);
|
|
||||||
//debug!("sent: {:x?}", &mut_tx_dma_buf[0..encoded_len + 2]);
|
|
||||||
let tx_idle = tx.take().unwrap();
|
|
||||||
// Transfer completion and re-scheduling of new TX transfers will be done
|
|
||||||
// by the IRQ handler.
|
|
||||||
let transfer = tx_idle
|
|
||||||
.tx
|
|
||||||
.write_all(&mut_tx_dma_buf[0..encoded_len + 2], tx_idle.dma_channel);
|
|
||||||
*tx_state = UartTxState::Transmitting(Some(transfer));
|
|
||||||
// The memory block is automatically returned to the pool when it is dropped.
|
|
||||||
}
|
|
||||||
UartTxState::Transmitting(_) => {
|
|
||||||
// This is a SW configuration error. Only the ISR which
|
|
||||||
// detects transfer completion should be able to spawn a new
|
|
||||||
// task, and that ISR should set the state to IDLE.
|
|
||||||
panic!("invalid internal tx state detected")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
cx.shared.tx_transfer.lock(|tx_state| {
|
|
||||||
if let UartTxState::Idle(_) = tx_state {
|
|
||||||
serial_tx_handler::spawn_after(MsDuration::from_ticks(TX_HANDLER_FREQ_MS))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
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(
|
#[task(
|
||||||
local = [
|
local = [
|
||||||
stamp_buf: [u8; 7] = [0; 7],
|
verif_reporter,
|
||||||
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
|
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
|
||||||
src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
|
src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
|
||||||
verif_reporter
|
timestamp: [u8; 7] = [0; 7],
|
||||||
],
|
],
|
||||||
|
shared = [blink_freq]
|
||||||
)]
|
)]
|
||||||
fn serial_rx_handler(
|
async fn serial_rx_handler(
|
||||||
cx: serial_rx_handler::Context,
|
mut cx: serial_rx_handler::Context,
|
||||||
received_packet: Box<poolmod::TC>,
|
received_packet: Vec<u8, MAX_TC_LEN>,
|
||||||
rx_len: usize,
|
|
||||||
) {
|
) {
|
||||||
let tgt: &'static str = "serial_rx_handler";
|
cx.local.timestamp[0] = P_FIELD_BASE;
|
||||||
cx.local.stamp_buf[0] = P_FIELD_BASE;
|
defmt::info!("Received packet with {} bytes", received_packet.len());
|
||||||
info!(target: tgt, "Received packet with {} bytes", rx_len);
|
|
||||||
let decode_buf = cx.local.decode_buf;
|
let decode_buf = cx.local.decode_buf;
|
||||||
let packet = received_packet.as_slice();
|
let packet = received_packet.as_slice();
|
||||||
let mut start_idx = None;
|
let mut start_idx = None;
|
||||||
@ -393,168 +492,149 @@ mod app {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start_idx.is_none() {
|
if start_idx.is_none() {
|
||||||
warn!(
|
defmt::warn!("decoding error, can only process cobs encoded frames, data is all 0");
|
||||||
target: tgt,
|
|
||||||
"decoding error, can only process cobs encoded frames, data is all 0"
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let start_idx = start_idx.unwrap();
|
let start_idx = start_idx.unwrap();
|
||||||
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
|
match cobs::decode(&received_packet.as_slice()[start_idx..], decode_buf) {
|
||||||
Ok(len) => {
|
Ok(len) => {
|
||||||
info!(target: tgt, "Decoded packet length: {}", len);
|
defmt::info!("Decoded packet length: {}", len);
|
||||||
let pus_tc = PusTc::from_bytes(decode_buf);
|
let pus_tc = PusTcReader::new(decode_buf);
|
||||||
let verif_reporter = cx.local.verif_reporter;
|
|
||||||
match pus_tc {
|
match pus_tc {
|
||||||
Ok((tc, tc_len)) => handle_tc(
|
Ok((tc, _tc_len)) => {
|
||||||
tc,
|
match convert_pus_tc_to_request(
|
||||||
tc_len,
|
&tc,
|
||||||
verif_reporter,
|
cx.local.verif_reporter,
|
||||||
cx.local.src_data_buf,
|
cx.local.src_data_buf,
|
||||||
cx.local.stamp_buf,
|
cx.local.timestamp,
|
||||||
tgt,
|
) {
|
||||||
),
|
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) => {
|
Err(e) => {
|
||||||
warn!(target: tgt, "Error unpacking PUS TC: {}", e);
|
defmt::warn!("Error unpacking PUS TC: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!(
|
defmt::warn!("decoding error, can only process cobs encoded frames")
|
||||||
target: tgt,
|
|
||||||
"decoding error, can only process cobs encoded frames"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_tc(
|
fn handle_ping_request(timestamp: &[u8]) {
|
||||||
tc: PusTc,
|
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
|
||||||
tc_len: usize,
|
let sp_header =
|
||||||
verif_reporter: &mut VerificationReporterCore,
|
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
|
||||||
src_data_buf: &mut [u8; MAX_TM_LEN],
|
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
|
||||||
stamp_buf: &[u8; 7],
|
let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
|
||||||
tgt: &'static str,
|
let mut tm_packet = TmPacket::new();
|
||||||
) {
|
tm_packet
|
||||||
info!(
|
.resize(ping_reply.len_written(), 0)
|
||||||
target: tgt,
|
.expect("vec resize failed");
|
||||||
"Found PUS TC [{},{}] with length {}",
|
ping_reply.write_to_bytes(&mut tm_packet).unwrap();
|
||||||
tc.service(),
|
if TM_REQUESTS.enqueue(tm_packet).is_err() {
|
||||||
tc.subservice(),
|
defmt::warn!("TC queue full");
|
||||||
tc_len
|
|
||||||
);
|
|
||||||
|
|
||||||
let token = verif_reporter.add_tc(&tc);
|
|
||||||
if tc.apid() != PUS_APID {
|
|
||||||
warn!(target: tgt, "Received tc with unknown APID {}", tc.apid());
|
|
||||||
let sendable = verif_reporter
|
|
||||||
.acceptance_failure(
|
|
||||||
src_data_buf,
|
|
||||||
token,
|
|
||||||
&SEQ_COUNT_PROVIDER,
|
|
||||||
FailParams::new(stamp_buf, &EcssEnumU16::new(0), None),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
|
|
||||||
let mut sender = TmSender::new(mem_block, tgt);
|
|
||||||
if let Err(e) =
|
|
||||||
verif_reporter.send_acceptance_failure(sendable, &SEQ_COUNT_PROVIDER, &mut sender)
|
|
||||||
{
|
|
||||||
warn!(target: tgt, "Sending acceptance failure failed: {:?}", e.0);
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let sendable = verif_reporter
|
}
|
||||||
.acceptance_success(src_data_buf, token, &SEQ_COUNT_PROVIDER, stamp_buf)
|
|
||||||
|
fn handle_start_verification(
|
||||||
|
accepted_token: VerificationToken<TcStateAccepted>,
|
||||||
|
verif_reporter: &mut VerificationReportCreator,
|
||||||
|
src_data_buf: &mut [u8],
|
||||||
|
timestamp: &[u8],
|
||||||
|
) -> VerificationToken<TcStateStarted> {
|
||||||
|
let (tm_creator, started_token) = verif_reporter
|
||||||
|
.start_success(
|
||||||
|
src_data_buf,
|
||||||
|
accepted_token,
|
||||||
|
SEQ_COUNT_PROVIDER.get(),
|
||||||
|
0,
|
||||||
|
×tamp,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let result = send_tm(tm_creator);
|
||||||
|
if let Err(e) = result {
|
||||||
|
handle_tm_send_error(e);
|
||||||
|
}
|
||||||
|
started_token
|
||||||
|
}
|
||||||
|
|
||||||
let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
|
fn handle_completion_verification(
|
||||||
let mut sender = TmSender::new(mem_block, tgt);
|
started_token: VerificationToken<TcStateStarted>,
|
||||||
let accepted_token = match verif_reporter.send_acceptance_success(
|
verif_reporter: &mut VerificationReportCreator,
|
||||||
sendable,
|
src_data_buf: &mut [u8],
|
||||||
&SEQ_COUNT_PROVIDER,
|
timestamp: &[u8],
|
||||||
&mut sender,
|
) {
|
||||||
) {
|
let result = send_tm(
|
||||||
Ok(token) => token,
|
verif_reporter
|
||||||
Err(e) => {
|
.completion_success(
|
||||||
warn!(target: "serial_rx_handler", "Sending acceptance success failed: {:?}", e.0);
|
src_data_buf,
|
||||||
return;
|
started_token,
|
||||||
}
|
SEQ_COUNT_PROVIDER.get(),
|
||||||
};
|
0,
|
||||||
|
timestamp,
|
||||||
if tc.service() == 17 {
|
)
|
||||||
if tc.subservice() == 1 {
|
.unwrap(),
|
||||||
let sendable = verif_reporter
|
);
|
||||||
.start_success(src_data_buf, accepted_token, &SEQ_COUNT_PROVIDER, stamp_buf)
|
if let Err(e) = result {
|
||||||
.unwrap();
|
handle_tm_send_error(e);
|
||||||
let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
|
|
||||||
let mut sender = TmSender::new(mem_block, tgt);
|
|
||||||
let started_token = match verif_reporter.send_start_success(
|
|
||||||
sendable,
|
|
||||||
&SEQ_COUNT_PROVIDER,
|
|
||||||
&mut sender,
|
|
||||||
) {
|
|
||||||
Ok(token) => token,
|
|
||||||
Err(e) => {
|
|
||||||
warn!(target: tgt, "Sending acceptance success failed: {:?}", e.0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
info!(
|
|
||||||
target: tgt,
|
|
||||||
"Received PUS ping telecommand, sending ping reply TM[17,2]"
|
|
||||||
);
|
|
||||||
let mut sp_header =
|
|
||||||
SpHeader::tc_unseg(PUS_APID, SEQ_COUNT_PROVIDER.get(), 0).unwrap();
|
|
||||||
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, stamp_buf);
|
|
||||||
let ping_reply = PusTm::new(&mut sp_header, sec_header, None, true);
|
|
||||||
let mut mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
|
|
||||||
let reply_len = ping_reply.write_to_bytes(mem_block.as_mut_slice()).unwrap();
|
|
||||||
if TX_REQUESTS.enqueue((mem_block, reply_len)).is_err() {
|
|
||||||
warn!(target: tgt, "TC queue full");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SEQ_COUNT_PROVIDER.increment();
|
|
||||||
let sendable = verif_reporter
|
|
||||||
.completion_success(src_data_buf, started_token, &SEQ_COUNT_PROVIDER, stamp_buf)
|
|
||||||
.unwrap();
|
|
||||||
let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
|
|
||||||
let mut sender = TmSender::new(mem_block, tgt);
|
|
||||||
if let Err(e) = verif_reporter.send_step_or_completion_success(
|
|
||||||
sendable,
|
|
||||||
&SEQ_COUNT_PROVIDER,
|
|
||||||
&mut sender,
|
|
||||||
) {
|
|
||||||
warn!(target: tgt, "Sending completion success failed: {:?}", e.0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Invalid subservice
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
|
#[task(binds = DMA1_CH6, shared = [rx_transfer])]
|
||||||
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
|
fn rx_dma_isr(mut cx: rx_dma_isr::Context) {
|
||||||
|
let mut tc_packet = TcPacket::new();
|
||||||
cx.shared.rx_transfer.lock(|rx_transfer| {
|
cx.shared.rx_transfer.lock(|rx_transfer| {
|
||||||
let rx_ref = rx_transfer.as_ref().unwrap();
|
let rx_ref = rx_transfer.as_ref().unwrap();
|
||||||
if rx_ref.is_complete() {
|
if rx_ref.is_complete() {
|
||||||
let uart_rx_owned = rx_transfer.take().unwrap();
|
let uart_rx_owned = rx_transfer.take().unwrap();
|
||||||
let (buf, c, rx) = uart_rx_owned.stop();
|
let (buf, c, rx) = uart_rx_owned.stop();
|
||||||
// The received data is transferred to another task now to avoid any processing overhead
|
// The received data is transferred to another task now to avoid any processing overhead
|
||||||
// during the interrupt. There are multiple ways to do this, we use a memory pool here
|
// during the interrupt. There are multiple ways to do this, we use a stack allocaed vector here
|
||||||
// to do this.
|
// to do this.
|
||||||
let mut mem_block = poolmod::TC::alloc()
|
tc_packet.resize(buf.len(), 0).expect("vec resize failed");
|
||||||
.expect("allocating memory block for rx failed")
|
tc_packet.copy_from_slice(buf);
|
||||||
.init([0u8; TC_BUF_LEN]);
|
|
||||||
// Copy data into memory pool.
|
// Start the next transfer as soon as possible.
|
||||||
mem_block.copy_from_slice(buf);
|
|
||||||
*rx_transfer = Some(rx.read_exact(buf, c));
|
*rx_transfer = Some(rx.read_exact(buf, c));
|
||||||
// Only send owning pointer to pool memory and the received packet length.
|
|
||||||
serial_rx_handler::spawn(mem_block, TC_BUF_LEN)
|
// Send the vector to a regular task.
|
||||||
.expect("spawning rx handler task failed");
|
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
|
// 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.
|
// exceeded. Circular mode is not used here, so data might be missed.
|
||||||
warn!(
|
defmt::warn!(
|
||||||
"rx transfer with maximum length {}, might miss data",
|
"rx transfer with maximum length {}, might miss data",
|
||||||
TC_BUF_LEN
|
TC_BUF_LEN
|
||||||
);
|
);
|
||||||
@ -562,23 +642,26 @@ mod app {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_transfer])]
|
#[task(binds = USART2_EXTI26, shared = [rx_transfer, tx_shared])]
|
||||||
fn serial_isr(mut cx: serial_isr::Context) {
|
fn serial_isr(mut cx: serial_isr::Context) {
|
||||||
cx.shared.tx_transfer.lock(|tx_state| match tx_state {
|
cx.shared
|
||||||
UartTxState::Idle(_) => (),
|
.tx_shared
|
||||||
UartTxState::Transmitting(transfer) => {
|
.lock(|tx_shared| match &mut tx_shared.state {
|
||||||
let transfer_ref = transfer.as_ref().unwrap();
|
UartTxState::Idle(_) => (),
|
||||||
if transfer_ref.is_complete() {
|
UartTxState::Transmitting(transfer) => {
|
||||||
let transfer = transfer.take().unwrap();
|
let transfer_ref = transfer.as_ref().unwrap();
|
||||||
let (_, dma_channel, tx) = transfer.stop();
|
if transfer_ref.is_complete() {
|
||||||
*tx_state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
|
let transfer = transfer.take().unwrap();
|
||||||
serial_tx_handler::spawn_after(MsDuration::from_ticks(
|
let (_, dma_channel, mut tx) = transfer.stop();
|
||||||
MIN_DELAY_BETWEEN_TX_PACKETS_MS.into(),
|
tx.clear_event(TxEvent::TransmissionComplete);
|
||||||
))
|
tx_shared.state = UartTxState::Idle(Some(TxIdle { tx, dma_channel }));
|
||||||
.unwrap();
|
// 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| {
|
cx.shared.rx_transfer.lock(|rx_transfer| {
|
||||||
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
|
let rx_transfer_ref = rx_transfer.as_ref().unwrap();
|
||||||
// Received a partial packet.
|
// Received a partial packet.
|
||||||
@ -586,17 +669,14 @@ mod app {
|
|||||||
let rx_transfer_owned = rx_transfer.take().unwrap();
|
let rx_transfer_owned = rx_transfer.take().unwrap();
|
||||||
let (buf, ch, mut rx, rx_len) = rx_transfer_owned.stop_and_return_received_bytes();
|
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
|
// The received data is transferred to another task now to avoid any processing overhead
|
||||||
// during the interrupt. There are multiple ways to do this, we use a memory pool here
|
// during the interrupt. There are multiple ways to do this, we use a stack
|
||||||
// to do this.
|
// allocated vector to do this.
|
||||||
let mut mem_block = poolmod::TC::alloc()
|
tc_packet
|
||||||
.expect("allocating memory block for rx failed")
|
.resize(rx_len as usize, 0)
|
||||||
.init([0u8; TC_BUF_LEN]);
|
.expect("vec resize failed");
|
||||||
// Copy data into memory pool.
|
tc_packet[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
|
||||||
mem_block[0..rx_len as usize].copy_from_slice(&buf[0..rx_len as usize]);
|
|
||||||
rx.clear_event(RxEvent::Idle);
|
rx.clear_event(RxEvent::Idle);
|
||||||
// Only send owning pointer to pool memory and the received packet length.
|
serial_rx_handler::spawn(tc_packet).expect("spawning rx handler failed");
|
||||||
serial_rx_handler::spawn(mem_block, rx_len as usize)
|
|
||||||
.expect("spawning rx handler task failed");
|
|
||||||
*rx_transfer = Some(rx.read_exact(buf, ch));
|
*rx_transfer = Some(rx.read_exact(buf, ch));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"rust-lang.rust",
|
"rust-lang.rust",
|
||||||
"marus25.cortex-debug",
|
"probe-rs.probe-rs-debugger"
|
||||||
],
|
],
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
"unwantedRecommendations": []
|
"unwantedRecommendations": []
|
||||||
}
|
}
|
22
satrs-example-stm32f3-disco/vscode/launch.json
Normal file
22
satrs-example-stm32f3-disco/vscode/launch.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"preLaunchTask": "${defaultBuildTask}",
|
||||||
|
"type": "probe-rs-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "probe-rs Debugging ",
|
||||||
|
"flashingConfig": {
|
||||||
|
"flashingEnabled": true
|
||||||
|
},
|
||||||
|
"chip": "STM32F303VCTx",
|
||||||
|
"coreConfigs": [
|
||||||
|
{
|
||||||
|
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-example-stm32f3-disco",
|
||||||
|
"rttEnabled": true,
|
||||||
|
"svdFile": "STM32F303.svd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -11,7 +11,8 @@ proc CDSWOConfigure { CDCPUFreqHz CDSWOFreqHz CDSWOOutput } {
|
|||||||
# Alternative option: Pipe ITM output into itm.txt file
|
# Alternative option: Pipe ITM output into itm.txt file
|
||||||
# tpiu config internal itm.txt uart off $CDCPUFreqHz
|
# tpiu config internal itm.txt uart off $CDCPUFreqHz
|
||||||
|
|
||||||
# Default option so SWO display of VS code works.
|
# Default option so SWO display of VS code works. Please note that this might not be required
|
||||||
|
# anymore starting at openocd v0.12.0
|
||||||
tpiu config internal $CDSWOOutput uart off $CDCPUFreqHz $CDSWOFreqHz
|
tpiu config internal $CDSWOOutput uart off $CDCPUFreqHz $CDSWOFreqHz
|
||||||
itm port 0 on
|
itm port 0 on
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "satrs-example"
|
name = "satrs-example"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
default-run = "satrs-example"
|
default-run = "satrs-example"
|
||||||
@ -17,16 +17,23 @@ zerocopy = "0.6"
|
|||||||
csv = "1"
|
csv = "1"
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
lazy_static = "1"
|
||||||
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
derive-new = "0.5"
|
derive-new = "0.5"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
[dependencies.satrs-core]
|
[dependencies.satrs]
|
||||||
# version = "0.1.0-alpha.1"
|
path = "../satrs"
|
||||||
path = "../satrs-core"
|
features = ["test_util"]
|
||||||
|
|
||||||
[dependencies.satrs-mib]
|
[dependencies.satrs-mib]
|
||||||
# version = "0.1.0-alpha.1"
|
version = "0.1.1"
|
||||||
path = "../satrs-mib"
|
path = "../satrs-mib"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dyn_tmtc = []
|
dyn_tmtc = []
|
||||||
default = ["dyn_tmtc"]
|
default = ["dyn_tmtc"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.11"
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from spacepackets.ccsds import CdsShortTimestamp
|
|
||||||
from spacepackets.ecss import PusTelecommand
|
|
||||||
from tmtccmd.config import CmdTreeNode
|
|
||||||
from tmtccmd.tmtc import DefaultPusQueueHelper
|
|
||||||
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
|
|
||||||
from tmtccmd.pus.tc.s3_fsfw_hk import create_request_one_hk_command
|
|
||||||
|
|
||||||
from common import (
|
|
||||||
EXAMPLE_PUS_APID,
|
|
||||||
make_addressable_id,
|
|
||||||
RequestTargetId,
|
|
||||||
AcsHkIds,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def create_cmd_definition_tree() -> CmdTreeNode:
|
|
||||||
|
|
||||||
root_node = CmdTreeNode.root_node()
|
|
||||||
|
|
||||||
test_node = CmdTreeNode("test", "Test Node")
|
|
||||||
test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
|
|
||||||
test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
|
|
||||||
root_node.add_child(test_node)
|
|
||||||
|
|
||||||
scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
|
|
||||||
scheduler_node.add_child(
|
|
||||||
CmdTreeNode(
|
|
||||||
"schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
root_node.add_child(scheduler_node)
|
|
||||||
|
|
||||||
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
|
|
||||||
mgm_node = CmdTreeNode("mgms", "MGM devices node")
|
|
||||||
mgm_node.add_child(CmdTreeNode("one_shot_hk", "Request one shot HK"))
|
|
||||||
acs_node.add_child(mgm_node)
|
|
||||||
root_node.add_child(acs_node)
|
|
||||||
|
|
||||||
return root_node
|
|
||||||
|
|
||||||
|
|
||||||
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
|
|
||||||
# It should always be at least the root path "/", so we split of the empty portion left of it.
|
|
||||||
cmd_path_list = cmd_path.split("/")[1:]
|
|
||||||
if len(cmd_path_list) == 0:
|
|
||||||
_LOGGER.warning("empty command path")
|
|
||||||
return
|
|
||||||
if cmd_path_list[0] == "test":
|
|
||||||
assert len(cmd_path_list) >= 2
|
|
||||||
if cmd_path_list[1] == "ping":
|
|
||||||
q.add_log_cmd("Sending PUS ping telecommand")
|
|
||||||
return q.add_pus_tc(PusTelecommand(service=17, subservice=1))
|
|
||||||
elif cmd_path_list[1] == "trigger_event":
|
|
||||||
q.add_log_cmd("Triggering test event")
|
|
||||||
return q.add_pus_tc(PusTelecommand(service=17, subservice=128))
|
|
||||||
if cmd_path_list[0] == "scheduler":
|
|
||||||
assert len(cmd_path_list) >= 2
|
|
||||||
if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
|
|
||||||
q.add_log_cmd("Sending PUS scheduled TC telecommand")
|
|
||||||
crt_time = CdsShortTimestamp.from_now()
|
|
||||||
time_stamp = crt_time + datetime.timedelta(seconds=10)
|
|
||||||
time_stamp = time_stamp.pack()
|
|
||||||
return q.add_pus_tc(
|
|
||||||
create_time_tagged_cmd(
|
|
||||||
time_stamp,
|
|
||||||
PusTelecommand(service=17, subservice=1),
|
|
||||||
apid=EXAMPLE_PUS_APID,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if cmd_path_list[0] == "acs":
|
|
||||||
assert len(cmd_path_list) >= 2
|
|
||||||
if cmd_path_list[1] == "mgm":
|
|
||||||
assert len(cmd_path_list) >= 3
|
|
||||||
if cmd_path_list[2] == "one_shot_hk":
|
|
||||||
q.add_log_cmd("Sending HK one shot request")
|
|
||||||
q.add_pus_tc(
|
|
||||||
create_request_one_hk_command(
|
|
||||||
make_addressable_id(RequestTargetId.ACS, AcsHkIds.MGM_SET)
|
|
||||||
)
|
|
||||||
)
|
|
@ -4,11 +4,12 @@ import dataclasses
|
|||||||
import enum
|
import enum
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from spacepackets.ecss.tc import PacketId, PacketType
|
|
||||||
|
|
||||||
EXAMPLE_PUS_APID = 0x02
|
class Apid(enum.IntEnum):
|
||||||
EXAMPLE_PUS_PACKET_ID_TM = PacketId(PacketType.TM, True, EXAMPLE_PUS_APID)
|
SCHED = 1
|
||||||
TM_PACKET_IDS = [EXAMPLE_PUS_PACKET_ID_TM]
|
GENERIC_PUS = 2
|
||||||
|
ACS = 3
|
||||||
|
CFDP = 4
|
||||||
|
|
||||||
|
|
||||||
class EventSeverity(enum.IntEnum):
|
class EventSeverity(enum.IntEnum):
|
||||||
@ -36,8 +37,8 @@ class EventU32:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RequestTargetId(enum.IntEnum):
|
class AcsId(enum.IntEnum):
|
||||||
ACS = 1
|
MGM_0 = 0
|
||||||
|
|
||||||
|
|
||||||
class AcsHkIds(enum.IntEnum):
|
class AcsHkIds(enum.IntEnum):
|
@ -3,10 +3,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
from prompt_toolkit.history import History
|
from prompt_toolkit.history import History
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
|
|
||||||
|
from spacepackets.ccsds import PacketId, PacketType
|
||||||
import tmtccmd
|
import tmtccmd
|
||||||
from spacepackets.ecss import PusTelemetry, PusVerificator
|
from spacepackets.ecss import PusTelemetry, PusVerificator
|
||||||
from spacepackets.ecss.pus_17_test import Service17Tm
|
from spacepackets.ecss.pus_17_test import Service17Tm
|
||||||
@ -16,7 +17,7 @@ from spacepackets.ccsds.time import CdsShortTimestamp
|
|||||||
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
||||||
from tmtccmd.core.base import BackendRequest
|
from tmtccmd.core.base import BackendRequest
|
||||||
from tmtccmd.pus import VerificationWrapper
|
from tmtccmd.pus import VerificationWrapper
|
||||||
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
|
from tmtccmd.tmtc import CcsdsTmHandler, GenericApidHandlerBase
|
||||||
from tmtccmd.com import ComInterface
|
from tmtccmd.com import ComInterface
|
||||||
from tmtccmd.config import (
|
from tmtccmd.config import (
|
||||||
CmdTreeNode,
|
CmdTreeNode,
|
||||||
@ -46,7 +47,7 @@ from tmtccmd.util.obj_id import ObjectIdDictT
|
|||||||
|
|
||||||
|
|
||||||
import pus_tc
|
import pus_tc
|
||||||
from common import EXAMPLE_PUS_APID, TM_PACKET_IDS, EventU32
|
from common import Apid, EventU32
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
_LOGGER = logging.getLogger()
|
||||||
|
|
||||||
@ -62,10 +63,13 @@ class SatRsConfigHook(HookBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert self.cfg_path is not None
|
assert self.cfg_path is not None
|
||||||
|
packet_id_list = []
|
||||||
|
for apid in Apid:
|
||||||
|
packet_id_list.append(PacketId(PacketType.TM, True, apid))
|
||||||
cfg = create_com_interface_cfg_default(
|
cfg = create_com_interface_cfg_default(
|
||||||
com_if_key=com_if_key,
|
com_if_key=com_if_key,
|
||||||
json_cfg_path=self.cfg_path,
|
json_cfg_path=self.cfg_path,
|
||||||
space_packet_ids=TM_PACKET_IDS,
|
space_packet_ids=packet_id_list,
|
||||||
)
|
)
|
||||||
assert cfg is not None
|
assert cfg is not None
|
||||||
return create_com_interface_default(cfg)
|
return create_com_interface_default(cfg)
|
||||||
@ -85,19 +89,19 @@ class SatRsConfigHook(HookBase):
|
|||||||
return get_core_object_ids()
|
return get_core_object_ids()
|
||||||
|
|
||||||
|
|
||||||
class PusHandler(SpecificApidHandlerBase):
|
class PusHandler(GenericApidHandlerBase):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
file_logger: logging.Logger,
|
file_logger: logging.Logger,
|
||||||
verif_wrapper: VerificationWrapper,
|
verif_wrapper: VerificationWrapper,
|
||||||
raw_logger: RawTmtcTimedLogWrapper,
|
raw_logger: RawTmtcTimedLogWrapper,
|
||||||
):
|
):
|
||||||
super().__init__(EXAMPLE_PUS_APID, None)
|
super().__init__(None)
|
||||||
self.file_logger = file_logger
|
self.file_logger = file_logger
|
||||||
self.raw_logger = raw_logger
|
self.raw_logger = raw_logger
|
||||||
self.verif_wrapper = verif_wrapper
|
self.verif_wrapper = verif_wrapper
|
||||||
|
|
||||||
def handle_tm(self, packet: bytes, _user_args: any):
|
def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
|
||||||
try:
|
try:
|
||||||
pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
|
pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@ -177,7 +181,7 @@ class TcHandler(TcHandlerBase):
|
|||||||
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
|
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
|
||||||
seq_cnt_provider=seq_count_provider,
|
seq_cnt_provider=seq_count_provider,
|
||||||
pus_verificator=self.verif_wrapper.pus_verificator,
|
pus_verificator=self.verif_wrapper.pus_verificator,
|
||||||
default_pus_apid=EXAMPLE_PUS_APID,
|
default_pus_apid=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_cb(self, send_params: SendCbParams):
|
def send_cb(self, send_params: SendCbParams):
|
||||||
@ -221,7 +225,6 @@ def main():
|
|||||||
post_args_wrapper.set_params_without_prompts(proc_wrapper)
|
post_args_wrapper.set_params_without_prompts(proc_wrapper)
|
||||||
else:
|
else:
|
||||||
post_args_wrapper.set_params_with_prompts(proc_wrapper)
|
post_args_wrapper.set_params_with_prompts(proc_wrapper)
|
||||||
params.apid = EXAMPLE_PUS_APID
|
|
||||||
setup_args = SetupWrapper(
|
setup_args = SetupWrapper(
|
||||||
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
||||||
)
|
)
|
||||||
@ -233,8 +236,9 @@ def main():
|
|||||||
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
|
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
|
||||||
# Create primary TM handler and add it to the CCSDS Packet Handler
|
# Create primary TM handler and add it to the CCSDS Packet Handler
|
||||||
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
|
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
|
||||||
ccsds_handler = CcsdsTmHandler(generic_handler=None)
|
ccsds_handler = CcsdsTmHandler(generic_handler=tm_handler)
|
||||||
ccsds_handler.add_apid_handler(tm_handler)
|
# TODO: We could add the CFDP handler for the CFDP APID at a later stage.
|
||||||
|
# ccsds_handler.add_apid_handler(tm_handler)
|
||||||
|
|
||||||
# Create TC handler
|
# Create TC handler
|
||||||
seq_count_provider = PusFileSeqCountProvider()
|
seq_count_provider = PusFileSeqCountProvider()
|
143
satrs-example/satrs-tmtc/pus_tc.py
Normal file
143
satrs-example/satrs-tmtc/pus_tc.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import datetime
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from spacepackets.ccsds import CdsShortTimestamp
|
||||||
|
from spacepackets.ecss import PusTelecommand
|
||||||
|
from tmtccmd.config import CmdTreeNode
|
||||||
|
from tmtccmd.pus.tc.s200_fsfw_mode import Mode
|
||||||
|
from tmtccmd.tmtc import DefaultPusQueueHelper
|
||||||
|
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
|
||||||
|
from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice
|
||||||
|
|
||||||
|
from common import AcsId, Apid
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_set_mode_cmd(
|
||||||
|
apid: int, unique_id: int, mode: int, submode: int
|
||||||
|
) -> PusTelecommand:
|
||||||
|
app_data = bytearray()
|
||||||
|
app_data.extend(struct.pack("!I", unique_id))
|
||||||
|
app_data.extend(struct.pack("!I", mode))
|
||||||
|
app_data.extend(struct.pack("!H", submode))
|
||||||
|
return PusTelecommand(
|
||||||
|
service=200,
|
||||||
|
subservice=ModeSubservice.TC_MODE_COMMAND,
|
||||||
|
apid=apid,
|
||||||
|
app_data=app_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_cmd_definition_tree() -> CmdTreeNode:
|
||||||
|
|
||||||
|
root_node = CmdTreeNode.root_node()
|
||||||
|
|
||||||
|
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
|
||||||
|
hk_node.add_child(CmdTreeNode("one_shot_hk", "Request One Shot HK set"))
|
||||||
|
hk_node.add_child(
|
||||||
|
CmdTreeNode("enable", "Enable periodic housekeeping data generation")
|
||||||
|
)
|
||||||
|
hk_node.add_child(
|
||||||
|
CmdTreeNode("disable", "Disable periodic housekeeping data generation")
|
||||||
|
)
|
||||||
|
|
||||||
|
mode_node = CmdTreeNode("mode", "Mode Node", hide_children_for_print=True)
|
||||||
|
set_mode_node = CmdTreeNode(
|
||||||
|
"set_mode", "Set Node", hide_children_which_are_leaves=True
|
||||||
|
)
|
||||||
|
set_mode_node.add_child(CmdTreeNode("off", "Set OFF Mode"))
|
||||||
|
set_mode_node.add_child(CmdTreeNode("on", "Set ON Mode"))
|
||||||
|
set_mode_node.add_child(CmdTreeNode("normal", "Set NORMAL Mode"))
|
||||||
|
mode_node.add_child(set_mode_node)
|
||||||
|
mode_node.add_child(CmdTreeNode("read_mode", "Read Mode"))
|
||||||
|
|
||||||
|
test_node = CmdTreeNode("test", "Test Node")
|
||||||
|
test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
|
||||||
|
test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
|
||||||
|
root_node.add_child(test_node)
|
||||||
|
|
||||||
|
scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
|
||||||
|
scheduler_node.add_child(
|
||||||
|
CmdTreeNode(
|
||||||
|
"schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
root_node.add_child(scheduler_node)
|
||||||
|
|
||||||
|
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
|
||||||
|
mgm_node = CmdTreeNode("mgms", "MGM devices node")
|
||||||
|
mgm_node.add_child(mode_node)
|
||||||
|
mgm_node.add_child(hk_node)
|
||||||
|
|
||||||
|
acs_node.add_child(mgm_node)
|
||||||
|
root_node.add_child(acs_node)
|
||||||
|
|
||||||
|
return root_node
|
||||||
|
|
||||||
|
|
||||||
|
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
|
||||||
|
# It should always be at least the root path "/", so we split of the empty portion left of it.
|
||||||
|
cmd_path_list = cmd_path.split("/")[1:]
|
||||||
|
if len(cmd_path_list) == 0:
|
||||||
|
_LOGGER.warning("empty command path")
|
||||||
|
return
|
||||||
|
if cmd_path_list[0] == "test":
|
||||||
|
assert len(cmd_path_list) >= 2
|
||||||
|
if cmd_path_list[1] == "ping":
|
||||||
|
q.add_log_cmd("Sending PUS ping telecommand")
|
||||||
|
return q.add_pus_tc(
|
||||||
|
PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=1)
|
||||||
|
)
|
||||||
|
elif cmd_path_list[1] == "trigger_event":
|
||||||
|
q.add_log_cmd("Triggering test event")
|
||||||
|
return q.add_pus_tc(
|
||||||
|
PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=128)
|
||||||
|
)
|
||||||
|
if cmd_path_list[0] == "scheduler":
|
||||||
|
assert len(cmd_path_list) >= 2
|
||||||
|
if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
|
||||||
|
q.add_log_cmd("Sending PUS scheduled TC telecommand")
|
||||||
|
crt_time = CdsShortTimestamp.from_now()
|
||||||
|
time_stamp = crt_time + datetime.timedelta(seconds=10)
|
||||||
|
time_stamp = time_stamp.pack()
|
||||||
|
return q.add_pus_tc(
|
||||||
|
create_time_tagged_cmd(
|
||||||
|
time_stamp,
|
||||||
|
PusTelecommand(service=17, subservice=1),
|
||||||
|
apid=Apid.SCHED,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if cmd_path_list[0] == "acs":
|
||||||
|
assert len(cmd_path_list) >= 2
|
||||||
|
if cmd_path_list[1] == "mgms":
|
||||||
|
assert len(cmd_path_list) >= 3
|
||||||
|
if cmd_path_list[2] == "hk":
|
||||||
|
if cmd_path_list[3] == "one_shot_hk":
|
||||||
|
q.add_log_cmd("Sending HK one shot request")
|
||||||
|
# TODO: Fix
|
||||||
|
# q.add_pus_tc(
|
||||||
|
# create_request_one_hk_command(
|
||||||
|
# make_addressable_id(Apid.ACS, AcsId.MGM_SET)
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
if cmd_path_list[2] == "mode":
|
||||||
|
if cmd_path_list[3] == "set_mode":
|
||||||
|
handle_set_mode_cmd(
|
||||||
|
q, "MGM 0", cmd_path_list[4], Apid.ACS, AcsId.MGM_0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_set_mode_cmd(
|
||||||
|
q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
|
||||||
|
):
|
||||||
|
if mode_str == "off":
|
||||||
|
q.add_log_cmd(f"Sending Mode OFF to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
|
||||||
|
elif mode_str == "on":
|
||||||
|
q.add_log_cmd(f"Sending Mode ON to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
|
||||||
|
elif mode_str == "normal":
|
||||||
|
q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
|
||||||
|
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))
|
@ -1,117 +0,0 @@
|
|||||||
use std::sync::mpsc::{self, TryRecvError};
|
|
||||||
|
|
||||||
use log::{info, warn};
|
|
||||||
use satrs_core::pus::verification::VerificationReporterWithSender;
|
|
||||||
use satrs_core::pus::{EcssTmSender, PusTmWrapper};
|
|
||||||
use satrs_core::spacepackets::ecss::hk::Subservice as HkSubservice;
|
|
||||||
use satrs_core::{
|
|
||||||
hk::HkRequest,
|
|
||||||
spacepackets::{
|
|
||||||
ecss::tm::{PusTmCreator, PusTmSecondaryHeader},
|
|
||||||
time::cds::{DaysLen16Bits, TimeProvider},
|
|
||||||
SequenceFlags, SpHeader,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use satrs_example::config::{RequestTargetId, PUS_APID};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
hk::{AcsHkIds, HkUniqueId},
|
|
||||||
requests::{Request, RequestWithToken},
|
|
||||||
update_time,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct AcsTask {
|
|
||||||
timestamp: [u8; 7],
|
|
||||||
time_provider: TimeProvider<DaysLen16Bits>,
|
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
tm_sender: Box<dyn EcssTmSender>,
|
|
||||||
request_rx: mpsc::Receiver<RequestWithToken>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AcsTask {
|
|
||||||
pub fn new(
|
|
||||||
tm_sender: impl EcssTmSender,
|
|
||||||
request_rx: mpsc::Receiver<RequestWithToken>,
|
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp: [0; 7],
|
|
||||||
time_provider: TimeProvider::new_with_u16_days(0, 0),
|
|
||||||
verif_reporter,
|
|
||||||
tm_sender: Box::new(tm_sender),
|
|
||||||
request_rx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_hk_request(&mut self, target_id: u32, unique_id: u32) {
|
|
||||||
assert_eq!(target_id, RequestTargetId::AcsSubsystem as u32);
|
|
||||||
if unique_id == AcsHkIds::TestMgmSet as u32 {
|
|
||||||
let mut sp_header = SpHeader::tm(PUS_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
|
||||||
let sec_header = PusTmSecondaryHeader::new_simple(
|
|
||||||
3,
|
|
||||||
HkSubservice::TmHkPacket as u8,
|
|
||||||
&self.timestamp,
|
|
||||||
);
|
|
||||||
let mut buf: [u8; 8] = [0; 8];
|
|
||||||
let hk_id = HkUniqueId::new(target_id, unique_id);
|
|
||||||
hk_id.write_to_be_bytes(&mut buf).unwrap();
|
|
||||||
let pus_tm = PusTmCreator::new(&mut sp_header, sec_header, &buf, true);
|
|
||||||
self.tm_sender
|
|
||||||
.send_tm(PusTmWrapper::Direct(pus_tm))
|
|
||||||
.expect("Sending HK TM failed");
|
|
||||||
}
|
|
||||||
// TODO: Verification failure for invalid unique IDs.
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_reading_one_request(&mut self) -> bool {
|
|
||||||
match self.request_rx.try_recv() {
|
|
||||||
Ok(request) => {
|
|
||||||
info!(
|
|
||||||
"ACS thread: Received HK request {:?}",
|
|
||||||
request.targeted_request
|
|
||||||
);
|
|
||||||
match request.targeted_request.request {
|
|
||||||
Request::Hk(hk_req) => match hk_req {
|
|
||||||
HkRequest::OneShot(unique_id) => self.handle_hk_request(
|
|
||||||
request.targeted_request.target_id_with_apid.target_id(),
|
|
||||||
unique_id,
|
|
||||||
),
|
|
||||||
HkRequest::Enable(_) => {}
|
|
||||||
HkRequest::Disable(_) => {}
|
|
||||||
HkRequest::ModifyCollectionInterval(_, _) => {}
|
|
||||||
},
|
|
||||||
Request::Mode(_mode_req) => {
|
|
||||||
warn!("mode request handling not implemented yet")
|
|
||||||
}
|
|
||||||
Request::Action(_action_req) => {
|
|
||||||
warn!("action request handling not implemented yet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let started_token = self
|
|
||||||
.verif_reporter
|
|
||||||
.start_success(request.token, Some(&self.timestamp))
|
|
||||||
.expect("Sending start success failed");
|
|
||||||
self.verif_reporter
|
|
||||||
.completion_success(started_token, Some(&self.timestamp))
|
|
||||||
.expect("Sending completion success failed");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => false,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
warn!("ACS thread: Message Queue TX disconnected!");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
update_time(&mut self.time_provider, &mut self.timestamp);
|
|
||||||
loop {
|
|
||||||
if !self.try_reading_one_request() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
282
satrs-example/src/acs/mgm.rs
Normal file
282
satrs-example/src/acs/mgm.rs
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
use satrs::hk::{HkRequest, HkRequestVariant};
|
||||||
|
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
|
||||||
|
use satrs::spacepackets::ecss::hk;
|
||||||
|
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
|
||||||
|
use satrs::spacepackets::SpHeader;
|
||||||
|
use satrs_example::{DeviceMode, TimeStampHelper};
|
||||||
|
use std::sync::mpsc::{self};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use satrs::mode::{
|
||||||
|
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
|
||||||
|
};
|
||||||
|
use satrs::pus::{EcssTmSender, PusTmVariant};
|
||||||
|
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||||
|
use satrs_example::config::components::PUS_MODE_SERVICE;
|
||||||
|
|
||||||
|
use crate::pus::hk::{HkReply, HkReplyVariant};
|
||||||
|
use crate::requests::CompositeRequest;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0;
|
||||||
|
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
|
||||||
|
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
|
||||||
|
|
||||||
|
pub trait SpiInterface {
|
||||||
|
type Error;
|
||||||
|
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SpiDummyInterface {
|
||||||
|
pub dummy_val_0: i16,
|
||||||
|
pub dummy_val_1: i16,
|
||||||
|
pub dummy_val_2: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiInterface for SpiDummyInterface {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
rx[0..2].copy_from_slice(&self.dummy_val_0.to_be_bytes());
|
||||||
|
rx[2..4].copy_from_slice(&self.dummy_val_1.to_be_bytes());
|
||||||
|
rx[4..6].copy_from_slice(&self.dummy_val_2.to_be_bytes());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MgmData {
|
||||||
|
pub valid: bool,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MpscModeLeafInterface {
|
||||||
|
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||||
|
pub reply_tx_to_pus: mpsc::Sender<GenericMessage<ModeReply>>,
|
||||||
|
pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
|
||||||
|
#[derive(new)]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender> {
|
||||||
|
id: UniqueApidTargetId,
|
||||||
|
dev_str: &'static str,
|
||||||
|
mode_interface: MpscModeLeafInterface,
|
||||||
|
composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
|
hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
|
||||||
|
tm_sender: TmSender,
|
||||||
|
com_interface: ComInterface,
|
||||||
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
|
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
|
||||||
|
mode_and_submode: ModeAndSubmode,
|
||||||
|
#[new(default)]
|
||||||
|
tx_buf: [u8; 12],
|
||||||
|
#[new(default)]
|
||||||
|
rx_buf: [u8; 12],
|
||||||
|
#[new(default)]
|
||||||
|
tm_buf: [u8; 16],
|
||||||
|
#[new(default)]
|
||||||
|
stamp_helper: TimeStampHelper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.stamp_helper.update_from_now();
|
||||||
|
// Handle requests.
|
||||||
|
self.handle_composite_requests();
|
||||||
|
self.handle_mode_requests();
|
||||||
|
if self.mode() == DeviceMode::Normal as u32 {
|
||||||
|
log::trace!("polling LIS3MDL sensor {}", self.dev_str);
|
||||||
|
// Communicate with the device.
|
||||||
|
let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
// Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
|
||||||
|
let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap());
|
||||||
|
let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap());
|
||||||
|
let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap());
|
||||||
|
// Simple scaling to retrieve the float value, assuming a sensor resolution of
|
||||||
|
let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
|
||||||
|
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
|
||||||
|
drop(mgm_guard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_composite_requests(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.composite_request_receiver.try_recv() {
|
||||||
|
Ok(ref msg) => match &msg.message {
|
||||||
|
CompositeRequest::Hk(hk_request) => {
|
||||||
|
self.handle_hk_request(&msg.requestor_info, hk_request)
|
||||||
|
}
|
||||||
|
// TODO: This object does not have actions (yet).. Still send back completion failure
|
||||||
|
// reply.
|
||||||
|
CompositeRequest::Action(_action_req) => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
if e != mpsc::TryRecvError::Empty {
|
||||||
|
log::warn!(
|
||||||
|
"{}: failed to receive composite request: {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
|
||||||
|
match hk_request.variant {
|
||||||
|
HkRequestVariant::OneShot => {
|
||||||
|
self.hk_reply_sender
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
*requestor_info,
|
||||||
|
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
|
||||||
|
))
|
||||||
|
.expect("failed to send HK reply");
|
||||||
|
let sec_header = PusTmSecondaryHeader::new(
|
||||||
|
3,
|
||||||
|
hk::Subservice::TmHkPacket as u8,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.stamp_helper.stamp(),
|
||||||
|
);
|
||||||
|
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
|
||||||
|
// Use binary serialization here. We want the data to be tightly packed.
|
||||||
|
self.tm_buf[0] = mgm_snapshot.valid as u8;
|
||||||
|
self.tm_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
|
||||||
|
self.tm_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
|
||||||
|
self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
|
||||||
|
let hk_tm = PusTmCreator::new(
|
||||||
|
SpHeader::new_from_apid(self.id.apid),
|
||||||
|
sec_header,
|
||||||
|
&self.tm_buf[0..12],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.tm_sender
|
||||||
|
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
|
||||||
|
.expect("failed to send HK TM");
|
||||||
|
}
|
||||||
|
HkRequestVariant::EnablePeriodic => todo!(),
|
||||||
|
HkRequestVariant::DisablePeriodic => todo!(),
|
||||||
|
HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_mode_requests(&mut self) {
|
||||||
|
loop {
|
||||||
|
// TODO: Only allow one set mode request per cycle?
|
||||||
|
match self.mode_interface.request_rx.try_recv() {
|
||||||
|
Ok(msg) => {
|
||||||
|
let result = self.handle_mode_request(msg);
|
||||||
|
// TODO: Trigger event?
|
||||||
|
if result.is_err() {
|
||||||
|
log::warn!(
|
||||||
|
"{}: mode request failed with error {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
result.err().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e != mpsc::TryRecvError::Empty {
|
||||||
|
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeProvider
|
||||||
|
for MgmHandlerLis3Mdl<ComInterface, TmSender>
|
||||||
|
{
|
||||||
|
fn mode_and_submode(&self) -> ModeAndSubmode {
|
||||||
|
self.mode_and_submode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
|
||||||
|
for MgmHandlerLis3Mdl<ComInterface, TmSender>
|
||||||
|
{
|
||||||
|
type Error = ModeError;
|
||||||
|
fn start_transition(
|
||||||
|
&mut self,
|
||||||
|
requestor: MessageMetadata,
|
||||||
|
mode_and_submode: ModeAndSubmode,
|
||||||
|
) -> Result<(), satrs::mode::ModeError> {
|
||||||
|
log::info!(
|
||||||
|
"{}: transitioning to mode {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
mode_and_submode
|
||||||
|
);
|
||||||
|
self.mode_and_submode = mode_and_submode;
|
||||||
|
self.handle_mode_reached(Some(requestor))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
|
||||||
|
log::info!(
|
||||||
|
"{} announcing mode: {:?}",
|
||||||
|
self.dev_str,
|
||||||
|
self.mode_and_submode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mode_reached(
|
||||||
|
&mut self,
|
||||||
|
requestor: Option<MessageMetadata>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.announce_mode(requestor, false);
|
||||||
|
if let Some(requestor) = requestor {
|
||||||
|
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||||
|
log::warn!(
|
||||||
|
"can not send back mode reply to sender {}",
|
||||||
|
requestor.sender_id()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_mode_reply(
|
||||||
|
&self,
|
||||||
|
requestor: MessageMetadata,
|
||||||
|
reply: ModeReply,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
|
||||||
|
log::warn!(
|
||||||
|
"can not send back mode reply to sender {}",
|
||||||
|
requestor.sender_id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.mode_interface
|
||||||
|
.reply_tx_to_pus
|
||||||
|
.send(GenericMessage::new(requestor, reply))
|
||||||
|
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mode_info(
|
||||||
|
&mut self,
|
||||||
|
_requestor_info: MessageMetadata,
|
||||||
|
_info: ModeAndSubmode,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
1
satrs-example/src/acs/mod.rs
Normal file
1
satrs-example/src/acs/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod mgm;
|
@ -1,7 +1,7 @@
|
|||||||
use satrs_core::pus::verification::RequestId;
|
use satrs::pus::verification::RequestId;
|
||||||
use satrs_core::spacepackets::ecss::tc::PusTcCreator;
|
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
||||||
use satrs_core::spacepackets::ecss::tm::PusTmReader;
|
use satrs::spacepackets::ecss::tm::PusTmReader;
|
||||||
use satrs_core::{
|
use satrs::{
|
||||||
spacepackets::ecss::{PusPacket, WritablePusPacket},
|
spacepackets::ecss::{PusPacket, WritablePusPacket},
|
||||||
spacepackets::SpHeader,
|
spacepackets::SpHeader,
|
||||||
};
|
};
|
||||||
@ -12,8 +12,7 @@ use std::time::Duration;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut buf = [0; 32];
|
let mut buf = [0; 32];
|
||||||
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
|
let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
|
||||||
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
|
|
||||||
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
|
||||||
let tc_req_id = RequestId::new(&pus_tc);
|
let tc_req_id = RequestId::new(&pus_tc);
|
||||||
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
|
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
use satrs_core::pus::ReceivesEcssPusTc;
|
|
||||||
use satrs_core::spacepackets::{CcsdsPacket, SpHeader};
|
|
||||||
use satrs_core::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
|
|
||||||
use satrs_example::config::PUS_APID;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CcsdsReceiver<
|
|
||||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone,
|
|
||||||
E,
|
|
||||||
> {
|
|
||||||
pub tc_source: TcSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
|
|
||||||
E: 'static,
|
|
||||||
> CcsdsPacketHandler for CcsdsReceiver<TcSource, E>
|
|
||||||
{
|
|
||||||
type Error = E;
|
|
||||||
|
|
||||||
fn valid_apids(&self) -> &'static [u16] {
|
|
||||||
&[PUS_APID]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_known_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
if sp_header.apid() == PUS_APID {
|
|
||||||
return self.tc_source.pass_ccsds(sp_header, tc_raw);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_unknown_apid(
|
|
||||||
&mut self,
|
|
||||||
sp_header: &SpHeader,
|
|
||||||
_tc_raw: &[u8],
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
println!("Unknown APID 0x{:x?} detected", sp_header.apid());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +1,19 @@
|
|||||||
use satrs_core::res_code::ResultU16;
|
use lazy_static::lazy_static;
|
||||||
|
use satrs::{
|
||||||
|
res_code::ResultU16,
|
||||||
|
spacepackets::{PacketId, PacketType},
|
||||||
|
};
|
||||||
use satrs_mib::res_code::ResultU16Info;
|
use satrs_mib::res_code::ResultU16Info;
|
||||||
use satrs_mib::resultcode;
|
use satrs_mib::resultcode;
|
||||||
use std::net::Ipv4Addr;
|
use std::{collections::HashSet, net::Ipv4Addr};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use satrs_core::{
|
use satrs::{
|
||||||
events::{EventU32TypedSev, SeverityInfo},
|
events::{EventU32TypedSev, SeverityInfo},
|
||||||
pool::{StaticMemoryPool, StaticPoolConfig},
|
pool::{StaticMemoryPool, StaticPoolConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PUS_APID: u16 = 0x02;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum CustomPusServiceId {
|
pub enum CustomPusServiceId {
|
||||||
@ -29,6 +32,7 @@ pub const AOCS_APID: u16 = 1;
|
|||||||
pub enum GroupId {
|
pub enum GroupId {
|
||||||
Tmtc = 0,
|
Tmtc = 0,
|
||||||
Hk = 1,
|
Hk = 1,
|
||||||
|
Mode = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
||||||
@ -37,29 +41,55 @@ pub const SERVER_PORT: u16 = 7301;
|
|||||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||||
EventU32TypedSev::<SeverityInfo>::const_new(0, 0);
|
EventU32TypedSev::<SeverityInfo>::const_new(0, 0);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
for id in components::Apid::iter() {
|
||||||
|
set.insert(PacketId::new(PacketType::Tc, true, id as u16));
|
||||||
|
}
|
||||||
|
set
|
||||||
|
};
|
||||||
|
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
for id in components::Apid::iter() {
|
||||||
|
set.insert(id as u16);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub mod tmtc_err {
|
pub mod tmtc_err {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const INVALID_PUS_SERVICE: ResultU16 = ResultU16::const_new(GroupId::Tmtc as u8, 0);
|
pub const INVALID_PUS_SERVICE: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 0);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const INVALID_PUS_SUBSERVICE: ResultU16 = ResultU16::const_new(GroupId::Tmtc as u8, 1);
|
pub const INVALID_PUS_SUBSERVICE: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 1);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const PUS_SERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::const_new(GroupId::Tmtc as u8, 2);
|
pub const PUS_SERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::const_new(GroupId::Tmtc as u8, 3);
|
pub const PUS_SUBSERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 3);
|
||||||
|
#[resultcode]
|
||||||
|
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 4);
|
||||||
|
#[resultcode]
|
||||||
|
pub const ROUTING_ERROR: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 5);
|
||||||
|
#[resultcode(info = "Request timeout for targeted PUS request. P1: Request ID. P2: Target ID")]
|
||||||
|
pub const REQUEST_TIMEOUT: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 6);
|
||||||
|
|
||||||
#[resultcode(
|
#[resultcode(
|
||||||
info = "Not enough data inside the TC application data field. Optionally includes: \
|
info = "Not enough data inside the TC application data field. Optionally includes: \
|
||||||
8 bytes of failure data containing 2 failure parameters, \
|
8 bytes of failure data containing 2 failure parameters, \
|
||||||
P1 (u32 big endian): Expected data length, P2: Found data length"
|
P1 (u32 big endian): Expected data length, P2: Found data length"
|
||||||
)]
|
)]
|
||||||
pub const NOT_ENOUGH_APP_DATA: ResultU16 = ResultU16::const_new(GroupId::Tmtc as u8, 2);
|
pub const NOT_ENOUGH_APP_DATA: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2);
|
||||||
|
|
||||||
pub const TMTC_RESULTS: &[ResultU16Info] = &[
|
pub const TMTC_RESULTS: &[ResultU16Info] = &[
|
||||||
INVALID_PUS_SERVICE_EXT,
|
INVALID_PUS_SERVICE_EXT,
|
||||||
INVALID_PUS_SUBSERVICE_EXT,
|
INVALID_PUS_SUBSERVICE_EXT,
|
||||||
|
PUS_SERVICE_NOT_IMPLEMENTED_EXT,
|
||||||
|
UNKNOWN_TARGET_ID_EXT,
|
||||||
|
ROUTING_ERROR_EXT,
|
||||||
NOT_ENOUGH_APP_DATA_EXT,
|
NOT_ENOUGH_APP_DATA_EXT,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -69,36 +99,86 @@ pub mod hk_err {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const TARGET_ID_MISSING: ResultU16 = ResultU16::const_new(GroupId::Hk as u8, 0);
|
pub const TARGET_ID_MISSING: ResultU16 = ResultU16::new(GroupId::Hk as u8, 0);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const UNIQUE_ID_MISSING: ResultU16 = ResultU16::const_new(GroupId::Hk as u8, 1);
|
pub const UNIQUE_ID_MISSING: ResultU16 = ResultU16::new(GroupId::Hk as u8, 1);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::const_new(GroupId::Hk as u8, 2);
|
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Hk as u8, 2);
|
||||||
#[resultcode]
|
#[resultcode]
|
||||||
pub const COLLECTION_INTERVAL_MISSING: ResultU16 = ResultU16::const_new(GroupId::Hk as u8, 3);
|
pub const COLLECTION_INTERVAL_MISSING: ResultU16 = ResultU16::new(GroupId::Hk as u8, 3);
|
||||||
|
|
||||||
|
pub const HK_ERR_RESULTS: &[ResultU16Info] = &[
|
||||||
|
TARGET_ID_MISSING_EXT,
|
||||||
|
UNKNOWN_TARGET_ID_EXT,
|
||||||
|
UNKNOWN_TARGET_ID_EXT,
|
||||||
|
COLLECTION_INTERVAL_MISSING_EXT,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
pub mod mode_err {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
use super::*;
|
||||||
pub enum TmSenderId {
|
|
||||||
PusVerification = 0,
|
#[resultcode]
|
||||||
PusTest = 1,
|
pub const WRONG_MODE: ResultU16 = ResultU16::new(GroupId::Mode as u8, 0);
|
||||||
PusEvent = 2,
|
|
||||||
PusHk = 3,
|
|
||||||
PusAction = 4,
|
|
||||||
PusSched = 5,
|
|
||||||
AllEvents = 6,
|
|
||||||
AcsSubsystem = 7,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
pub mod components {
|
||||||
pub enum TcReceiverId {
|
use satrs::request::UniqueApidTargetId;
|
||||||
PusTest = 1,
|
use strum::EnumIter;
|
||||||
PusEvent = 2,
|
|
||||||
PusHk = 3,
|
#[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
|
||||||
PusAction = 4,
|
pub enum Apid {
|
||||||
PusSched = 5,
|
Sched = 1,
|
||||||
|
GenericPus = 2,
|
||||||
|
Acs = 3,
|
||||||
|
Cfdp = 4,
|
||||||
|
Tmtc = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component IDs for components with the PUS APID.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum PusId {
|
||||||
|
PusEventManagement = 0,
|
||||||
|
PusRouting = 1,
|
||||||
|
PusTest = 2,
|
||||||
|
PusAction = 3,
|
||||||
|
PusMode = 4,
|
||||||
|
PusHk = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum AcsId {
|
||||||
|
Mgm0 = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TmtcId {
|
||||||
|
UdpServer = 0,
|
||||||
|
TcpServer = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
|
||||||
|
pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
|
||||||
|
pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
|
||||||
|
pub const PUS_TEST_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
|
||||||
|
pub const PUS_MODE_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
|
||||||
|
pub const PUS_HK_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
|
||||||
|
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::Sched as u16, 0);
|
||||||
|
pub const MGM_HANDLER_0: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
|
||||||
|
pub const UDP_SERVER: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
|
||||||
|
pub const TCP_SERVER: UniqueApidTargetId =
|
||||||
|
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod pool {
|
pub mod pool {
|
||||||
use super::*;
|
use super::*;
|
||||||
pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) {
|
pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) {
|
||||||
|
@ -1,68 +1,87 @@
|
|||||||
use std::sync::mpsc::{self, SendError};
|
use std::sync::mpsc::{self};
|
||||||
|
|
||||||
use satrs_core::{
|
use crate::pus::create_verification_reporter;
|
||||||
|
use satrs::event_man::{EventMessageU32, EventRoutingError};
|
||||||
|
use satrs::params::WritableToBeBytes;
|
||||||
|
use satrs::pus::event::EventTmHookProvider;
|
||||||
|
use satrs::pus::verification::VerificationReporter;
|
||||||
|
use satrs::pus::EcssTmSender;
|
||||||
|
use satrs::request::UniqueApidTargetId;
|
||||||
|
use satrs::{
|
||||||
event_man::{
|
event_man::{
|
||||||
EventManager, EventManagerWithMpscQueue, MpscEventReceiver, MpscEventU32SendProvider,
|
EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded,
|
||||||
SendEventProvider,
|
MpscEventReceiver,
|
||||||
},
|
},
|
||||||
events::EventU32,
|
|
||||||
params::Params,
|
|
||||||
pus::{
|
pus::{
|
||||||
event_man::{
|
event_man::{
|
||||||
DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken,
|
DefaultPusEventU32Dispatcher, EventReporter, EventRequest, EventRequestWithToken,
|
||||||
PusEventDispatcher,
|
|
||||||
},
|
},
|
||||||
verification::{TcStateStarted, VerificationReporterWithSender, VerificationToken},
|
verification::{TcStateStarted, VerificationReportingProvider, VerificationToken},
|
||||||
EcssTmSender,
|
|
||||||
},
|
},
|
||||||
spacepackets::time::cds::{self, TimeProvider},
|
spacepackets::time::cds::CdsTime,
|
||||||
};
|
};
|
||||||
use satrs_example::config::PUS_APID;
|
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
|
||||||
|
|
||||||
use crate::update_time;
|
use crate::update_time;
|
||||||
|
|
||||||
pub type MpscEventManager = EventManager<SendError<(EventU32, Option<Params>)>>;
|
// This helper sets the APID of the event sender for the PUS telemetry.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct PusEventHandler {
|
pub struct EventApidSetter {
|
||||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
pub next_apid: u16,
|
||||||
pus_event_dispatcher: PusEventDispatcher<(), EventU32>,
|
|
||||||
pus_event_man_rx: mpsc::Receiver<(EventU32, Option<Params>)>,
|
|
||||||
tm_sender: Box<dyn EcssTmSender>,
|
|
||||||
time_provider: TimeProvider,
|
|
||||||
timestamp: [u8; 7],
|
|
||||||
verif_handler: VerificationReporterWithSender,
|
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
*/
|
|
||||||
|
|
||||||
impl PusEventHandler {
|
impl EventTmHookProvider for EventApidSetter {
|
||||||
|
fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
|
||||||
|
tm.set_apid(self.next_apid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
|
||||||
|
/// packets. It also handles the verification completion of PUS event service requests.
|
||||||
|
pub struct PusEventHandler<TmSender: EcssTmSender> {
|
||||||
|
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||||
|
pus_event_dispatcher: DefaultPusEventU32Dispatcher<()>,
|
||||||
|
pus_event_man_rx: mpsc::Receiver<EventMessageU32>,
|
||||||
|
tm_sender: TmSender,
|
||||||
|
time_provider: CdsTime,
|
||||||
|
timestamp: [u8; 7],
|
||||||
|
verif_handler: VerificationReporter,
|
||||||
|
event_apid_setter: EventApidSetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
verif_handler: VerificationReporterWithSender,
|
tm_sender: TmSender,
|
||||||
event_manager: &mut MpscEventManager,
|
verif_handler: VerificationReporter,
|
||||||
|
event_manager: &mut EventManagerWithBoundedMpsc,
|
||||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||||
tm_sender: impl EcssTmSender,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (pus_event_man_tx, pus_event_man_rx) = mpsc::channel();
|
let event_queue_cap = 30;
|
||||||
|
let (pus_event_man_tx, pus_event_man_rx) = mpsc::sync_channel(event_queue_cap);
|
||||||
|
|
||||||
// All events sent to the manager are routed to the PUS event manager, which generates PUS event
|
// All events sent to the manager are routed to the PUS event manager, which generates PUS event
|
||||||
// telemetry for each event.
|
// telemetry for each event.
|
||||||
let event_reporter = EventReporter::new(PUS_APID, 128).unwrap();
|
let event_reporter = EventReporter::new(PUS_EVENT_MANAGEMENT.raw(), 0, 0, 128).unwrap();
|
||||||
let pus_tm_backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
|
|
||||||
let pus_event_dispatcher =
|
let pus_event_dispatcher =
|
||||||
PusEventDispatcher::new(event_reporter, Box::new(pus_tm_backend));
|
DefaultPusEventU32Dispatcher::new_with_default_backend(event_reporter);
|
||||||
let pus_event_man_send_provider = MpscEventU32SendProvider::new(1, pus_event_man_tx);
|
let pus_event_man_send_provider = EventU32SenderMpscBounded::new(
|
||||||
|
PUS_EVENT_MANAGEMENT.raw(),
|
||||||
|
pus_event_man_tx,
|
||||||
|
event_queue_cap,
|
||||||
|
);
|
||||||
|
|
||||||
event_manager.subscribe_all(pus_event_man_send_provider.id());
|
event_manager.subscribe_all(pus_event_man_send_provider.target_id());
|
||||||
event_manager.add_sender(pus_event_man_send_provider);
|
event_manager.add_sender(pus_event_man_send_provider);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
event_request_rx,
|
event_request_rx,
|
||||||
pus_event_dispatcher,
|
pus_event_dispatcher,
|
||||||
pus_event_man_rx,
|
pus_event_man_rx,
|
||||||
time_provider: cds::TimeProvider::new_with_u16_days(0, 0),
|
time_provider: CdsTime::new_with_u16_days(0, 0),
|
||||||
timestamp: [0; 7],
|
timestamp: [0; 7],
|
||||||
verif_handler,
|
verif_handler,
|
||||||
tm_sender: Box::new(tm_sender),
|
tm_sender,
|
||||||
|
event_apid_setter: EventApidSetter::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +92,7 @@ impl PusEventHandler {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.expect("expected start verification token");
|
.expect("expected start verification token");
|
||||||
self.verif_handler
|
self.verif_handler
|
||||||
.completion_success(started_token, Some(timestamp))
|
.completion_success(&self.tm_sender, started_token, timestamp)
|
||||||
.expect("Sending completion success failed");
|
.expect("Sending completion success failed");
|
||||||
};
|
};
|
||||||
// handle event requests
|
// handle event requests
|
||||||
@ -99,23 +118,29 @@ impl PusEventHandler {
|
|||||||
|
|
||||||
pub fn generate_pus_event_tm(&mut self) {
|
pub fn generate_pus_event_tm(&mut self) {
|
||||||
// Perform the generation of PUS event packets
|
// Perform the generation of PUS event packets
|
||||||
if let Ok((event, _param)) = self.pus_event_man_rx.try_recv() {
|
if let Ok(event_msg) = self.pus_event_man_rx.try_recv() {
|
||||||
update_time(&mut self.time_provider, &mut self.timestamp);
|
update_time(&mut self.time_provider, &mut self.timestamp);
|
||||||
|
let param_vec = event_msg.params().map_or(Vec::new(), |param| {
|
||||||
|
param.to_vec().expect("failed to convert params to vec")
|
||||||
|
});
|
||||||
|
self.event_apid_setter.next_apid = UniqueApidTargetId::from(event_msg.sender_id()).apid;
|
||||||
self.pus_event_dispatcher
|
self.pus_event_dispatcher
|
||||||
.generate_pus_event_tm_generic(
|
.generate_pus_event_tm_generic(
|
||||||
self.tm_sender.upcast_mut(),
|
&self.tm_sender,
|
||||||
&self.timestamp,
|
&self.timestamp,
|
||||||
event,
|
event_msg.event(),
|
||||||
None,
|
Some(¶m_vec),
|
||||||
)
|
)
|
||||||
.expect("Sending TM as event failed");
|
.expect("Sending TM as event failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is a thin wrapper around the event manager which also caches the sender component
|
||||||
|
/// used to send events to the event manager.
|
||||||
pub struct EventManagerWrapper {
|
pub struct EventManagerWrapper {
|
||||||
event_manager: MpscEventManager,
|
event_manager: EventManagerWithBoundedMpsc,
|
||||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
event_sender: mpsc::Sender<EventMessageU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventManagerWrapper {
|
impl EventManagerWrapper {
|
||||||
@ -123,46 +148,51 @@ impl EventManagerWrapper {
|
|||||||
// The sender handle is the primary sender handle for all components which want to create events.
|
// The sender handle is the primary sender handle for all components which want to create events.
|
||||||
// The event manager will receive the RX handle to receive all the events.
|
// The event manager will receive the RX handle to receive all the events.
|
||||||
let (event_sender, event_man_rx) = mpsc::channel();
|
let (event_sender, event_man_rx) = mpsc::channel();
|
||||||
let event_recv = MpscEventReceiver::<EventU32>::new(event_man_rx);
|
let event_recv = MpscEventReceiver::new(event_man_rx);
|
||||||
Self {
|
Self {
|
||||||
event_manager: EventManagerWithMpscQueue::new(Box::new(event_recv)),
|
event_manager: EventManagerWithBoundedMpsc::new(event_recv),
|
||||||
event_sender,
|
event_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_event_sender(&self) -> mpsc::Sender<(EventU32, Option<Params>)> {
|
// Returns a cached event sender to send events to the event manager for routing.
|
||||||
|
pub fn clone_event_sender(&self) -> mpsc::Sender<EventMessageU32> {
|
||||||
self.event_sender.clone()
|
self.event_sender.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_manager(&mut self) -> &mut MpscEventManager {
|
pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
|
||||||
&mut self.event_manager
|
&mut self.event_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_event_routing(&mut self) {
|
pub fn try_event_routing(&mut self) {
|
||||||
|
let error_handler = |event_msg: &EventMessageU32, error: EventRoutingError| {
|
||||||
|
self.routing_error_handler(event_msg, error)
|
||||||
|
};
|
||||||
// Perform the event routing.
|
// Perform the event routing.
|
||||||
self.event_manager
|
self.event_manager.try_event_handling(error_handler);
|
||||||
.try_event_handling()
|
}
|
||||||
.expect("event handling failed");
|
|
||||||
|
pub fn routing_error_handler(&self, event_msg: &EventMessageU32, error: EventRoutingError) {
|
||||||
|
log::warn!("event routing error for event {event_msg:?}: {error:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler<TmSender: EcssTmSender> {
|
||||||
pub event_man_wrapper: EventManagerWrapper,
|
pub event_man_wrapper: EventManagerWrapper,
|
||||||
pub pus_event_handler: PusEventHandler,
|
pub pus_event_handler: PusEventHandler<TmSender>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl<TmSender: EcssTmSender> EventHandler<TmSender> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
tm_sender: impl EcssTmSender,
|
tm_sender: TmSender,
|
||||||
verif_handler: VerificationReporterWithSender,
|
|
||||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut event_man_wrapper = EventManagerWrapper::new();
|
let mut event_man_wrapper = EventManagerWrapper::new();
|
||||||
let pus_event_handler = PusEventHandler::new(
|
let pus_event_handler = PusEventHandler::new(
|
||||||
verif_handler,
|
tm_sender,
|
||||||
|
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||||
event_man_wrapper.event_manager(),
|
event_man_wrapper.event_manager(),
|
||||||
event_request_rx,
|
event_request_rx,
|
||||||
tm_sender,
|
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
event_man_wrapper,
|
event_man_wrapper,
|
||||||
@ -170,12 +200,12 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_event_sender(&self) -> mpsc::Sender<(EventU32, Option<Params>)> {
|
pub fn clone_event_sender(&self) -> mpsc::Sender<EventMessageU32> {
|
||||||
self.event_man_wrapper.clone_event_sender()
|
self.event_man_wrapper.clone_event_sender()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn event_manager(&mut self) -> &mut MpscEventManager {
|
pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
|
||||||
self.event_man_wrapper.event_manager()
|
self.event_man_wrapper.event_manager()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use satrs_core::spacepackets::ByteConversionError;
|
use satrs::hk::UniqueId;
|
||||||
|
use satrs::request::UniqueApidTargetId;
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
use satrs::spacepackets::ByteConversionError;
|
||||||
pub enum AcsHkIds {
|
|
||||||
TestMgmSet = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, new, Copy, Clone)]
|
#[derive(Debug, new, Copy, Clone)]
|
||||||
pub struct HkUniqueId {
|
pub struct HkUniqueId {
|
||||||
target_id: u32,
|
target_id: UniqueApidTargetId,
|
||||||
set_id: u32,
|
set_id: UniqueId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HkUniqueId {
|
impl HkUniqueId {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn target_id(&self) -> u32 {
|
pub fn target_id(&self) -> UniqueApidTargetId {
|
||||||
self.target_id
|
self.target_id
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_id(&self) -> u32 {
|
pub fn set_id(&self) -> UniqueId {
|
||||||
self.set_id
|
self.set_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||||
if buf.len() < 8 {
|
if buf.len() < 8 {
|
||||||
return Err(ByteConversionError::ToSliceTooSmall {
|
return Err(ByteConversionError::ToSliceTooSmall {
|
||||||
@ -29,7 +27,7 @@ impl HkUniqueId {
|
|||||||
expected: 8,
|
expected: 8,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
buf[0..4].copy_from_slice(&self.target_id.to_be_bytes());
|
buf[0..4].copy_from_slice(&self.target_id.unique_id.to_be_bytes());
|
||||||
buf[4..8].copy_from_slice(&self.set_id.to_be_bytes());
|
buf[4..8].copy_from_slice(&self.set_id.to_be_bytes());
|
||||||
|
|
||||||
Ok(8)
|
Ok(8)
|
||||||
|
3
satrs-example/src/interface/mod.rs
Normal file
3
satrs-example/src/interface/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! This module contains all component related to the direct interface of the example.
|
||||||
|
pub mod tcp;
|
||||||
|
pub mod udp;
|
151
satrs-example/src/interface/tcp.rs
Normal file
151
satrs-example/src/interface/tcp.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashSet, VecDeque},
|
||||||
|
fmt::Debug,
|
||||||
|
marker::PhantomData,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{info, warn};
|
||||||
|
use satrs::{
|
||||||
|
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
||||||
|
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
|
||||||
|
spacepackets::{CcsdsPacket, PacketId},
|
||||||
|
tmtc::{PacketSenderRaw, PacketSource},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ConnectionFinishedHandler {}
|
||||||
|
|
||||||
|
pub struct SimplePacketValidator {
|
||||||
|
pub valid_ids: HashSet<PacketId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpacePacketValidator for SimplePacketValidator {
|
||||||
|
fn validate(
|
||||||
|
&self,
|
||||||
|
sp_header: &satrs::spacepackets::SpHeader,
|
||||||
|
_raw_buf: &[u8],
|
||||||
|
) -> satrs::encoding::ccsds::SpValidity {
|
||||||
|
if self.valid_ids.contains(&sp_header.packet_id()) {
|
||||||
|
return SpValidity::Valid;
|
||||||
|
}
|
||||||
|
log::warn!("ignoring space packet with header {:?}", sp_header);
|
||||||
|
// We could perform a CRC check.. but lets keep this simple and assume that TCP ensures
|
||||||
|
// data integrity.
|
||||||
|
SpValidity::Skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandledConnectionHandler for ConnectionFinishedHandler {
|
||||||
|
fn handled_connection(&mut self, info: satrs::hal::std::tcp_server::HandledConnectionInfo) {
|
||||||
|
info!(
|
||||||
|
"Served {} TMs and {} TCs for client {:?}",
|
||||||
|
info.num_sent_tms, info.num_received_tcs, info.addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct SyncTcpTmSource {
|
||||||
|
tm_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
||||||
|
max_packets_stored: usize,
|
||||||
|
pub silent_packet_overwrite: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncTcpTmSource {
|
||||||
|
pub fn new(max_packets_stored: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
tm_queue: Arc::default(),
|
||||||
|
max_packets_stored,
|
||||||
|
silent_packet_overwrite: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tm(&mut self, tm: &[u8]) {
|
||||||
|
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec");
|
||||||
|
if tm_queue.len() > self.max_packets_stored {
|
||||||
|
if !self.silent_packet_overwrite {
|
||||||
|
warn!("TPC TM source is full, deleting oldest packet");
|
||||||
|
}
|
||||||
|
tm_queue.pop_front();
|
||||||
|
}
|
||||||
|
tm_queue.push_back(tm.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketSource for SyncTcpTmSource {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed");
|
||||||
|
if !tm_queue.is_empty() {
|
||||||
|
let next_vec = tm_queue.front().unwrap();
|
||||||
|
if buffer.len() < next_vec.len() {
|
||||||
|
panic!(
|
||||||
|
"provided buffer too small, must be at least {} bytes",
|
||||||
|
next_vec.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let next_vec = tm_queue.pop_front().unwrap();
|
||||||
|
buffer[0..next_vec.len()].copy_from_slice(&next_vec);
|
||||||
|
if next_vec.len() > 9 {
|
||||||
|
let service = next_vec[7];
|
||||||
|
let subservice = next_vec[8];
|
||||||
|
info!("Sending PUS TM[{service},{subservice}]")
|
||||||
|
} else {
|
||||||
|
info!("Sending PUS TM");
|
||||||
|
}
|
||||||
|
return Ok(next_vec.len());
|
||||||
|
}
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
|
||||||
|
SyncTcpTmSource,
|
||||||
|
ReceivesTc,
|
||||||
|
SimplePacketValidator,
|
||||||
|
ConnectionFinishedHandler,
|
||||||
|
(),
|
||||||
|
SendError,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
|
||||||
|
pub TcpServer<TcSender, SendError>,
|
||||||
|
PhantomData<SendError>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
|
||||||
|
TcpTask<TcSender, SendError>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
cfg: ServerConfig,
|
||||||
|
tm_source: SyncTcpTmSource,
|
||||||
|
tc_sender: TcSender,
|
||||||
|
valid_ids: HashSet<PacketId>,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
Ok(Self(
|
||||||
|
TcpSpacepacketsServer::new(
|
||||||
|
cfg,
|
||||||
|
tm_source,
|
||||||
|
tc_sender,
|
||||||
|
SimplePacketValidator { valid_ids },
|
||||||
|
ConnectionFinishedHandler::default(),
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
PhantomData,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
loop {
|
||||||
|
let result = self.0.handle_all_connections(None);
|
||||||
|
match result {
|
||||||
|
Ok(_conn_result) => (),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("TCP server error: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,35 @@
|
|||||||
use std::{
|
use core::fmt::Debug;
|
||||||
net::{SocketAddr, UdpSocket},
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
sync::mpsc::Receiver,
|
use std::sync::mpsc;
|
||||||
};
|
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use satrs_core::{
|
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
|
||||||
|
use satrs::{
|
||||||
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
||||||
pool::{PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr},
|
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
|
||||||
tmtc::CcsdsError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::pus::HandlingStatus;
|
||||||
|
|
||||||
pub trait UdpTmHandler {
|
pub trait UdpTmHandler {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StaticUdpTmHandler {
|
pub struct StaticUdpTmHandler {
|
||||||
pub tm_rx: Receiver<StoreAddr>,
|
pub tm_rx: mpsc::Receiver<PacketInPool>,
|
||||||
pub tm_store: SharedStaticMemoryPool,
|
pub tm_store: SharedStaticMemoryPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpTmHandler for StaticUdpTmHandler {
|
impl UdpTmHandler for StaticUdpTmHandler {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, &recv_addr: &SocketAddr) {
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, &recv_addr: &SocketAddr) {
|
||||||
while let Ok(addr) = self.tm_rx.try_recv() {
|
while let Ok(pus_tm_in_pool) = self.tm_rx.try_recv() {
|
||||||
let store_lock = self.tm_store.write();
|
let store_lock = self.tm_store.write();
|
||||||
if store_lock.is_err() {
|
if store_lock.is_err() {
|
||||||
warn!("Locking TM store failed");
|
warn!("Locking TM store failed");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut store_lock = store_lock.unwrap();
|
let mut store_lock = store_lock.unwrap();
|
||||||
let pg = store_lock.read_with_guard(addr);
|
let pg = store_lock.read_with_guard(pus_tm_in_pool.store_addr);
|
||||||
let read_res = pg.read_as_vec();
|
let read_res = pg.read_as_vec();
|
||||||
if read_res.is_err() {
|
if read_res.is_err() {
|
||||||
warn!("Error reading TM pool data");
|
warn!("Error reading TM pool data");
|
||||||
@ -44,20 +45,20 @@ impl UdpTmHandler for StaticUdpTmHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DynamicUdpTmHandler {
|
pub struct DynamicUdpTmHandler {
|
||||||
pub tm_rx: Receiver<Vec<u8>>,
|
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpTmHandler for DynamicUdpTmHandler {
|
impl UdpTmHandler for DynamicUdpTmHandler {
|
||||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
|
||||||
while let Ok(tm) = self.tm_rx.try_recv() {
|
while let Ok(tm) = self.tm_rx.try_recv() {
|
||||||
if tm.len() > 9 {
|
if tm.packet.len() > 9 {
|
||||||
let service = tm[7];
|
let service = tm.packet[7];
|
||||||
let subservice = tm[8];
|
let subservice = tm.packet[8];
|
||||||
info!("Sending PUS TM[{service},{subservice}]")
|
info!("Sending PUS TM[{service},{subservice}]")
|
||||||
} else {
|
} else {
|
||||||
info!("Sending PUS TM");
|
info!("Sending PUS TM");
|
||||||
}
|
}
|
||||||
let result = socket.send_to(&tm, recv_addr);
|
let result = socket.send_to(&tm.packet, recv_addr);
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
warn!("Sending TM with UDP socket failed: {e}")
|
warn!("Sending TM with UDP socket failed: {e}")
|
||||||
}
|
}
|
||||||
@ -65,42 +66,48 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler, SendError> {
|
pub struct UdpTmtcServer<
|
||||||
pub udp_tc_server: UdpTcServer<CcsdsError<SendError>>,
|
TcSender: PacketSenderRaw<Error = SendError>,
|
||||||
|
TmHandler: UdpTmHandler,
|
||||||
|
SendError,
|
||||||
|
> {
|
||||||
|
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
|
||||||
pub tm_handler: TmHandler,
|
pub tm_handler: TmHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TmHandler: UdpTmHandler, SendError: core::fmt::Debug + 'static>
|
impl<
|
||||||
UdpTmtcServer<TmHandler, SendError>
|
TcSender: PacketSenderRaw<Error = SendError>,
|
||||||
|
TmHandler: UdpTmHandler,
|
||||||
|
SendError: Debug + 'static,
|
||||||
|
> UdpTmtcServer<TcSender, TmHandler, SendError>
|
||||||
{
|
{
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
while self.poll_tc_server() {}
|
loop {
|
||||||
|
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(recv_addr) = self.udp_tc_server.last_sender() {
|
if let Some(recv_addr) = self.udp_tc_server.last_sender() {
|
||||||
self.tm_handler
|
self.tm_handler
|
||||||
.send_tm_to_udp_client(&self.udp_tc_server.socket, &recv_addr);
|
.send_tm_to_udp_client(&self.udp_tc_server.socket, &recv_addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_tc_server(&mut self) -> bool {
|
fn poll_tc_server(&mut self) -> HandlingStatus {
|
||||||
match self.udp_tc_server.try_recv_tc() {
|
match self.udp_tc_server.try_recv_tc() {
|
||||||
Ok(_) => true,
|
Ok(_) => HandlingStatus::HandledOne,
|
||||||
Err(e) => match e {
|
Err(e) => {
|
||||||
ReceiveResult::ReceiverError(e) => match e {
|
match e {
|
||||||
CcsdsError::ByteConversionError(e) => {
|
ReceiveResult::NothingReceived => (),
|
||||||
warn!("packet error: {e:?}");
|
ReceiveResult::Io(e) => {
|
||||||
true
|
warn!("IO error {e}");
|
||||||
}
|
}
|
||||||
CcsdsError::CustomError(e) => {
|
ReceiveResult::Send(send_error) => {
|
||||||
warn!("mpsc custom error {e:?}");
|
warn!("send error {send_error:?}");
|
||||||
true
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
ReceiveResult::IoError(e) => {
|
|
||||||
warn!("IO error {e}");
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
ReceiveResult::NothingReceived => false,
|
HandlingStatus::Empty
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,31 +115,37 @@ impl<TmHandler: UdpTmHandler, SendError: core::fmt::Debug + 'static>
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use satrs_core::{
|
use satrs::{
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||||
SpHeader,
|
SpHeader,
|
||||||
},
|
},
|
||||||
tmtc::ReceivesTcCore,
|
tmtc::PacketSenderRaw,
|
||||||
|
ComponentId,
|
||||||
};
|
};
|
||||||
use satrs_example::config::{OBSW_SERVER_ADDR, PUS_APID};
|
use satrs_example::config::{components, OBSW_SERVER_ADDR};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
const UDP_SERVER_ID: ComponentId = 0x05;
|
||||||
pub struct TestReceiver {
|
|
||||||
tc_vec: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
#[derive(Default, Debug)]
|
||||||
|
pub struct TestSender {
|
||||||
|
tc_vec: RefCell<VecDeque<PacketAsVec>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReceivesTcCore for TestReceiver {
|
impl PacketSenderRaw for TestSender {
|
||||||
type Error = CcsdsError<()>;
|
type Error = ();
|
||||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
self.tc_vec.lock().unwrap().push_back(tc_raw.to_vec());
|
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let mut mut_queue = self.tc_vec.borrow_mut();
|
||||||
|
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,9 +164,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||||
let test_receiver = TestReceiver::default();
|
let test_receiver = TestSender::default();
|
||||||
let tc_queue = test_receiver.tc_vec.clone();
|
// let tc_queue = test_receiver.tc_vec.clone();
|
||||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
|
let udp_tc_server =
|
||||||
|
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||||
let tm_handler = TestTmHandler::default();
|
let tm_handler = TestTmHandler::default();
|
||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
let mut udp_dyn_server = UdpTmtcServer {
|
let mut udp_dyn_server = UdpTmtcServer {
|
||||||
@ -161,16 +175,18 @@ mod tests {
|
|||||||
tm_handler,
|
tm_handler,
|
||||||
};
|
};
|
||||||
udp_dyn_server.periodic_operation();
|
udp_dyn_server.periodic_operation();
|
||||||
assert!(tc_queue.lock().unwrap().is_empty());
|
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
|
||||||
|
assert!(queue.is_empty());
|
||||||
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transactions() {
|
fn test_transactions() {
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||||
let test_receiver = TestReceiver::default();
|
let test_receiver = TestSender::default();
|
||||||
let tc_queue = test_receiver.tc_vec.clone();
|
// let tc_queue = test_receiver.tc_vec.clone();
|
||||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
|
let udp_tc_server =
|
||||||
|
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
|
||||||
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
||||||
let tm_handler = TestTmHandler::default();
|
let tm_handler = TestTmHandler::default();
|
||||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||||
@ -178,8 +194,8 @@ mod tests {
|
|||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler,
|
tm_handler,
|
||||||
};
|
};
|
||||||
let mut sph = SpHeader::tc_unseg(PUS_APID, 0, 0).unwrap();
|
let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
|
||||||
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true)
|
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
|
||||||
.to_vec()
|
.to_vec()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client = UdpSocket::bind("127.0.0.1:0").expect("Connecting to UDP server failed");
|
let client = UdpSocket::bind("127.0.0.1:0").expect("Connecting to UDP server failed");
|
||||||
@ -188,10 +204,11 @@ mod tests {
|
|||||||
client.send(&ping_tc).unwrap();
|
client.send(&ping_tc).unwrap();
|
||||||
udp_dyn_server.periodic_operation();
|
udp_dyn_server.periodic_operation();
|
||||||
{
|
{
|
||||||
let mut tc_queue = tc_queue.lock().unwrap();
|
let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut();
|
||||||
assert!(!tc_queue.is_empty());
|
assert!(!queue.is_empty());
|
||||||
let received_tc = tc_queue.pop_front().unwrap();
|
let packet_with_sender = queue.pop_front().unwrap();
|
||||||
assert_eq!(received_tc, ping_tc);
|
assert_eq!(packet_with_sender.packet, ping_tc);
|
||||||
|
assert_eq!(packet_with_sender.sender_id, UDP_SERVER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -202,7 +219,9 @@ mod tests {
|
|||||||
assert_eq!(received_addr, client_addr);
|
assert_eq!(received_addr, client_addr);
|
||||||
}
|
}
|
||||||
udp_dyn_server.periodic_operation();
|
udp_dyn_server.periodic_operation();
|
||||||
assert!(tc_queue.lock().unwrap().is_empty());
|
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
drop(queue);
|
||||||
// Still tries to send to the same client.
|
// Still tries to send to the same client.
|
||||||
{
|
{
|
||||||
let mut tm_handler_calls = tm_handler_calls.lock().unwrap();
|
let mut tm_handler_calls = tm_handler_calls.lock().unwrap();
|
@ -1,59 +1,39 @@
|
|||||||
use derive_new::new;
|
use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
|
||||||
use satrs_core::spacepackets::ecss::tc::IsPusTelecommand;
|
|
||||||
use satrs_core::spacepackets::ecss::PusPacket;
|
|
||||||
use satrs_core::spacepackets::{ByteConversionError, CcsdsPacket};
|
|
||||||
use satrs_core::tmtc::TargetId;
|
|
||||||
use std::fmt;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
pub type Apid = u16;
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum DeviceMode {
|
||||||
#[derive(Debug, Error)]
|
Off = 0,
|
||||||
pub enum TargetIdCreationError {
|
On = 1,
|
||||||
#[error("byte conversion")]
|
Normal = 2,
|
||||||
ByteConversion(#[from] ByteConversionError),
|
|
||||||
#[error("not enough app data to generate target ID")]
|
|
||||||
NotEnoughAppData(usize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: can these stay pub?
|
pub struct TimeStampHelper {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, new)]
|
stamper: CdsTime,
|
||||||
pub struct TargetIdWithApid {
|
time_stamp: [u8; 7],
|
||||||
pub apid: Apid,
|
|
||||||
pub target: TargetId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TargetIdWithApid {
|
impl TimeStampHelper {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
pub fn stamp(&self) -> &[u8] {
|
||||||
write!(f, "{}, {}", self.apid, self.target)
|
&self.time_stamp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_from_now(&mut self) {
|
||||||
|
self.stamper
|
||||||
|
.update_from_now()
|
||||||
|
.expect("Updating timestamp failed");
|
||||||
|
self.stamper
|
||||||
|
.write_to_bytes(&mut self.time_stamp)
|
||||||
|
.expect("Writing timestamp failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetIdWithApid {
|
impl Default for TimeStampHelper {
|
||||||
pub fn apid(&self) -> Apid {
|
fn default() -> Self {
|
||||||
self.apid
|
Self {
|
||||||
}
|
stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"),
|
||||||
pub fn target_id(&self) -> TargetId {
|
time_stamp: Default::default(),
|
||||||
self.target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TargetIdWithApid {
|
|
||||||
pub fn from_tc(
|
|
||||||
tc: &(impl CcsdsPacket + PusPacket + IsPusTelecommand),
|
|
||||||
) -> Result<Self, TargetIdCreationError> {
|
|
||||||
if tc.user_data().len() < 4 {
|
|
||||||
return Err(ByteConversionError::FromSliceTooSmall {
|
|
||||||
found: tc.user_data().len(),
|
|
||||||
expected: 8,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
Ok(Self {
|
|
||||||
apid: tc.apid(),
|
|
||||||
target: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,99 +1,81 @@
|
|||||||
mod acs;
|
mod acs;
|
||||||
mod ccsds;
|
|
||||||
mod events;
|
mod events;
|
||||||
mod hk;
|
mod hk;
|
||||||
|
mod interface;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod pus;
|
mod pus;
|
||||||
mod requests;
|
mod requests;
|
||||||
mod tcp;
|
|
||||||
mod tm_funnel;
|
|
||||||
mod tmtc;
|
mod tmtc;
|
||||||
mod udp;
|
|
||||||
|
|
||||||
use crate::events::EventHandler;
|
use crate::events::EventHandler;
|
||||||
|
use crate::interface::udp::DynamicUdpTmHandler;
|
||||||
use crate::pus::stack::PusStack;
|
use crate::pus::stack::PusStack;
|
||||||
use crate::tm_funnel::{TmFunnelDynamic, TmFunnelStatic};
|
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
|
||||||
|
use crate::tmtc::tm_sink::{TmFunnelDynamic, TmFunnelStatic};
|
||||||
use log::info;
|
use log::info;
|
||||||
use pus::test::create_test_service_dynamic;
|
use pus::test::create_test_service_dynamic;
|
||||||
use satrs_core::hal::std::tcp_server::ServerConfig;
|
use satrs::hal::std::tcp_server::ServerConfig;
|
||||||
use satrs_core::hal::std::udp_server::UdpTcServer;
|
use satrs::hal::std::udp_server::UdpTcServer;
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
use satrs::request::GenericMessage;
|
||||||
|
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
||||||
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
|
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
|
||||||
use satrs_example::config::tasks::{
|
use satrs_example::config::tasks::{
|
||||||
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
|
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
|
||||||
};
|
};
|
||||||
use satrs_example::config::{RequestTargetId, TmSenderId, OBSW_SERVER_ADDR, PUS_APID, SERVER_PORT};
|
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
|
||||||
use tmtc::PusTcSourceProviderDynamic;
|
|
||||||
use udp::DynamicUdpTmHandler;
|
|
||||||
|
|
||||||
use crate::acs::AcsTask;
|
use crate::acs::mgm::{MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface};
|
||||||
use crate::ccsds::CcsdsReceiver;
|
use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
|
||||||
|
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
|
||||||
use crate::logger::setup_logger;
|
use crate::logger::setup_logger;
|
||||||
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
|
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
|
||||||
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
|
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
|
||||||
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
|
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
|
||||||
|
use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static};
|
||||||
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
|
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
|
||||||
use crate::pus::test::create_test_service_static;
|
use crate::pus::test::create_test_service_static;
|
||||||
use crate::pus::{PusReceiver, PusTcMpscRouter};
|
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
|
||||||
use crate::requests::RequestWithToken;
|
use crate::requests::{CompositeRequest, GenericRequestRouter};
|
||||||
use crate::tcp::{SyncTcpTmSource, TcpTask};
|
use satrs::mode::ModeRequest;
|
||||||
use crate::tmtc::{
|
use satrs::pus::event_man::EventRequestWithToken;
|
||||||
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
|
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
|
||||||
};
|
use satrs_example::config::components::{MGM_HANDLER_0, TCP_SERVER, UDP_SERVER};
|
||||||
use crate::udp::{StaticUdpTmHandler, UdpTmtcServer};
|
|
||||||
use satrs_core::pus::event_man::EventRequestWithToken;
|
|
||||||
use satrs_core::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
|
|
||||||
use satrs_core::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInSharedPoolSender};
|
|
||||||
use satrs_core::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
|
|
||||||
use satrs_core::tmtc::{CcsdsDistributor, TargetId};
|
|
||||||
use satrs_core::ChannelId;
|
|
||||||
use satrs_example::TargetIdWithApid;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::sync::mpsc::{self, channel};
|
use std::sync::mpsc;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
fn create_verification_reporter(verif_sender: impl EcssTmSender) -> VerificationReporterWithSender {
|
|
||||||
let verif_cfg = VerificationReporterCfg::new(PUS_APID, 1, 2, 8).unwrap();
|
|
||||||
// Every software component which needs to generate verification telemetry, gets a cloned
|
|
||||||
// verification reporter.
|
|
||||||
VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn static_tmtc_pool_main() {
|
fn static_tmtc_pool_main() {
|
||||||
let (tm_pool, tc_pool) = create_static_pools();
|
let (tm_pool, tc_pool) = create_static_pools();
|
||||||
let shared_tm_pool = SharedTmPool::new(tm_pool);
|
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
|
||||||
let shared_tc_pool = SharedTcPool {
|
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
|
||||||
pool: Arc::new(RwLock::new(tc_pool)),
|
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
|
||||||
};
|
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
|
||||||
let (tc_source_tx, tc_source_rx) = channel();
|
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||||
let (tm_funnel_tx, tm_funnel_rx) = channel();
|
let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(50);
|
||||||
let (tm_server_tx, tm_server_rx) = channel();
|
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
||||||
|
|
||||||
// Every software component which needs to generate verification telemetry, receives a cloned
|
let tm_funnel_tx_sender =
|
||||||
// verification reporter.
|
PacketSenderWithSharedPool::new(tm_funnel_tx.clone(), shared_tm_pool_wrapper.clone());
|
||||||
let verif_reporter = create_verification_reporter(MpscTmInSharedPoolSender::new(
|
|
||||||
TmSenderId::PusVerification as ChannelId,
|
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
|
||||||
"verif_sender",
|
mpsc::channel::<GenericMessage<CompositeRequest>>();
|
||||||
shared_tm_pool.clone(),
|
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let acs_target_id = TargetIdWithApid::new(PUS_APID, RequestTargetId::AcsSubsystem as TargetId);
|
|
||||||
let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
|
|
||||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||||
let mut request_map = HashMap::new();
|
let mut request_map = GenericRequestRouter::default();
|
||||||
request_map.insert(acs_target_id, acs_thread_tx);
|
request_map
|
||||||
|
.composite_router_map
|
||||||
|
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
|
||||||
|
request_map
|
||||||
|
.mode_router_map
|
||||||
|
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
|
||||||
|
|
||||||
// This helper structure is used by all telecommand providers which need to send telecommands
|
// This helper structure is used by all telecommand providers which need to send telecommands
|
||||||
// to the TC source.
|
// to the TC source.
|
||||||
let tc_source = PusTcSourceProviderSharedPool {
|
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
|
||||||
shared_pool: shared_tc_pool.clone(),
|
|
||||||
tc_source: tc_source_tx,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create event handling components
|
// Create event handling components
|
||||||
// These sender handles are used to send event requests, for example to enable or disable
|
// These sender handles are used to send event requests, for example to enable or disable
|
||||||
@ -102,127 +84,139 @@ fn static_tmtc_pool_main() {
|
|||||||
|
|
||||||
// The event task is the core handler to perform the event routing and TM handling as specified
|
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||||
// in the sat-rs documentation.
|
// in the sat-rs documentation.
|
||||||
let mut event_handler = EventHandler::new(
|
let mut event_handler = EventHandler::new(tm_funnel_tx.clone(), event_request_rx);
|
||||||
MpscTmInSharedPoolSender::new(
|
|
||||||
TmSenderId::AllEvents as ChannelId,
|
let (pus_test_tx, pus_test_rx) = mpsc::channel();
|
||||||
"ALL_EVENTS_TX",
|
let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
||||||
shared_tm_pool.clone(),
|
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
||||||
tm_funnel_tx.clone(),
|
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
||||||
),
|
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||||
verif_reporter.clone(),
|
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
||||||
event_request_rx,
|
|
||||||
);
|
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||||
|
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
|
||||||
|
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
|
||||||
|
|
||||||
let (pus_test_tx, pus_test_rx) = channel();
|
|
||||||
let (pus_event_tx, pus_event_rx) = channel();
|
|
||||||
let (pus_sched_tx, pus_sched_rx) = channel();
|
|
||||||
let (pus_hk_tx, pus_hk_rx) = channel();
|
|
||||||
let (pus_action_tx, pus_action_rx) = channel();
|
|
||||||
let pus_router = PusTcMpscRouter {
|
let pus_router = PusTcMpscRouter {
|
||||||
test_service_receiver: pus_test_tx,
|
test_tc_sender: pus_test_tx,
|
||||||
event_service_receiver: pus_event_tx,
|
event_tc_sender: pus_event_tx,
|
||||||
sched_service_receiver: pus_sched_tx,
|
sched_tc_sender: pus_sched_tx,
|
||||||
hk_service_receiver: pus_hk_tx,
|
hk_tc_sender: pus_hk_tx,
|
||||||
action_service_receiver: pus_action_tx,
|
action_tc_sender: pus_action_tx,
|
||||||
|
mode_tc_sender: pus_mode_tx,
|
||||||
};
|
};
|
||||||
let pus_test_service = create_test_service_static(
|
let pus_test_service = create_test_service_static(
|
||||||
shared_tm_pool.clone(),
|
tm_funnel_tx_sender.clone(),
|
||||||
tm_funnel_tx.clone(),
|
shared_tc_pool.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
shared_tc_pool.pool.clone(),
|
|
||||||
event_handler.clone_event_sender(),
|
event_handler.clone_event_sender(),
|
||||||
pus_test_rx,
|
pus_test_rx,
|
||||||
);
|
);
|
||||||
let pus_scheduler_service = create_scheduler_service_static(
|
let pus_scheduler_service = create_scheduler_service_static(
|
||||||
shared_tm_pool.clone(),
|
tm_funnel_tx_sender.clone(),
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
verif_reporter.clone(),
|
|
||||||
tc_source.clone(),
|
tc_source.clone(),
|
||||||
pus_sched_rx,
|
pus_sched_rx,
|
||||||
create_sched_tc_pool(),
|
create_sched_tc_pool(),
|
||||||
);
|
);
|
||||||
let pus_event_service = create_event_service_static(
|
let pus_event_service = create_event_service_static(
|
||||||
shared_tm_pool.clone(),
|
tm_funnel_tx_sender.clone(),
|
||||||
tm_funnel_tx.clone(),
|
shared_tc_pool.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
shared_tc_pool.pool.clone(),
|
|
||||||
pus_event_rx,
|
pus_event_rx,
|
||||||
event_request_tx,
|
event_request_tx,
|
||||||
);
|
);
|
||||||
let pus_action_service = create_action_service_static(
|
let pus_action_service = create_action_service_static(
|
||||||
shared_tm_pool.clone(),
|
tm_funnel_tx_sender.clone(),
|
||||||
tm_funnel_tx.clone(),
|
shared_tc_pool.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
shared_tc_pool.pool.clone(),
|
|
||||||
pus_action_rx,
|
pus_action_rx,
|
||||||
request_map.clone(),
|
request_map.clone(),
|
||||||
|
pus_action_reply_rx,
|
||||||
);
|
);
|
||||||
let pus_hk_service = create_hk_service_static(
|
let pus_hk_service = create_hk_service_static(
|
||||||
shared_tm_pool.clone(),
|
tm_funnel_tx_sender.clone(),
|
||||||
tm_funnel_tx.clone(),
|
shared_tc_pool.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
shared_tc_pool.pool.clone(),
|
|
||||||
pus_hk_rx,
|
pus_hk_rx,
|
||||||
|
request_map.clone(),
|
||||||
|
pus_hk_reply_rx,
|
||||||
|
);
|
||||||
|
let pus_mode_service = create_mode_service_static(
|
||||||
|
tm_funnel_tx_sender.clone(),
|
||||||
|
shared_tc_pool.clone(),
|
||||||
|
pus_mode_rx,
|
||||||
request_map,
|
request_map,
|
||||||
|
pus_mode_reply_rx,
|
||||||
);
|
);
|
||||||
let mut pus_stack = PusStack::new(
|
let mut pus_stack = PusStack::new(
|
||||||
|
pus_test_service,
|
||||||
pus_hk_service,
|
pus_hk_service,
|
||||||
pus_event_service,
|
pus_event_service,
|
||||||
pus_action_service,
|
pus_action_service,
|
||||||
pus_scheduler_service,
|
pus_scheduler_service,
|
||||||
pus_test_service,
|
pus_mode_service,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ccsds_receiver = CcsdsReceiver { tc_source };
|
|
||||||
let mut tmtc_task = TcSourceTaskStatic::new(
|
let mut tmtc_task = TcSourceTaskStatic::new(
|
||||||
shared_tc_pool.clone(),
|
shared_tc_pool_wrapper.clone(),
|
||||||
tc_source_rx,
|
tc_source_rx,
|
||||||
PusReceiver::new(verif_reporter.clone(), pus_router),
|
PusTcDistributor::new(tm_funnel_tx_sender, pus_router),
|
||||||
);
|
);
|
||||||
|
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
let udp_ccsds_distributor = CcsdsDistributor::new(Box::new(ccsds_receiver.clone()));
|
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
|
||||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(udp_ccsds_distributor))
|
|
||||||
.expect("creating UDP TMTC server failed");
|
.expect("creating UDP TMTC server failed");
|
||||||
let mut udp_tmtc_server = UdpTmtcServer {
|
let mut udp_tmtc_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
tm_handler: StaticUdpTmHandler {
|
tm_handler: StaticUdpTmHandler {
|
||||||
tm_rx: tm_server_rx,
|
tm_rx: tm_server_rx,
|
||||||
tm_store: shared_tm_pool.clone_backing_pool(),
|
tm_store: shared_tm_pool.clone(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let tcp_ccsds_distributor = CcsdsDistributor::new(Box::new(ccsds_receiver));
|
let tcp_server_cfg = ServerConfig::new(
|
||||||
let tcp_server_cfg = ServerConfig::new(sock_addr, Duration::from_millis(400), 4096, 8192);
|
TCP_SERVER.id(),
|
||||||
|
sock_addr,
|
||||||
|
Duration::from_millis(400),
|
||||||
|
4096,
|
||||||
|
8192,
|
||||||
|
);
|
||||||
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
||||||
let mut tcp_server = TcpTask::new(
|
let mut tcp_server = TcpTask::new(
|
||||||
tcp_server_cfg,
|
tcp_server_cfg,
|
||||||
sync_tm_tcp_source.clone(),
|
sync_tm_tcp_source.clone(),
|
||||||
tcp_ccsds_distributor,
|
tc_source.clone(),
|
||||||
|
PACKET_ID_VALIDATOR.clone(),
|
||||||
)
|
)
|
||||||
.expect("tcp server creation failed");
|
.expect("tcp server creation failed");
|
||||||
|
|
||||||
let mut acs_task = AcsTask::new(
|
|
||||||
MpscTmInSharedPoolSender::new(
|
|
||||||
TmSenderId::AcsSubsystem as ChannelId,
|
|
||||||
"ACS_TASK_SENDER",
|
|
||||||
shared_tm_pool.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
),
|
|
||||||
acs_thread_rx,
|
|
||||||
verif_reporter,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut tm_funnel = TmFunnelStatic::new(
|
let mut tm_funnel = TmFunnelStatic::new(
|
||||||
shared_tm_pool,
|
shared_tm_pool_wrapper,
|
||||||
sync_tm_tcp_source,
|
sync_tm_tcp_source,
|
||||||
tm_funnel_rx,
|
tm_funnel_rx,
|
||||||
tm_server_tx,
|
tm_server_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
|
||||||
|
mpsc::channel();
|
||||||
|
|
||||||
|
let dummy_spi_interface = SpiDummyInterface::default();
|
||||||
|
let shared_mgm_set = Arc::default();
|
||||||
|
let mode_leaf_interface = MpscModeLeafInterface {
|
||||||
|
request_rx: mgm_handler_mode_rx,
|
||||||
|
reply_tx_to_pus: pus_mode_reply_tx,
|
||||||
|
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
|
||||||
|
};
|
||||||
|
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
||||||
|
MGM_HANDLER_0,
|
||||||
|
"MGM_0",
|
||||||
|
mode_leaf_interface,
|
||||||
|
mgm_handler_composite_rx,
|
||||||
|
pus_hk_reply_tx,
|
||||||
|
tm_funnel_tx,
|
||||||
|
dummy_spi_interface,
|
||||||
|
shared_mgm_set,
|
||||||
|
);
|
||||||
|
|
||||||
info!("Starting TMTC and UDP task");
|
info!("Starting TMTC and UDP task");
|
||||||
let jh_udp_tmtc = thread::Builder::new()
|
let jh_udp_tmtc = thread::Builder::new()
|
||||||
.name("TMTC and UDP".to_string())
|
.name("SATRS tmtc-udp".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running UDP server on port {SERVER_PORT}");
|
info!("Running UDP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
@ -235,7 +229,7 @@ fn static_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting TCP task");
|
info!("Starting TCP task");
|
||||||
let jh_tcp = thread::Builder::new()
|
let jh_tcp = thread::Builder::new()
|
||||||
.name("TCP".to_string())
|
.name("sat-rs tcp".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running TCP server on port {SERVER_PORT}");
|
info!("Running TCP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
@ -254,7 +248,7 @@ fn static_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting event handling task");
|
info!("Starting event handling task");
|
||||||
let jh_event_handling = thread::Builder::new()
|
let jh_event_handling = thread::Builder::new()
|
||||||
.name("Event".to_string())
|
.name("sat-rs events".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
event_handler.periodic_operation();
|
event_handler.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
||||||
@ -263,16 +257,16 @@ fn static_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting AOCS thread");
|
info!("Starting AOCS thread");
|
||||||
let jh_aocs = thread::Builder::new()
|
let jh_aocs = thread::Builder::new()
|
||||||
.name("AOCS".to_string())
|
.name("sat-rs aocs".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
acs_task.periodic_operation();
|
mgm_handler.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting PUS handler thread");
|
info!("Starting PUS handler thread");
|
||||||
let jh_pus_handler = thread::Builder::new()
|
let jh_pus_handler = thread::Builder::new()
|
||||||
.name("PUS".to_string())
|
.name("sat-rs pus".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
pus_stack.periodic_operation();
|
pus_stack.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||||
@ -299,24 +293,23 @@ fn static_tmtc_pool_main() {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn dyn_tmtc_pool_main() {
|
fn dyn_tmtc_pool_main() {
|
||||||
let (tc_source_tx, tc_source_rx) = channel();
|
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||||
let (tm_funnel_tx, tm_funnel_rx) = channel();
|
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
||||||
let (tm_server_tx, tm_server_rx) = channel();
|
let (tm_server_tx, tm_server_rx) = mpsc::channel();
|
||||||
// Every software component which needs to generate verification telemetry, gets a cloned
|
|
||||||
// verification reporter.
|
|
||||||
let verif_reporter = create_verification_reporter(MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::PusVerification as ChannelId,
|
|
||||||
"verif_sender",
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let acs_target_id = TargetIdWithApid::new(PUS_APID, RequestTargetId::AcsSubsystem as TargetId);
|
|
||||||
let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
|
|
||||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||||
let mut request_map = HashMap::new();
|
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
|
||||||
request_map.insert(acs_target_id, acs_thread_tx);
|
mpsc::channel::<GenericMessage<CompositeRequest>>();
|
||||||
|
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
|
||||||
|
|
||||||
let tc_source = PusTcSourceProviderDynamic(tc_source_tx);
|
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||||
|
let mut request_map = GenericRequestRouter::default();
|
||||||
|
request_map
|
||||||
|
.composite_router_map
|
||||||
|
.insert(MGM_HANDLER_0.raw(), mgm_handler_composite_tx);
|
||||||
|
request_map
|
||||||
|
.mode_router_map
|
||||||
|
.insert(MGM_HANDLER_0.raw(), mgm_handler_mode_tx);
|
||||||
|
|
||||||
// Create event handling components
|
// Create event handling components
|
||||||
// These sender handles are used to send event requests, for example to enable or disable
|
// These sender handles are used to send event requests, for example to enable or disable
|
||||||
@ -324,79 +317,76 @@ fn dyn_tmtc_pool_main() {
|
|||||||
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
|
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
|
||||||
// The event task is the core handler to perform the event routing and TM handling as specified
|
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||||
// in the sat-rs documentation.
|
// in the sat-rs documentation.
|
||||||
let mut event_handler = EventHandler::new(
|
let mut event_handler = EventHandler::new(tm_funnel_tx.clone(), event_request_rx);
|
||||||
MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::AllEvents as ChannelId,
|
let (pus_test_tx, pus_test_rx) = mpsc::channel();
|
||||||
"ALL_EVENTS_TX",
|
let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
||||||
tm_funnel_tx.clone(),
|
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
||||||
),
|
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
||||||
verif_reporter.clone(),
|
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||||
event_request_rx,
|
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
||||||
);
|
|
||||||
|
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||||
|
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
|
||||||
|
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
|
||||||
|
|
||||||
let (pus_test_tx, pus_test_rx) = channel();
|
|
||||||
let (pus_event_tx, pus_event_rx) = channel();
|
|
||||||
let (pus_sched_tx, pus_sched_rx) = channel();
|
|
||||||
let (pus_hk_tx, pus_hk_rx) = channel();
|
|
||||||
let (pus_action_tx, pus_action_rx) = channel();
|
|
||||||
let pus_router = PusTcMpscRouter {
|
let pus_router = PusTcMpscRouter {
|
||||||
test_service_receiver: pus_test_tx,
|
test_tc_sender: pus_test_tx,
|
||||||
event_service_receiver: pus_event_tx,
|
event_tc_sender: pus_event_tx,
|
||||||
sched_service_receiver: pus_sched_tx,
|
sched_tc_sender: pus_sched_tx,
|
||||||
hk_service_receiver: pus_hk_tx,
|
hk_tc_sender: pus_hk_tx,
|
||||||
action_service_receiver: pus_action_tx,
|
action_tc_sender: pus_action_tx,
|
||||||
|
mode_tc_sender: pus_mode_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
let pus_test_service = create_test_service_dynamic(
|
let pus_test_service = create_test_service_dynamic(
|
||||||
tm_funnel_tx.clone(),
|
tm_funnel_tx.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
event_handler.clone_event_sender(),
|
event_handler.clone_event_sender(),
|
||||||
pus_test_rx,
|
pus_test_rx,
|
||||||
);
|
);
|
||||||
let pus_scheduler_service = create_scheduler_service_dynamic(
|
let pus_scheduler_service = create_scheduler_service_dynamic(
|
||||||
tm_funnel_tx.clone(),
|
tm_funnel_tx.clone(),
|
||||||
verif_reporter.clone(),
|
tc_source_tx.clone(),
|
||||||
tc_source.0.clone(),
|
|
||||||
pus_sched_rx,
|
pus_sched_rx,
|
||||||
create_sched_tc_pool(),
|
create_sched_tc_pool(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let pus_event_service = create_event_service_dynamic(
|
let pus_event_service =
|
||||||
tm_funnel_tx.clone(),
|
create_event_service_dynamic(tm_funnel_tx.clone(), pus_event_rx, event_request_tx);
|
||||||
verif_reporter.clone(),
|
|
||||||
pus_event_rx,
|
|
||||||
event_request_tx,
|
|
||||||
);
|
|
||||||
let pus_action_service = create_action_service_dynamic(
|
let pus_action_service = create_action_service_dynamic(
|
||||||
tm_funnel_tx.clone(),
|
tm_funnel_tx.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
pus_action_rx,
|
pus_action_rx,
|
||||||
request_map.clone(),
|
request_map.clone(),
|
||||||
|
pus_action_reply_rx,
|
||||||
);
|
);
|
||||||
let pus_hk_service = create_hk_service_dynamic(
|
let pus_hk_service = create_hk_service_dynamic(
|
||||||
tm_funnel_tx.clone(),
|
tm_funnel_tx.clone(),
|
||||||
verif_reporter.clone(),
|
|
||||||
pus_hk_rx,
|
pus_hk_rx,
|
||||||
|
request_map.clone(),
|
||||||
|
pus_hk_reply_rx,
|
||||||
|
);
|
||||||
|
let pus_mode_service = create_mode_service_dynamic(
|
||||||
|
tm_funnel_tx.clone(),
|
||||||
|
pus_mode_rx,
|
||||||
request_map,
|
request_map,
|
||||||
|
pus_mode_reply_rx,
|
||||||
);
|
);
|
||||||
let mut pus_stack = PusStack::new(
|
let mut pus_stack = PusStack::new(
|
||||||
|
pus_test_service,
|
||||||
pus_hk_service,
|
pus_hk_service,
|
||||||
pus_event_service,
|
pus_event_service,
|
||||||
pus_action_service,
|
pus_action_service,
|
||||||
pus_scheduler_service,
|
pus_scheduler_service,
|
||||||
pus_test_service,
|
pus_mode_service,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ccsds_receiver = CcsdsReceiver { tc_source };
|
|
||||||
|
|
||||||
let mut tmtc_task = TcSourceTaskDynamic::new(
|
let mut tmtc_task = TcSourceTaskDynamic::new(
|
||||||
tc_source_rx,
|
tc_source_rx,
|
||||||
PusReceiver::new(verif_reporter.clone(), pus_router),
|
PusTcDistributor::new(tm_funnel_tx.clone(), pus_router),
|
||||||
);
|
);
|
||||||
|
|
||||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||||
let udp_ccsds_distributor = CcsdsDistributor::new(Box::new(ccsds_receiver.clone()));
|
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source_tx.clone())
|
||||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(udp_ccsds_distributor))
|
|
||||||
.expect("creating UDP TMTC server failed");
|
.expect("creating UDP TMTC server failed");
|
||||||
let mut udp_tmtc_server = UdpTmtcServer {
|
let mut udp_tmtc_server = UdpTmtcServer {
|
||||||
udp_tc_server,
|
udp_tc_server,
|
||||||
@ -405,30 +395,47 @@ fn dyn_tmtc_pool_main() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let tcp_ccsds_distributor = CcsdsDistributor::new(Box::new(ccsds_receiver));
|
let tcp_server_cfg = ServerConfig::new(
|
||||||
let tcp_server_cfg = ServerConfig::new(sock_addr, Duration::from_millis(400), 4096, 8192);
|
TCP_SERVER.id(),
|
||||||
|
sock_addr,
|
||||||
|
Duration::from_millis(400),
|
||||||
|
4096,
|
||||||
|
8192,
|
||||||
|
);
|
||||||
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
||||||
let mut tcp_server = TcpTask::new(
|
let mut tcp_server = TcpTask::new(
|
||||||
tcp_server_cfg,
|
tcp_server_cfg,
|
||||||
sync_tm_tcp_source.clone(),
|
sync_tm_tcp_source.clone(),
|
||||||
tcp_ccsds_distributor,
|
tc_source_tx.clone(),
|
||||||
|
PACKET_ID_VALIDATOR.clone(),
|
||||||
)
|
)
|
||||||
.expect("tcp server creation failed");
|
.expect("tcp server creation failed");
|
||||||
|
|
||||||
let mut acs_task = AcsTask::new(
|
|
||||||
MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::AcsSubsystem as ChannelId,
|
|
||||||
"ACS_TASK_SENDER",
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
),
|
|
||||||
acs_thread_rx,
|
|
||||||
verif_reporter,
|
|
||||||
);
|
|
||||||
let mut tm_funnel = TmFunnelDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
|
let mut tm_funnel = TmFunnelDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
|
||||||
|
|
||||||
|
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
|
||||||
|
mpsc::channel();
|
||||||
|
let dummy_spi_interface = SpiDummyInterface::default();
|
||||||
|
let shared_mgm_set = Arc::default();
|
||||||
|
let mode_leaf_interface = MpscModeLeafInterface {
|
||||||
|
request_rx: mgm_handler_mode_rx,
|
||||||
|
reply_tx_to_pus: pus_mode_reply_tx,
|
||||||
|
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
|
||||||
|
};
|
||||||
|
let mut mgm_handler = MgmHandlerLis3Mdl::new(
|
||||||
|
MGM_HANDLER_0,
|
||||||
|
"MGM_0",
|
||||||
|
mode_leaf_interface,
|
||||||
|
mgm_handler_composite_rx,
|
||||||
|
pus_hk_reply_tx,
|
||||||
|
tm_funnel_tx,
|
||||||
|
dummy_spi_interface,
|
||||||
|
shared_mgm_set,
|
||||||
|
);
|
||||||
|
|
||||||
info!("Starting TMTC and UDP task");
|
info!("Starting TMTC and UDP task");
|
||||||
let jh_udp_tmtc = thread::Builder::new()
|
let jh_udp_tmtc = thread::Builder::new()
|
||||||
.name("TMTC and UDP".to_string())
|
.name("sat-rs tmtc-udp".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running UDP server on port {SERVER_PORT}");
|
info!("Running UDP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
@ -441,7 +448,7 @@ fn dyn_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting TCP task");
|
info!("Starting TCP task");
|
||||||
let jh_tcp = thread::Builder::new()
|
let jh_tcp = thread::Builder::new()
|
||||||
.name("TCP".to_string())
|
.name("sat-rs tcp".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Running TCP server on port {SERVER_PORT}");
|
info!("Running TCP server on port {SERVER_PORT}");
|
||||||
loop {
|
loop {
|
||||||
@ -452,7 +459,7 @@ fn dyn_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting TM funnel task");
|
info!("Starting TM funnel task");
|
||||||
let jh_tm_funnel = thread::Builder::new()
|
let jh_tm_funnel = thread::Builder::new()
|
||||||
.name("TM Funnel".to_string())
|
.name("sat-rs tm-funnel".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
tm_funnel.operation();
|
tm_funnel.operation();
|
||||||
})
|
})
|
||||||
@ -460,7 +467,7 @@ fn dyn_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting event handling task");
|
info!("Starting event handling task");
|
||||||
let jh_event_handling = thread::Builder::new()
|
let jh_event_handling = thread::Builder::new()
|
||||||
.name("Event".to_string())
|
.name("sat-rs events".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
event_handler.periodic_operation();
|
event_handler.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
|
||||||
@ -469,16 +476,16 @@ fn dyn_tmtc_pool_main() {
|
|||||||
|
|
||||||
info!("Starting AOCS thread");
|
info!("Starting AOCS thread");
|
||||||
let jh_aocs = thread::Builder::new()
|
let jh_aocs = thread::Builder::new()
|
||||||
.name("AOCS".to_string())
|
.name("sat-rs aocs".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
acs_task.periodic_operation();
|
mgm_handler.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting PUS handler thread");
|
info!("Starting PUS handler thread");
|
||||||
let jh_pus_handler = thread::Builder::new()
|
let jh_pus_handler = thread::Builder::new()
|
||||||
.name("PUS".to_string())
|
.name("sat-rs pus".to_string())
|
||||||
.spawn(move || loop {
|
.spawn(move || loop {
|
||||||
pus_stack.periodic_operation();
|
pus_stack.periodic_operation();
|
||||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||||
@ -512,7 +519,7 @@ fn main() {
|
|||||||
dyn_tmtc_pool_main();
|
dyn_tmtc_pool_main();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_time(time_provider: &mut TimeProvider, timestamp: &mut [u8]) {
|
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
|
||||||
time_provider
|
time_provider
|
||||||
.update_from_now()
|
.update_from_now()
|
||||||
.expect("Could not get current time");
|
.expect("Could not get current time");
|
||||||
|
@ -1,215 +1,274 @@
|
|||||||
use crate::requests::{ActionRequest, Request, RequestWithToken};
|
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||||
use satrs_core::pus::verification::{
|
use satrs::params::WritableToBeBytes;
|
||||||
FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationToken,
|
use satrs::pool::SharedStaticMemoryPool;
|
||||||
|
use satrs::pus::action::{
|
||||||
|
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
|
||||||
};
|
};
|
||||||
use satrs_core::pus::{
|
use satrs::pus::verification::{
|
||||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
FailParams, FailParamsWithStep, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||||
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
|
VerificationReportingProvider, VerificationToken,
|
||||||
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
|
|
||||||
};
|
};
|
||||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
use satrs::pus::{
|
||||||
use satrs_core::spacepackets::ecss::PusPacket;
|
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
|
||||||
use satrs_core::ChannelId;
|
MpscTmAsVecSender, PusPacketHandlerResult, PusReplyHandler, PusServiceHelper,
|
||||||
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
|
PusTcToRequestConverter,
|
||||||
use satrs_example::TargetIdWithApid;
|
};
|
||||||
use std::collections::HashMap;
|
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||||
use std::sync::mpsc::{self, Sender};
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
|
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket};
|
||||||
|
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||||
|
use satrs_example::config::components::PUS_ACTION_SERVICE;
|
||||||
|
use satrs_example::config::tmtc_err;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn create_action_service_static(
|
use crate::requests::GenericRequestRouter;
|
||||||
shared_tm_store: SharedTmPool,
|
|
||||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
use super::{
|
||||||
verif_reporter: VerificationReporterWithSender,
|
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
||||||
tc_pool: SharedStaticMemoryPool,
|
PusTargetedRequestService, TargetedPusService,
|
||||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
};
|
||||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
|
||||||
) -> Pus8Wrapper<EcssTcInSharedStoreConverter> {
|
pub struct ActionReplyHandler {
|
||||||
let action_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
fail_data_buf: [u8; 128],
|
||||||
TmSenderId::PusAction as ChannelId,
|
|
||||||
"PUS_8_TM_SENDER",
|
|
||||||
shared_tm_store.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let action_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusAction as ChannelId,
|
|
||||||
"PUS_8_TC_RECV",
|
|
||||||
pus_action_rx,
|
|
||||||
);
|
|
||||||
let pus_8_handler = PusService8ActionHandler::new(
|
|
||||||
Box::new(action_srv_receiver),
|
|
||||||
Box::new(action_srv_tm_sender),
|
|
||||||
PUS_APID,
|
|
||||||
verif_reporter.clone(),
|
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
|
||||||
request_map.clone(),
|
|
||||||
);
|
|
||||||
Pus8Wrapper { pus_8_handler }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_action_service_dynamic(
|
impl Default for ActionReplyHandler {
|
||||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
fn default() -> Self {
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
|
||||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
|
||||||
) -> Pus8Wrapper<EcssTcInVecConverter> {
|
|
||||||
let action_srv_tm_sender = MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::PusAction as ChannelId,
|
|
||||||
"PUS_8_TM_SENDER",
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let action_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusAction as ChannelId,
|
|
||||||
"PUS_8_TC_RECV",
|
|
||||||
pus_action_rx,
|
|
||||||
);
|
|
||||||
let pus_8_handler = PusService8ActionHandler::new(
|
|
||||||
Box::new(action_srv_receiver),
|
|
||||||
Box::new(action_srv_tm_sender),
|
|
||||||
PUS_APID,
|
|
||||||
verif_reporter.clone(),
|
|
||||||
EcssTcInVecConverter::default(),
|
|
||||||
request_map.clone(),
|
|
||||||
);
|
|
||||||
Pus8Wrapper { pus_8_handler }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PusService8ActionHandler<TcInMemConverter: EcssTcInMemConverter> {
|
|
||||||
service_helper: PusServiceHelper<TcInMemConverter>,
|
|
||||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> PusService8ActionHandler<TcInMemConverter> {
|
|
||||||
pub fn new(
|
|
||||||
tc_receiver: Box<dyn EcssTcReceiver>,
|
|
||||||
tm_sender: Box<dyn EcssTmSender>,
|
|
||||||
tm_apid: u16,
|
|
||||||
verification_handler: VerificationReporterWithSender,
|
|
||||||
tc_in_mem_converter: TcInMemConverter,
|
|
||||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
service_helper: PusServiceHelper::new(
|
fail_data_buf: [0; 128],
|
||||||
tc_receiver,
|
|
||||||
tm_sender,
|
|
||||||
tm_apid,
|
|
||||||
verification_handler,
|
|
||||||
tc_in_mem_converter,
|
|
||||||
),
|
|
||||||
request_handlers,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_action_request_with_id(
|
impl PusReplyHandler<ActivePusActionRequestStd, ActionReplyPus> for ActionReplyHandler {
|
||||||
&self,
|
type Error = EcssTmtcError;
|
||||||
token: VerificationToken<TcStateAccepted>,
|
|
||||||
tc: &PusTcReader,
|
fn handle_unrequested_reply(
|
||||||
time_stamp: &[u8],
|
&mut self,
|
||||||
) -> Result<(), PusPacketHandlingError> {
|
reply: &GenericMessage<ActionReplyPus>,
|
||||||
let user_data = tc.user_data();
|
_tm_sender: &impl EcssTmSender,
|
||||||
if user_data.len() < 8 {
|
) -> Result<(), Self::Error> {
|
||||||
self.service_helper
|
warn!("received unexpected reply for service 8: {reply:?}");
|
||||||
.common
|
|
||||||
.verification_handler
|
|
||||||
.borrow_mut()
|
|
||||||
.start_failure(
|
|
||||||
token,
|
|
||||||
FailParams::new(Some(time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
|
|
||||||
)
|
|
||||||
.expect("Sending start failure failed");
|
|
||||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
|
||||||
"Expected at least 4 bytes".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
//let target_id = u32::from_be_bytes(user_data[0..4].try_into().unwrap());
|
|
||||||
let target_id = TargetIdWithApid::from_tc(tc).unwrap();
|
|
||||||
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
|
|
||||||
if let Some(sender) = self.request_handlers.get(&target_id) {
|
|
||||||
sender
|
|
||||||
.send(RequestWithToken::new(
|
|
||||||
target_id,
|
|
||||||
Request::Action(ActionRequest::CmdWithU32Id((
|
|
||||||
action_id,
|
|
||||||
Vec::from(&user_data[8..]),
|
|
||||||
))),
|
|
||||||
token,
|
|
||||||
))
|
|
||||||
.expect("Forwarding action request failed");
|
|
||||||
} else {
|
|
||||||
let mut fail_data: [u8; 4] = [0; 4];
|
|
||||||
fail_data.copy_from_slice(&target_id.target.to_be_bytes());
|
|
||||||
self.service_helper
|
|
||||||
.common
|
|
||||||
.verification_handler
|
|
||||||
.borrow_mut()
|
|
||||||
.start_failure(
|
|
||||||
token,
|
|
||||||
FailParams::new(
|
|
||||||
Some(time_stamp),
|
|
||||||
&tmtc_err::UNKNOWN_TARGET_ID,
|
|
||||||
Some(&fail_data),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.expect("Sending start failure failed");
|
|
||||||
return Err(PusPacketHandlingError::Other(format!(
|
|
||||||
"Unknown target ID {target_id}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
|
fn handle_reply(
|
||||||
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
|
&mut self,
|
||||||
if possible_packet.is_none() {
|
reply: &GenericMessage<ActionReplyPus>,
|
||||||
return Ok(PusPacketHandlerResult::Empty);
|
active_request: &ActivePusActionRequestStd,
|
||||||
}
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
let ecss_tc_and_token = possible_packet.unwrap();
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
self.service_helper
|
time_stamp: &[u8],
|
||||||
.tc_in_mem_converter
|
) -> Result<bool, Self::Error> {
|
||||||
.cache_ecss_tc_in_memory(&ecss_tc_and_token.tc_in_memory)?;
|
let verif_token: VerificationToken<TcStateStarted> = active_request
|
||||||
let tc = PusTcReader::new(self.service_helper.tc_in_mem_converter.tc_slice_raw())?.0;
|
.token()
|
||||||
let subservice = tc.subservice();
|
.try_into()
|
||||||
let mut partial_error = None;
|
.expect("invalid token state");
|
||||||
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
|
let remove_entry = match &reply.message.variant {
|
||||||
match subservice {
|
ActionReplyVariant::CompletionFailed { error_code, params } => {
|
||||||
128 => {
|
let mut fail_data_len = 0;
|
||||||
self.handle_action_request_with_id(ecss_tc_and_token.token, &tc, &time_stamp)?;
|
if let Some(params) = params {
|
||||||
|
fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?;
|
||||||
|
}
|
||||||
|
verification_handler.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
verif_token,
|
||||||
|
FailParams::new(time_stamp, error_code, &self.fail_data_buf[..fail_data_len]),
|
||||||
|
)?;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
_ => {
|
ActionReplyVariant::StepFailed {
|
||||||
let fail_data = [subservice];
|
error_code,
|
||||||
self.service_helper
|
step,
|
||||||
.common
|
params,
|
||||||
.verification_handler
|
} => {
|
||||||
.get_mut()
|
let mut fail_data_len = 0;
|
||||||
.start_failure(
|
if let Some(params) = params {
|
||||||
ecss_tc_and_token.token,
|
fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?;
|
||||||
FailParams::new(
|
}
|
||||||
Some(&time_stamp),
|
verification_handler.step_failure(
|
||||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
tm_sender,
|
||||||
Some(&fail_data),
|
verif_token,
|
||||||
),
|
FailParamsWithStep::new(
|
||||||
)
|
time_stamp,
|
||||||
.expect("Sending start failure failed");
|
&EcssEnumU16::new(*step),
|
||||||
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
|
error_code,
|
||||||
|
&self.fail_data_buf[..fail_data_len],
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
ActionReplyVariant::Completed => {
|
||||||
if let Some(partial_error) = partial_error {
|
verification_handler.completion_success(tm_sender, verif_token, time_stamp)?;
|
||||||
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
|
true
|
||||||
partial_error,
|
}
|
||||||
));
|
ActionReplyVariant::StepSuccess { step } => {
|
||||||
}
|
verification_handler.step_success(
|
||||||
Ok(PusPacketHandlerResult::RequestHandled)
|
tm_sender,
|
||||||
|
&verif_token,
|
||||||
|
time_stamp,
|
||||||
|
EcssEnumU16::new(*step),
|
||||||
|
)?;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
Ok(remove_entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request_timeout(
|
||||||
|
&mut self,
|
||||||
|
active_request: &ActivePusActionRequestStd,
|
||||||
|
tm_sender: &impl EcssTmSender,
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
generic_pus_request_timeout_handler(
|
||||||
|
tm_sender,
|
||||||
|
active_request,
|
||||||
|
verification_handler,
|
||||||
|
time_stamp,
|
||||||
|
"action",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pus8Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
#[derive(Default)]
|
||||||
pub(crate) pus_8_handler: PusService8ActionHandler<TcInMemConverter>,
|
pub struct ActionRequestConverter {}
|
||||||
|
|
||||||
|
impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for ActionRequestConverter {
|
||||||
|
type Error = GenericConversionError;
|
||||||
|
|
||||||
|
fn convert(
|
||||||
|
&mut self,
|
||||||
|
token: VerificationToken<TcStateAccepted>,
|
||||||
|
tc: &PusTcReader,
|
||||||
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(ActivePusActionRequestStd, ActionRequest), Self::Error> {
|
||||||
|
let subservice = tc.subservice();
|
||||||
|
let user_data = tc.user_data();
|
||||||
|
if user_data.len() < 8 {
|
||||||
|
verif_reporter
|
||||||
|
.start_failure(
|
||||||
|
tm_sender,
|
||||||
|
token,
|
||||||
|
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
return Err(GenericConversionError::NotEnoughAppData {
|
||||||
|
expected: 8,
|
||||||
|
found: user_data.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).unwrap();
|
||||||
|
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
|
||||||
|
if subservice == 128 {
|
||||||
|
let req_variant = if user_data.len() == 8 {
|
||||||
|
ActionRequestVariant::NoData
|
||||||
|
} else {
|
||||||
|
ActionRequestVariant::VecData(user_data[8..].to_vec())
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
ActivePusActionRequestStd::new(
|
||||||
|
action_id,
|
||||||
|
target_id_and_apid.into(),
|
||||||
|
token.into(),
|
||||||
|
Duration::from_secs(30),
|
||||||
|
),
|
||||||
|
ActionRequest::new(action_id, req_variant),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
verif_reporter
|
||||||
|
.start_failure(
|
||||||
|
tm_sender,
|
||||||
|
token,
|
||||||
|
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
Err(GenericConversionError::InvalidSubservice(subservice))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> {
|
pub fn create_action_service_static(
|
||||||
pub fn handle_next_packet(&mut self) -> bool {
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
match self.pus_8_handler.handle_one_tc() {
|
tc_pool: SharedStaticMemoryPool,
|
||||||
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
action_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||||
|
) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
|
let action_request_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_ACTION_SERVICE.id(),
|
||||||
|
pus_action_rx,
|
||||||
|
tm_sender,
|
||||||
|
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
||||||
|
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
||||||
|
),
|
||||||
|
ActionRequestConverter::default(),
|
||||||
|
// TODO: Implementation which does not use run-time allocation? Maybe something like
|
||||||
|
// a bounded wrapper which pre-allocates using [HashMap::with_capacity]..
|
||||||
|
DefaultActiveActionRequestMap::default(),
|
||||||
|
ActionReplyHandler::default(),
|
||||||
|
action_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
ActionServiceWrapper {
|
||||||
|
service: action_request_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_action_service_dynamic(
|
||||||
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
action_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||||
|
) -> ActionServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
|
let action_request_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_ACTION_SERVICE.id(),
|
||||||
|
pus_action_rx,
|
||||||
|
tm_funnel_tx,
|
||||||
|
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
||||||
|
EcssTcInVecConverter::default(),
|
||||||
|
),
|
||||||
|
ActionRequestConverter::default(),
|
||||||
|
DefaultActiveActionRequestMap::default(),
|
||||||
|
ActionReplyHandler::default(),
|
||||||
|
action_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
ActionServiceWrapper {
|
||||||
|
service: action_request_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||||
|
pub(crate) service: PusTargetedRequestService<
|
||||||
|
MpscTcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
ActionRequestConverter,
|
||||||
|
ActionReplyHandler,
|
||||||
|
DefaultActiveActionRequestMap,
|
||||||
|
ActivePusActionRequestStd,
|
||||||
|
ActionRequest,
|
||||||
|
ActionReplyPus,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
||||||
|
for ActionServiceWrapper<TmSender, TcInMemConverter>
|
||||||
|
{
|
||||||
|
/// Returns [true] if the packet handling is finished.
|
||||||
|
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
match self.service.poll_and_handle_next_tc(time_stamp) {
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
PusPacketHandlerResult::RequestHandled => {}
|
PusPacketHandlerResult::RequestHandled => {}
|
||||||
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||||
@ -221,14 +280,476 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> {
|
|||||||
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||||
warn!("PUS 8 subservice {subservice} not implemented");
|
warn!("PUS 8 subservice {subservice} not implemented");
|
||||||
}
|
}
|
||||||
PusPacketHandlerResult::Empty => {
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("PUS packet handling error: {error:?}")
|
error!("PUS packet handling error: {error:?}");
|
||||||
|
// To avoid permanent loops on error cases.
|
||||||
|
return HandlingStatus::Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
// This only fails if all senders disconnected. Treat it like an empty queue.
|
||||||
|
self.service
|
||||||
|
.poll_and_check_next_reply(time_stamp)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("PUS 8: Handling reply failed with error {e:?}");
|
||||||
|
HandlingStatus::Empty
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_request_timeouts(&mut self) {
|
||||||
|
self.service.check_for_request_timeouts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use satrs::pus::test_util::{
|
||||||
|
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
||||||
|
};
|
||||||
|
use satrs::pus::verification;
|
||||||
|
use satrs::pus::verification::test_util::TestVerificationReporter;
|
||||||
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::ComponentId;
|
||||||
|
use satrs::{
|
||||||
|
res_code::ResultU16,
|
||||||
|
spacepackets::{
|
||||||
|
ecss::{
|
||||||
|
tc::{PusTcCreator, PusTcSecondaryHeader},
|
||||||
|
tm::PusTmReader,
|
||||||
|
WritablePusPacket,
|
||||||
|
},
|
||||||
|
SpHeader,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
pus::tests::{PusConverterTestbench, ReplyHandlerTestbench, TargetedPusRequestTestbench},
|
||||||
|
requests::CompositeRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl
|
||||||
|
TargetedPusRequestTestbench<
|
||||||
|
ActionRequestConverter,
|
||||||
|
ActionReplyHandler,
|
||||||
|
DefaultActiveActionRequestMap,
|
||||||
|
ActivePusActionRequestStd,
|
||||||
|
ActionRequest,
|
||||||
|
ActionReplyPus,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
|
||||||
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
||||||
|
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||||
|
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||||
|
let (action_req_tx, action_req_rx) = mpsc::channel();
|
||||||
|
let verif_reporter = TestVerificationReporter::new(owner_id);
|
||||||
|
let mut generic_req_router = GenericRequestRouter::default();
|
||||||
|
generic_req_router
|
||||||
|
.composite_router_map
|
||||||
|
.insert(target_id, action_req_tx);
|
||||||
|
Self {
|
||||||
|
service: PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
owner_id,
|
||||||
|
pus_action_rx,
|
||||||
|
tm_funnel_tx.clone(),
|
||||||
|
verif_reporter,
|
||||||
|
EcssTcInVecConverter::default(),
|
||||||
|
),
|
||||||
|
ActionRequestConverter::default(),
|
||||||
|
DefaultActiveActionRequestMap::default(),
|
||||||
|
ActionReplyHandler::default(),
|
||||||
|
generic_req_router,
|
||||||
|
action_reply_rx,
|
||||||
|
),
|
||||||
|
request_id: None,
|
||||||
|
pus_packet_tx: pus_action_tx,
|
||||||
|
tm_funnel_rx,
|
||||||
|
reply_tx: action_reply_tx,
|
||||||
|
request_rx: action_req_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_packet_started(&self) {
|
||||||
|
self.service
|
||||||
|
.service_helper
|
||||||
|
.common
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_started_success(
|
||||||
|
self.service.service_helper.id(),
|
||||||
|
self.request_id.expect("request ID not set").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_packet_completed(&self) {
|
||||||
|
self.service
|
||||||
|
.service_helper
|
||||||
|
.common
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_completion_success(
|
||||||
|
self.service.service_helper.id(),
|
||||||
|
self.request_id.expect("request ID not set").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_tm_empty(&self) {
|
||||||
|
let packet = self.tm_funnel_rx.try_recv();
|
||||||
|
if let Err(mpsc::TryRecvError::Empty) = packet {
|
||||||
|
} else {
|
||||||
|
let tm = packet.unwrap();
|
||||||
|
let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap().0;
|
||||||
|
panic!("unexpected TM packet {unexpected_tm:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_next_tc_is_handled_properly(&mut self, time_stamp: &[u8]) {
|
||||||
|
let result = self.service.poll_and_handle_next_tc(time_stamp);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("unexpected error {:?}", e);
|
||||||
|
}
|
||||||
|
let result = result.unwrap();
|
||||||
|
match result {
|
||||||
|
PusPacketHandlerResult::RequestHandled => (),
|
||||||
|
_ => panic!("unexpected result {result:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_all_tcs_handled(&mut self, time_stamp: &[u8]) {
|
||||||
|
let result = self.service.poll_and_handle_next_tc(time_stamp);
|
||||||
|
if let Err(e) = result {
|
||||||
|
panic!("unexpected error {:?}", e);
|
||||||
|
}
|
||||||
|
let result = result.unwrap();
|
||||||
|
match result {
|
||||||
|
PusPacketHandlerResult::Empty => (),
|
||||||
|
_ => panic!("unexpected result {result:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_next_reply_is_handled_properly(&mut self, time_stamp: &[u8]) {
|
||||||
|
let result = self.service.poll_and_check_next_reply(time_stamp);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), HandlingStatus::HandledOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_all_replies_handled(&mut self, time_stamp: &[u8]) {
|
||||||
|
let result = self.service.poll_and_check_next_reply(time_stamp);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), HandlingStatus::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tc(&mut self, tc: &PusTcCreator) {
|
||||||
|
self.request_id = Some(verification::RequestId::new(tc).into());
|
||||||
|
let token = self.service.service_helper.verif_reporter_mut().add_tc(tc);
|
||||||
|
let accepted_token = self
|
||||||
|
.service
|
||||||
|
.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.acceptance_success(self.service.service_helper.tm_sender(), token, &[0; 7])
|
||||||
|
.expect("TC acceptance failed");
|
||||||
|
self.service
|
||||||
|
.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.check_next_was_added(accepted_token.request_id());
|
||||||
|
let id = self.service.service_helper.id();
|
||||||
|
self.service
|
||||||
|
.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.check_next_is_acceptance_success(id, accepted_token.request_id());
|
||||||
|
self.pus_packet_tx
|
||||||
|
.send(EcssTcAndToken::new(
|
||||||
|
PacketAsVec::new(self.service.service_helper.id(), tc.to_vec().unwrap()),
|
||||||
|
accepted_token,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_request() {
|
||||||
|
let mut testbench = TargetedPusRequestTestbench::new_for_action(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
TEST_COMPONENT_ID_1.id(),
|
||||||
|
);
|
||||||
|
// Create a basic action request and verify forwarding.
|
||||||
|
let sp_header = SpHeader::new_from_apid(TEST_APID);
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
|
let pus8_packet = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||||
|
testbench.add_tc(&pus8_packet);
|
||||||
|
let time_stamp: [u8; 7] = [0; 7];
|
||||||
|
testbench.verify_next_tc_is_handled_properly(&time_stamp);
|
||||||
|
testbench.verify_all_tcs_handled(&time_stamp);
|
||||||
|
|
||||||
|
testbench.verify_packet_started();
|
||||||
|
|
||||||
|
let possible_req = testbench.request_rx.try_recv();
|
||||||
|
assert!(possible_req.is_ok());
|
||||||
|
let req = possible_req.unwrap();
|
||||||
|
if let CompositeRequest::Action(action_req) = req.message {
|
||||||
|
assert_eq!(action_req.action_id, action_id);
|
||||||
|
assert_eq!(action_req.variant, ActionRequestVariant::NoData);
|
||||||
|
let action_reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
|
||||||
|
testbench
|
||||||
|
.reply_tx
|
||||||
|
.send(GenericMessage::new(req.requestor_info, action_reply))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
panic!("unexpected request type");
|
||||||
|
}
|
||||||
|
testbench.verify_next_reply_is_handled_properly(&time_stamp);
|
||||||
|
testbench.verify_all_replies_handled(&time_stamp);
|
||||||
|
|
||||||
|
testbench.verify_packet_completed();
|
||||||
|
testbench.verify_tm_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_request_routing_error() {
|
||||||
|
let mut testbench = TargetedPusRequestTestbench::new_for_action(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
TEST_COMPONENT_ID_1.id(),
|
||||||
|
);
|
||||||
|
// Create a basic action request and verify forwarding.
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
// Invalid ID, routing should fail.
|
||||||
|
app_data[0..4].copy_from_slice(&0_u32.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
|
let pus8_packet = PusTcCreator::new(
|
||||||
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
|
sec_header,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
testbench.add_tc(&pus8_packet);
|
||||||
|
let time_stamp: [u8; 7] = [0; 7];
|
||||||
|
|
||||||
|
let result = testbench.service.poll_and_handle_next_tc(&time_stamp);
|
||||||
|
assert!(result.is_err());
|
||||||
|
// Verify the correct result and completion failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converter_action_req_no_data() {
|
||||||
|
let mut testbench = PusConverterTestbench::new(
|
||||||
|
TEST_COMPONENT_ID_0.raw(),
|
||||||
|
ActionRequestConverter::default(),
|
||||||
|
);
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
// Invalid ID, routing should fail.
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
|
let pus8_packet = PusTcCreator::new(
|
||||||
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
|
sec_header,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let token = testbench.add_tc(&pus8_packet);
|
||||||
|
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let (active_req, request) = result.unwrap();
|
||||||
|
if let ActionRequestVariant::NoData = request.variant {
|
||||||
|
assert_eq!(request.action_id, action_id);
|
||||||
|
assert_eq!(active_req.action_id, action_id);
|
||||||
|
assert_eq!(
|
||||||
|
active_req.target_id(),
|
||||||
|
UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0).raw()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
active_req.token().request_id(),
|
||||||
|
testbench.request_id().unwrap()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected action request variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converter_action_req_with_data() {
|
||||||
|
let mut testbench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ActionRequestConverter::default());
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 16] = [0; 16];
|
||||||
|
// Invalid ID, routing should fail.
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||||
|
for i in 0..8 {
|
||||||
|
app_data[i + 8] = i as u8;
|
||||||
|
}
|
||||||
|
let pus8_packet = PusTcCreator::new(
|
||||||
|
SpHeader::new_from_apid(TEST_APID),
|
||||||
|
sec_header,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let token = testbench.add_tc(&pus8_packet);
|
||||||
|
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let (active_req, request) = result.unwrap();
|
||||||
|
if let ActionRequestVariant::VecData(vec) = request.variant {
|
||||||
|
assert_eq!(request.action_id, action_id);
|
||||||
|
assert_eq!(active_req.action_id, action_id);
|
||||||
|
assert_eq!(vec, app_data[8..].to_vec());
|
||||||
|
} else {
|
||||||
|
panic!("unexpected action request variant");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_completion_success() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let active_action_req =
|
||||||
|
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||||
|
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
|
||||||
|
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||||
|
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
testbench.verif_reporter.assert_full_completion_success(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
req_id,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_completion_failure() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let active_action_req =
|
||||||
|
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||||
|
let error_code = ResultU16::new(2, 3);
|
||||||
|
let reply = ActionReplyPus::new(
|
||||||
|
action_id,
|
||||||
|
ActionReplyVariant::CompletionFailed {
|
||||||
|
error_code,
|
||||||
|
params: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||||
|
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
testbench.verif_reporter.assert_completion_failure(
|
||||||
|
TEST_COMPONENT_ID_0.into(),
|
||||||
|
req_id,
|
||||||
|
None,
|
||||||
|
error_code.raw() as u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_step_success() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let active_action_req =
|
||||||
|
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||||
|
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::StepSuccess { step: 1 });
|
||||||
|
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||||
|
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
// Entry should not be removed, completion not done yet.
|
||||||
|
assert!(!result.unwrap());
|
||||||
|
testbench.verif_reporter.check_next_was_added(req_id);
|
||||||
|
testbench
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.raw(), req_id);
|
||||||
|
testbench
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_started_success(TEST_COMPONENT_ID_0.raw(), req_id);
|
||||||
|
testbench
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_step_success(TEST_COMPONENT_ID_0.raw(), req_id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_step_failure() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let active_action_req =
|
||||||
|
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||||
|
let error_code = ResultU16::new(2, 3);
|
||||||
|
let reply = ActionReplyPus::new(
|
||||||
|
action_id,
|
||||||
|
ActionReplyVariant::StepFailed {
|
||||||
|
error_code,
|
||||||
|
step: 1,
|
||||||
|
params: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||||
|
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
testbench.verif_reporter.check_next_was_added(req_id);
|
||||||
|
testbench
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.id(), req_id);
|
||||||
|
testbench
|
||||||
|
.verif_reporter
|
||||||
|
.check_next_is_started_success(TEST_COMPONENT_ID_0.id(), req_id);
|
||||||
|
testbench.verif_reporter.check_next_is_step_failure(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
req_id,
|
||||||
|
error_code.raw().into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_unrequested_reply() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_reply = ActionReplyPus::new(5_u32, ActionReplyVariant::Completed);
|
||||||
|
let unrequested_reply =
|
||||||
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
|
||||||
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
|
// weird stuff.
|
||||||
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_reply_timeout() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||||
|
let action_id = 5_u32;
|
||||||
|
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let result = testbench.handle_request_timeout(
|
||||||
|
&ActivePusActionRequestStd::new_from_common_req(action_id, active_request),
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
testbench.verif_reporter.assert_completion_failure(
|
||||||
|
TEST_COMPONENT_ID_0.raw(),
|
||||||
|
req_id,
|
||||||
|
None,
|
||||||
|
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,71 @@
|
|||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::pus::create_verification_reporter;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
use satrs::pool::SharedStaticMemoryPool;
|
||||||
use satrs_core::pus::event_man::EventRequestWithToken;
|
use satrs::pus::event_man::EventRequestWithToken;
|
||||||
use satrs_core::pus::event_srv::PusService5EventHandler;
|
use satrs::pus::event_srv::PusEventServiceHandler;
|
||||||
use satrs_core::pus::verification::VerificationReporterWithSender;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs_core::pus::{
|
use satrs::pus::{
|
||||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||||
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
|
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper,
|
||||||
PusServiceHelper,
|
|
||||||
};
|
};
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||||
use satrs_core::ChannelId;
|
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
|
||||||
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
|
|
||||||
|
use super::HandlingStatus;
|
||||||
|
|
||||||
pub fn create_event_service_static(
|
pub fn create_event_service_static(
|
||||||
shared_tm_store: SharedTmPool,
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_pool: SharedStaticMemoryPool,
|
||||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||||
) -> Pus5Wrapper<EcssTcInSharedStoreConverter> {
|
) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
let event_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
let pus_5_handler = PusEventServiceHandler::new(
|
||||||
TmSenderId::PusEvent as ChannelId,
|
|
||||||
"PUS_5_TM_SENDER",
|
|
||||||
shared_tm_store.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let event_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusEvent as ChannelId,
|
|
||||||
"PUS_5_TC_RECV",
|
|
||||||
pus_event_rx,
|
|
||||||
);
|
|
||||||
let pus_5_handler = PusService5EventHandler::new(
|
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
Box::new(event_srv_receiver),
|
PUS_EVENT_MANAGEMENT.id(),
|
||||||
Box::new(event_srv_tm_sender),
|
pus_event_rx,
|
||||||
PUS_APID,
|
tm_sender,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
||||||
),
|
),
|
||||||
event_request_tx,
|
event_request_tx,
|
||||||
);
|
);
|
||||||
Pus5Wrapper { pus_5_handler }
|
EventServiceWrapper {
|
||||||
|
handler: pus_5_handler,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_event_service_dynamic(
|
pub fn create_event_service_dynamic(
|
||||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||||
) -> Pus5Wrapper<EcssTcInVecConverter> {
|
) -> EventServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
let event_srv_tm_sender = MpscTmAsVecSender::new(
|
let pus_5_handler = PusEventServiceHandler::new(
|
||||||
TmSenderId::PusEvent as ChannelId,
|
|
||||||
"PUS_5_TM_SENDER",
|
|
||||||
tm_funnel_tx,
|
|
||||||
);
|
|
||||||
let event_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusEvent as ChannelId,
|
|
||||||
"PUS_5_TC_RECV",
|
|
||||||
pus_event_rx,
|
|
||||||
);
|
|
||||||
let pus_5_handler = PusService5EventHandler::new(
|
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
Box::new(event_srv_receiver),
|
PUS_EVENT_MANAGEMENT.id(),
|
||||||
Box::new(event_srv_tm_sender),
|
pus_event_rx,
|
||||||
PUS_APID,
|
tm_funnel_tx,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
|
||||||
EcssTcInVecConverter::default(),
|
EcssTcInVecConverter::default(),
|
||||||
),
|
),
|
||||||
event_request_tx,
|
event_request_tx,
|
||||||
);
|
);
|
||||||
Pus5Wrapper { pus_5_handler }
|
EventServiceWrapper {
|
||||||
|
handler: pus_5_handler,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pus5Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
pub struct EventServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||||
pub pus_5_handler: PusService5EventHandler<TcInMemConverter>,
|
pub handler:
|
||||||
|
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> {
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
pub fn handle_next_packet(&mut self) -> bool {
|
EventServiceWrapper<TmSender, TcInMemConverter>
|
||||||
match self.pus_5_handler.handle_one_tc() {
|
{
|
||||||
|
pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
match self.handler.poll_and_handle_next_tc(time_stamp) {
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
PusPacketHandlerResult::RequestHandled => {}
|
PusPacketHandlerResult::RequestHandled => {}
|
||||||
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||||
@ -93,14 +77,12 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> {
|
|||||||
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||||
warn!("PUS 5 subservice {subservice} not implemented");
|
warn!("PUS 5 subservice {subservice} not implemented");
|
||||||
}
|
}
|
||||||
PusPacketHandlerResult::Empty => {
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("PUS packet handling error: {error:?}")
|
error!("PUS packet handling error: {error:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
HandlingStatus::HandledOne
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,127 +1,139 @@
|
|||||||
use crate::requests::{Request, RequestWithToken};
|
use derive_new::new;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use satrs_core::hk::{CollectionIntervalFactor, HkRequest};
|
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
|
||||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
use satrs::pool::SharedStaticMemoryPool;
|
||||||
use satrs_core::pus::verification::{
|
use satrs::pus::verification::{
|
||||||
FailParams, StdVerifReporterWithSender, VerificationReporterWithSender,
|
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||||
|
VerificationReportingProvider, VerificationToken,
|
||||||
};
|
};
|
||||||
use satrs_core::pus::{
|
use satrs::pus::{
|
||||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
|
||||||
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
|
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
|
||||||
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
|
EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
|
||||||
|
PusPacketHandlerResult, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
|
||||||
};
|
};
|
||||||
use satrs_core::spacepackets::ecss::{hk, PusPacket};
|
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs_core::ChannelId;
|
use satrs::spacepackets::ecss::{hk, PusPacket};
|
||||||
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
|
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||||
use satrs_example::TargetIdWithApid;
|
use satrs_example::config::components::PUS_HK_SERVICE;
|
||||||
use std::collections::HashMap;
|
use satrs_example::config::{hk_err, tmtc_err};
|
||||||
use std::sync::mpsc::{self, Sender};
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn create_hk_service_static(
|
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
|
||||||
shared_tm_store: SharedTmPool,
|
use crate::requests::GenericRequestRouter;
|
||||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
|
||||||
verif_reporter: VerificationReporterWithSender,
|
use super::{HandlingStatus, PusTargetedRequestService};
|
||||||
tc_pool: SharedStaticMemoryPool,
|
|
||||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
#[derive(Clone, PartialEq, Debug, new)]
|
||||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
pub struct HkReply {
|
||||||
) -> Pus3Wrapper<EcssTcInSharedStoreConverter> {
|
pub unique_id: UniqueId,
|
||||||
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
pub variant: HkReplyVariant,
|
||||||
TmSenderId::PusHk as ChannelId,
|
|
||||||
"PUS_3_TM_SENDER",
|
|
||||||
shared_tm_store.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let hk_srv_receiver =
|
|
||||||
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
|
|
||||||
let pus_3_handler = PusService3HkHandler::new(
|
|
||||||
Box::new(hk_srv_receiver),
|
|
||||||
Box::new(hk_srv_tm_sender),
|
|
||||||
PUS_APID,
|
|
||||||
verif_reporter.clone(),
|
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
|
||||||
request_map,
|
|
||||||
);
|
|
||||||
Pus3Wrapper { pus_3_handler }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_hk_service_dynamic(
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
pub enum HkReplyVariant {
|
||||||
verif_reporter: VerificationReporterWithSender,
|
Ack,
|
||||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
|
||||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
|
||||||
) -> Pus3Wrapper<EcssTcInVecConverter> {
|
|
||||||
let hk_srv_tm_sender = MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::PusHk as ChannelId,
|
|
||||||
"PUS_3_TM_SENDER",
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let hk_srv_receiver =
|
|
||||||
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
|
|
||||||
let pus_3_handler = PusService3HkHandler::new(
|
|
||||||
Box::new(hk_srv_receiver),
|
|
||||||
Box::new(hk_srv_tm_sender),
|
|
||||||
PUS_APID,
|
|
||||||
verif_reporter.clone(),
|
|
||||||
EcssTcInVecConverter::default(),
|
|
||||||
request_map,
|
|
||||||
);
|
|
||||||
Pus3Wrapper { pus_3_handler }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PusService3HkHandler<TcInMemConverter: EcssTcInMemConverter> {
|
#[derive(Default)]
|
||||||
psb: PusServiceHelper<TcInMemConverter>,
|
pub struct HkReplyHandler {}
|
||||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> PusService3HkHandler<TcInMemConverter> {
|
impl PusReplyHandler<ActivePusRequestStd, HkReply> for HkReplyHandler {
|
||||||
pub fn new(
|
type Error = EcssTmtcError;
|
||||||
tc_receiver: Box<dyn EcssTcReceiver>,
|
|
||||||
tm_sender: Box<dyn EcssTmSender>,
|
fn handle_unrequested_reply(
|
||||||
tm_apid: u16,
|
&mut self,
|
||||||
verification_handler: StdVerifReporterWithSender,
|
reply: &GenericMessage<HkReply>,
|
||||||
tc_in_mem_converter: TcInMemConverter,
|
_tm_sender: &impl EcssTmSender,
|
||||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
) -> Result<(), Self::Error> {
|
||||||
) -> Self {
|
log::warn!("received unexpected reply for service 3: {reply:?}");
|
||||||
Self {
|
Ok(())
|
||||||
psb: PusServiceHelper::new(
|
|
||||||
tc_receiver,
|
|
||||||
tm_sender,
|
|
||||||
tm_apid,
|
|
||||||
verification_handler,
|
|
||||||
tc_in_mem_converter,
|
|
||||||
),
|
|
||||||
request_handlers,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
|
fn handle_reply(
|
||||||
let possible_packet = self.psb.retrieve_and_accept_next_packet()?;
|
&mut self,
|
||||||
if possible_packet.is_none() {
|
reply: &GenericMessage<HkReply>,
|
||||||
return Ok(PusPacketHandlerResult::Empty);
|
active_request: &ActivePusRequestStd,
|
||||||
|
tm_sender: &impl EcssTmSender,
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<bool, Self::Error> {
|
||||||
|
let started_token: VerificationToken<TcStateStarted> = active_request
|
||||||
|
.token()
|
||||||
|
.try_into()
|
||||||
|
.expect("invalid token state");
|
||||||
|
match reply.message.variant {
|
||||||
|
HkReplyVariant::Ack => {
|
||||||
|
verification_handler
|
||||||
|
.completion_success(tm_sender, started_token, time_stamp)
|
||||||
|
.expect("sending completion success verification failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request_timeout(
|
||||||
|
&mut self,
|
||||||
|
active_request: &ActivePusRequestStd,
|
||||||
|
tm_sender: &impl EcssTmSender,
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
generic_pus_request_timeout_handler(
|
||||||
|
tm_sender,
|
||||||
|
active_request,
|
||||||
|
verification_handler,
|
||||||
|
time_stamp,
|
||||||
|
"HK",
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HkRequestConverter {
|
||||||
|
timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HkRequestConverter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
timeout: Duration::from_secs(60),
|
||||||
}
|
}
|
||||||
let ecss_tc_and_token = possible_packet.unwrap();
|
}
|
||||||
let tc = self
|
}
|
||||||
.psb
|
|
||||||
.tc_in_mem_converter
|
impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConverter {
|
||||||
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
|
type Error = GenericConversionError;
|
||||||
let subservice = tc.subservice();
|
|
||||||
let mut partial_error = None;
|
fn convert(
|
||||||
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
|
&mut self,
|
||||||
|
token: VerificationToken<TcStateAccepted>,
|
||||||
|
tc: &PusTcReader,
|
||||||
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(ActivePusRequestStd, HkRequest), Self::Error> {
|
||||||
let user_data = tc.user_data();
|
let user_data = tc.user_data();
|
||||||
if user_data.is_empty() {
|
if user_data.is_empty() {
|
||||||
self.psb
|
let user_data_len = user_data.len() as u32;
|
||||||
.common
|
let user_data_len_raw = user_data_len.to_be_bytes();
|
||||||
.verification_handler
|
verif_reporter
|
||||||
.borrow_mut()
|
|
||||||
.start_failure(
|
.start_failure(
|
||||||
ecss_tc_and_token.token,
|
tm_sender,
|
||||||
FailParams::new(Some(&time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
|
token,
|
||||||
|
FailParams::new(
|
||||||
|
time_stamp,
|
||||||
|
&tmtc_err::NOT_ENOUGH_APP_DATA,
|
||||||
|
&user_data_len_raw,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.expect("Sending start failure TM failed");
|
.expect("Sending start failure TM failed");
|
||||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
return Err(GenericConversionError::NotEnoughAppData {
|
||||||
"Expected at least 8 bytes of app data".into(),
|
expected: 4,
|
||||||
));
|
found: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if user_data.len() < 8 {
|
if user_data.len() < 8 {
|
||||||
let err = if user_data.len() < 4 {
|
let err = if user_data.len() < 4 {
|
||||||
@ -129,89 +141,167 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService3HkHandler<TcInMemConvert
|
|||||||
} else {
|
} else {
|
||||||
&hk_err::UNIQUE_ID_MISSING
|
&hk_err::UNIQUE_ID_MISSING
|
||||||
};
|
};
|
||||||
self.psb
|
let user_data_len = user_data.len() as u32;
|
||||||
.common
|
let user_data_len_raw = user_data_len.to_be_bytes();
|
||||||
.verification_handler
|
verif_reporter
|
||||||
.borrow_mut()
|
|
||||||
.start_failure(
|
.start_failure(
|
||||||
ecss_tc_and_token.token,
|
tm_sender,
|
||||||
FailParams::new(Some(&time_stamp), err, None),
|
token,
|
||||||
|
FailParams::new(time_stamp, err, &user_data_len_raw),
|
||||||
)
|
)
|
||||||
.expect("Sending start failure TM failed");
|
.expect("Sending start failure TM failed");
|
||||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
return Err(GenericConversionError::NotEnoughAppData {
|
||||||
"Expected at least 8 bytes of app data".into(),
|
expected: 8,
|
||||||
));
|
found: 4,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let target_id = TargetIdWithApid::from_tc(&tc).expect("invalid tc format");
|
let subservice = tc.subservice();
|
||||||
let unique_id = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
|
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).expect("invalid tc format");
|
||||||
if !self.request_handlers.contains_key(&target_id) {
|
let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
|
||||||
self.psb
|
|
||||||
.common
|
let standard_subservice = hk::Subservice::try_from(subservice);
|
||||||
.verification_handler
|
if standard_subservice.is_err() {
|
||||||
.borrow_mut()
|
verif_reporter
|
||||||
.start_failure(
|
.start_failure(
|
||||||
ecss_tc_and_token.token,
|
tm_sender,
|
||||||
FailParams::new(Some(&time_stamp), &hk_err::UNKNOWN_TARGET_ID, None),
|
token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE, &[subservice]),
|
||||||
)
|
)
|
||||||
.expect("Sending start failure TM failed");
|
.expect("Sending start failure TM failed");
|
||||||
return Err(PusPacketHandlingError::NotEnoughAppData(format!(
|
return Err(GenericConversionError::InvalidSubservice(subservice));
|
||||||
"Unknown target ID {target_id}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
let send_request = |target: TargetIdWithApid, request: HkRequest| {
|
let request = match standard_subservice.unwrap() {
|
||||||
let sender = self.request_handlers.get(&target).unwrap();
|
hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
|
||||||
sender
|
HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic)
|
||||||
.send(RequestWithToken::new(
|
}
|
||||||
target,
|
hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
|
||||||
Request::Hk(request),
|
HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic)
|
||||||
ecss_tc_and_token.token,
|
}
|
||||||
))
|
hk::Subservice::TcReportHkReportStructures => todo!(),
|
||||||
.unwrap_or_else(|_| panic!("Sending HK request {request:?} failed"));
|
hk::Subservice::TmHkPacket => todo!(),
|
||||||
};
|
hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
|
||||||
if subservice == hk::Subservice::TcEnableHkGeneration as u8 {
|
HkRequest::new(unique_id, HkRequestVariant::OneShot)
|
||||||
send_request(target_id, HkRequest::Enable(unique_id));
|
}
|
||||||
} else if subservice == hk::Subservice::TcDisableHkGeneration as u8 {
|
hk::Subservice::TcModifyDiagCollectionInterval
|
||||||
send_request(target_id, HkRequest::Disable(unique_id));
|
| hk::Subservice::TcModifyHkCollectionInterval => {
|
||||||
} else if subservice == hk::Subservice::TcGenerateOneShotHk as u8 {
|
if user_data.len() < 12 {
|
||||||
send_request(target_id, HkRequest::OneShot(unique_id));
|
verif_reporter
|
||||||
} else if subservice == hk::Subservice::TcModifyHkCollectionInterval as u8 {
|
.start_failure(
|
||||||
if user_data.len() < 12 {
|
tm_sender,
|
||||||
self.psb
|
token,
|
||||||
.common
|
FailParams::new_no_fail_data(
|
||||||
.verification_handler
|
time_stamp,
|
||||||
.borrow_mut()
|
&tmtc_err::NOT_ENOUGH_APP_DATA,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure TM failed");
|
||||||
|
return Err(GenericConversionError::NotEnoughAppData {
|
||||||
|
expected: 12,
|
||||||
|
found: user_data.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
HkRequest::new(
|
||||||
|
unique_id,
|
||||||
|
HkRequestVariant::ModifyCollectionInterval(
|
||||||
|
CollectionIntervalFactor::from_be_bytes(
|
||||||
|
user_data[8..12].try_into().unwrap(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
verif_reporter
|
||||||
.start_failure(
|
.start_failure(
|
||||||
ecss_tc_and_token.token,
|
tm_sender,
|
||||||
|
token,
|
||||||
FailParams::new(
|
FailParams::new(
|
||||||
Some(&time_stamp),
|
time_stamp,
|
||||||
&hk_err::COLLECTION_INTERVAL_MISSING,
|
&tmtc_err::PUS_SUBSERVICE_NOT_IMPLEMENTED,
|
||||||
None,
|
&[subservice],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Sending start failure TM failed");
|
.expect("Sending start failure TM failed");
|
||||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
return Err(GenericConversionError::InvalidSubservice(subservice));
|
||||||
"Collection interval missing".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
send_request(
|
};
|
||||||
target_id,
|
Ok((
|
||||||
HkRequest::ModifyCollectionInterval(
|
ActivePusRequestStd::new(target_id_and_apid.into(), token, self.timeout),
|
||||||
unique_id,
|
request,
|
||||||
CollectionIntervalFactor::from_be_bytes(user_data[8..12].try_into().unwrap()),
|
))
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(PusPacketHandlerResult::RequestHandled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pus3Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
pub fn create_hk_service_static(
|
||||||
pub(crate) pus_3_handler: PusService3HkHandler<TcInMemConverter>,
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
|
tc_pool: SharedStaticMemoryPool,
|
||||||
|
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
request_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||||
|
) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
|
let pus_3_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_HK_SERVICE.id(),
|
||||||
|
pus_hk_rx,
|
||||||
|
tm_sender,
|
||||||
|
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
|
||||||
|
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||||
|
),
|
||||||
|
HkRequestConverter::default(),
|
||||||
|
DefaultActiveRequestMap::default(),
|
||||||
|
HkReplyHandler::default(),
|
||||||
|
request_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
HkServiceWrapper {
|
||||||
|
service: pus_3_handler,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> {
|
pub fn create_hk_service_dynamic(
|
||||||
pub fn handle_next_packet(&mut self) -> bool {
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
match self.pus_3_handler.handle_one_tc() {
|
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
request_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
|
||||||
|
) -> HkServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
|
let pus_3_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_HK_SERVICE.id(),
|
||||||
|
pus_hk_rx,
|
||||||
|
tm_funnel_tx,
|
||||||
|
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
|
||||||
|
EcssTcInVecConverter::default(),
|
||||||
|
),
|
||||||
|
HkRequestConverter::default(),
|
||||||
|
DefaultActiveRequestMap::default(),
|
||||||
|
HkReplyHandler::default(),
|
||||||
|
request_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
HkServiceWrapper {
|
||||||
|
service: pus_3_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||||
|
pub(crate) service: PusTargetedRequestService<
|
||||||
|
MpscTcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
HkRequestConverter,
|
||||||
|
HkReplyHandler,
|
||||||
|
DefaultActiveRequestMap<ActivePusRequestStd>,
|
||||||
|
ActivePusRequestStd,
|
||||||
|
HkRequest,
|
||||||
|
HkReply,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
|
HkServiceWrapper<TmSender, TcInMemConverter>
|
||||||
|
{
|
||||||
|
pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
match self.service.poll_and_handle_next_tc(time_stamp) {
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
PusPacketHandlerResult::RequestHandled => {}
|
PusPacketHandlerResult::RequestHandled => {}
|
||||||
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||||
@ -223,14 +313,252 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> {
|
|||||||
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||||
warn!("PUS 3 subservice {subservice} not implemented");
|
warn!("PUS 3 subservice {subservice} not implemented");
|
||||||
}
|
}
|
||||||
PusPacketHandlerResult::Empty => {
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("PUS packet handling error: {error:?}")
|
error!("PUS packet handling error: {error:?}");
|
||||||
|
// To avoid permanent loops on error cases.
|
||||||
|
return HandlingStatus::Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
// This only fails if all senders disconnected. Treat it like an empty queue.
|
||||||
|
self.service
|
||||||
|
.poll_and_check_next_reply(time_stamp)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("PUS 3: Handling reply failed with error {e:?}");
|
||||||
|
HandlingStatus::Empty
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_for_request_timeouts(&mut self) {
|
||||||
|
self.service.check_for_request_timeouts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use satrs::pus::test_util::{
|
||||||
|
TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
||||||
|
};
|
||||||
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::{
|
||||||
|
hk::HkRequestVariant,
|
||||||
|
pus::test_util::TEST_APID,
|
||||||
|
request::GenericMessage,
|
||||||
|
spacepackets::{
|
||||||
|
ecss::{hk::Subservice, tc::PusTcCreator},
|
||||||
|
SpHeader,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use satrs_example::config::tmtc_err;
|
||||||
|
|
||||||
|
use crate::pus::{
|
||||||
|
hk::HkReplyVariant,
|
||||||
|
tests::{PusConverterTestbench, ReplyHandlerTestbench},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{HkReply, HkReplyHandler, HkRequestConverter};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hk_converter_one_shot_req() {
|
||||||
|
let mut hk_bench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
|
let unique_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
|
|
||||||
|
let hk_req = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcGenerateOneShotHk as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let accepted_token = hk_bench.add_tc(&hk_req);
|
||||||
|
let (_active_req, req) = hk_bench
|
||||||
|
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion failed");
|
||||||
|
|
||||||
|
assert_eq!(req.unique_id, unique_id);
|
||||||
|
if let HkRequestVariant::OneShot = req.variant {
|
||||||
|
} else {
|
||||||
|
panic!("unexpected HK request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hk_converter_enable_periodic_generation() {
|
||||||
|
let mut hk_bench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
|
let unique_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
|
let mut generic_check = |tc: &PusTcCreator| {
|
||||||
|
let accepted_token = hk_bench.add_tc(tc);
|
||||||
|
let (_active_req, req) = hk_bench
|
||||||
|
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion failed");
|
||||||
|
assert_eq!(req.unique_id, unique_id);
|
||||||
|
if let HkRequestVariant::EnablePeriodic = req.variant {
|
||||||
|
} else {
|
||||||
|
panic!("unexpected HK request")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tc0 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcEnableHkGeneration as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc0);
|
||||||
|
let tc1 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcEnableDiagGeneration as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hk_conversion_disable_periodic_generation() {
|
||||||
|
let mut hk_bench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
|
let unique_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 8] = [0; 8];
|
||||||
|
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
|
let mut generic_check = |tc: &PusTcCreator| {
|
||||||
|
let accepted_token = hk_bench.add_tc(tc);
|
||||||
|
let (_active_req, req) = hk_bench
|
||||||
|
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion failed");
|
||||||
|
assert_eq!(req.unique_id, unique_id);
|
||||||
|
if let HkRequestVariant::DisablePeriodic = req.variant {
|
||||||
|
} else {
|
||||||
|
panic!("unexpected HK request")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tc0 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcDisableHkGeneration as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc0);
|
||||||
|
let tc1 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcDisableDiagGeneration as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hk_conversion_modify_interval() {
|
||||||
|
let mut hk_bench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let target_id = TEST_UNIQUE_ID_0;
|
||||||
|
let unique_id = 5_u32;
|
||||||
|
let mut app_data: [u8; 12] = [0; 12];
|
||||||
|
let collection_interval_factor = 5_u32;
|
||||||
|
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
|
||||||
|
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
|
||||||
|
app_data[8..12].copy_from_slice(&collection_interval_factor.to_be_bytes());
|
||||||
|
|
||||||
|
let mut generic_check = |tc: &PusTcCreator| {
|
||||||
|
let accepted_token = hk_bench.add_tc(tc);
|
||||||
|
let (_active_req, req) = hk_bench
|
||||||
|
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion failed");
|
||||||
|
assert_eq!(req.unique_id, unique_id);
|
||||||
|
if let HkRequestVariant::ModifyCollectionInterval(interval_factor) = req.variant {
|
||||||
|
assert_eq!(interval_factor, collection_interval_factor);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected HK request")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tc0 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcModifyHkCollectionInterval as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc0);
|
||||||
|
let tc1 = PusTcCreator::new_simple(
|
||||||
|
sp_header,
|
||||||
|
3,
|
||||||
|
Subservice::TcModifyDiagCollectionInterval as u8,
|
||||||
|
&app_data,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
generic_check(&tc1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hk_reply_handler() {
|
||||||
|
let mut reply_testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), HkReplyHandler::default());
|
||||||
|
let sender_id = 2_u64;
|
||||||
|
let apid_target_id = 3_u32;
|
||||||
|
let unique_id = 5_u32;
|
||||||
|
let (req_id, active_req) = reply_testbench.add_tc(TEST_APID, apid_target_id, &[]);
|
||||||
|
let reply = GenericMessage::new(
|
||||||
|
MessageMetadata::new(req_id.into(), sender_id),
|
||||||
|
HkReply::new(unique_id, HkReplyVariant::Ack),
|
||||||
|
);
|
||||||
|
let result = reply_testbench.handle_reply(&reply, &active_req, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
reply_testbench
|
||||||
|
.verif_reporter
|
||||||
|
.assert_full_completion_success(TEST_COMPONENT_ID_0.raw(), req_id, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_unrequested_reply() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
|
||||||
|
let action_reply = HkReply::new(5_u32, HkReplyVariant::Ack);
|
||||||
|
let unrequested_reply =
|
||||||
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
|
||||||
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
|
// weird stuff.
|
||||||
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_reply_timeout() {
|
||||||
|
let mut testbench =
|
||||||
|
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
|
||||||
|
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_1, &[]);
|
||||||
|
let result = testbench.handle_request_timeout(&active_request, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
testbench.verif_reporter.assert_completion_failure(
|
||||||
|
TEST_COMPONENT_ID_1.raw(),
|
||||||
|
req_id,
|
||||||
|
None,
|
||||||
|
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,123 +1,159 @@
|
|||||||
use crate::tmtc::MpscStoreAndSendError;
|
use crate::requests::GenericRequestRouter;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use satrs_core::pus::verification::{FailParams, StdVerifReporterWithSender};
|
use satrs::pool::PoolAddr;
|
||||||
use satrs_core::pus::{EcssTcAndToken, PusPacketHandlerResult, TcInMemory};
|
use satrs::pus::verification::{
|
||||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||||
use satrs_core::spacepackets::ecss::PusServiceId;
|
VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
|
||||||
use satrs_core::spacepackets::time::cds::TimeProvider;
|
};
|
||||||
use satrs_core::spacepackets::time::TimeWriter;
|
use satrs::pus::{
|
||||||
|
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
|
||||||
|
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
|
||||||
|
PusPacketHandlerResult, PusPacketHandlingError, PusReplyHandler, PusRequestRouter,
|
||||||
|
PusServiceHelper, PusTcToRequestConverter, TcInMemory,
|
||||||
|
};
|
||||||
|
use satrs::queue::{GenericReceiveError, GenericSendError};
|
||||||
|
use satrs::request::{Apid, GenericMessage, MessageMetadata};
|
||||||
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
|
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
||||||
|
use satrs::tmtc::{PacketAsVec, PacketInPool};
|
||||||
|
use satrs::ComponentId;
|
||||||
|
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
||||||
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
||||||
use std::sync::mpsc::Sender;
|
use satrs_example::TimeStampHelper;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::mpsc::{self, Sender};
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod hk;
|
pub mod hk;
|
||||||
|
pub mod mode;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
pub struct PusTcMpscRouter {
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub test_service_receiver: Sender<EcssTcAndToken>,
|
pub enum HandlingStatus {
|
||||||
pub event_service_receiver: Sender<EcssTcAndToken>,
|
Empty,
|
||||||
pub sched_service_receiver: Sender<EcssTcAndToken>,
|
HandledOne,
|
||||||
pub hk_service_receiver: Sender<EcssTcAndToken>,
|
|
||||||
pub action_service_receiver: Sender<EcssTcAndToken>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PusReceiver {
|
pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> VerificationReporter {
|
||||||
pub verif_reporter: StdVerifReporterWithSender,
|
let verif_cfg = VerificationReporterCfg::new(apid, 1, 2, 8).unwrap();
|
||||||
|
// Every software component which needs to generate verification telemetry, gets a cloned
|
||||||
|
// verification reporter.
|
||||||
|
VerificationReporter::new(owner_id, &verif_cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
|
||||||
|
pub struct PusTcMpscRouter {
|
||||||
|
pub test_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
pub event_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
pub sched_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
pub hk_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
pub action_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
pub mode_tc_sender: Sender<EcssTcAndToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PusTcDistributor<TmSender: EcssTmSender> {
|
||||||
|
pub id: ComponentId,
|
||||||
|
pub tm_sender: TmSender,
|
||||||
|
pub verif_reporter: VerificationReporter,
|
||||||
pub pus_router: PusTcMpscRouter,
|
pub pus_router: PusTcMpscRouter,
|
||||||
stamp_helper: TimeStampHelper,
|
stamp_helper: TimeStampHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimeStampHelper {
|
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
||||||
stamper: TimeProvider,
|
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
||||||
time_stamp: [u8; 7],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeStampHelper {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
stamper: TimeProvider::new_with_u16_days(0, 0),
|
id: PUS_ROUTING_SERVICE.raw(),
|
||||||
time_stamp: [0; 7],
|
tm_sender,
|
||||||
}
|
verif_reporter: create_verification_reporter(
|
||||||
}
|
PUS_ROUTING_SERVICE.id(),
|
||||||
|
PUS_ROUTING_SERVICE.apid,
|
||||||
pub fn stamp(&self) -> &[u8] {
|
),
|
||||||
&self.time_stamp
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_now(&mut self) {
|
|
||||||
self.stamper
|
|
||||||
.update_from_now()
|
|
||||||
.expect("Updating timestamp failed");
|
|
||||||
self.stamper
|
|
||||||
.write_to_bytes(&mut self.time_stamp)
|
|
||||||
.expect("Writing timestamp failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusReceiver {
|
|
||||||
pub fn new(verif_reporter: StdVerifReporterWithSender, pus_router: PusTcMpscRouter) -> Self {
|
|
||||||
Self {
|
|
||||||
verif_reporter,
|
|
||||||
pus_router,
|
pus_router,
|
||||||
stamp_helper: TimeStampHelper::new(),
|
stamp_helper: TimeStampHelper::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl PusReceiver {
|
pub fn handle_tc_packet_vec(
|
||||||
pub fn handle_tc_packet(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
tc_in_memory: TcInMemory,
|
packet_as_vec: PacketAsVec,
|
||||||
service: u8,
|
) -> Result<PusPacketHandlerResult, GenericSendError> {
|
||||||
pus_tc: &PusTcReader,
|
self.handle_tc_generic(packet_as_vec.sender_id, None, &packet_as_vec.packet)
|
||||||
) -> Result<PusPacketHandlerResult, MpscStoreAndSendError> {
|
}
|
||||||
let init_token = self.verif_reporter.add_tc(pus_tc);
|
|
||||||
|
pub fn handle_tc_packet_in_store(
|
||||||
|
&mut self,
|
||||||
|
packet_in_pool: PacketInPool,
|
||||||
|
pus_tc_copy: &[u8],
|
||||||
|
) -> Result<PusPacketHandlerResult, GenericSendError> {
|
||||||
|
self.handle_tc_generic(
|
||||||
|
packet_in_pool.sender_id,
|
||||||
|
Some(packet_in_pool.store_addr),
|
||||||
|
pus_tc_copy,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_tc_generic(
|
||||||
|
&mut self,
|
||||||
|
sender_id: ComponentId,
|
||||||
|
addr_opt: Option<PoolAddr>,
|
||||||
|
raw_tc: &[u8],
|
||||||
|
) -> Result<PusPacketHandlerResult, GenericSendError> {
|
||||||
|
let pus_tc_result = PusTcReader::new(raw_tc);
|
||||||
|
if pus_tc_result.is_err() {
|
||||||
|
log::warn!(
|
||||||
|
"error creating PUS TC from raw data received from {}: {}",
|
||||||
|
sender_id,
|
||||||
|
pus_tc_result.unwrap_err()
|
||||||
|
);
|
||||||
|
log::warn!("raw data: {:x?}", raw_tc);
|
||||||
|
return Ok(PusPacketHandlerResult::RequestHandled);
|
||||||
|
}
|
||||||
|
let pus_tc = pus_tc_result.unwrap().0;
|
||||||
|
let init_token = self.verif_reporter.add_tc(&pus_tc);
|
||||||
self.stamp_helper.update_from_now();
|
self.stamp_helper.update_from_now();
|
||||||
let accepted_token = self
|
let accepted_token = self
|
||||||
.verif_reporter
|
.verif_reporter
|
||||||
.acceptance_success(init_token, Some(self.stamp_helper.stamp()))
|
.acceptance_success(&self.tm_sender, init_token, self.stamp_helper.stamp())
|
||||||
.expect("Acceptance success failure");
|
.expect("Acceptance success failure");
|
||||||
let service = PusServiceId::try_from(service);
|
let service = PusServiceId::try_from(pus_tc.service());
|
||||||
|
let tc_in_memory: TcInMemory = if let Some(store_addr) = addr_opt {
|
||||||
|
PacketInPool::new(sender_id, store_addr).into()
|
||||||
|
} else {
|
||||||
|
PacketAsVec::new(sender_id, Vec::from(raw_tc)).into()
|
||||||
|
};
|
||||||
match service {
|
match service {
|
||||||
Ok(standard_service) => match standard_service {
|
Ok(standard_service) => match standard_service {
|
||||||
PusServiceId::Test => {
|
PusServiceId::Test => self.pus_router.test_tc_sender.send(EcssTcAndToken {
|
||||||
self.pus_router.test_service_receiver.send(EcssTcAndToken {
|
tc_in_memory,
|
||||||
tc_in_memory,
|
token: Some(accepted_token.into()),
|
||||||
token: Some(accepted_token.into()),
|
})?,
|
||||||
})?
|
|
||||||
}
|
|
||||||
PusServiceId::Housekeeping => {
|
PusServiceId::Housekeeping => {
|
||||||
self.pus_router.hk_service_receiver.send(EcssTcAndToken {
|
self.pus_router.hk_tc_sender.send(EcssTcAndToken {
|
||||||
tc_in_memory,
|
tc_in_memory,
|
||||||
token: Some(accepted_token.into()),
|
token: Some(accepted_token.into()),
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
PusServiceId::Event => {
|
PusServiceId::Event => self.pus_router.event_tc_sender.send(EcssTcAndToken {
|
||||||
self.pus_router
|
tc_in_memory,
|
||||||
.event_service_receiver
|
token: Some(accepted_token.into()),
|
||||||
.send(EcssTcAndToken {
|
})?,
|
||||||
tc_in_memory,
|
|
||||||
token: Some(accepted_token.into()),
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
PusServiceId::Scheduling => {
|
PusServiceId::Scheduling => {
|
||||||
self.pus_router
|
self.pus_router.sched_tc_sender.send(EcssTcAndToken {
|
||||||
.sched_service_receiver
|
tc_in_memory,
|
||||||
.send(EcssTcAndToken {
|
token: Some(accepted_token.into()),
|
||||||
tc_in_memory,
|
})?
|
||||||
token: Some(accepted_token.into()),
|
|
||||||
})?
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let result = self.verif_reporter.start_failure(
|
let result = self.verif_reporter.start_failure(
|
||||||
|
&self.tm_sender,
|
||||||
accepted_token,
|
accepted_token,
|
||||||
FailParams::new(
|
FailParams::new(
|
||||||
Some(self.stamp_helper.stamp()),
|
self.stamp_helper.stamp(),
|
||||||
&tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED,
|
&tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED,
|
||||||
Some(&[standard_service as u8]),
|
&[standard_service as u8],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
@ -128,20 +164,25 @@ impl PusReceiver {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Ok(custom_service) = CustomPusServiceId::try_from(e.number) {
|
if let Ok(custom_service) = CustomPusServiceId::try_from(e.number) {
|
||||||
match custom_service {
|
match custom_service {
|
||||||
CustomPusServiceId::Mode => {
|
CustomPusServiceId::Mode => self
|
||||||
// TODO: Fix mode service.
|
.pus_router
|
||||||
//self.handle_mode_service(pus_tc, accepted_token)
|
.mode_tc_sender
|
||||||
}
|
.send(EcssTcAndToken {
|
||||||
|
tc_in_memory,
|
||||||
|
token: Some(accepted_token.into()),
|
||||||
|
})
|
||||||
|
.map_err(|_| GenericSendError::RxDisconnected)?,
|
||||||
CustomPusServiceId::Health => {}
|
CustomPusServiceId::Health => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.verif_reporter
|
self.verif_reporter
|
||||||
.start_failure(
|
.start_failure(
|
||||||
|
&self.tm_sender,
|
||||||
accepted_token,
|
accepted_token,
|
||||||
FailParams::new(
|
FailParams::new(
|
||||||
Some(self.stamp_helper.stamp()),
|
self.stamp_helper.stamp(),
|
||||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||||
Some(&[e.number]),
|
&[e.number],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Start failure verification failed")
|
.expect("Start failure verification failed")
|
||||||
@ -151,3 +192,552 @@ impl PusReceiver {
|
|||||||
Ok(PusPacketHandlerResult::RequestHandled)
|
Ok(PusPacketHandlerResult::RequestHandled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TargetedPusService {
|
||||||
|
/// Returns [true] if the packet handling is finished.
|
||||||
|
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus;
|
||||||
|
fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus;
|
||||||
|
fn check_for_request_timeouts(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a generic handler class for all PUS services where a PUS telecommand is converted
|
||||||
|
/// to a targeted request.
|
||||||
|
///
|
||||||
|
/// The generic steps for this process are the following
|
||||||
|
///
|
||||||
|
/// 1. Poll for TC packets
|
||||||
|
/// 2. Convert the raw packets to a [PusTcReader].
|
||||||
|
/// 3. Convert the PUS TC to a typed request using the [PusTcToRequestConverter].
|
||||||
|
/// 4. Route the requests using the [GenericRequestRouter].
|
||||||
|
/// 5. Add the request to the active request map using the [ActiveRequestMapProvider] abstraction.
|
||||||
|
/// 6. Check for replies which complete the forwarded request. The handler takes care of
|
||||||
|
/// the verification process.
|
||||||
|
/// 7. Check for timeouts of active requests. Generally, the timeout on the service level should
|
||||||
|
/// be highest expected timeout for the given target.
|
||||||
|
///
|
||||||
|
/// The handler exposes the following API:
|
||||||
|
///
|
||||||
|
/// 1. [Self::poll_and_handle_next_tc] which tries to poll and handle one TC packet, covering
|
||||||
|
/// steps 1-5.
|
||||||
|
/// 2. [Self::poll_and_check_next_reply] which tries to poll and handle one reply, covering step 6.
|
||||||
|
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
|
||||||
|
pub struct PusTargetedRequestService<
|
||||||
|
TcReceiver: EcssTcReceiver,
|
||||||
|
TmSender: EcssTmSender,
|
||||||
|
TcInMemConverter: EcssTcInMemConverter,
|
||||||
|
VerificationReporter: VerificationReportingProvider,
|
||||||
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
|
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
RequestType,
|
||||||
|
ReplyType,
|
||||||
|
> {
|
||||||
|
pub service_helper:
|
||||||
|
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||||
|
pub request_router: GenericRequestRouter,
|
||||||
|
pub request_converter: RequestConverter,
|
||||||
|
pub active_request_map: ActiveRequestMap,
|
||||||
|
pub reply_handler: ReplyHandler,
|
||||||
|
pub reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||||
|
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
TcReceiver: EcssTcReceiver,
|
||||||
|
TmSender: EcssTmSender,
|
||||||
|
TcInMemConverter: EcssTcInMemConverter,
|
||||||
|
VerificationReporter: VerificationReportingProvider,
|
||||||
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
|
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
RequestType,
|
||||||
|
ReplyType,
|
||||||
|
>
|
||||||
|
PusTargetedRequestService<
|
||||||
|
TcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
RequestConverter,
|
||||||
|
ReplyHandler,
|
||||||
|
ActiveRequestMap,
|
||||||
|
ActiveRequestInfo,
|
||||||
|
RequestType,
|
||||||
|
ReplyType,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
GenericRequestRouter: PusRequestRouter<RequestType, Error = GenericRoutingError>,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
service_helper: PusServiceHelper<
|
||||||
|
TcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
>,
|
||||||
|
request_converter: RequestConverter,
|
||||||
|
active_request_map: ActiveRequestMap,
|
||||||
|
reply_hook: ReplyHandler,
|
||||||
|
request_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
service_helper,
|
||||||
|
request_converter,
|
||||||
|
active_request_map,
|
||||||
|
reply_handler: reply_hook,
|
||||||
|
request_router,
|
||||||
|
reply_receiver,
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_and_handle_next_tc(
|
||||||
|
&mut self,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
|
||||||
|
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
|
||||||
|
if possible_packet.is_none() {
|
||||||
|
return Ok(PusPacketHandlerResult::Empty);
|
||||||
|
}
|
||||||
|
let ecss_tc_and_token = possible_packet.unwrap();
|
||||||
|
self.service_helper
|
||||||
|
.tc_in_mem_converter_mut()
|
||||||
|
.cache(&ecss_tc_and_token.tc_in_memory)?;
|
||||||
|
let tc = self.service_helper.tc_in_mem_converter().convert()?;
|
||||||
|
let (mut request_info, request) = match self.request_converter.convert(
|
||||||
|
ecss_tc_and_token.token,
|
||||||
|
&tc,
|
||||||
|
self.service_helper.tm_sender(),
|
||||||
|
&self.service_helper.common.verif_reporter,
|
||||||
|
time_stamp,
|
||||||
|
) {
|
||||||
|
Ok((info, req)) => (info, req),
|
||||||
|
Err(e) => {
|
||||||
|
self.handle_conversion_to_request_error(&e, ecss_tc_and_token.token, time_stamp);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let accepted_token: VerificationToken<TcStateAccepted> = request_info
|
||||||
|
.token()
|
||||||
|
.try_into()
|
||||||
|
.expect("token not in expected accepted state");
|
||||||
|
let verif_request_id = verification::RequestId::new(&tc).raw();
|
||||||
|
match self.request_router.route(
|
||||||
|
MessageMetadata::new(verif_request_id, self.service_helper.id()),
|
||||||
|
request_info.target_id(),
|
||||||
|
request,
|
||||||
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
let started_token = self
|
||||||
|
.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.start_success(
|
||||||
|
&self.service_helper.common.tm_sender,
|
||||||
|
accepted_token,
|
||||||
|
time_stamp,
|
||||||
|
)
|
||||||
|
.expect("Start success failure");
|
||||||
|
request_info.set_token(started_token.into());
|
||||||
|
self.active_request_map
|
||||||
|
.insert(&verif_request_id, request_info);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.request_router.handle_error_generic(
|
||||||
|
&request_info,
|
||||||
|
&tc,
|
||||||
|
e.clone(),
|
||||||
|
self.service_helper.tm_sender(),
|
||||||
|
self.service_helper.verif_reporter(),
|
||||||
|
time_stamp,
|
||||||
|
);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(PusPacketHandlerResult::RequestHandled)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_conversion_to_request_error(
|
||||||
|
&mut self,
|
||||||
|
error: &GenericConversionError,
|
||||||
|
token: VerificationToken<TcStateAccepted>,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) {
|
||||||
|
match error {
|
||||||
|
GenericConversionError::WrongService(service) => {
|
||||||
|
let service_slice: [u8; 1] = [*service];
|
||||||
|
self.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.completion_failure(
|
||||||
|
self.service_helper.tm_sender(),
|
||||||
|
token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SERVICE, &service_slice),
|
||||||
|
)
|
||||||
|
.expect("Sending completion failure failed");
|
||||||
|
}
|
||||||
|
GenericConversionError::InvalidSubservice(subservice) => {
|
||||||
|
let subservice_slice: [u8; 1] = [*subservice];
|
||||||
|
self.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.completion_failure(
|
||||||
|
self.service_helper.tm_sender(),
|
||||||
|
token,
|
||||||
|
FailParams::new(
|
||||||
|
time_stamp,
|
||||||
|
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||||
|
&subservice_slice,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("Sending completion failure failed");
|
||||||
|
}
|
||||||
|
GenericConversionError::NotEnoughAppData { expected, found } => {
|
||||||
|
let mut context_info = (*found as u32).to_be_bytes().to_vec();
|
||||||
|
context_info.extend_from_slice(&(*expected as u32).to_be_bytes());
|
||||||
|
self.service_helper
|
||||||
|
.verif_reporter()
|
||||||
|
.completion_failure(
|
||||||
|
self.service_helper.tm_sender(),
|
||||||
|
token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA, &context_info),
|
||||||
|
)
|
||||||
|
.expect("Sending completion failure failed");
|
||||||
|
}
|
||||||
|
// Do nothing.. this is service-level and can not be handled generically here.
|
||||||
|
GenericConversionError::InvalidAppData(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_and_check_next_reply(
|
||||||
|
&mut self,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<HandlingStatus, EcssTmtcError> {
|
||||||
|
match self.reply_receiver.try_recv() {
|
||||||
|
Ok(reply) => {
|
||||||
|
self.handle_reply(&reply, time_stamp)?;
|
||||||
|
Ok(HandlingStatus::HandledOne)
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
mpsc::TryRecvError::Empty => Ok(HandlingStatus::Empty),
|
||||||
|
mpsc::TryRecvError::Disconnected => Err(EcssTmtcError::Receive(
|
||||||
|
GenericReceiveError::TxDisconnected(None),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_reply(
|
||||||
|
&mut self,
|
||||||
|
reply: &GenericMessage<ReplyType>,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(), EcssTmtcError> {
|
||||||
|
let active_req_opt = self.active_request_map.get(reply.request_id());
|
||||||
|
if active_req_opt.is_none() {
|
||||||
|
self.reply_handler
|
||||||
|
.handle_unrequested_reply(reply, &self.service_helper.common.tm_sender)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let active_request = active_req_opt.unwrap();
|
||||||
|
let request_finished = self
|
||||||
|
.reply_handler
|
||||||
|
.handle_reply(
|
||||||
|
reply,
|
||||||
|
active_request,
|
||||||
|
&self.service_helper.common.tm_sender,
|
||||||
|
&self.service_helper.common.verif_reporter,
|
||||||
|
time_stamp,
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if request_finished {
|
||||||
|
self.active_request_map.remove(reply.request_id());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_for_request_timeouts(&mut self) {
|
||||||
|
let mut requests_to_delete = Vec::new();
|
||||||
|
self.active_request_map
|
||||||
|
.for_each(|request_id, request_info| {
|
||||||
|
if request_info.has_timed_out() {
|
||||||
|
requests_to_delete.push(*request_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !requests_to_delete.is_empty() {
|
||||||
|
for request_id in requests_to_delete {
|
||||||
|
self.active_request_map.remove(request_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic timeout handling: Handle the verification failure with a dedicated return code
|
||||||
|
/// and also log the error.
|
||||||
|
pub fn generic_pus_request_timeout_handler(
|
||||||
|
sender: &(impl EcssTmSender + ?Sized),
|
||||||
|
active_request: &(impl ActiveRequestProvider + Debug),
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
service_str: &'static str,
|
||||||
|
) -> Result<(), EcssTmtcError> {
|
||||||
|
log::warn!("timeout for active request {active_request:?} on {service_str} service");
|
||||||
|
let started_token: VerificationToken<TcStateStarted> = active_request
|
||||||
|
.token()
|
||||||
|
.try_into()
|
||||||
|
.expect("token not in expected started state");
|
||||||
|
verification_handler.completion_failure(
|
||||||
|
sender,
|
||||||
|
started_token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::REQUEST_TIMEOUT, &[]),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
|
||||||
|
use satrs::pus::{MpscTmAsVecSender, PusTmVariant};
|
||||||
|
use satrs::request::RequestId;
|
||||||
|
use satrs::{
|
||||||
|
pus::{
|
||||||
|
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
|
||||||
|
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
|
||||||
|
},
|
||||||
|
request::UniqueApidTargetId,
|
||||||
|
spacepackets::{
|
||||||
|
ecss::{
|
||||||
|
tc::{PusTcCreator, PusTcSecondaryHeader},
|
||||||
|
WritablePusPacket,
|
||||||
|
},
|
||||||
|
SpHeader,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::requests::CompositeRequest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Testbench dedicated to the testing of [PusReplyHandler]s
|
||||||
|
pub struct ReplyHandlerTestbench<
|
||||||
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
Reply,
|
||||||
|
> {
|
||||||
|
pub id: ComponentId,
|
||||||
|
pub verif_reporter: TestVerificationReporter,
|
||||||
|
pub reply_handler: ReplyHandler,
|
||||||
|
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
|
pub default_timeout: Duration,
|
||||||
|
tm_sender: MpscTmAsVecSender,
|
||||||
|
phantom: std::marker::PhantomData<(ActiveRequestInfo, Reply)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
Reply,
|
||||||
|
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
|
||||||
|
{
|
||||||
|
pub fn new(owner_id: ComponentId, reply_handler: ReplyHandler) -> Self {
|
||||||
|
let test_verif_reporter = TestVerificationReporter::new(owner_id);
|
||||||
|
let (tm_sender, tm_receiver) = mpsc::channel();
|
||||||
|
Self {
|
||||||
|
id: TEST_COMPONENT_ID_0.raw(),
|
||||||
|
verif_reporter: test_verif_reporter,
|
||||||
|
reply_handler,
|
||||||
|
default_timeout: Duration::from_secs(30),
|
||||||
|
tm_sender,
|
||||||
|
tm_receiver,
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tc(
|
||||||
|
&mut self,
|
||||||
|
apid: u16,
|
||||||
|
apid_target: u32,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> (verification::RequestId, ActivePusRequestStd) {
|
||||||
|
let sp_header = SpHeader::new_from_apid(apid);
|
||||||
|
let sec_header_dummy = PusTcSecondaryHeader::new_simple(0, 0);
|
||||||
|
let init = self.verif_reporter.add_tc(&PusTcCreator::new(
|
||||||
|
sp_header,
|
||||||
|
sec_header_dummy,
|
||||||
|
&[],
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
let accepted = self
|
||||||
|
.verif_reporter
|
||||||
|
.acceptance_success(&self.tm_sender, init, time_stamp)
|
||||||
|
.expect("acceptance failed");
|
||||||
|
let started = self
|
||||||
|
.verif_reporter
|
||||||
|
.start_success(&self.tm_sender, accepted, time_stamp)
|
||||||
|
.expect("start failed");
|
||||||
|
(
|
||||||
|
started.request_id(),
|
||||||
|
ActivePusRequestStd::new(
|
||||||
|
UniqueApidTargetId::new(apid, apid_target).raw(),
|
||||||
|
started,
|
||||||
|
self.default_timeout,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_reply(
|
||||||
|
&mut self,
|
||||||
|
reply: &GenericMessage<Reply>,
|
||||||
|
active_request: &ActiveRequestInfo,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<bool, ReplyHandler::Error> {
|
||||||
|
self.reply_handler.handle_reply(
|
||||||
|
reply,
|
||||||
|
active_request,
|
||||||
|
&self.tm_sender,
|
||||||
|
&self.verif_reporter,
|
||||||
|
time_stamp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_unrequested_reply(
|
||||||
|
&mut self,
|
||||||
|
reply: &GenericMessage<Reply>,
|
||||||
|
) -> Result<(), ReplyHandler::Error> {
|
||||||
|
self.reply_handler
|
||||||
|
.handle_unrequested_reply(reply, &self.tm_sender)
|
||||||
|
}
|
||||||
|
pub fn handle_request_timeout(
|
||||||
|
&mut self,
|
||||||
|
active_request_info: &ActiveRequestInfo,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(), ReplyHandler::Error> {
|
||||||
|
self.reply_handler.handle_request_timeout(
|
||||||
|
active_request_info,
|
||||||
|
&self.tm_sender,
|
||||||
|
&self.verif_reporter,
|
||||||
|
time_stamp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DummySender {}
|
||||||
|
|
||||||
|
/// Dummy sender component which does nothing on the [Self::send_tm] call.
|
||||||
|
///
|
||||||
|
/// Useful for unit tests.
|
||||||
|
impl EcssTmSender for DummySender {
|
||||||
|
fn send_tm(&self, _source_id: ComponentId, _tm: PusTmVariant) -> Result<(), EcssTmtcError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testbench dedicated to the testing of [PusTcToRequestConverter]s
|
||||||
|
pub struct PusConverterTestbench<
|
||||||
|
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
Request,
|
||||||
|
> {
|
||||||
|
pub id: ComponentId,
|
||||||
|
pub verif_reporter: TestVerificationReporter,
|
||||||
|
pub converter: Converter,
|
||||||
|
dummy_sender: DummySender,
|
||||||
|
current_request_id: Option<verification::RequestId>,
|
||||||
|
current_packet: Option<Vec<u8>>,
|
||||||
|
phantom: std::marker::PhantomData<(ActiveRequestInfo, Request)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
Request,
|
||||||
|
> PusConverterTestbench<Converter, ActiveRequestInfo, Request>
|
||||||
|
{
|
||||||
|
pub fn new(owner_id: ComponentId, converter: Converter) -> Self {
|
||||||
|
let test_verif_reporter = TestVerificationReporter::new(owner_id);
|
||||||
|
Self {
|
||||||
|
id: owner_id,
|
||||||
|
verif_reporter: test_verif_reporter,
|
||||||
|
converter,
|
||||||
|
dummy_sender: DummySender::default(),
|
||||||
|
current_request_id: None,
|
||||||
|
current_packet: None,
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
|
||||||
|
let token = self.verif_reporter.add_tc(tc);
|
||||||
|
self.current_request_id = Some(verification::RequestId::new(tc));
|
||||||
|
self.current_packet = Some(tc.to_vec().unwrap());
|
||||||
|
self.verif_reporter
|
||||||
|
.acceptance_success(&self.dummy_sender, token, &[])
|
||||||
|
.expect("acceptance failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_id(&self) -> Option<verification::RequestId> {
|
||||||
|
self.current_request_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert(
|
||||||
|
&mut self,
|
||||||
|
token: VerificationToken<TcStateAccepted>,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
expected_apid: u16,
|
||||||
|
expected_apid_target: u32,
|
||||||
|
) -> Result<(ActiveRequestInfo, Request), Converter::Error> {
|
||||||
|
if self.current_packet.is_none() {
|
||||||
|
return Err(GenericConversionError::InvalidAppData(
|
||||||
|
"call add_tc first".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let current_packet = self.current_packet.take().unwrap();
|
||||||
|
let tc_reader = PusTcReader::new(¤t_packet).unwrap();
|
||||||
|
let (active_info, request) = self.converter.convert(
|
||||||
|
token,
|
||||||
|
&tc_reader.0,
|
||||||
|
&self.dummy_sender,
|
||||||
|
&self.verif_reporter,
|
||||||
|
time_stamp,
|
||||||
|
)?;
|
||||||
|
assert_eq!(
|
||||||
|
active_info.token().request_id(),
|
||||||
|
self.request_id().expect("no request id is set")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
active_info.target_id(),
|
||||||
|
UniqueApidTargetId::new(expected_apid, expected_apid_target).raw()
|
||||||
|
);
|
||||||
|
Ok((active_info, request))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TargetedPusRequestTestbench<
|
||||||
|
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||||
|
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||||
|
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
|
||||||
|
ActiveRequestInfo: ActiveRequestProvider,
|
||||||
|
RequestType,
|
||||||
|
ReplyType,
|
||||||
|
> {
|
||||||
|
pub service: PusTargetedRequestService<
|
||||||
|
MpscTcReceiver,
|
||||||
|
MpscTmAsVecSender,
|
||||||
|
EcssTcInVecConverter,
|
||||||
|
TestVerificationReporter,
|
||||||
|
RequestConverter,
|
||||||
|
ReplyHandler,
|
||||||
|
ActiveRequestMap,
|
||||||
|
ActiveRequestInfo,
|
||||||
|
RequestType,
|
||||||
|
ReplyType,
|
||||||
|
>,
|
||||||
|
pub request_id: Option<RequestId>,
|
||||||
|
pub tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||||
|
pub pus_packet_tx: mpsc::Sender<EcssTcAndToken>,
|
||||||
|
pub reply_tx: mpsc::Sender<GenericMessage<ReplyType>>,
|
||||||
|
pub request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
435
satrs-example/src/pus/mode.rs
Normal file
435
satrs-example/src/pus/mode.rs
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
use log::{error, warn};
|
||||||
|
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::requests::GenericRequestRouter;
|
||||||
|
use satrs::pool::SharedStaticMemoryPool;
|
||||||
|
use satrs::pus::verification::VerificationReporter;
|
||||||
|
use satrs::pus::{
|
||||||
|
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
|
||||||
|
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult,
|
||||||
|
PusServiceHelper,
|
||||||
|
};
|
||||||
|
use satrs::request::GenericMessage;
|
||||||
|
use satrs::{
|
||||||
|
mode::{ModeAndSubmode, ModeReply, ModeRequest},
|
||||||
|
pus::{
|
||||||
|
mode::Subservice,
|
||||||
|
verification::{
|
||||||
|
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
|
||||||
|
VerificationToken,
|
||||||
|
},
|
||||||
|
ActivePusRequestStd, ActiveRequestProvider, EcssTmSender, EcssTmtcError,
|
||||||
|
GenericConversionError, PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
|
||||||
|
},
|
||||||
|
request::UniqueApidTargetId,
|
||||||
|
spacepackets::{
|
||||||
|
ecss::{
|
||||||
|
tc::PusTcReader,
|
||||||
|
tm::{PusTmCreator, PusTmSecondaryHeader},
|
||||||
|
PusPacket,
|
||||||
|
},
|
||||||
|
SpHeader,
|
||||||
|
},
|
||||||
|
ComponentId,
|
||||||
|
};
|
||||||
|
use satrs_example::config::components::PUS_MODE_SERVICE;
|
||||||
|
use satrs_example::config::{mode_err, tmtc_err};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
||||||
|
PusTargetedRequestService, TargetedPusService,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct ModeReplyHandler {
|
||||||
|
owner_id: ComponentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
|
||||||
|
type Error = EcssTmtcError;
|
||||||
|
|
||||||
|
fn handle_unrequested_reply(
|
||||||
|
&mut self,
|
||||||
|
reply: &GenericMessage<ModeReply>,
|
||||||
|
_tm_sender: &impl EcssTmSender,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
log::warn!("received unexpected reply for mode service 5: {reply:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_reply(
|
||||||
|
&mut self,
|
||||||
|
reply: &GenericMessage<ModeReply>,
|
||||||
|
active_request: &ActivePusRequestStd,
|
||||||
|
tm_sender: &impl EcssTmSender,
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<bool, Self::Error> {
|
||||||
|
let started_token: VerificationToken<TcStateStarted> = active_request
|
||||||
|
.token()
|
||||||
|
.try_into()
|
||||||
|
.expect("invalid token state");
|
||||||
|
match reply.message {
|
||||||
|
ModeReply::ModeReply(mode_reply) => {
|
||||||
|
let mut source_data: [u8; 12] = [0; 12];
|
||||||
|
mode_reply
|
||||||
|
.write_to_be_bytes(&mut source_data)
|
||||||
|
.expect("writing mode reply failed");
|
||||||
|
let req_id = verification::RequestId::from(reply.request_id());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tm(req_id.packet_id().apid(), 0, 0);
|
||||||
|
let sec_header =
|
||||||
|
PusTmSecondaryHeader::new(200, Subservice::TmModeReply as u8, 0, 0, time_stamp);
|
||||||
|
let pus_tm = PusTmCreator::new(sp_header, sec_header, &source_data, true);
|
||||||
|
tm_sender.send_tm(self.owner_id, PusTmVariant::Direct(pus_tm))?;
|
||||||
|
verification_handler.completion_success(tm_sender, started_token, time_stamp)?;
|
||||||
|
}
|
||||||
|
ModeReply::CantReachMode(error_code) => {
|
||||||
|
verification_handler.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
started_token,
|
||||||
|
FailParams::new(time_stamp, &error_code, &[]),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ModeReply::WrongMode { expected, reached } => {
|
||||||
|
let mut error_info: [u8; 24] = [0; 24];
|
||||||
|
let mut written_len = expected
|
||||||
|
.write_to_be_bytes(&mut error_info[0..ModeAndSubmode::RAW_LEN])
|
||||||
|
.expect("writing expected mode failed");
|
||||||
|
written_len += reached
|
||||||
|
.write_to_be_bytes(&mut error_info[ModeAndSubmode::RAW_LEN..])
|
||||||
|
.expect("writing reached mode failed");
|
||||||
|
verification_handler.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
started_token,
|
||||||
|
FailParams::new(
|
||||||
|
time_stamp,
|
||||||
|
&mode_err::WRONG_MODE,
|
||||||
|
&error_info[..written_len],
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request_timeout(
|
||||||
|
&mut self,
|
||||||
|
active_request: &ActivePusRequestStd,
|
||||||
|
tm_sender: &impl EcssTmSender,
|
||||||
|
verification_handler: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
generic_pus_request_timeout_handler(
|
||||||
|
tm_sender,
|
||||||
|
active_request,
|
||||||
|
verification_handler,
|
||||||
|
time_stamp,
|
||||||
|
"HK",
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModeRequestConverter {}
|
||||||
|
|
||||||
|
impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestConverter {
|
||||||
|
type Error = GenericConversionError;
|
||||||
|
|
||||||
|
fn convert(
|
||||||
|
&mut self,
|
||||||
|
token: VerificationToken<TcStateAccepted>,
|
||||||
|
tc: &PusTcReader,
|
||||||
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) -> Result<(ActivePusRequestStd, ModeRequest), Self::Error> {
|
||||||
|
let subservice = tc.subservice();
|
||||||
|
let user_data = tc.user_data();
|
||||||
|
let not_enough_app_data = |expected: usize| {
|
||||||
|
verif_reporter
|
||||||
|
.start_failure(
|
||||||
|
tm_sender,
|
||||||
|
token,
|
||||||
|
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
Err(GenericConversionError::NotEnoughAppData {
|
||||||
|
expected,
|
||||||
|
found: user_data.len(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if user_data.len() < core::mem::size_of::<u32>() {
|
||||||
|
return not_enough_app_data(4);
|
||||||
|
}
|
||||||
|
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).unwrap();
|
||||||
|
let active_request =
|
||||||
|
ActivePusRequestStd::new(target_id_and_apid.into(), token, Duration::from_secs(30));
|
||||||
|
let subservice_typed = Subservice::try_from(subservice);
|
||||||
|
let invalid_subservice = || {
|
||||||
|
// Invalid subservice
|
||||||
|
verif_reporter
|
||||||
|
.start_failure(
|
||||||
|
tm_sender,
|
||||||
|
token,
|
||||||
|
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
Err(GenericConversionError::InvalidSubservice(subservice))
|
||||||
|
};
|
||||||
|
if subservice_typed.is_err() {
|
||||||
|
return invalid_subservice();
|
||||||
|
}
|
||||||
|
let subservice_typed = subservice_typed.unwrap();
|
||||||
|
match subservice_typed {
|
||||||
|
Subservice::TcSetMode => {
|
||||||
|
if user_data.len() < core::mem::size_of::<u32>() + ModeAndSubmode::RAW_LEN {
|
||||||
|
return not_enough_app_data(4 + ModeAndSubmode::RAW_LEN);
|
||||||
|
}
|
||||||
|
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
|
||||||
|
.expect("mode and submode extraction failed");
|
||||||
|
Ok((active_request, ModeRequest::SetMode(mode_and_submode)))
|
||||||
|
}
|
||||||
|
Subservice::TcReadMode => Ok((active_request, ModeRequest::ReadMode)),
|
||||||
|
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
|
||||||
|
Subservice::TcAnnounceModeRecursive => {
|
||||||
|
Ok((active_request, ModeRequest::AnnounceModeRecursive))
|
||||||
|
}
|
||||||
|
_ => invalid_subservice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_mode_service_static(
|
||||||
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
|
tc_pool: SharedStaticMemoryPool,
|
||||||
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
mode_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
|
) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
|
let mode_request_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_MODE_SERVICE.id(),
|
||||||
|
pus_action_rx,
|
||||||
|
tm_sender,
|
||||||
|
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
|
||||||
|
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||||
|
),
|
||||||
|
ModeRequestConverter::default(),
|
||||||
|
DefaultActiveRequestMap::default(),
|
||||||
|
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
|
||||||
|
mode_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
ModeServiceWrapper {
|
||||||
|
service: mode_request_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_mode_service_dynamic(
|
||||||
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
|
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
|
mode_router: GenericRequestRouter,
|
||||||
|
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
|
||||||
|
) -> ModeServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
|
let mode_request_handler = PusTargetedRequestService::new(
|
||||||
|
PusServiceHelper::new(
|
||||||
|
PUS_MODE_SERVICE.id(),
|
||||||
|
pus_action_rx,
|
||||||
|
tm_funnel_tx,
|
||||||
|
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
|
||||||
|
EcssTcInVecConverter::default(),
|
||||||
|
),
|
||||||
|
ModeRequestConverter::default(),
|
||||||
|
DefaultActiveRequestMap::default(),
|
||||||
|
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
|
||||||
|
mode_router,
|
||||||
|
reply_receiver,
|
||||||
|
);
|
||||||
|
ModeServiceWrapper {
|
||||||
|
service: mode_request_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||||
|
pub(crate) service: PusTargetedRequestService<
|
||||||
|
MpscTcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
ModeRequestConverter,
|
||||||
|
ModeReplyHandler,
|
||||||
|
DefaultActiveRequestMap<ActivePusRequestStd>,
|
||||||
|
ActivePusRequestStd,
|
||||||
|
ModeRequest,
|
||||||
|
ModeReply,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
|
||||||
|
for ModeServiceWrapper<TmSender, TcInMemConverter>
|
||||||
|
{
|
||||||
|
/// Returns [true] if the packet handling is finished.
|
||||||
|
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
match self.service.poll_and_handle_next_tc(time_stamp) {
|
||||||
|
Ok(result) => match result {
|
||||||
|
PusPacketHandlerResult::RequestHandled => {}
|
||||||
|
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||||
|
warn!("PUS mode service: partial packet handling success: {e:?}")
|
||||||
|
}
|
||||||
|
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
|
||||||
|
warn!("PUS mode service: invalid subservice {invalid}");
|
||||||
|
}
|
||||||
|
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||||
|
warn!("PUS mode service: {subservice} not implemented");
|
||||||
|
}
|
||||||
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
error!("PUS mode service: packet handling error: {error:?}");
|
||||||
|
// To avoid permanent loops on error cases.
|
||||||
|
return HandlingStatus::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
self.service
|
||||||
|
.poll_and_check_next_reply(time_stamp)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("PUS action service: Handling reply failed with error {e:?}");
|
||||||
|
HandlingStatus::HandledOne
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_request_timeouts(&mut self) {
|
||||||
|
self.service.check_for_request_timeouts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0, TEST_UNIQUE_ID_0};
|
||||||
|
use satrs::request::MessageMetadata;
|
||||||
|
use satrs::{
|
||||||
|
mode::{ModeAndSubmode, ModeReply, ModeRequest},
|
||||||
|
pus::mode::Subservice,
|
||||||
|
request::GenericMessage,
|
||||||
|
spacepackets::{
|
||||||
|
ecss::tc::{PusTcCreator, PusTcSecondaryHeader},
|
||||||
|
SpHeader,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use satrs_example::config::tmtc_err;
|
||||||
|
|
||||||
|
use crate::pus::{
|
||||||
|
mode::ModeReplyHandler,
|
||||||
|
tests::{PusConverterTestbench, ReplyHandlerTestbench},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::ModeRequestConverter;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_converter_read_mode_request() {
|
||||||
|
let mut testbench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcReadMode as u8);
|
||||||
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||||
|
let token = testbench.add_tc(&tc);
|
||||||
|
let (_active_req, req) = testbench
|
||||||
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion has failed");
|
||||||
|
assert_eq!(req, ModeRequest::ReadMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_converter_set_mode_request() {
|
||||||
|
let mut testbench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcSetMode as u8);
|
||||||
|
let mut app_data: [u8; 4 + ModeAndSubmode::RAW_LEN] = [0; 4 + ModeAndSubmode::RAW_LEN];
|
||||||
|
let mode_and_submode = ModeAndSubmode::new(2, 1);
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
mode_and_submode
|
||||||
|
.write_to_be_bytes(&mut app_data[4..])
|
||||||
|
.unwrap();
|
||||||
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||||
|
let token = testbench.add_tc(&tc);
|
||||||
|
let (_active_req, req) = testbench
|
||||||
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion has failed");
|
||||||
|
assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_converter_announce_mode() {
|
||||||
|
let mut testbench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceMode as u8);
|
||||||
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||||
|
let token = testbench.add_tc(&tc);
|
||||||
|
let (_active_req, req) = testbench
|
||||||
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion has failed");
|
||||||
|
assert_eq!(req, ModeRequest::AnnounceMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_converter_announce_mode_recursively() {
|
||||||
|
let mut testbench =
|
||||||
|
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
|
||||||
|
let sec_header =
|
||||||
|
PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceModeRecursive as u8);
|
||||||
|
let mut app_data: [u8; 4] = [0; 4];
|
||||||
|
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||||
|
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||||
|
let token = testbench.add_tc(&tc);
|
||||||
|
let (_active_req, req) = testbench
|
||||||
|
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
|
||||||
|
.expect("conversion has failed");
|
||||||
|
assert_eq!(req, ModeRequest::AnnounceModeRecursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_unrequested_reply() {
|
||||||
|
let mut testbench = ReplyHandlerTestbench::new(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
ModeReplyHandler::new(TEST_COMPONENT_ID_0.id()),
|
||||||
|
);
|
||||||
|
let mode_reply = ModeReply::ModeReply(ModeAndSubmode::new(5, 1));
|
||||||
|
let unrequested_reply =
|
||||||
|
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), mode_reply);
|
||||||
|
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||||
|
// weird stuff.
|
||||||
|
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_handling_reply_timeout() {
|
||||||
|
let mut testbench = ReplyHandlerTestbench::new(
|
||||||
|
TEST_COMPONENT_ID_0.id(),
|
||||||
|
ModeReplyHandler::new(TEST_COMPONENT_ID_0.id()),
|
||||||
|
);
|
||||||
|
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||||
|
let result = testbench.handle_request_timeout(&active_request, &[]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
testbench.verif_reporter.assert_completion_failure(
|
||||||
|
TEST_COMPONENT_ID_0.raw(),
|
||||||
|
req_id,
|
||||||
|
None,
|
||||||
|
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,67 +1,89 @@
|
|||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::pus::create_verification_reporter;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use satrs_core::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
|
use satrs::pool::{PoolProvider, StaticMemoryPool};
|
||||||
use satrs_core::pus::scheduler::{PusScheduler, TcInfo};
|
use satrs::pus::scheduler::{PusScheduler, TcInfo};
|
||||||
use satrs_core::pus::scheduler_srv::PusService11SchedHandler;
|
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
|
||||||
use satrs_core::pus::verification::VerificationReporterWithSender;
|
use satrs::pus::verification::VerificationReporter;
|
||||||
use satrs_core::pus::{
|
use satrs::pus::{
|
||||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||||
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
|
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper,
|
||||||
PusServiceHelper,
|
|
||||||
};
|
};
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
|
||||||
use satrs_core::ChannelId;
|
use satrs::ComponentId;
|
||||||
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
|
use satrs_example::config::components::PUS_SCHED_SERVICE;
|
||||||
|
|
||||||
use crate::tmtc::PusTcSourceProviderSharedPool;
|
use super::HandlingStatus;
|
||||||
|
|
||||||
pub trait TcReleaser {
|
pub trait TcReleaser {
|
||||||
fn release(&mut self, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
|
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcReleaser for PusTcSourceProviderSharedPool {
|
impl TcReleaser for PacketSenderWithSharedPool {
|
||||||
fn release(&mut self, enabled: bool, _info: &TcInfo, tc: &[u8]) -> bool {
|
fn release(
|
||||||
|
&mut self,
|
||||||
|
sender_id: ComponentId,
|
||||||
|
enabled: bool,
|
||||||
|
_info: &TcInfo,
|
||||||
|
tc: &[u8],
|
||||||
|
) -> bool {
|
||||||
if enabled {
|
if enabled {
|
||||||
|
let shared_pool = self.shared_pool.get_mut();
|
||||||
// Transfer TC from scheduler TC pool to shared TC pool.
|
// Transfer TC from scheduler TC pool to shared TC pool.
|
||||||
let released_tc_addr = self
|
let released_tc_addr = shared_pool
|
||||||
.shared_pool
|
.0
|
||||||
.pool
|
|
||||||
.write()
|
.write()
|
||||||
.expect("locking pool failed")
|
.expect("locking pool failed")
|
||||||
.add(tc)
|
.add(tc)
|
||||||
.expect("adding TC to shared pool failed");
|
.expect("adding TC to shared pool failed");
|
||||||
self.tc_source
|
self.sender
|
||||||
.send(released_tc_addr)
|
.send(PacketInPool::new(sender_id, released_tc_addr))
|
||||||
.expect("sending TC to TC source failed");
|
.expect("sending TC to TC source failed");
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcReleaser for mpsc::Sender<Vec<u8>> {
|
impl TcReleaser for mpsc::Sender<PacketAsVec> {
|
||||||
fn release(&mut self, enabled: bool, _info: &TcInfo, tc: &[u8]) -> bool {
|
fn release(
|
||||||
|
&mut self,
|
||||||
|
sender_id: ComponentId,
|
||||||
|
enabled: bool,
|
||||||
|
_info: &TcInfo,
|
||||||
|
tc: &[u8],
|
||||||
|
) -> bool {
|
||||||
if enabled {
|
if enabled {
|
||||||
// Send released TC to centralized TC source.
|
// Send released TC to centralized TC source.
|
||||||
self.send(tc.to_vec())
|
self.send(PacketAsVec::new(sender_id, tc.to_vec()))
|
||||||
.expect("sending TC to TC source failed");
|
.expect("sending TC to TC source failed");
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
pub struct SchedulingServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
pub pus_11_handler: PusService11SchedHandler<TcInMemConverter, PusScheduler>,
|
{
|
||||||
|
pub pus_11_handler: PusSchedServiceHandler<
|
||||||
|
MpscTcReceiver,
|
||||||
|
TmSender,
|
||||||
|
TcInMemConverter,
|
||||||
|
VerificationReporter,
|
||||||
|
PusScheduler,
|
||||||
|
>,
|
||||||
pub sched_tc_pool: StaticMemoryPool,
|
pub sched_tc_pool: StaticMemoryPool,
|
||||||
pub releaser_buf: [u8; 4096],
|
pub releaser_buf: [u8; 4096],
|
||||||
pub tc_releaser: Box<dyn TcReleaser + Send>,
|
pub tc_releaser: Box<dyn TcReleaser + Send>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
|
SchedulingServiceWrapper<TmSender, TcInMemConverter>
|
||||||
|
{
|
||||||
pub fn release_tcs(&mut self) {
|
pub fn release_tcs(&mut self) {
|
||||||
|
let id = self.pus_11_handler.service_helper.id();
|
||||||
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
||||||
self.tc_releaser.release(enabled, info, tc)
|
self.tc_releaser.release(id, enabled, info, tc)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.pus_11_handler
|
self.pus_11_handler
|
||||||
@ -82,8 +104,11 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_next_packet(&mut self) -> bool {
|
pub fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
match self.pus_11_handler.handle_one_tc(&mut self.sched_tc_pool) {
|
match self
|
||||||
|
.pus_11_handler
|
||||||
|
.poll_and_handle_next_tc(time_stamp, &mut self.sched_tc_pool)
|
||||||
|
{
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
PusPacketHandlerResult::RequestHandled => {}
|
PusPacketHandlerResult::RequestHandled => {}
|
||||||
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||||
@ -95,50 +120,35 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
|
|||||||
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||||
warn!("PUS11: Subservice {subservice} not implemented");
|
warn!("PUS11: Subservice {subservice} not implemented");
|
||||||
}
|
}
|
||||||
PusPacketHandlerResult::Empty => {
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("PUS packet handling error: {error:?}")
|
error!("PUS packet handling error: {error:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
HandlingStatus::HandledOne
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_scheduler_service_static(
|
pub fn create_scheduler_service_static(
|
||||||
shared_tm_store: SharedTmPool,
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
tc_releaser: PacketSenderWithSharedPool,
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
tc_releaser: PusTcSourceProviderSharedPool,
|
|
||||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
sched_tc_pool: StaticMemoryPool,
|
sched_tc_pool: StaticMemoryPool,
|
||||||
) -> Pus11Wrapper<EcssTcInSharedStoreConverter> {
|
) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
let sched_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
|
||||||
TmSenderId::PusSched as ChannelId,
|
|
||||||
"PUS_11_TM_SENDER",
|
|
||||||
shared_tm_store.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let sched_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusSched as ChannelId,
|
|
||||||
"PUS_11_TC_RECV",
|
|
||||||
pus_sched_rx,
|
|
||||||
);
|
|
||||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||||
.expect("Creating PUS Scheduler failed");
|
.expect("Creating PUS Scheduler failed");
|
||||||
let pus_11_handler = PusService11SchedHandler::new(
|
let pus_11_handler = PusSchedServiceHandler::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
Box::new(sched_srv_receiver),
|
PUS_SCHED_SERVICE.id(),
|
||||||
Box::new(sched_srv_tm_sender),
|
pus_sched_rx,
|
||||||
PUS_APID,
|
tm_sender,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_releaser.clone_backing_pool(), 2048),
|
EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
|
||||||
),
|
),
|
||||||
scheduler,
|
scheduler,
|
||||||
);
|
);
|
||||||
Pus11Wrapper {
|
SchedulingServiceWrapper {
|
||||||
pus_11_handler,
|
pus_11_handler,
|
||||||
sched_tc_pool,
|
sched_tc_pool,
|
||||||
releaser_buf: [0; 4096],
|
releaser_buf: [0; 4096],
|
||||||
@ -147,35 +157,26 @@ pub fn create_scheduler_service_static(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_scheduler_service_dynamic(
|
pub fn create_scheduler_service_dynamic(
|
||||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
verif_reporter: VerificationReporterWithSender,
|
tc_source_sender: mpsc::Sender<PacketAsVec>,
|
||||||
tc_source_sender: mpsc::Sender<Vec<u8>>,
|
|
||||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
sched_tc_pool: StaticMemoryPool,
|
sched_tc_pool: StaticMemoryPool,
|
||||||
) -> Pus11Wrapper<EcssTcInVecConverter> {
|
) -> SchedulingServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
let sched_srv_tm_sender = MpscTmAsVecSender::new(
|
//let sched_srv_receiver =
|
||||||
TmSenderId::PusSched as ChannelId,
|
//MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx);
|
||||||
"PUS_11_TM_SENDER",
|
|
||||||
tm_funnel_tx,
|
|
||||||
);
|
|
||||||
let sched_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusSched as ChannelId,
|
|
||||||
"PUS_11_TC_RECV",
|
|
||||||
pus_sched_rx,
|
|
||||||
);
|
|
||||||
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
|
||||||
.expect("Creating PUS Scheduler failed");
|
.expect("Creating PUS Scheduler failed");
|
||||||
let pus_11_handler = PusService11SchedHandler::new(
|
let pus_11_handler = PusSchedServiceHandler::new(
|
||||||
PusServiceHelper::new(
|
PusServiceHelper::new(
|
||||||
Box::new(sched_srv_receiver),
|
PUS_SCHED_SERVICE.id(),
|
||||||
Box::new(sched_srv_tm_sender),
|
pus_sched_rx,
|
||||||
PUS_APID,
|
tm_funnel_tx,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
|
||||||
EcssTcInVecConverter::default(),
|
EcssTcInVecConverter::default(),
|
||||||
),
|
),
|
||||||
scheduler,
|
scheduler,
|
||||||
);
|
);
|
||||||
Pus11Wrapper {
|
SchedulingServiceWrapper {
|
||||||
pus_11_handler,
|
pus_11_handler,
|
||||||
sched_tc_pool,
|
sched_tc_pool,
|
||||||
releaser_buf: [0; 4096],
|
releaser_buf: [0; 4096],
|
||||||
|
@ -1,50 +1,84 @@
|
|||||||
use satrs_core::pus::EcssTcInMemConverter;
|
use crate::pus::mode::ModeServiceWrapper;
|
||||||
|
use derive_new::new;
|
||||||
use super::{
|
use satrs::{
|
||||||
action::Pus8Wrapper, event::Pus5Wrapper, hk::Pus3Wrapper, scheduler::Pus11Wrapper,
|
pus::{EcssTcInMemConverter, EcssTmSender},
|
||||||
test::Service17CustomWrapper,
|
spacepackets::time::{cds, TimeWriter},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PusStack<TcInMemConverter: EcssTcInMemConverter> {
|
use super::{
|
||||||
event_srv: Pus5Wrapper<TcInMemConverter>,
|
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
|
||||||
hk_srv: Pus3Wrapper<TcInMemConverter>,
|
scheduler::SchedulingServiceWrapper, test::TestCustomServiceWrapper, HandlingStatus,
|
||||||
action_srv: Pus8Wrapper<TcInMemConverter>,
|
TargetedPusService,
|
||||||
schedule_srv: Pus11Wrapper<TcInMemConverter>,
|
};
|
||||||
test_srv: Service17CustomWrapper<TcInMemConverter>,
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
|
||||||
|
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
|
hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
|
event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
|
action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
|
schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
|
mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> PusStack<TcInMemConverter> {
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
pub fn new(
|
PusStack<TmSender, TcInMemConverter>
|
||||||
hk_srv: Pus3Wrapper<TcInMemConverter>,
|
{
|
||||||
event_srv: Pus5Wrapper<TcInMemConverter>,
|
|
||||||
action_srv: Pus8Wrapper<TcInMemConverter>,
|
|
||||||
schedule_srv: Pus11Wrapper<TcInMemConverter>,
|
|
||||||
test_srv: Service17CustomWrapper<TcInMemConverter>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
event_srv,
|
|
||||||
action_srv,
|
|
||||||
schedule_srv,
|
|
||||||
test_srv,
|
|
||||||
hk_srv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
pub fn periodic_operation(&mut self) {
|
||||||
|
// Release all telecommands which reached their release time before calling the service
|
||||||
|
// handlers.
|
||||||
self.schedule_srv.release_tcs();
|
self.schedule_srv.release_tcs();
|
||||||
|
let time_stamp = cds::CdsTime::now_with_u16_days()
|
||||||
|
.expect("time stamp generation error")
|
||||||
|
.to_vec()
|
||||||
|
.unwrap();
|
||||||
loop {
|
loop {
|
||||||
let mut all_queues_empty = true;
|
let mut nothing_to_do = true;
|
||||||
let mut is_srv_finished = |srv_handler_finished: bool| {
|
let mut is_srv_finished =
|
||||||
if !srv_handler_finished {
|
|_srv_id: u8,
|
||||||
all_queues_empty = false;
|
tc_handling_status: HandlingStatus,
|
||||||
}
|
reply_handling_status: Option<HandlingStatus>| {
|
||||||
};
|
if tc_handling_status == HandlingStatus::HandledOne
|
||||||
is_srv_finished(self.test_srv.handle_next_packet());
|
|| (reply_handling_status.is_some()
|
||||||
is_srv_finished(self.schedule_srv.handle_next_packet());
|
&& reply_handling_status.unwrap() == HandlingStatus::HandledOne)
|
||||||
is_srv_finished(self.event_srv.handle_next_packet());
|
{
|
||||||
is_srv_finished(self.action_srv.handle_next_packet());
|
nothing_to_do = false;
|
||||||
is_srv_finished(self.hk_srv.handle_next_packet());
|
}
|
||||||
if all_queues_empty {
|
};
|
||||||
|
is_srv_finished(
|
||||||
|
17,
|
||||||
|
self.test_srv.poll_and_handle_next_packet(&time_stamp),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
is_srv_finished(
|
||||||
|
11,
|
||||||
|
self.schedule_srv.poll_and_handle_next_tc(&time_stamp),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
is_srv_finished(5, self.event_srv.poll_and_handle_next_tc(&time_stamp), None);
|
||||||
|
is_srv_finished(
|
||||||
|
8,
|
||||||
|
self.action_srv_wrapper.poll_and_handle_next_tc(&time_stamp),
|
||||||
|
Some(
|
||||||
|
self.action_srv_wrapper
|
||||||
|
.poll_and_handle_next_reply(&time_stamp),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
is_srv_finished(
|
||||||
|
3,
|
||||||
|
self.hk_srv_wrapper.poll_and_handle_next_tc(&time_stamp),
|
||||||
|
Some(self.hk_srv_wrapper.poll_and_handle_next_reply(&time_stamp)),
|
||||||
|
);
|
||||||
|
is_srv_finished(
|
||||||
|
200,
|
||||||
|
self.mode_srv.poll_and_handle_next_tc(&time_stamp),
|
||||||
|
Some(self.mode_srv.poll_and_handle_next_reply(&time_stamp)),
|
||||||
|
);
|
||||||
|
if nothing_to_do {
|
||||||
|
// Timeout checking is only done once.
|
||||||
|
self.action_srv_wrapper.check_for_request_timeouts();
|
||||||
|
self.hk_srv_wrapper.check_for_request_timeouts();
|
||||||
|
self.mode_srv.check_for_request_timeouts();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,94 +1,77 @@
|
|||||||
|
use crate::pus::create_verification_reporter;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use satrs_core::params::Params;
|
use satrs::event_man::{EventMessage, EventMessageU32};
|
||||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
use satrs::pool::SharedStaticMemoryPool;
|
||||||
use satrs_core::pus::test::PusService17TestHandler;
|
use satrs::pus::test::PusService17TestHandler;
|
||||||
use satrs_core::pus::verification::{FailParams, VerificationReporterWithSender};
|
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
|
||||||
use satrs_core::pus::{
|
use satrs::pus::EcssTcInSharedStoreConverter;
|
||||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
|
use satrs::pus::{
|
||||||
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper,
|
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
|
||||||
|
MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper,
|
||||||
};
|
};
|
||||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
use satrs_core::spacepackets::ecss::PusPacket;
|
use satrs::spacepackets::ecss::PusPacket;
|
||||||
use satrs_core::spacepackets::time::cds::TimeProvider;
|
use satrs::spacepackets::time::cds::CdsTime;
|
||||||
use satrs_core::spacepackets::time::TimeWriter;
|
use satrs::spacepackets::time::TimeWriter;
|
||||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
|
||||||
use satrs_core::ChannelId;
|
use satrs_example::config::components::PUS_TEST_SERVICE;
|
||||||
use satrs_core::{events::EventU32, pus::EcssTcInSharedStoreConverter};
|
use satrs_example::config::{tmtc_err, TEST_EVENT};
|
||||||
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID, TEST_EVENT};
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::{self, Sender};
|
|
||||||
|
use super::HandlingStatus;
|
||||||
|
|
||||||
pub fn create_test_service_static(
|
pub fn create_test_service_static(
|
||||||
shared_tm_store: SharedTmPool,
|
tm_sender: PacketSenderWithSharedPool,
|
||||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
|
||||||
verif_reporter: VerificationReporterWithSender,
|
|
||||||
tc_pool: SharedStaticMemoryPool,
|
tc_pool: SharedStaticMemoryPool,
|
||||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
event_sender: mpsc::Sender<EventMessageU32>,
|
||||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
) -> Service17CustomWrapper<EcssTcInSharedStoreConverter> {
|
) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
|
||||||
let test_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
|
||||||
TmSenderId::PusTest as ChannelId,
|
|
||||||
"PUS_17_TM_SENDER",
|
|
||||||
shared_tm_store.clone(),
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let test_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusTest as ChannelId,
|
|
||||||
"PUS_17_TC_RECV",
|
|
||||||
pus_test_rx,
|
|
||||||
);
|
|
||||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||||
Box::new(test_srv_receiver),
|
PUS_TEST_SERVICE.id(),
|
||||||
Box::new(test_srv_tm_sender),
|
pus_test_rx,
|
||||||
PUS_APID,
|
tm_sender,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
|
||||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||||
));
|
));
|
||||||
Service17CustomWrapper {
|
TestCustomServiceWrapper {
|
||||||
pus17_handler,
|
handler: pus17_handler,
|
||||||
test_srv_event_sender: event_sender,
|
test_srv_event_sender: event_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_test_service_dynamic(
|
pub fn create_test_service_dynamic(
|
||||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||||
verif_reporter: VerificationReporterWithSender,
|
event_sender: mpsc::Sender<EventMessageU32>,
|
||||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
|
||||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||||
) -> Service17CustomWrapper<EcssTcInVecConverter> {
|
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||||
let test_srv_tm_sender = MpscTmAsVecSender::new(
|
|
||||||
TmSenderId::PusTest as ChannelId,
|
|
||||||
"PUS_17_TM_SENDER",
|
|
||||||
tm_funnel_tx.clone(),
|
|
||||||
);
|
|
||||||
let test_srv_receiver = MpscTcReceiver::new(
|
|
||||||
TcReceiverId::PusTest as ChannelId,
|
|
||||||
"PUS_17_TC_RECV",
|
|
||||||
pus_test_rx,
|
|
||||||
);
|
|
||||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||||
Box::new(test_srv_receiver),
|
PUS_TEST_SERVICE.id(),
|
||||||
Box::new(test_srv_tm_sender),
|
pus_test_rx,
|
||||||
PUS_APID,
|
tm_funnel_tx,
|
||||||
verif_reporter.clone(),
|
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
|
||||||
EcssTcInVecConverter::default(),
|
EcssTcInVecConverter::default(),
|
||||||
));
|
));
|
||||||
Service17CustomWrapper {
|
TestCustomServiceWrapper {
|
||||||
pus17_handler,
|
handler: pus17_handler,
|
||||||
test_srv_event_sender: event_sender,
|
test_srv_event_sender: event_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Service17CustomWrapper<TcInMemConverter: EcssTcInMemConverter> {
|
pub struct TestCustomServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
pub pus17_handler: PusService17TestHandler<TcInMemConverter>,
|
{
|
||||||
pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>,
|
pub handler:
|
||||||
|
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||||
|
pub test_srv_event_sender: mpsc::Sender<EventMessageU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConverter> {
|
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
|
||||||
pub fn handle_next_packet(&mut self) -> bool {
|
TestCustomServiceWrapper<TmSender, TcInMemConverter>
|
||||||
let res = self.pus17_handler.handle_one_tc();
|
{
|
||||||
|
pub fn poll_and_handle_next_packet(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||||
|
let res = self.handler.poll_and_handle_next_tc(time_stamp);
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
warn!("PUS17 handler failed with error {:?}", res.unwrap_err());
|
warn!("PUS17 handler failed with error {:?}", res.unwrap_err());
|
||||||
return true;
|
return HandlingStatus::HandledOne;
|
||||||
}
|
}
|
||||||
match res.unwrap() {
|
match res.unwrap() {
|
||||||
PusPacketHandlerResult::RequestHandled => {
|
PusPacketHandlerResult::RequestHandled => {
|
||||||
@ -106,57 +89,54 @@ impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConve
|
|||||||
}
|
}
|
||||||
PusPacketHandlerResult::CustomSubservice(subservice, token) => {
|
PusPacketHandlerResult::CustomSubservice(subservice, token) => {
|
||||||
let (tc, _) = PusTcReader::new(
|
let (tc, _) = PusTcReader::new(
|
||||||
self.pus17_handler
|
self.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.tc_in_mem_converter
|
.tc_in_mem_converter
|
||||||
.tc_slice_raw(),
|
.tc_slice_raw(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let time_stamper = TimeProvider::from_now_with_u16_days().unwrap();
|
let time_stamper = CdsTime::now_with_u16_days().unwrap();
|
||||||
let mut stamp_buf: [u8; 7] = [0; 7];
|
let mut stamp_buf: [u8; 7] = [0; 7];
|
||||||
time_stamper.write_to_bytes(&mut stamp_buf).unwrap();
|
time_stamper.write_to_bytes(&mut stamp_buf).unwrap();
|
||||||
if subservice == 128 {
|
if subservice == 128 {
|
||||||
info!("Generating test event");
|
info!("Generating test event");
|
||||||
self.test_srv_event_sender
|
self.test_srv_event_sender
|
||||||
.send((TEST_EVENT.into(), None))
|
.send(EventMessage::new(PUS_TEST_SERVICE.id(), TEST_EVENT.into()))
|
||||||
.expect("Sending test event failed");
|
.expect("Sending test event failed");
|
||||||
let start_token = self
|
let start_token = self
|
||||||
.pus17_handler
|
.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.common
|
.verif_reporter()
|
||||||
.verification_handler
|
.start_success(self.handler.service_helper.tm_sender(), token, &stamp_buf)
|
||||||
.get_mut()
|
|
||||||
.start_success(token, Some(&stamp_buf))
|
|
||||||
.expect("Error sending start success");
|
.expect("Error sending start success");
|
||||||
self.pus17_handler
|
self.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.common
|
.verif_reporter()
|
||||||
.verification_handler
|
.completion_success(
|
||||||
.get_mut()
|
self.handler.service_helper.tm_sender(),
|
||||||
.completion_success(start_token, Some(&stamp_buf))
|
start_token,
|
||||||
|
&stamp_buf,
|
||||||
|
)
|
||||||
.expect("Error sending completion success");
|
.expect("Error sending completion success");
|
||||||
} else {
|
} else {
|
||||||
let fail_data = [tc.subservice()];
|
let fail_data = [tc.subservice()];
|
||||||
self.pus17_handler
|
self.handler
|
||||||
.service_helper
|
.service_helper
|
||||||
.common
|
.verif_reporter()
|
||||||
.verification_handler
|
|
||||||
.get_mut()
|
|
||||||
.start_failure(
|
.start_failure(
|
||||||
|
self.handler.service_helper.tm_sender(),
|
||||||
token,
|
token,
|
||||||
FailParams::new(
|
FailParams::new(
|
||||||
Some(&stamp_buf),
|
&stamp_buf,
|
||||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||||
Some(&fail_data),
|
&fail_data,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Sending start failure verification failed");
|
.expect("Sending start failure verification failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PusPacketHandlerResult::Empty => {
|
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
false
|
HandlingStatus::HandledOne
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,152 @@
|
|||||||
use derive_new::new;
|
use std::collections::HashMap;
|
||||||
use satrs_core::hk::HkRequest;
|
use std::sync::mpsc;
|
||||||
use satrs_core::mode::ModeRequest;
|
|
||||||
use satrs_core::pus::verification::{TcStateAccepted, VerificationToken};
|
|
||||||
use satrs_example::TargetIdWithApid;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
use log::warn;
|
||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
use satrs::action::ActionRequest;
|
||||||
pub enum ActionRequest {
|
use satrs::hk::HkRequest;
|
||||||
CmdWithU32Id((u32, Vec<u8>)),
|
use satrs::mode::ModeRequest;
|
||||||
CmdWithStringId((String, Vec<u8>)),
|
use satrs::pus::verification::{
|
||||||
}
|
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||||
|
};
|
||||||
|
use satrs::pus::{ActiveRequestProvider, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
||||||
|
use satrs::queue::GenericSendError;
|
||||||
|
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||||
|
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||||
|
use satrs::spacepackets::ecss::PusPacket;
|
||||||
|
use satrs::ComponentId;
|
||||||
|
use satrs_example::config::components::PUS_ROUTING_SERVICE;
|
||||||
|
use satrs_example::config::tmtc_err;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[derive(Clone, Debug)]
|
||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Request {
|
pub enum CompositeRequest {
|
||||||
Hk(HkRequest),
|
Hk(HkRequest),
|
||||||
Mode(ModeRequest),
|
|
||||||
Action(ActionRequest),
|
Action(ActionRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, new)]
|
#[derive(Clone)]
|
||||||
pub struct TargetedRequest {
|
pub struct GenericRequestRouter {
|
||||||
pub(crate) target_id_with_apid: TargetIdWithApid,
|
pub id: ComponentId,
|
||||||
pub(crate) request: Request,
|
// All messages which do not have a dedicated queue.
|
||||||
|
pub composite_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<CompositeRequest>>>,
|
||||||
|
pub mode_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<ModeRequest>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
impl Default for GenericRequestRouter {
|
||||||
pub struct RequestWithToken {
|
fn default() -> Self {
|
||||||
pub(crate) targeted_request: TargetedRequest,
|
|
||||||
pub(crate) token: VerificationToken<TcStateAccepted>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RequestWithToken {
|
|
||||||
pub fn new(
|
|
||||||
target_id: TargetIdWithApid,
|
|
||||||
request: Request,
|
|
||||||
token: VerificationToken<TcStateAccepted>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
targeted_request: TargetedRequest::new(target_id, request),
|
id: PUS_ROUTING_SERVICE.raw(),
|
||||||
token,
|
composite_router_map: Default::default(),
|
||||||
|
mode_router_map: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl GenericRequestRouter {
|
||||||
|
pub(crate) fn handle_error_generic(
|
||||||
|
&self,
|
||||||
|
active_request: &impl ActiveRequestProvider,
|
||||||
|
tc: &PusTcReader,
|
||||||
|
error: GenericRoutingError,
|
||||||
|
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||||
|
verif_reporter: &impl VerificationReportingProvider,
|
||||||
|
time_stamp: &[u8],
|
||||||
|
) {
|
||||||
|
warn!(
|
||||||
|
"Routing request for service {} failed: {error:?}",
|
||||||
|
tc.service()
|
||||||
|
);
|
||||||
|
let accepted_token: VerificationToken<TcStateAccepted> = active_request
|
||||||
|
.token()
|
||||||
|
.try_into()
|
||||||
|
.expect("token is not in accepted state");
|
||||||
|
match error {
|
||||||
|
GenericRoutingError::UnknownTargetId(id) => {
|
||||||
|
let apid_target_id = UniqueApidTargetId::from(id);
|
||||||
|
warn!("Target APID for request: {}", apid_target_id.apid);
|
||||||
|
warn!("Target Unique ID for request: {}", apid_target_id.unique_id);
|
||||||
|
let mut fail_data: [u8; 8] = [0; 8];
|
||||||
|
fail_data.copy_from_slice(&id.to_be_bytes());
|
||||||
|
verif_reporter
|
||||||
|
.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
accepted_token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::UNKNOWN_TARGET_ID, &fail_data),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
}
|
||||||
|
GenericRoutingError::Send(_) => {
|
||||||
|
let mut fail_data: [u8; 8] = [0; 8];
|
||||||
|
fail_data.copy_from_slice(&active_request.target_id().to_be_bytes());
|
||||||
|
verif_reporter
|
||||||
|
.completion_failure(
|
||||||
|
tm_sender,
|
||||||
|
accepted_token,
|
||||||
|
FailParams::new(time_stamp, &tmtc_err::ROUTING_ERROR, &fail_data),
|
||||||
|
)
|
||||||
|
.expect("Sending start failure failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PusRequestRouter<HkRequest> for GenericRequestRouter {
|
||||||
|
type Error = GenericRoutingError;
|
||||||
|
|
||||||
|
fn route(
|
||||||
|
&self,
|
||||||
|
requestor_info: MessageMetadata,
|
||||||
|
target_id: ComponentId,
|
||||||
|
hk_request: HkRequest,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if let Some(sender) = self.composite_router_map.get(&target_id) {
|
||||||
|
sender
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
requestor_info,
|
||||||
|
CompositeRequest::Hk(hk_request),
|
||||||
|
))
|
||||||
|
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(GenericRoutingError::UnknownTargetId(target_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PusRequestRouter<ActionRequest> for GenericRequestRouter {
|
||||||
|
type Error = GenericRoutingError;
|
||||||
|
|
||||||
|
fn route(
|
||||||
|
&self,
|
||||||
|
requestor_info: MessageMetadata,
|
||||||
|
target_id: ComponentId,
|
||||||
|
action_request: ActionRequest,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if let Some(sender) = self.composite_router_map.get(&target_id) {
|
||||||
|
sender
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
requestor_info,
|
||||||
|
CompositeRequest::Action(action_request),
|
||||||
|
))
|
||||||
|
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(GenericRoutingError::UnknownTargetId(target_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PusRequestRouter<ModeRequest> for GenericRequestRouter {
|
||||||
|
type Error = GenericRoutingError;
|
||||||
|
|
||||||
|
fn route(
|
||||||
|
&self,
|
||||||
|
requestor_info: MessageMetadata,
|
||||||
|
target_id: ComponentId,
|
||||||
|
request: ModeRequest,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if let Some(sender) = self.mode_router_map.get(&target_id) {
|
||||||
|
sender
|
||||||
|
.send(GenericMessage::new(requestor_info, request))
|
||||||
|
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(GenericRoutingError::UnknownTargetId(target_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use log::{info, warn};
|
|
||||||
use satrs_core::{
|
|
||||||
hal::std::tcp_server::{ServerConfig, TcpSpacepacketsServer},
|
|
||||||
spacepackets::PacketId,
|
|
||||||
tmtc::{CcsdsDistributor, CcsdsError, TmPacketSourceCore},
|
|
||||||
};
|
|
||||||
use satrs_example::config::PUS_APID;
|
|
||||||
|
|
||||||
pub const PACKET_ID_LOOKUP: &[PacketId] = &[PacketId::const_tc(true, PUS_APID)];
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct SyncTcpTmSource {
|
|
||||||
tm_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
|
||||||
max_packets_stored: usize,
|
|
||||||
pub silent_packet_overwrite: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyncTcpTmSource {
|
|
||||||
pub fn new(max_packets_stored: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
tm_queue: Arc::default(),
|
|
||||||
max_packets_stored,
|
|
||||||
silent_packet_overwrite: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_tm(&mut self, tm: &[u8]) {
|
|
||||||
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec");
|
|
||||||
if tm_queue.len() > self.max_packets_stored {
|
|
||||||
if !self.silent_packet_overwrite {
|
|
||||||
warn!("TPC TM source is full, deleting oldest packet");
|
|
||||||
}
|
|
||||||
tm_queue.pop_front();
|
|
||||||
}
|
|
||||||
tm_queue.push_back(tm.to_vec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TmPacketSourceCore for SyncTcpTmSource {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
|
|
||||||
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed");
|
|
||||||
if !tm_queue.is_empty() {
|
|
||||||
let next_vec = tm_queue.front().unwrap();
|
|
||||||
if buffer.len() < next_vec.len() {
|
|
||||||
panic!(
|
|
||||||
"provided buffer too small, must be at least {} bytes",
|
|
||||||
next_vec.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let next_vec = tm_queue.pop_front().unwrap();
|
|
||||||
buffer[0..next_vec.len()].copy_from_slice(&next_vec);
|
|
||||||
if next_vec.len() > 9 {
|
|
||||||
let service = next_vec[7];
|
|
||||||
let subservice = next_vec[8];
|
|
||||||
info!("Sending PUS TM[{service},{subservice}]")
|
|
||||||
} else {
|
|
||||||
info!("Sending PUS TM");
|
|
||||||
}
|
|
||||||
return Ok(next_vec.len());
|
|
||||||
}
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TcpTask<MpscErrorType: 'static> {
|
|
||||||
server: TcpSpacepacketsServer<
|
|
||||||
(),
|
|
||||||
CcsdsError<MpscErrorType>,
|
|
||||||
SyncTcpTmSource,
|
|
||||||
CcsdsDistributor<MpscErrorType>,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<MpscErrorType: 'static + core::fmt::Debug> TcpTask<MpscErrorType> {
|
|
||||||
pub fn new(
|
|
||||||
cfg: ServerConfig,
|
|
||||||
tm_source: SyncTcpTmSource,
|
|
||||||
tc_receiver: CcsdsDistributor<MpscErrorType>,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
Ok(Self {
|
|
||||||
server: TcpSpacepacketsServer::new(
|
|
||||||
cfg,
|
|
||||||
tm_source,
|
|
||||||
tc_receiver,
|
|
||||||
Box::new(PACKET_ID_LOOKUP),
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
loop {
|
|
||||||
let result = self.server.handle_next_connection();
|
|
||||||
match result {
|
|
||||||
Ok(conn_result) => {
|
|
||||||
info!(
|
|
||||||
"Served {} TMs and {} TCs for client {:?}",
|
|
||||||
conn_result.num_sent_tms, conn_result.num_received_tcs, conn_result.addr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("TCP server error: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
use log::warn;
|
|
||||||
use satrs_core::pus::{EcssTcAndToken, ReceivesEcssPusTc};
|
|
||||||
use satrs_core::spacepackets::SpHeader;
|
|
||||||
use std::sync::mpsc::{self, Receiver, SendError, Sender, TryRecvError};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::pus::PusReceiver;
|
|
||||||
use satrs_core::pool::{PoolProvider, SharedStaticMemoryPool, StoreAddr, StoreError};
|
|
||||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
|
||||||
use satrs_core::spacepackets::ecss::PusPacket;
|
|
||||||
use satrs_core::tmtc::ReceivesCcsdsTc;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
|
||||||
pub enum MpscStoreAndSendError {
|
|
||||||
#[error("Store error: {0}")]
|
|
||||||
Store(#[from] StoreError),
|
|
||||||
#[error("TC send error: {0}")]
|
|
||||||
TcSend(#[from] SendError<EcssTcAndToken>),
|
|
||||||
#[error("TMTC send error: {0}")]
|
|
||||||
TmTcSend(#[from] SendError<StoreAddr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SharedTcPool {
|
|
||||||
pub pool: SharedStaticMemoryPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SharedTcPool {
|
|
||||||
pub fn add_pus_tc(&mut self, pus_tc: &PusTcReader) -> Result<StoreAddr, StoreError> {
|
|
||||||
let mut pg = self.pool.write().expect("error locking TC store");
|
|
||||||
let addr = pg.free_element(pus_tc.len_packed(), |buf| {
|
|
||||||
buf[0..pus_tc.len_packed()].copy_from_slice(pus_tc.raw_data());
|
|
||||||
})?;
|
|
||||||
Ok(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PusTcSourceProviderSharedPool {
|
|
||||||
pub tc_source: Sender<StoreAddr>,
|
|
||||||
pub shared_pool: SharedTcPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PusTcSourceProviderSharedPool {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn clone_backing_pool(&self) -> SharedStaticMemoryPool {
|
|
||||||
self.shared_pool.pool.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReceivesEcssPusTc for PusTcSourceProviderSharedPool {
|
|
||||||
type Error = MpscStoreAndSendError;
|
|
||||||
|
|
||||||
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
|
||||||
let addr = self.shared_pool.add_pus_tc(pus_tc)?;
|
|
||||||
self.tc_source.send(addr)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReceivesCcsdsTc for PusTcSourceProviderSharedPool {
|
|
||||||
type Error = MpscStoreAndSendError;
|
|
||||||
|
|
||||||
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
let mut pool = self.shared_pool.pool.write().expect("locking pool failed");
|
|
||||||
let addr = pool.add(tc_raw)?;
|
|
||||||
drop(pool);
|
|
||||||
self.tc_source.send(addr)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newtype, can not implement necessary traits on MPSC sender directly because of orphan rules.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PusTcSourceProviderDynamic(pub Sender<Vec<u8>>);
|
|
||||||
|
|
||||||
impl ReceivesEcssPusTc for PusTcSourceProviderDynamic {
|
|
||||||
type Error = SendError<Vec<u8>>;
|
|
||||||
|
|
||||||
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
|
||||||
self.0.send(pus_tc.raw_data().to_vec())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReceivesCcsdsTc for PusTcSourceProviderDynamic {
|
|
||||||
type Error = mpsc::SendError<Vec<u8>>;
|
|
||||||
|
|
||||||
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
self.0.send(tc_raw.to_vec())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TC source components where static pools are the backing memory of the received telecommands.
|
|
||||||
pub struct TcSourceTaskStatic {
|
|
||||||
shared_tc_pool: SharedTcPool,
|
|
||||||
tc_receiver: Receiver<StoreAddr>,
|
|
||||||
tc_buf: [u8; 4096],
|
|
||||||
pus_receiver: PusReceiver,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcSourceTaskStatic {
|
|
||||||
pub fn new(
|
|
||||||
shared_tc_pool: SharedTcPool,
|
|
||||||
tc_receiver: Receiver<StoreAddr>,
|
|
||||||
pus_receiver: PusReceiver,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
shared_tc_pool,
|
|
||||||
tc_receiver,
|
|
||||||
tc_buf: [0; 4096],
|
|
||||||
pus_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
self.poll_tc();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_tc(&mut self) -> bool {
|
|
||||||
match self.tc_receiver.try_recv() {
|
|
||||||
Ok(addr) => {
|
|
||||||
let pool = self
|
|
||||||
.shared_tc_pool
|
|
||||||
.pool
|
|
||||||
.read()
|
|
||||||
.expect("locking tc pool failed");
|
|
||||||
pool.read(&addr, &mut self.tc_buf)
|
|
||||||
.expect("reading pool failed");
|
|
||||||
drop(pool);
|
|
||||||
match PusTcReader::new(&self.tc_buf) {
|
|
||||||
Ok((pus_tc, _)) => {
|
|
||||||
self.pus_receiver
|
|
||||||
.handle_tc_packet(
|
|
||||||
satrs_core::pus::TcInMemory::StoreAddr(addr),
|
|
||||||
pus_tc.service(),
|
|
||||||
&pus_tc,
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("error creating PUS TC from raw data: {e}");
|
|
||||||
warn!("raw data: {:x?}", self.tc_buf);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => false,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
warn!("tmtc thread: sender disconnected");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TC source components where the heap is the backing memory of the received telecommands.
|
|
||||||
pub struct TcSourceTaskDynamic {
|
|
||||||
pub tc_receiver: Receiver<Vec<u8>>,
|
|
||||||
pus_receiver: PusReceiver,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcSourceTaskDynamic {
|
|
||||||
pub fn new(tc_receiver: Receiver<Vec<u8>>, pus_receiver: PusReceiver) -> Self {
|
|
||||||
Self {
|
|
||||||
tc_receiver,
|
|
||||||
pus_receiver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn periodic_operation(&mut self) {
|
|
||||||
self.poll_tc();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_tc(&mut self) -> bool {
|
|
||||||
match self.tc_receiver.try_recv() {
|
|
||||||
Ok(tc) => match PusTcReader::new(&tc) {
|
|
||||||
Ok((pus_tc, _)) => {
|
|
||||||
self.pus_receiver
|
|
||||||
.handle_tc_packet(
|
|
||||||
satrs_core::pus::TcInMemory::Vec(tc.clone()),
|
|
||||||
pus_tc.service(),
|
|
||||||
&pus_tc,
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("error creating PUS TC from raw data: {e}");
|
|
||||||
warn!("raw data: {:x?}", tc);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => match e {
|
|
||||||
TryRecvError::Empty => false,
|
|
||||||
TryRecvError::Disconnected => {
|
|
||||||
warn!("tmtc thread: sender disconnected");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
2
satrs-example/src/tmtc/mod.rs
Normal file
2
satrs-example/src/tmtc/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod tc_source;
|
||||||
|
pub mod tm_sink;
|
106
satrs-example/src/tmtc/tc_source.rs
Normal file
106
satrs-example/src/tmtc/tc_source.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use satrs::{
|
||||||
|
pool::PoolProvider,
|
||||||
|
tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
|
||||||
|
};
|
||||||
|
use std::sync::mpsc::{self, TryRecvError};
|
||||||
|
|
||||||
|
use satrs::pus::MpscTmAsVecSender;
|
||||||
|
|
||||||
|
use crate::pus::{HandlingStatus, PusTcDistributor};
|
||||||
|
|
||||||
|
// TC source components where static pools are the backing memory of the received telecommands.
|
||||||
|
pub struct TcSourceTaskStatic {
|
||||||
|
shared_tc_pool: SharedPacketPool,
|
||||||
|
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||||
|
tc_buf: [u8; 4096],
|
||||||
|
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcSourceTaskStatic {
|
||||||
|
pub fn new(
|
||||||
|
shared_tc_pool: SharedPacketPool,
|
||||||
|
tc_receiver: mpsc::Receiver<PacketInPool>,
|
||||||
|
pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
shared_tc_pool,
|
||||||
|
tc_receiver,
|
||||||
|
tc_buf: [0; 4096],
|
||||||
|
pus_distributor: pus_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.poll_tc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_tc(&mut self) -> HandlingStatus {
|
||||||
|
// Right now, we only expect ECSS PUS packets.
|
||||||
|
// If packets like CFDP are expected, we might have to check the APID first.
|
||||||
|
match self.tc_receiver.try_recv() {
|
||||||
|
Ok(packet_in_pool) => {
|
||||||
|
let pool = self
|
||||||
|
.shared_tc_pool
|
||||||
|
.0
|
||||||
|
.read()
|
||||||
|
.expect("locking tc pool failed");
|
||||||
|
pool.read(&packet_in_pool.store_addr, &mut self.tc_buf)
|
||||||
|
.expect("reading pool failed");
|
||||||
|
drop(pool);
|
||||||
|
self.pus_distributor
|
||||||
|
.handle_tc_packet_in_store(packet_in_pool, &self.tc_buf)
|
||||||
|
.ok();
|
||||||
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
TryRecvError::Empty => HandlingStatus::Empty,
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
log::warn!("tmtc thread: sender disconnected");
|
||||||
|
HandlingStatus::Empty
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TC source components where the heap is the backing memory of the received telecommands.
|
||||||
|
pub struct TcSourceTaskDynamic {
|
||||||
|
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
|
pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcSourceTaskDynamic {
|
||||||
|
pub fn new(
|
||||||
|
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
|
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tc_receiver,
|
||||||
|
pus_distributor: pus_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn periodic_operation(&mut self) {
|
||||||
|
self.poll_tc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_tc(&mut self) -> HandlingStatus {
|
||||||
|
// Right now, we only expect ECSS PUS packets.
|
||||||
|
// If packets like CFDP are expected, we might have to check the APID first.
|
||||||
|
match self.tc_receiver.try_recv() {
|
||||||
|
Ok(packet_as_vec) => {
|
||||||
|
self.pus_distributor
|
||||||
|
.handle_tc_packet_vec(packet_as_vec)
|
||||||
|
.ok();
|
||||||
|
HandlingStatus::HandledOne
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
TryRecvError::Empty => HandlingStatus::Empty,
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
log::warn!("tmtc thread: sender disconnected");
|
||||||
|
HandlingStatus::Empty
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::mpsc::{Receiver, Sender},
|
sync::mpsc::{self},
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use satrs_core::{
|
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
|
||||||
pool::{PoolProvider, StoreAddr},
|
use satrs::{
|
||||||
|
pool::PoolProvider,
|
||||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||||
time::cds::MIN_CDS_FIELD_LEN,
|
time::cds::MIN_CDS_FIELD_LEN,
|
||||||
CcsdsPacket,
|
CcsdsPacket,
|
||||||
},
|
},
|
||||||
tmtc::tm_helper::SharedTmPool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tcp::SyncTcpTmSource;
|
use crate::interface::tcp::SyncTcpTmSource;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CcsdsSeqCounterMap {
|
pub struct CcsdsSeqCounterMap {
|
||||||
@ -76,17 +76,17 @@ impl TmFunnelCommon {
|
|||||||
|
|
||||||
pub struct TmFunnelStatic {
|
pub struct TmFunnelStatic {
|
||||||
common: TmFunnelCommon,
|
common: TmFunnelCommon,
|
||||||
shared_tm_store: SharedTmPool,
|
shared_tm_store: SharedPacketPool,
|
||||||
tm_funnel_rx: Receiver<StoreAddr>,
|
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
|
||||||
tm_server_tx: Sender<StoreAddr>,
|
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TmFunnelStatic {
|
impl TmFunnelStatic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
shared_tm_store: SharedTmPool,
|
shared_tm_store: SharedPacketPool,
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
sync_tm_tcp_source: SyncTcpTmSource,
|
||||||
tm_funnel_rx: Receiver<StoreAddr>,
|
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
|
||||||
tm_server_tx: Sender<StoreAddr>,
|
tm_server_tx: mpsc::SyncSender<PacketInPool>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||||
@ -97,14 +97,14 @@ impl TmFunnelStatic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn operation(&mut self) {
|
pub fn operation(&mut self) {
|
||||||
if let Ok(addr) = self.tm_funnel_rx.recv() {
|
if let Ok(pus_tm_in_pool) = self.tm_funnel_rx.recv() {
|
||||||
// Read the TM, set sequence counter and message counter, and finally update
|
// Read the TM, set sequence counter and message counter, and finally update
|
||||||
// the CRC.
|
// the CRC.
|
||||||
let shared_pool = self.shared_tm_store.clone_backing_pool();
|
let shared_pool = self.shared_tm_store.0.clone();
|
||||||
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
|
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
|
||||||
let mut tm_copy = Vec::new();
|
let mut tm_copy = Vec::new();
|
||||||
pool_guard
|
pool_guard
|
||||||
.modify(&addr, |buf| {
|
.modify(&pus_tm_in_pool.store_addr, |buf| {
|
||||||
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
|
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
|
||||||
.expect("Creating TM zero copy writer failed");
|
.expect("Creating TM zero copy writer failed");
|
||||||
self.common.apply_packet_processing(zero_copy_writer);
|
self.common.apply_packet_processing(zero_copy_writer);
|
||||||
@ -112,7 +112,7 @@ impl TmFunnelStatic {
|
|||||||
})
|
})
|
||||||
.expect("Reading TM from pool failed");
|
.expect("Reading TM from pool failed");
|
||||||
self.tm_server_tx
|
self.tm_server_tx
|
||||||
.send(addr)
|
.send(pus_tm_in_pool)
|
||||||
.expect("Sending TM to server failed");
|
.expect("Sending TM to server failed");
|
||||||
// We could also do this step in the update closure, but I'd rather avoid this, could
|
// We could also do this step in the update closure, but I'd rather avoid this, could
|
||||||
// lead to nested locking.
|
// lead to nested locking.
|
||||||
@ -123,15 +123,15 @@ impl TmFunnelStatic {
|
|||||||
|
|
||||||
pub struct TmFunnelDynamic {
|
pub struct TmFunnelDynamic {
|
||||||
common: TmFunnelCommon,
|
common: TmFunnelCommon,
|
||||||
tm_funnel_rx: Receiver<Vec<u8>>,
|
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||||
tm_server_tx: Sender<Vec<u8>>,
|
tm_server_tx: mpsc::Sender<PacketAsVec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TmFunnelDynamic {
|
impl TmFunnelDynamic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sync_tm_tcp_source: SyncTcpTmSource,
|
sync_tm_tcp_source: SyncTcpTmSource,
|
||||||
tm_funnel_rx: Receiver<Vec<u8>>,
|
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||||
tm_server_tx: Sender<Vec<u8>>,
|
tm_server_tx: mpsc::Sender<PacketAsVec>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||||
@ -144,13 +144,13 @@ impl TmFunnelDynamic {
|
|||||||
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
||||||
// Read the TM, set sequence counter and message counter, and finally update
|
// Read the TM, set sequence counter and message counter, and finally update
|
||||||
// the CRC.
|
// the CRC.
|
||||||
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm, MIN_CDS_FIELD_LEN)
|
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
|
||||||
.expect("Creating TM zero copy writer failed");
|
.expect("Creating TM zero copy writer failed");
|
||||||
self.common.apply_packet_processing(zero_copy_writer);
|
self.common.apply_packet_processing(zero_copy_writer);
|
||||||
|
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
|
||||||
self.tm_server_tx
|
self.tm_server_tx
|
||||||
.send(tm.clone())
|
.send(tm)
|
||||||
.expect("Sending TM to server failed");
|
.expect("Sending TM to server failed");
|
||||||
self.common.sync_tm_tcp_source.add_tm(&tm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,3 +7,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
|
# [v0.1.2] 2024-04-17
|
||||||
|
|
||||||
|
Allow `satrs-shared` from `v0.1.3` to `<v0.2`.
|
||||||
|
|
||||||
|
# [v0.1.1] 2024-02-17
|
||||||
|
|
||||||
|
- Bumped `spacepackets` to v0.10.0
|
||||||
|
|
||||||
|
# [v0.1.0] 2024-02-12
|
||||||
|
|
||||||
|
Initial release containing the `resultcode` macro.
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "satrs-mib"
|
name = "satrs-mib"
|
||||||
version = "0.1.0-alpha.2"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.61"
|
rust-version = "1.61"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
description = """
|
description = """
|
||||||
Helper crate of the sat-rs framework to build a mission information base (MIB) from the
|
Helper crate of the sat-rs framework to build a mission information base (MIB) from the
|
||||||
On-Board Software (OBSW) code directly."""
|
On-Board Software (OBSW) code directly."""
|
||||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
keywords = ["no-std", "space", "aerospace"]
|
keywords = ["no-std", "space", "aerospace"]
|
||||||
@ -22,16 +22,13 @@ serde-hex = "0.1.0"
|
|||||||
version = "1"
|
version = "1"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.satrs-core]
|
[dependencies.satrs-shared]
|
||||||
version = "0.1.0-alpha.3"
|
version = ">=0.1.3, <0.2"
|
||||||
# path = "../satrs-core"
|
features = ["serde"]
|
||||||
# git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git"
|
|
||||||
# branch = "main"
|
|
||||||
# rev = "35e1f7a983f6535c5571186e361fe101d4306b89"
|
|
||||||
|
|
||||||
[dependencies.satrs-mib-codegen]
|
[dependencies.satrs-mib-codegen]
|
||||||
path = "codegen"
|
path = "codegen"
|
||||||
version = "0.1.0-alpha.2"
|
version = "0.1.2"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
|
@ -1,2 +1,8 @@
|
|||||||
|
[](https://crates.io/crates/satrs-mib)
|
||||||
|
[](https://docs.rs/satrs-mib)
|
||||||
|
|
||||||
satrs-mib
|
satrs-mib
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
This helper crate contains the procedural macros for the sat-rs framework.
|
||||||
|
You can find more information on the [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user