Compare commits
65 Commits
satrs-mib-
...
f3f928f79f
Author | SHA1 | Date | |
---|---|---|---|
f3f928f79f
|
|||
249eb12bb4 | |||
f21ab0017e | |||
a7ca00317f
|
|||
75fda42f4f
|
|||
7380a3b24c
|
|||
97a8035333
|
|||
f91075a598
|
|||
1d0460a63f
|
|||
85b9f6a002
|
|||
75c687feed | |||
b5b5e92863 | |||
0d5f06c589 | |||
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 |
@ -1,9 +1,10 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"satrs-core",
|
||||
"satrs",
|
||||
"satrs-mib",
|
||||
"satrs-example",
|
||||
"satrs-shared",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
|
17
README.md
17
README.md
@ -1,5 +1,10 @@
|
||||
<p align="center"> <img src="misc/satrs-logo.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
|
||||
=========
|
||||
|
||||
@ -7,7 +12,7 @@ This is the repository of the sat-rs framework. Its primary goal is to provide r
|
||||
to write on-board software for remote systems like rovers or satellites. It is specifically written
|
||||
for the special requirements for these systems. You can find an overview of the project and the
|
||||
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
|
||||
at the [IRS 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/).
|
||||
|
||||
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
|
||||
@ -22,12 +27,12 @@ This project currently contains following crates:
|
||||
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
|
||||
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):
|
||||
Core components of sat-rs.
|
||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-example):
|
||||
* [`satrs`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs):
|
||||
Primary crate.
|
||||
* [`satrs-example`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example):
|
||||
Example of a simple example on-board software using various sat-rs components which can be run
|
||||
on a host computer or on any system with a standard runtime like a Raspberry Pi.
|
||||
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/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.
|
||||
* [`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
|
||||
@ -41,7 +46,7 @@ Each project has its own `CHANGELOG.md`.
|
||||
|
||||
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
|
||||
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.
|
||||
|
||||
# Coverage
|
||||
|
@ -15,7 +15,9 @@ RUN rustup install nightly && \
|
||||
rustup component add rustfmt clippy
|
||||
|
||||
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/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && \
|
||||
unzip mdbook-linkcheck.zip && \
|
||||
|
4
automation/Jenkinsfile
vendored
4
automation/Jenkinsfile
vendored
@ -32,7 +32,7 @@ pipeline {
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'cargo test --all-features'
|
||||
sh 'cargo nextest r --all-features'
|
||||
}
|
||||
}
|
||||
stage('Check with all features') {
|
||||
@ -47,7 +47,7 @@ pipeline {
|
||||
}
|
||||
stage('Check Cross Embedded Bare Metal') {
|
||||
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') {
|
||||
|
@ -43,8 +43,8 @@ def main():
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--package",
|
||||
choices=["satrs-core"],
|
||||
default="satrs-core",
|
||||
choices=["satrs"],
|
||||
default="satrs",
|
||||
help="Choose project to generate coverage for",
|
||||
)
|
||||
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
@ -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
|
||||
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/host/udp_server/index.html).
|
||||
UDP is already packet based which makes it an excellent fit for exchanging space packets.
|
||||
2. [TCP TMTC Server Components](https://docs.rs/satrs-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
|
||||
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.
|
||||
- [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
|
||||
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
|
||||
|
||||
|
@ -26,15 +26,17 @@ For example, a very small telecommand (TC) pool might look like this:
|
||||

|
||||
|
||||
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
|
||||
of how the data is stored.
|
||||
|
||||
This trait is implemented by a static memory pool implementation.
|
||||
The code to generate this static pool would look like this:
|
||||
|
||||
```rust
|
||||
use satrs_core::pool::{StaticMemoryPool, StaticPoolConfig};
|
||||
<!-- Would be nice to test this code sample, but need to wait
|
||||
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
|
||||
- [`StaticMemoryPool` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticMemoryPool.html)
|
||||
- [`StaticPoolConfig` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticPoolConfig.html)
|
||||
- [`StaticMemoryPool` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticMemoryPool.html)
|
||||
|
||||
for more details.
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
[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
|
||||
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
|
||||
start delimiter. All available telemetry will be sent back to a client after having read all
|
||||
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
|
||||
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.
|
||||
- Service 3 for housekeeping telemetry handling.
|
||||
- Service 5 for management and downlink of on-board events.
|
||||
- Service 8 for handling on-board actions.
|
||||
- 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
|
||||
scheduler are sent to the central TC source using a message.
|
||||
- Service 17 for test purposes like pings.
|
||||
@ -65,10 +65,10 @@ services. This currently includes the following services:
|
||||
### Event Management Component
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
@ -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,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 +0,0 @@
|
||||
pub use spacepackets::ecss::hk::*;
|
@ -1 +0,0 @@
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "satrs-example"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
default-run = "satrs-example"
|
||||
@ -19,13 +19,13 @@ num_enum = "0.7"
|
||||
thiserror = "1"
|
||||
derive-new = "0.5"
|
||||
|
||||
[dependencies.satrs-core]
|
||||
# version = "0.1.0-alpha.1"
|
||||
path = "../satrs-core"
|
||||
[dependencies.satrs]
|
||||
# version = "0.2.0-rc.0"
|
||||
path = "../satrs"
|
||||
|
||||
[dependencies.satrs-mib]
|
||||
# version = "0.1.0-alpha.1"
|
||||
path = "../satrs-mib"
|
||||
version = "0.1.1"
|
||||
# path = "../satrs-mib"
|
||||
|
||||
[features]
|
||||
dyn_tmtc = []
|
||||
|
@ -1,10 +1,11 @@
|
||||
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::{
|
||||
use satrs::pus::verification::VerificationReportingProvider;
|
||||
use satrs::pus::{EcssTmSender, PusTmWrapper};
|
||||
use satrs::request::TargetAndApidId;
|
||||
use satrs::spacepackets::ecss::hk::Subservice as HkSubservice;
|
||||
use satrs::{
|
||||
hk::HkRequest,
|
||||
spacepackets::{
|
||||
ecss::tm::{PusTmCreator, PusTmSecondaryHeader},
|
||||
@ -20,19 +21,19 @@ use crate::{
|
||||
update_time,
|
||||
};
|
||||
|
||||
pub struct AcsTask {
|
||||
pub struct AcsTask<VerificationReporter: VerificationReportingProvider> {
|
||||
timestamp: [u8; 7],
|
||||
time_provider: TimeProvider<DaysLen16Bits>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporter,
|
||||
tm_sender: Box<dyn EcssTmSender>,
|
||||
request_rx: mpsc::Receiver<RequestWithToken>,
|
||||
}
|
||||
|
||||
impl AcsTask {
|
||||
impl<VerificationReporter: VerificationReportingProvider> AcsTask<VerificationReporter> {
|
||||
pub fn new(
|
||||
tm_sender: impl EcssTmSender,
|
||||
request_rx: mpsc::Receiver<RequestWithToken>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporter,
|
||||
) -> Self {
|
||||
Self {
|
||||
timestamp: [0; 7],
|
||||
@ -70,12 +71,12 @@ impl AcsTask {
|
||||
"ACS thread: Received HK request {:?}",
|
||||
request.targeted_request
|
||||
);
|
||||
let target_and_apid_id = TargetAndApidId::from(request.targeted_request.target_id);
|
||||
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::OneShot(unique_id) => {
|
||||
self.handle_hk_request(target_and_apid_id.target(), unique_id)
|
||||
}
|
||||
HkRequest::Enable(_) => {}
|
||||
HkRequest::Disable(_) => {}
|
||||
HkRequest::ModifyCollectionInterval(_, _) => {}
|
||||
@ -89,10 +90,10 @@ impl AcsTask {
|
||||
}
|
||||
let started_token = self
|
||||
.verif_reporter
|
||||
.start_success(request.token, Some(&self.timestamp))
|
||||
.start_success(request.token, &self.timestamp)
|
||||
.expect("Sending start success failed");
|
||||
self.verif_reporter
|
||||
.completion_success(started_token, Some(&self.timestamp))
|
||||
.completion_success(started_token, &self.timestamp)
|
||||
.expect("Sending completion success failed");
|
||||
true
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use satrs_core::pus::verification::RequestId;
|
||||
use satrs_core::spacepackets::ecss::tc::PusTcCreator;
|
||||
use satrs_core::spacepackets::ecss::tm::PusTmReader;
|
||||
use satrs_core::{
|
||||
use satrs::pus::verification::RequestId;
|
||||
use satrs::spacepackets::ecss::tc::PusTcCreator;
|
||||
use satrs::spacepackets::ecss::tm::PusTmReader;
|
||||
use satrs::{
|
||||
spacepackets::ecss::{PusPacket, WritablePusPacket},
|
||||
spacepackets::SpHeader,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
use satrs_core::pus::ReceivesEcssPusTc;
|
||||
use satrs_core::spacepackets::{CcsdsPacket, SpHeader};
|
||||
use satrs_core::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
|
||||
use satrs::pus::ReceivesEcssPusTc;
|
||||
use satrs::spacepackets::{CcsdsPacket, SpHeader};
|
||||
use satrs::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
|
||||
use satrs_example::config::PUS_APID;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -1,10 +1,10 @@
|
||||
use satrs_core::res_code::ResultU16;
|
||||
use satrs::res_code::ResultU16;
|
||||
use satrs_mib::res_code::ResultU16Info;
|
||||
use satrs_mib::resultcode;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
events::{EventU32TypedSev, SeverityInfo},
|
||||
pool::{StaticMemoryPool, StaticPoolConfig},
|
||||
};
|
||||
@ -42,24 +42,31 @@ pub mod tmtc_err {
|
||||
use super::*;
|
||||
|
||||
#[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]
|
||||
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]
|
||||
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]
|
||||
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 = "Not enough data inside the TC application data field. Optionally includes: \
|
||||
8 bytes of failure data containing 2 failure parameters, \
|
||||
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] = &[
|
||||
INVALID_PUS_SERVICE_EXT,
|
||||
INVALID_PUS_SUBSERVICE_EXT,
|
||||
PUS_SERVICE_NOT_IMPLEMENTED_EXT,
|
||||
UNKNOWN_TARGET_ID_EXT,
|
||||
ROUTING_ERROR_EXT,
|
||||
NOT_ENOUGH_APP_DATA_EXT,
|
||||
];
|
||||
}
|
||||
@ -69,13 +76,20 @@ pub mod hk_err {
|
||||
use super::*;
|
||||
|
||||
#[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]
|
||||
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]
|
||||
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]
|
||||
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)]
|
||||
|
@ -1,18 +1,17 @@
|
||||
use std::sync::mpsc::{self, SendError};
|
||||
use std::sync::mpsc::{self};
|
||||
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
event_man::{
|
||||
EventManager, EventManagerWithMpscQueue, MpscEventReceiver, MpscEventU32SendProvider,
|
||||
SendEventProvider,
|
||||
EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded,
|
||||
MpscEventReceiver,
|
||||
},
|
||||
events::EventU32,
|
||||
params::Params,
|
||||
pus::{
|
||||
event_man::{
|
||||
DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken,
|
||||
PusEventDispatcher,
|
||||
DefaultPusEventU32Dispatcher, EventReporter, EventRequest, EventRequestWithToken,
|
||||
},
|
||||
verification::{TcStateStarted, VerificationReporterWithSender, VerificationToken},
|
||||
verification::{TcStateStarted, VerificationReportingProvider, VerificationToken},
|
||||
EcssTmSender,
|
||||
},
|
||||
spacepackets::time::cds::{self, TimeProvider},
|
||||
@ -21,38 +20,37 @@ use satrs_example::config::PUS_APID;
|
||||
|
||||
use crate::update_time;
|
||||
|
||||
pub type MpscEventManager = EventManager<SendError<(EventU32, Option<Params>)>>;
|
||||
|
||||
pub struct PusEventHandler {
|
||||
pub struct PusEventHandler<VerificationReporter: VerificationReportingProvider> {
|
||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||
pus_event_dispatcher: PusEventDispatcher<(), EventU32>,
|
||||
pus_event_dispatcher: DefaultPusEventU32Dispatcher<()>,
|
||||
pus_event_man_rx: mpsc::Receiver<(EventU32, Option<Params>)>,
|
||||
tm_sender: Box<dyn EcssTmSender>,
|
||||
time_provider: TimeProvider,
|
||||
timestamp: [u8; 7],
|
||||
verif_handler: VerificationReporterWithSender,
|
||||
verif_handler: VerificationReporter,
|
||||
}
|
||||
/*
|
||||
*/
|
||||
|
||||
impl PusEventHandler {
|
||||
impl<VerificationReporter: VerificationReportingProvider> PusEventHandler<VerificationReporter> {
|
||||
pub fn new(
|
||||
verif_handler: VerificationReporterWithSender,
|
||||
event_manager: &mut MpscEventManager,
|
||||
verif_handler: VerificationReporter,
|
||||
event_manager: &mut EventManagerWithBoundedMpsc,
|
||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||
tm_sender: impl EcssTmSender,
|
||||
) -> 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
|
||||
// telemetry for each event.
|
||||
let event_reporter = EventReporter::new(PUS_APID, 128).unwrap();
|
||||
let pus_tm_backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
|
||||
let pus_event_dispatcher =
|
||||
PusEventDispatcher::new(event_reporter, Box::new(pus_tm_backend));
|
||||
let pus_event_man_send_provider = MpscEventU32SendProvider::new(1, pus_event_man_tx);
|
||||
DefaultPusEventU32Dispatcher::new_with_default_backend(event_reporter);
|
||||
let pus_event_man_send_provider =
|
||||
EventU32SenderMpscBounded::new(1, 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.channel_id());
|
||||
event_manager.add_sender(pus_event_man_send_provider);
|
||||
|
||||
Self {
|
||||
@ -73,7 +71,7 @@ impl PusEventHandler {
|
||||
.try_into()
|
||||
.expect("expected start verification token");
|
||||
self.verif_handler
|
||||
.completion_success(started_token, Some(timestamp))
|
||||
.completion_success(started_token, timestamp)
|
||||
.expect("Sending completion success failed");
|
||||
};
|
||||
// handle event requests
|
||||
@ -114,7 +112,7 @@ impl PusEventHandler {
|
||||
}
|
||||
|
||||
pub struct EventManagerWrapper {
|
||||
event_manager: MpscEventManager,
|
||||
event_manager: EventManagerWithBoundedMpsc,
|
||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
||||
}
|
||||
|
||||
@ -125,7 +123,7 @@ impl EventManagerWrapper {
|
||||
let (event_sender, event_man_rx) = mpsc::channel();
|
||||
let event_recv = MpscEventReceiver::<EventU32>::new(event_man_rx);
|
||||
Self {
|
||||
event_manager: EventManagerWithMpscQueue::new(Box::new(event_recv)),
|
||||
event_manager: EventManagerWithBoundedMpsc::new(event_recv),
|
||||
event_sender,
|
||||
}
|
||||
}
|
||||
@ -134,7 +132,7 @@ impl EventManagerWrapper {
|
||||
self.event_sender.clone()
|
||||
}
|
||||
|
||||
pub fn event_manager(&mut self) -> &mut MpscEventManager {
|
||||
pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
|
||||
&mut self.event_manager
|
||||
}
|
||||
|
||||
@ -146,15 +144,15 @@ impl EventManagerWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventHandler {
|
||||
pub struct EventHandler<VerificationReporter: VerificationReportingProvider> {
|
||||
pub event_man_wrapper: EventManagerWrapper,
|
||||
pub pus_event_handler: PusEventHandler,
|
||||
pub pus_event_handler: PusEventHandler<VerificationReporter>,
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
impl<VerificationReporter: VerificationReportingProvider> EventHandler<VerificationReporter> {
|
||||
pub fn new(
|
||||
tm_sender: impl EcssTmSender,
|
||||
verif_handler: VerificationReporterWithSender,
|
||||
verif_handler: VerificationReporter,
|
||||
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
|
||||
) -> Self {
|
||||
let mut event_man_wrapper = EventManagerWrapper::new();
|
||||
@ -175,7 +173,7 @@ impl EventHandler {
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use derive_new::new;
|
||||
use satrs_core::spacepackets::ByteConversionError;
|
||||
use satrs::spacepackets::ByteConversionError;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AcsHkIds {
|
||||
|
@ -1,59 +1 @@
|
||||
use derive_new::new;
|
||||
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 type Apid = u16;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TargetIdCreationError {
|
||||
#[error("byte conversion")]
|
||||
ByteConversion(#[from] ByteConversionError),
|
||||
#[error("not enough app data to generate target ID")]
|
||||
NotEnoughAppData(usize),
|
||||
}
|
||||
|
||||
// TODO: can these stay pub?
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, new)]
|
||||
pub struct TargetIdWithApid {
|
||||
pub apid: Apid,
|
||||
pub target: TargetId,
|
||||
}
|
||||
|
||||
impl fmt::Display for TargetIdWithApid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}, {}", self.apid, self.target)
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetIdWithApid {
|
||||
pub fn apid(&self) -> Apid {
|
||||
self.apid
|
||||
}
|
||||
pub fn target_id(&self) -> TargetId {
|
||||
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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ use crate::pus::stack::PusStack;
|
||||
use crate::tm_funnel::{TmFunnelDynamic, TmFunnelStatic};
|
||||
use log::info;
|
||||
use pus::test::create_test_service_dynamic;
|
||||
use satrs_core::hal::std::tcp_server::ServerConfig;
|
||||
use satrs_core::hal::std::udp_server::UdpTcServer;
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::hal::std::tcp_server::ServerConfig;
|
||||
use satrs::hal::std::udp_server::UdpTcServer;
|
||||
use satrs::request::TargetAndApidId;
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
|
||||
use satrs_example::config::tasks::{
|
||||
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
|
||||
@ -35,31 +36,31 @@ use crate::pus::hk::{create_hk_service_dynamic, create_hk_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::{PusReceiver, PusTcMpscRouter};
|
||||
use crate::requests::RequestWithToken;
|
||||
use crate::requests::{GenericRequestRouter, RequestWithToken};
|
||||
use crate::tcp::{SyncTcpTmSource, TcpTask};
|
||||
use crate::tmtc::{
|
||||
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
|
||||
};
|
||||
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 satrs::pus::event_man::EventRequestWithToken;
|
||||
use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
|
||||
use satrs::pus::{EcssTmSender, TmAsVecSenderWithId, TmInSharedPoolSenderWithId};
|
||||
use satrs::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
|
||||
use satrs::tmtc::CcsdsDistributor;
|
||||
use satrs::ChannelId;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::mpsc::{self, channel};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn create_verification_reporter(verif_sender: impl EcssTmSender) -> VerificationReporterWithSender {
|
||||
fn create_verification_reporter<Sender: EcssTmSender + Clone>(
|
||||
verif_sender: Sender,
|
||||
) -> VerificationReporterWithSender<Sender> {
|
||||
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))
|
||||
VerificationReporterWithSender::new(&verif_cfg, verif_sender)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -69,24 +70,24 @@ fn static_tmtc_pool_main() {
|
||||
let shared_tc_pool = SharedTcPool {
|
||||
pool: Arc::new(RwLock::new(tc_pool)),
|
||||
};
|
||||
let (tc_source_tx, tc_source_rx) = channel();
|
||||
let (tm_funnel_tx, tm_funnel_rx) = channel();
|
||||
let (tm_server_tx, tm_server_rx) = channel();
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
|
||||
let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(50);
|
||||
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
|
||||
|
||||
// Every software component which needs to generate verification telemetry, receives a cloned
|
||||
// verification reporter.
|
||||
let verif_reporter = create_verification_reporter(MpscTmInSharedPoolSender::new(
|
||||
let verif_reporter = create_verification_reporter(TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusVerification as ChannelId,
|
||||
"verif_sender",
|
||||
shared_tm_pool.clone(),
|
||||
tm_funnel_tx.clone(),
|
||||
));
|
||||
|
||||
let acs_target_id = TargetIdWithApid::new(PUS_APID, RequestTargetId::AcsSubsystem as TargetId);
|
||||
let acs_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
|
||||
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.
|
||||
let mut request_map = HashMap::new();
|
||||
request_map.insert(acs_target_id, acs_thread_tx);
|
||||
let mut request_map = GenericRequestRouter::default();
|
||||
request_map.0.insert(acs_target_id.into(), acs_thread_tx);
|
||||
|
||||
// This helper structure is used by all telecommand providers which need to send telecommands
|
||||
// to the TC source.
|
||||
@ -103,7 +104,7 @@ fn static_tmtc_pool_main() {
|
||||
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||
// in the sat-rs documentation.
|
||||
let mut event_handler = EventHandler::new(
|
||||
MpscTmInSharedPoolSender::new(
|
||||
TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::AllEvents as ChannelId,
|
||||
"ALL_EVENTS_TX",
|
||||
shared_tm_pool.clone(),
|
||||
@ -203,7 +204,7 @@ fn static_tmtc_pool_main() {
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
let mut acs_task = AcsTask::new(
|
||||
MpscTmInSharedPoolSender::new(
|
||||
TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::AcsSubsystem as ChannelId,
|
||||
"ACS_TASK_SENDER",
|
||||
shared_tm_pool.clone(),
|
||||
@ -304,17 +305,17 @@ fn dyn_tmtc_pool_main() {
|
||||
let (tm_server_tx, tm_server_rx) = channel();
|
||||
// Every software component which needs to generate verification telemetry, gets a cloned
|
||||
// verification reporter.
|
||||
let verif_reporter = create_verification_reporter(MpscTmAsVecSender::new(
|
||||
let verif_reporter = create_verification_reporter(TmAsVecSenderWithId::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_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
|
||||
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.
|
||||
let mut request_map = HashMap::new();
|
||||
request_map.insert(acs_target_id, acs_thread_tx);
|
||||
let mut request_map = GenericRequestRouter::default();
|
||||
request_map.0.insert(acs_target_id.into(), acs_thread_tx);
|
||||
|
||||
let tc_source = PusTcSourceProviderDynamic(tc_source_tx);
|
||||
|
||||
@ -325,7 +326,7 @@ fn dyn_tmtc_pool_main() {
|
||||
// The event task is the core handler to perform the event routing and TM handling as specified
|
||||
// in the sat-rs documentation.
|
||||
let mut event_handler = EventHandler::new(
|
||||
MpscTmAsVecSender::new(
|
||||
TmAsVecSenderWithId::new(
|
||||
TmSenderId::AllEvents as ChannelId,
|
||||
"ALL_EVENTS_TX",
|
||||
tm_funnel_tx.clone(),
|
||||
@ -416,7 +417,7 @@ fn dyn_tmtc_pool_main() {
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
let mut acs_task = AcsTask::new(
|
||||
MpscTmAsVecSender::new(
|
||||
TmAsVecSenderWithId::new(
|
||||
TmSenderId::AcsSubsystem as ChannelId,
|
||||
"ACS_TASK_SENDER",
|
||||
tm_funnel_tx.clone(),
|
||||
|
@ -1,32 +1,89 @@
|
||||
use crate::requests::{ActionRequest, Request, RequestWithToken};
|
||||
use log::{error, warn};
|
||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs_core::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationToken,
|
||||
use satrs::action::ActionRequest;
|
||||
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs::pus::action::{PusActionToRequestConverter, PusService8ActionHandler};
|
||||
use satrs::pus::verification::std_mod::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs_core::pus::{
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
|
||||
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
|
||||
MpscTcReceiver, PusPacketHandlerResult, PusPacketHandlingError, PusServiceHelper,
|
||||
TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
|
||||
};
|
||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs_core::spacepackets::ecss::PusPacket;
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_core::ChannelId;
|
||||
use satrs::request::TargetAndApidId;
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::{ChannelId, TargetId};
|
||||
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
|
||||
use satrs_example::TargetIdWithApid;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
use std::sync::mpsc::{self};
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
|
||||
use super::GenericRoutingErrorHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExampleActionRequestConverter {}
|
||||
|
||||
impl PusActionToRequestConverter for ExampleActionRequestConverter {
|
||||
type Error = PusPacketHandlingError;
|
||||
|
||||
fn convert(
|
||||
&mut self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
time_stamp: &[u8],
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
) -> Result<(TargetId, ActionRequest), Self::Error> {
|
||||
let subservice = tc.subservice();
|
||||
let user_data = tc.user_data();
|
||||
if user_data.len() < 8 {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData {
|
||||
expected: 8,
|
||||
found: user_data.len(),
|
||||
});
|
||||
}
|
||||
let target_id = TargetAndApidId::from_pus_tc(tc).unwrap();
|
||||
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
|
||||
if subservice == 128 {
|
||||
Ok((
|
||||
target_id.raw(),
|
||||
ActionRequest::UnsignedIdAndVecData {
|
||||
action_id,
|
||||
data: user_data[8..].to_vec(),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
Err(PusPacketHandlingError::InvalidSubservice(subservice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action_service_static(
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
||||
) -> Pus8Wrapper<EcssTcInSharedStoreConverter> {
|
||||
let action_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
||||
action_router: GenericRequestRouter,
|
||||
) -> Pus8Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
|
||||
{
|
||||
let action_srv_tm_sender = TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusAction as ChannelId,
|
||||
"PUS_8_TM_SENDER",
|
||||
shared_tm_store.clone(),
|
||||
@ -38,23 +95,27 @@ pub fn create_action_service_static(
|
||||
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(),
|
||||
PusServiceHelper::new(
|
||||
Box::new(action_srv_receiver),
|
||||
Box::new(action_srv_tm_sender),
|
||||
PUS_APID,
|
||||
verif_reporter.clone(),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
|
||||
),
|
||||
ExampleActionRequestConverter::default(),
|
||||
action_router,
|
||||
GenericRoutingErrorHandler::<8>::default(),
|
||||
);
|
||||
Pus8Wrapper { pus_8_handler }
|
||||
}
|
||||
|
||||
pub fn create_action_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporterWithVecMpscSender,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
||||
) -> Pus8Wrapper<EcssTcInVecConverter> {
|
||||
let action_srv_tm_sender = MpscTmAsVecSender::new(
|
||||
action_router: GenericRequestRouter,
|
||||
) -> Pus8Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
|
||||
let action_srv_tm_sender = TmAsVecSenderWithId::new(
|
||||
TmSenderId::PusAction as ChannelId,
|
||||
"PUS_8_TM_SENDER",
|
||||
tm_funnel_tx.clone(),
|
||||
@ -65,149 +126,38 @@ pub fn create_action_service_dynamic(
|
||||
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(),
|
||||
PusServiceHelper::new(
|
||||
Box::new(action_srv_receiver),
|
||||
Box::new(action_srv_tm_sender),
|
||||
PUS_APID,
|
||||
verif_reporter.clone(),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ExampleActionRequestConverter::default(),
|
||||
action_router,
|
||||
GenericRoutingErrorHandler::<8>::default(),
|
||||
);
|
||||
Pus8Wrapper { pus_8_handler }
|
||||
}
|
||||
|
||||
pub struct PusService8ActionHandler<TcInMemConverter: EcssTcInMemConverter> {
|
||||
service_helper: PusServiceHelper<TcInMemConverter>,
|
||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
||||
pub struct Pus8Wrapper<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
pub(crate) pus_8_handler: PusService8ActionHandler<
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
ExampleActionRequestConverter,
|
||||
GenericRequestRouter,
|
||||
GenericRoutingErrorHandler<8>,
|
||||
>,
|
||||
}
|
||||
|
||||
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 {
|
||||
service_helper: PusServiceHelper::new(
|
||||
tc_receiver,
|
||||
tm_sender,
|
||||
tm_apid,
|
||||
verification_handler,
|
||||
tc_in_mem_converter,
|
||||
),
|
||||
request_handlers,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_action_request_with_id(
|
||||
&self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
time_stamp: &[u8],
|
||||
) -> Result<(), PusPacketHandlingError> {
|
||||
let user_data = tc.user_data();
|
||||
if user_data.len() < 8 {
|
||||
self.service_helper
|
||||
.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(())
|
||||
}
|
||||
|
||||
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();
|
||||
self.service_helper
|
||||
.tc_in_mem_converter
|
||||
.cache_ecss_tc_in_memory(&ecss_tc_and_token.tc_in_memory)?;
|
||||
let tc = PusTcReader::new(self.service_helper.tc_in_mem_converter.tc_slice_raw())?.0;
|
||||
let subservice = tc.subservice();
|
||||
let mut partial_error = None;
|
||||
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
|
||||
match subservice {
|
||||
128 => {
|
||||
self.handle_action_request_with_id(ecss_tc_and_token.token, &tc, &time_stamp)?;
|
||||
}
|
||||
_ => {
|
||||
let fail_data = [subservice];
|
||||
self.service_helper
|
||||
.common
|
||||
.verification_handler
|
||||
.get_mut()
|
||||
.start_failure(
|
||||
ecss_tc_and_token.token,
|
||||
FailParams::new(
|
||||
Some(&time_stamp),
|
||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||
Some(&fail_data),
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
|
||||
}
|
||||
}
|
||||
if let Some(partial_error) = partial_error {
|
||||
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
|
||||
partial_error,
|
||||
));
|
||||
}
|
||||
Ok(PusPacketHandlerResult::RequestHandled)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pus8Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub(crate) pus_8_handler: PusService8ActionHandler<TcInMemConverter>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> Pus8Wrapper<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn handle_next_packet(&mut self) -> bool {
|
||||
match self.pus_8_handler.handle_one_tc() {
|
||||
Ok(result) => match result {
|
||||
|
@ -1,28 +1,32 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use log::{error, warn};
|
||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs_core::pus::event_man::EventRequestWithToken;
|
||||
use satrs_core::pus::event_srv::PusService5EventHandler;
|
||||
use satrs_core::pus::verification::VerificationReporterWithSender;
|
||||
use satrs_core::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
|
||||
PusServiceHelper,
|
||||
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs::pus::event_man::EventRequestWithToken;
|
||||
use satrs::pus::event_srv::PusService5EventHandler;
|
||||
use satrs::pus::verification::std_mod::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_core::ChannelId;
|
||||
use satrs::pus::verification::VerificationReportingProvider;
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId,
|
||||
TmInSharedPoolSenderWithId,
|
||||
};
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::ChannelId;
|
||||
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
|
||||
|
||||
pub fn create_event_service_static(
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||
) -> Pus5Wrapper<EcssTcInSharedStoreConverter> {
|
||||
let event_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
||||
) -> Pus5Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
|
||||
{
|
||||
let event_srv_tm_sender = TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusEvent as ChannelId,
|
||||
"PUS_5_TM_SENDER",
|
||||
shared_tm_store.clone(),
|
||||
@ -48,11 +52,11 @@ pub fn create_event_service_static(
|
||||
|
||||
pub fn create_event_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporterWithVecMpscSender,
|
||||
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
event_request_tx: mpsc::Sender<EventRequestWithToken>,
|
||||
) -> Pus5Wrapper<EcssTcInVecConverter> {
|
||||
let event_srv_tm_sender = MpscTmAsVecSender::new(
|
||||
) -> Pus5Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
|
||||
let event_srv_tm_sender = TmAsVecSenderWithId::new(
|
||||
TmSenderId::PusEvent as ChannelId,
|
||||
"PUS_5_TM_SENDER",
|
||||
tm_funnel_tx,
|
||||
@ -75,11 +79,18 @@ pub fn create_event_service_dynamic(
|
||||
Pus5Wrapper { pus_5_handler }
|
||||
}
|
||||
|
||||
pub struct Pus5Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub pus_5_handler: PusService5EventHandler<TcInMemConverter>,
|
||||
pub struct Pus5Wrapper<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
pub pus_5_handler: PusService5EventHandler<TcInMemConverter, VerificationReporter>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> Pus5Wrapper<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn handle_next_packet(&mut self) -> bool {
|
||||
match self.pus_5_handler.handle_one_tc() {
|
||||
Ok(result) => match result {
|
||||
|
@ -1,32 +1,158 @@
|
||||
use crate::requests::{Request, RequestWithToken};
|
||||
use log::{error, warn};
|
||||
use satrs_core::hk::{CollectionIntervalFactor, HkRequest};
|
||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs_core::pus::verification::{
|
||||
FailParams, StdVerifReporterWithSender, VerificationReporterWithSender,
|
||||
use satrs::hk::{CollectionIntervalFactor, HkRequest};
|
||||
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs::pus::hk::{PusHkToRequestConverter, PusService3HkHandler};
|
||||
use satrs::pus::verification::std_mod::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs_core::pus::{
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
|
||||
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
|
||||
MpscTcReceiver, PusPacketHandlerResult, PusPacketHandlingError, PusServiceHelper,
|
||||
TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
|
||||
};
|
||||
use satrs_core::spacepackets::ecss::{hk, PusPacket};
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_core::ChannelId;
|
||||
use satrs::request::TargetAndApidId;
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{hk, PusPacket};
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::{ChannelId, TargetId};
|
||||
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
|
||||
use satrs_example::TargetIdWithApid;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
use std::sync::mpsc::{self};
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
|
||||
use super::GenericRoutingErrorHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExampleHkRequestConverter {}
|
||||
|
||||
impl PusHkToRequestConverter for ExampleHkRequestConverter {
|
||||
type Error = PusPacketHandlingError;
|
||||
|
||||
fn convert(
|
||||
&mut self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
time_stamp: &[u8],
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
) -> Result<(TargetId, HkRequest), Self::Error> {
|
||||
let user_data = tc.user_data();
|
||||
if user_data.is_empty() {
|
||||
let user_data_len = user_data.len() as u32;
|
||||
let user_data_len_raw = user_data_len.to_be_bytes();
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(
|
||||
time_stamp,
|
||||
&tmtc_err::NOT_ENOUGH_APP_DATA,
|
||||
&user_data_len_raw,
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData {
|
||||
expected: 4,
|
||||
found: 0,
|
||||
});
|
||||
}
|
||||
if user_data.len() < 8 {
|
||||
let err = if user_data.len() < 4 {
|
||||
&hk_err::TARGET_ID_MISSING
|
||||
} else {
|
||||
&hk_err::UNIQUE_ID_MISSING
|
||||
};
|
||||
let user_data_len = user_data.len() as u32;
|
||||
let user_data_len_raw = user_data_len.to_be_bytes();
|
||||
verif_reporter
|
||||
.start_failure(token, FailParams::new(time_stamp, err, &user_data_len_raw))
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData {
|
||||
expected: 8,
|
||||
found: 4,
|
||||
});
|
||||
}
|
||||
let subservice = tc.subservice();
|
||||
let target_id = TargetAndApidId::from_pus_tc(tc).expect("invalid tc format");
|
||||
let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
|
||||
|
||||
let standard_subservice = hk::Subservice::try_from(subservice);
|
||||
if standard_subservice.is_err() {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE, &[subservice]),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
|
||||
}
|
||||
Ok((
|
||||
target_id.into(),
|
||||
match standard_subservice.unwrap() {
|
||||
hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
|
||||
HkRequest::Enable(unique_id)
|
||||
}
|
||||
hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
|
||||
HkRequest::Disable(unique_id)
|
||||
}
|
||||
hk::Subservice::TcReportHkReportStructures => todo!(),
|
||||
hk::Subservice::TmHkPacket => todo!(),
|
||||
hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
|
||||
HkRequest::OneShot(unique_id)
|
||||
}
|
||||
hk::Subservice::TcModifyDiagCollectionInterval
|
||||
| hk::Subservice::TcModifyHkCollectionInterval => {
|
||||
if user_data.len() < 12 {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new_no_fail_data(
|
||||
time_stamp,
|
||||
&tmtc_err::NOT_ENOUGH_APP_DATA,
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData {
|
||||
expected: 12,
|
||||
found: user_data.len(),
|
||||
});
|
||||
}
|
||||
HkRequest::ModifyCollectionInterval(
|
||||
unique_id,
|
||||
CollectionIntervalFactor::from_be_bytes(
|
||||
user_data[8..12].try_into().unwrap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(
|
||||
time_stamp,
|
||||
&tmtc_err::PUS_SUBSERVICE_NOT_IMPLEMENTED,
|
||||
&[subservice],
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_hk_service_static(
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
||||
) -> Pus3Wrapper<EcssTcInSharedStoreConverter> {
|
||||
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
||||
request_router: GenericRequestRouter,
|
||||
) -> Pus3Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
|
||||
{
|
||||
let hk_srv_tm_sender = TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusHk as ChannelId,
|
||||
"PUS_3_TM_SENDER",
|
||||
shared_tm_store.clone(),
|
||||
@ -35,23 +161,27 @@ pub fn create_hk_service_static(
|
||||
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,
|
||||
PusServiceHelper::new(
|
||||
Box::new(hk_srv_receiver),
|
||||
Box::new(hk_srv_tm_sender),
|
||||
PUS_APID,
|
||||
verif_reporter.clone(),
|
||||
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
|
||||
),
|
||||
ExampleHkRequestConverter::default(),
|
||||
request_router,
|
||||
GenericRoutingErrorHandler::default(),
|
||||
);
|
||||
Pus3Wrapper { pus_3_handler }
|
||||
}
|
||||
|
||||
pub fn create_hk_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporterWithVecMpscSender,
|
||||
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
|
||||
) -> Pus3Wrapper<EcssTcInVecConverter> {
|
||||
let hk_srv_tm_sender = MpscTmAsVecSender::new(
|
||||
request_router: GenericRequestRouter,
|
||||
) -> Pus3Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
|
||||
let hk_srv_tm_sender = TmAsVecSenderWithId::new(
|
||||
TmSenderId::PusHk as ChannelId,
|
||||
"PUS_3_TM_SENDER",
|
||||
tm_funnel_tx.clone(),
|
||||
@ -59,157 +189,38 @@ pub fn create_hk_service_dynamic(
|
||||
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,
|
||||
PusServiceHelper::new(
|
||||
Box::new(hk_srv_receiver),
|
||||
Box::new(hk_srv_tm_sender),
|
||||
PUS_APID,
|
||||
verif_reporter.clone(),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ExampleHkRequestConverter::default(),
|
||||
request_router,
|
||||
GenericRoutingErrorHandler::default(),
|
||||
);
|
||||
Pus3Wrapper { pus_3_handler }
|
||||
}
|
||||
|
||||
pub struct PusService3HkHandler<TcInMemConverter: EcssTcInMemConverter> {
|
||||
psb: PusServiceHelper<TcInMemConverter>,
|
||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
||||
pub struct Pus3Wrapper<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
pub(crate) pus_3_handler: PusService3HkHandler<
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
ExampleHkRequestConverter,
|
||||
GenericRequestRouter,
|
||||
GenericRoutingErrorHandler<3>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> PusService3HkHandler<TcInMemConverter> {
|
||||
pub fn new(
|
||||
tc_receiver: Box<dyn EcssTcReceiver>,
|
||||
tm_sender: Box<dyn EcssTmSender>,
|
||||
tm_apid: u16,
|
||||
verification_handler: StdVerifReporterWithSender,
|
||||
tc_in_mem_converter: TcInMemConverter,
|
||||
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
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> {
|
||||
let possible_packet = self.psb.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
|
||||
.psb
|
||||
.tc_in_mem_converter
|
||||
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
|
||||
let subservice = tc.subservice();
|
||||
let mut partial_error = None;
|
||||
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
|
||||
let user_data = tc.user_data();
|
||||
if user_data.is_empty() {
|
||||
self.psb
|
||||
.common
|
||||
.verification_handler
|
||||
.borrow_mut()
|
||||
.start_failure(
|
||||
ecss_tc_and_token.token,
|
||||
FailParams::new(Some(&time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
||||
"Expected at least 8 bytes of app data".into(),
|
||||
));
|
||||
}
|
||||
if user_data.len() < 8 {
|
||||
let err = if user_data.len() < 4 {
|
||||
&hk_err::TARGET_ID_MISSING
|
||||
} else {
|
||||
&hk_err::UNIQUE_ID_MISSING
|
||||
};
|
||||
self.psb
|
||||
.common
|
||||
.verification_handler
|
||||
.borrow_mut()
|
||||
.start_failure(
|
||||
ecss_tc_and_token.token,
|
||||
FailParams::new(Some(&time_stamp), err, None),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
||||
"Expected at least 8 bytes of app data".into(),
|
||||
));
|
||||
}
|
||||
let target_id = TargetIdWithApid::from_tc(&tc).expect("invalid tc format");
|
||||
let unique_id = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
|
||||
if !self.request_handlers.contains_key(&target_id) {
|
||||
self.psb
|
||||
.common
|
||||
.verification_handler
|
||||
.borrow_mut()
|
||||
.start_failure(
|
||||
ecss_tc_and_token.token,
|
||||
FailParams::new(Some(&time_stamp), &hk_err::UNKNOWN_TARGET_ID, None),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData(format!(
|
||||
"Unknown target ID {target_id}"
|
||||
)));
|
||||
}
|
||||
let send_request = |target: TargetIdWithApid, request: HkRequest| {
|
||||
let sender = self.request_handlers.get(&target).unwrap();
|
||||
sender
|
||||
.send(RequestWithToken::new(
|
||||
target,
|
||||
Request::Hk(request),
|
||||
ecss_tc_and_token.token,
|
||||
))
|
||||
.unwrap_or_else(|_| panic!("Sending HK request {request:?} failed"));
|
||||
};
|
||||
if subservice == hk::Subservice::TcEnableHkGeneration as u8 {
|
||||
send_request(target_id, HkRequest::Enable(unique_id));
|
||||
} else if subservice == hk::Subservice::TcDisableHkGeneration as u8 {
|
||||
send_request(target_id, HkRequest::Disable(unique_id));
|
||||
} else if subservice == hk::Subservice::TcGenerateOneShotHk as u8 {
|
||||
send_request(target_id, HkRequest::OneShot(unique_id));
|
||||
} else if subservice == hk::Subservice::TcModifyHkCollectionInterval as u8 {
|
||||
if user_data.len() < 12 {
|
||||
self.psb
|
||||
.common
|
||||
.verification_handler
|
||||
.borrow_mut()
|
||||
.start_failure(
|
||||
ecss_tc_and_token.token,
|
||||
FailParams::new(
|
||||
Some(&time_stamp),
|
||||
&hk_err::COLLECTION_INTERVAL_MISSING,
|
||||
None,
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure TM failed");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData(
|
||||
"Collection interval missing".into(),
|
||||
));
|
||||
}
|
||||
send_request(
|
||||
target_id,
|
||||
HkRequest::ModifyCollectionInterval(
|
||||
unique_id,
|
||||
CollectionIntervalFactor::from_be_bytes(user_data[8..12].try_into().unwrap()),
|
||||
),
|
||||
);
|
||||
}
|
||||
Ok(PusPacketHandlerResult::RequestHandled)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pus3Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub(crate) pus_3_handler: PusService3HkHandler<TcInMemConverter>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> Pus3Wrapper<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn handle_next_packet(&mut self) -> bool {
|
||||
match self.pus_3_handler.handle_one_tc() {
|
||||
Ok(result) => match result {
|
||||
|
@ -1,11 +1,13 @@
|
||||
use crate::tmtc::MpscStoreAndSendError;
|
||||
use log::warn;
|
||||
use satrs_core::pus::verification::{FailParams, StdVerifReporterWithSender};
|
||||
use satrs_core::pus::{EcssTcAndToken, PusPacketHandlerResult, TcInMemory};
|
||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs_core::spacepackets::ecss::PusServiceId;
|
||||
use satrs_core::spacepackets::time::cds::TimeProvider;
|
||||
use satrs_core::spacepackets::time::TimeWriter;
|
||||
use satrs::pus::verification::{FailParams, VerificationReportingProvider};
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, GenericRoutingError, PusPacketHandlerResult, PusRoutingErrorHandler, TcInMemory,
|
||||
};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::spacepackets::time::cds::TimeProvider;
|
||||
use satrs::spacepackets::time::TimeWriter;
|
||||
use satrs_example::config::{tmtc_err, CustomPusServiceId};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
@ -24,8 +26,8 @@ pub struct PusTcMpscRouter {
|
||||
pub action_service_receiver: Sender<EcssTcAndToken>,
|
||||
}
|
||||
|
||||
pub struct PusReceiver {
|
||||
pub verif_reporter: StdVerifReporterWithSender,
|
||||
pub struct PusReceiver<VerificationReporter: VerificationReportingProvider> {
|
||||
pub verif_reporter: VerificationReporter,
|
||||
pub pus_router: PusTcMpscRouter,
|
||||
stamp_helper: TimeStampHelper,
|
||||
}
|
||||
@ -57,8 +59,8 @@ impl TimeStampHelper {
|
||||
}
|
||||
}
|
||||
|
||||
impl PusReceiver {
|
||||
pub fn new(verif_reporter: StdVerifReporterWithSender, pus_router: PusTcMpscRouter) -> Self {
|
||||
impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
|
||||
pub fn new(verif_reporter: VerificationReporter, pus_router: PusTcMpscRouter) -> Self {
|
||||
Self {
|
||||
verif_reporter,
|
||||
pus_router,
|
||||
@ -67,7 +69,7 @@ impl PusReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
impl PusReceiver {
|
||||
impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
|
||||
pub fn handle_tc_packet(
|
||||
&mut self,
|
||||
tc_in_memory: TcInMemory,
|
||||
@ -78,7 +80,7 @@ impl PusReceiver {
|
||||
self.stamp_helper.update_from_now();
|
||||
let accepted_token = self
|
||||
.verif_reporter
|
||||
.acceptance_success(init_token, Some(self.stamp_helper.stamp()))
|
||||
.acceptance_success(init_token, self.stamp_helper.stamp())
|
||||
.expect("Acceptance success failure");
|
||||
let service = PusServiceId::try_from(service);
|
||||
match service {
|
||||
@ -115,9 +117,9 @@ impl PusReceiver {
|
||||
let result = self.verif_reporter.start_failure(
|
||||
accepted_token,
|
||||
FailParams::new(
|
||||
Some(self.stamp_helper.stamp()),
|
||||
self.stamp_helper.stamp(),
|
||||
&tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED,
|
||||
Some(&[standard_service as u8]),
|
||||
&[standard_service as u8],
|
||||
),
|
||||
);
|
||||
if result.is_err() {
|
||||
@ -139,9 +141,9 @@ impl PusReceiver {
|
||||
.start_failure(
|
||||
accepted_token,
|
||||
FailParams::new(
|
||||
Some(self.stamp_helper.stamp()),
|
||||
self.stamp_helper.stamp(),
|
||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||
Some(&[e.number]),
|
||||
&[e.number],
|
||||
),
|
||||
)
|
||||
.expect("Start failure verification failed")
|
||||
@ -151,3 +153,56 @@ impl PusReceiver {
|
||||
Ok(PusPacketHandlerResult::RequestHandled)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GenericRoutingErrorHandler<const SERVICE_ID: u8> {}
|
||||
|
||||
impl<const SERVICE_ID: u8> PusRoutingErrorHandler for GenericRoutingErrorHandler<SERVICE_ID> {
|
||||
type Error = satrs::pus::GenericRoutingError;
|
||||
|
||||
fn handle_error(
|
||||
&self,
|
||||
target_id: satrs::TargetId,
|
||||
token: satrs::pus::verification::VerificationToken<
|
||||
satrs::pus::verification::TcStateAccepted,
|
||||
>,
|
||||
_tc: &PusTcReader,
|
||||
error: Self::Error,
|
||||
time_stamp: &[u8],
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
) {
|
||||
warn!("Routing request for service {SERVICE_ID} failed: {error:?}");
|
||||
match error {
|
||||
GenericRoutingError::UnknownTargetId(id) => {
|
||||
let mut fail_data: [u8; 8] = [0; 8];
|
||||
fail_data.copy_from_slice(&id.to_be_bytes());
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(time_stamp, &tmtc_err::UNKNOWN_TARGET_ID, &fail_data),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
}
|
||||
GenericRoutingError::SendError(_) => {
|
||||
let mut fail_data: [u8; 8] = [0; 8];
|
||||
fail_data.copy_from_slice(&target_id.to_be_bytes());
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(time_stamp, &tmtc_err::ROUTING_ERROR, &fail_data),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
}
|
||||
GenericRoutingError::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());
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA, &context_info),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,20 @@ use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{error, info, warn};
|
||||
use satrs_core::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
|
||||
use satrs_core::pus::scheduler::{PusScheduler, TcInfo};
|
||||
use satrs_core::pus::scheduler_srv::PusService11SchedHandler;
|
||||
use satrs_core::pus::verification::VerificationReporterWithSender;
|
||||
use satrs_core::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
|
||||
PusServiceHelper,
|
||||
use satrs::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
|
||||
use satrs::pus::scheduler::{PusScheduler, TcInfo};
|
||||
use satrs::pus::scheduler_srv::PusService11SchedHandler;
|
||||
use satrs::pus::verification::std_mod::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_core::ChannelId;
|
||||
use satrs::pus::verification::VerificationReportingProvider;
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
|
||||
MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId,
|
||||
TmInSharedPoolSenderWithId,
|
||||
};
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::ChannelId;
|
||||
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
|
||||
|
||||
use crate::tmtc::PusTcSourceProviderSharedPool;
|
||||
@ -51,14 +54,22 @@ impl TcReleaser for mpsc::Sender<Vec<u8>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub pus_11_handler: PusService11SchedHandler<TcInMemConverter, PusScheduler>,
|
||||
pub struct Pus11Wrapper<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
pub pus_11_handler:
|
||||
PusService11SchedHandler<TcInMemConverter, VerificationReporter, PusScheduler>,
|
||||
pub sched_tc_pool: StaticMemoryPool,
|
||||
pub releaser_buf: [u8; 4096],
|
||||
pub tc_releaser: Box<dyn TcReleaser + Send>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> Pus11Wrapper<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn release_tcs(&mut self) {
|
||||
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
|
||||
self.tc_releaser.release(enabled, info, tc)
|
||||
@ -109,13 +120,14 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
|
||||
|
||||
pub fn create_scheduler_service_static(
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
tc_releaser: PusTcSourceProviderSharedPool,
|
||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
sched_tc_pool: StaticMemoryPool,
|
||||
) -> Pus11Wrapper<EcssTcInSharedStoreConverter> {
|
||||
let sched_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
||||
) -> Pus11Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
|
||||
{
|
||||
let sched_srv_tm_sender = TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusSched as ChannelId,
|
||||
"PUS_11_TM_SENDER",
|
||||
shared_tm_store.clone(),
|
||||
@ -148,12 +160,12 @@ pub fn create_scheduler_service_static(
|
||||
|
||||
pub fn create_scheduler_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporterWithVecMpscSender,
|
||||
tc_source_sender: mpsc::Sender<Vec<u8>>,
|
||||
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
sched_tc_pool: StaticMemoryPool,
|
||||
) -> Pus11Wrapper<EcssTcInVecConverter> {
|
||||
let sched_srv_tm_sender = MpscTmAsVecSender::new(
|
||||
) -> Pus11Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
|
||||
let sched_srv_tm_sender = TmAsVecSenderWithId::new(
|
||||
TmSenderId::PusSched as ChannelId,
|
||||
"PUS_11_TM_SENDER",
|
||||
tm_funnel_tx,
|
||||
|
@ -1,25 +1,32 @@
|
||||
use satrs_core::pus::EcssTcInMemConverter;
|
||||
use satrs::pus::{verification::VerificationReportingProvider, EcssTcInMemConverter};
|
||||
|
||||
use super::{
|
||||
action::Pus8Wrapper, event::Pus5Wrapper, hk::Pus3Wrapper, scheduler::Pus11Wrapper,
|
||||
test::Service17CustomWrapper,
|
||||
};
|
||||
|
||||
pub struct PusStack<TcInMemConverter: EcssTcInMemConverter> {
|
||||
event_srv: Pus5Wrapper<TcInMemConverter>,
|
||||
hk_srv: Pus3Wrapper<TcInMemConverter>,
|
||||
action_srv: Pus8Wrapper<TcInMemConverter>,
|
||||
schedule_srv: Pus11Wrapper<TcInMemConverter>,
|
||||
test_srv: Service17CustomWrapper<TcInMemConverter>,
|
||||
pub struct PusStack<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
event_srv: Pus5Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
hk_srv: Pus3Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
action_srv: Pus8Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
schedule_srv: Pus11Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
test_srv: Service17CustomWrapper<TcInMemConverter, VerificationReporter>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> PusStack<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> PusStack<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn new(
|
||||
hk_srv: Pus3Wrapper<TcInMemConverter>,
|
||||
event_srv: Pus5Wrapper<TcInMemConverter>,
|
||||
action_srv: Pus8Wrapper<TcInMemConverter>,
|
||||
schedule_srv: Pus11Wrapper<TcInMemConverter>,
|
||||
test_srv: Service17CustomWrapper<TcInMemConverter>,
|
||||
hk_srv: Pus3Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
event_srv: Pus5Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
action_srv: Pus8Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
schedule_srv: Pus11Wrapper<TcInMemConverter, VerificationReporter>,
|
||||
test_srv: Service17CustomWrapper<TcInMemConverter, VerificationReporter>,
|
||||
) -> Self {
|
||||
Self {
|
||||
event_srv,
|
||||
|
@ -1,31 +1,37 @@
|
||||
use log::{info, warn};
|
||||
use satrs_core::params::Params;
|
||||
use satrs_core::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs_core::pus::test::PusService17TestHandler;
|
||||
use satrs_core::pus::verification::{FailParams, VerificationReporterWithSender};
|
||||
use satrs_core::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
|
||||
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper,
|
||||
use satrs::params::Params;
|
||||
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
|
||||
use satrs::pus::test::PusService17TestHandler;
|
||||
use satrs::pus::verification::{FailParams, VerificationReportingProvider};
|
||||
use satrs::pus::verification::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs_core::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs_core::spacepackets::ecss::PusPacket;
|
||||
use satrs_core::spacepackets::time::cds::TimeProvider;
|
||||
use satrs_core::spacepackets::time::TimeWriter;
|
||||
use satrs_core::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs_core::ChannelId;
|
||||
use satrs_core::{events::EventU32, pus::EcssTcInSharedStoreConverter};
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver,
|
||||
PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
|
||||
};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::spacepackets::time::cds::TimeProvider;
|
||||
use satrs::spacepackets::time::TimeWriter;
|
||||
use satrs::tmtc::tm_helper::SharedTmPool;
|
||||
use satrs::ChannelId;
|
||||
use satrs::{events::EventU32, pus::EcssTcInSharedStoreConverter};
|
||||
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID, TEST_EVENT};
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
|
||||
pub fn create_test_service_static(
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_tx: mpsc::Sender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
|
||||
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
tc_pool: SharedStaticMemoryPool,
|
||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
) -> Service17CustomWrapper<EcssTcInSharedStoreConverter> {
|
||||
let test_srv_tm_sender = MpscTmInSharedPoolSender::new(
|
||||
) -> Service17CustomWrapper<
|
||||
EcssTcInSharedStoreConverter,
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender,
|
||||
> {
|
||||
let test_srv_tm_sender = TmInSharedPoolSenderWithId::new(
|
||||
TmSenderId::PusTest as ChannelId,
|
||||
"PUS_17_TM_SENDER",
|
||||
shared_tm_store.clone(),
|
||||
@ -51,11 +57,11 @@ pub fn create_test_service_static(
|
||||
|
||||
pub fn create_test_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
|
||||
verif_reporter: VerificationReporterWithSender,
|
||||
verif_reporter: VerificationReporterWithVecMpscSender,
|
||||
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
|
||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
) -> Service17CustomWrapper<EcssTcInVecConverter> {
|
||||
let test_srv_tm_sender = MpscTmAsVecSender::new(
|
||||
) -> Service17CustomWrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
|
||||
let test_srv_tm_sender = TmAsVecSenderWithId::new(
|
||||
TmSenderId::PusTest as ChannelId,
|
||||
"PUS_17_TM_SENDER",
|
||||
tm_funnel_tx.clone(),
|
||||
@ -78,12 +84,19 @@ pub fn create_test_service_dynamic(
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Service17CustomWrapper<TcInMemConverter: EcssTcInMemConverter> {
|
||||
pub pus17_handler: PusService17TestHandler<TcInMemConverter>,
|
||||
pub struct Service17CustomWrapper<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> {
|
||||
pub pus17_handler: PusService17TestHandler<TcInMemConverter, VerificationReporter>,
|
||||
pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>,
|
||||
}
|
||||
|
||||
impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConverter> {
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
> Service17CustomWrapper<TcInMemConverter, VerificationReporter>
|
||||
{
|
||||
pub fn handle_next_packet(&mut self) -> bool {
|
||||
let res = self.pus17_handler.handle_one_tc();
|
||||
if res.is_err() {
|
||||
@ -125,15 +138,13 @@ impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConve
|
||||
.service_helper
|
||||
.common
|
||||
.verification_handler
|
||||
.get_mut()
|
||||
.start_success(token, Some(&stamp_buf))
|
||||
.start_success(token, &stamp_buf)
|
||||
.expect("Error sending start success");
|
||||
self.pus17_handler
|
||||
.service_helper
|
||||
.common
|
||||
.verification_handler
|
||||
.get_mut()
|
||||
.completion_success(start_token, Some(&stamp_buf))
|
||||
.completion_success(start_token, &stamp_buf)
|
||||
.expect("Error sending completion success");
|
||||
} else {
|
||||
let fail_data = [tc.subservice()];
|
||||
@ -141,13 +152,12 @@ impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConve
|
||||
.service_helper
|
||||
.common
|
||||
.verification_handler
|
||||
.get_mut()
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(
|
||||
Some(&stamp_buf),
|
||||
&stamp_buf,
|
||||
&tmtc_err::INVALID_PUS_SUBSERVICE,
|
||||
Some(&fail_data),
|
||||
&fail_data,
|
||||
),
|
||||
)
|
||||
.expect("Sending start failure verification failed");
|
||||
|
45
satrs-example/src/queue.rs
Normal file
45
satrs-example/src/queue.rs
Normal file
@ -0,0 +1,45 @@
|
||||
/// Generic error type for sending something via a message queue.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum GenericSendError {
|
||||
RxDisconnected,
|
||||
QueueFull(Option<u32>),
|
||||
}
|
||||
|
||||
impl Display for GenericSendError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
GenericSendError::RxDisconnected => {
|
||||
write!(f, "rx side has disconnected")
|
||||
}
|
||||
GenericSendError::QueueFull(max_cap) => {
|
||||
write!(f, "queue with max capacity of {max_cap:?} is full")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for GenericSendError {}
|
||||
|
||||
/// Generic error type for sending something via a message queue.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum GenericRecvError {
|
||||
Empty,
|
||||
TxDisconnected,
|
||||
}
|
||||
|
||||
impl Display for GenericRecvError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::TxDisconnected => {
|
||||
write!(f, "tx side has disconnected")
|
||||
}
|
||||
Self::Empty => {
|
||||
write!(f, "nothing to receive")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for GenericRecvError {}
|
@ -1,15 +1,16 @@
|
||||
use derive_new::new;
|
||||
use satrs_core::hk::HkRequest;
|
||||
use satrs_core::mode::ModeRequest;
|
||||
use satrs_core::pus::verification::{TcStateAccepted, VerificationToken};
|
||||
use satrs_example::TargetIdWithApid;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum ActionRequest {
|
||||
CmdWithU32Id((u32, Vec<u8>)),
|
||||
CmdWithStringId((String, Vec<u8>)),
|
||||
}
|
||||
use derive_new::new;
|
||||
use satrs::action::ActionRequest;
|
||||
use satrs::hk::HkRequest;
|
||||
use satrs::mode::ModeRequest;
|
||||
use satrs::pus::action::PusActionRequestRouter;
|
||||
use satrs::pus::hk::PusHkRequestRouter;
|
||||
use satrs::pus::verification::{TcStateAccepted, VerificationToken};
|
||||
use satrs::pus::GenericRoutingError;
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::TargetId;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
@ -22,7 +23,7 @@ pub enum Request {
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, new)]
|
||||
pub struct TargetedRequest {
|
||||
pub(crate) target_id_with_apid: TargetIdWithApid,
|
||||
pub(crate) target_id: TargetId,
|
||||
pub(crate) request: Request,
|
||||
}
|
||||
|
||||
@ -34,7 +35,7 @@ pub struct RequestWithToken {
|
||||
|
||||
impl RequestWithToken {
|
||||
pub fn new(
|
||||
target_id: TargetIdWithApid,
|
||||
target_id: TargetId,
|
||||
request: Request,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
) -> Self {
|
||||
@ -44,3 +45,50 @@ impl RequestWithToken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct GenericRequestRouter(pub HashMap<TargetId, mpsc::Sender<RequestWithToken>>);
|
||||
|
||||
impl PusHkRequestRouter for GenericRequestRouter {
|
||||
type Error = GenericRoutingError;
|
||||
|
||||
fn route(
|
||||
&self,
|
||||
target_id: TargetId,
|
||||
hk_request: HkRequest,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
) -> Result<(), Self::Error> {
|
||||
if let Some(sender) = self.0.get(&target_id) {
|
||||
sender
|
||||
.send(RequestWithToken::new(
|
||||
target_id,
|
||||
Request::Hk(hk_request),
|
||||
token,
|
||||
))
|
||||
.map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PusActionRequestRouter for GenericRequestRouter {
|
||||
type Error = GenericRoutingError;
|
||||
|
||||
fn route(
|
||||
&self,
|
||||
target_id: TargetId,
|
||||
action_request: ActionRequest,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
) -> Result<(), Self::Error> {
|
||||
if let Some(sender) = self.0.get(&target_id) {
|
||||
sender
|
||||
.send(RequestWithToken::new(
|
||||
target_id,
|
||||
Request::Action(action_request),
|
||||
token,
|
||||
))
|
||||
.map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
hal::std::tcp_server::{ServerConfig, TcpSpacepacketsServer},
|
||||
spacepackets::PacketId,
|
||||
tmtc::{CcsdsDistributor, CcsdsError, TmPacketSourceCore},
|
||||
|
@ -1,10 +1,10 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
sync::mpsc::{self},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
pool::{PoolProvider, StoreAddr},
|
||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||
spacepackets::{
|
||||
@ -77,16 +77,16 @@ impl TmFunnelCommon {
|
||||
pub struct TmFunnelStatic {
|
||||
common: TmFunnelCommon,
|
||||
shared_tm_store: SharedTmPool,
|
||||
tm_funnel_rx: Receiver<StoreAddr>,
|
||||
tm_server_tx: Sender<StoreAddr>,
|
||||
tm_funnel_rx: mpsc::Receiver<StoreAddr>,
|
||||
tm_server_tx: mpsc::SyncSender<StoreAddr>,
|
||||
}
|
||||
|
||||
impl TmFunnelStatic {
|
||||
pub fn new(
|
||||
shared_tm_store: SharedTmPool,
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
tm_funnel_rx: Receiver<StoreAddr>,
|
||||
tm_server_tx: Sender<StoreAddr>,
|
||||
tm_funnel_rx: mpsc::Receiver<StoreAddr>,
|
||||
tm_server_tx: mpsc::SyncSender<StoreAddr>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
@ -123,15 +123,15 @@ impl TmFunnelStatic {
|
||||
|
||||
pub struct TmFunnelDynamic {
|
||||
common: TmFunnelCommon,
|
||||
tm_funnel_rx: Receiver<Vec<u8>>,
|
||||
tm_server_tx: Sender<Vec<u8>>,
|
||||
tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
|
||||
tm_server_tx: mpsc::Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TmFunnelDynamic {
|
||||
pub fn new(
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
tm_funnel_rx: Receiver<Vec<u8>>,
|
||||
tm_server_tx: Sender<Vec<u8>>,
|
||||
tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
|
||||
tm_server_tx: mpsc::Sender<Vec<u8>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
|
@ -1,14 +1,17 @@
|
||||
use log::warn;
|
||||
use satrs_core::pus::{EcssTcAndToken, ReceivesEcssPusTc};
|
||||
use satrs_core::spacepackets::SpHeader;
|
||||
use std::sync::mpsc::{self, Receiver, SendError, Sender, TryRecvError};
|
||||
use satrs::pus::verification::std_mod::{
|
||||
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
|
||||
};
|
||||
use satrs::pus::{EcssTcAndToken, ReceivesEcssPusTc};
|
||||
use satrs::spacepackets::SpHeader;
|
||||
use std::sync::mpsc::{self, Receiver, SendError, Sender, SyncSender, 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;
|
||||
use satrs::pool::{PoolProvider, SharedStaticMemoryPool, StoreAddr, StoreError};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::tmtc::ReceivesCcsdsTc;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum MpscStoreAndSendError {
|
||||
@ -37,7 +40,7 @@ impl SharedTcPool {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PusTcSourceProviderSharedPool {
|
||||
pub tc_source: Sender<StoreAddr>,
|
||||
pub tc_source: SyncSender<StoreAddr>,
|
||||
pub shared_pool: SharedTcPool,
|
||||
}
|
||||
|
||||
@ -97,14 +100,14 @@ pub struct TcSourceTaskStatic {
|
||||
shared_tc_pool: SharedTcPool,
|
||||
tc_receiver: Receiver<StoreAddr>,
|
||||
tc_buf: [u8; 4096],
|
||||
pus_receiver: PusReceiver,
|
||||
pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
|
||||
}
|
||||
|
||||
impl TcSourceTaskStatic {
|
||||
pub fn new(
|
||||
shared_tc_pool: SharedTcPool,
|
||||
tc_receiver: Receiver<StoreAddr>,
|
||||
pus_receiver: PusReceiver,
|
||||
pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared_tc_pool,
|
||||
@ -133,7 +136,7 @@ impl TcSourceTaskStatic {
|
||||
Ok((pus_tc, _)) => {
|
||||
self.pus_receiver
|
||||
.handle_tc_packet(
|
||||
satrs_core::pus::TcInMemory::StoreAddr(addr),
|
||||
satrs::pus::TcInMemory::StoreAddr(addr),
|
||||
pus_tc.service(),
|
||||
&pus_tc,
|
||||
)
|
||||
@ -161,11 +164,14 @@ impl TcSourceTaskStatic {
|
||||
// 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,
|
||||
pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
|
||||
}
|
||||
|
||||
impl TcSourceTaskDynamic {
|
||||
pub fn new(tc_receiver: Receiver<Vec<u8>>, pus_receiver: PusReceiver) -> Self {
|
||||
pub fn new(
|
||||
tc_receiver: Receiver<Vec<u8>>,
|
||||
pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
pus_receiver,
|
||||
@ -182,7 +188,7 @@ impl TcSourceTaskDynamic {
|
||||
Ok((pus_tc, _)) => {
|
||||
self.pus_receiver
|
||||
.handle_tc_packet(
|
||||
satrs_core::pus::TcInMemory::Vec(tc.clone()),
|
||||
satrs::pus::TcInMemory::Vec(tc.clone()),
|
||||
pus_tc.service(),
|
||||
&pus_tc,
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
||||
pool::{PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr},
|
||||
tmtc::CcsdsError,
|
||||
@ -113,7 +113,7 @@ mod tests {
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use satrs_core::{
|
||||
use satrs::{
|
||||
spacepackets::{
|
||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||
SpHeader,
|
||||
|
@ -7,3 +7,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.1.1] 2024-02-17
|
||||
|
||||
- Bumped `spacepackets` to v0.10.0
|
||||
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release containing the `resultcode` macro.
|
||||
|
@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "satrs-mib"
|
||||
version = "0.1.0-alpha.2"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = """
|
||||
Helper crate of the sat-rs framework to build a mission information base (MIB) from the
|
||||
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"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "space", "aerospace"]
|
||||
@ -22,16 +22,13 @@ serde-hex = "0.1.0"
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.satrs-core]
|
||||
version = "0.1.0-alpha.3"
|
||||
# path = "../satrs-core"
|
||||
# git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git"
|
||||
# branch = "main"
|
||||
# rev = "35e1f7a983f6535c5571186e361fe101d4306b89"
|
||||
[dependencies.satrs-shared]
|
||||
version = "0.1.2"
|
||||
features = ["serde"]
|
||||
|
||||
[dependencies.satrs-mib-codegen]
|
||||
path = "codegen"
|
||||
version = "0.1.0-alpha.2"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
|
@ -1,2 +1,8 @@
|
||||
[](https://crates.io/crates/satrs-mib)
|
||||
[](https://docs.rs/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).
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "satrs-mib-codegen"
|
||||
version = "0.1.0-alpha.2"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
description = "satrs-mib proc macro implementation"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
@ -19,19 +19,14 @@ path = "tests/tests.rs"
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
|
||||
[dependencies.satrs-core]
|
||||
version = "0.1.0-alpha.3"
|
||||
# path = "../../satrs-core"
|
||||
# git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git"
|
||||
# branch = "main"
|
||||
# rev = "35e1f7a983f6535c5571186e361fe101d4306b89"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1", features = ["diff"] }
|
||||
|
||||
[dev-dependencies.satrs-mib]
|
||||
path = ".."
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2"
|
||||
features = ["full"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1", features = ["diff"] }
|
||||
satrs-shared = "0.1.2"
|
||||
|
||||
[dev-dependencies.satrs-mib]
|
||||
path = ".."
|
||||
|
@ -1,6 +1,14 @@
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{parse_macro_input, ItemConst, LitStr};
|
||||
|
||||
/// This macro can be used to automatically generate introspection information for return codes.
|
||||
///
|
||||
/// For example, it can be applied to types like the
|
||||
/// [`satrs_mib::res_code::ResultU16`](https://docs.rs/satrs-mib/latest/satrs_mib/res_code/struct.ResultU16.html#) type
|
||||
/// to automatically generate
|
||||
/// [`satrs_mib::res_code::ResultU16Info`](https://docs.rs/satrs-mib/latest/satrs_mib/res_code/struct.ResultU16Info.html)
|
||||
/// instances. These instances can then be used for tasks like generating CSVs or YAML files with
|
||||
/// the list of all result codes. This information is valuable for both operators and developers.
|
||||
#[proc_macro_attribute]
|
||||
pub fn resultcode(
|
||||
args: proc_macro::TokenStream,
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Basic check which just verifies that everything compiles
|
||||
use satrs_core::res_code::ResultU16;
|
||||
use satrs_mib::resultcode;
|
||||
use satrs_shared::res_code::ResultU16;
|
||||
|
||||
#[resultcode]
|
||||
const _TEST_RESULT: ResultU16 = ResultU16::const_new(0, 1);
|
||||
const _TEST_RESULT: ResultU16 = ResultU16::new(0, 1);
|
||||
|
||||
fn main() {}
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Basic check which just verifies that everything compiles
|
||||
use satrs_core::res_code::ResultU16;
|
||||
use satrs_mib::resultcode;
|
||||
use satrs_shared::res_code::ResultU16;
|
||||
|
||||
#[resultcode(info = "This is a test result where the first parameter is foo")]
|
||||
const _TEST_RESULT: ResultU16 = ResultU16::const_new(0, 1);
|
||||
const _TEST_RESULT: ResultU16 = ResultU16::new(0, 1);
|
||||
|
||||
fn main() {}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use satrs_core::res_code::ResultU16;
|
||||
use satrs_mib::res_code::ResultU16Info;
|
||||
use satrs_mib::resultcode;
|
||||
use satrs_shared::res_code::ResultU16;
|
||||
|
||||
#[resultcode(info = "This is a test result where the first parameter is foo")]
|
||||
const TEST_RESULT: ResultU16 = ResultU16::const_new(0, 1);
|
||||
const TEST_RESULT: ResultU16 = ResultU16::new(0, 1);
|
||||
// Create named reference of auto-generated struct, which can be used by IDEs etc.
|
||||
const TEST_RESULT_EXT_REF: &ResultU16Info = &TEST_RESULT_EXT;
|
||||
|
||||
|
@ -19,7 +19,4 @@ Checklist for new releases
|
||||
|
||||
# Post-Release
|
||||
|
||||
1. Create a new annotaged tag and push it with `git tag -a satrs-mib-<version>` and
|
||||
`git push -u origin satrs-mib-<version>`
|
||||
2. Create a new release on `EGit` based on the tag.
|
||||
|
||||
1. Create a new release on `EGit` with the name `satrs-mib-<version>`.
|
||||
|
@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "std")]
|
||||
pub use stdmod::*;
|
||||
|
||||
pub use satrs_core::res_code::ResultU16;
|
||||
pub use satrs_shared::res_code::ResultU16;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_hex::{SerHex, StrictCapPfx};
|
||||
@ -73,6 +73,7 @@ pub mod stdmod {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function exports a slice of result code information objects to a CSV file.
|
||||
pub fn write_resultcodes_to_csv(
|
||||
writer_builder: csv::WriterBuilder,
|
||||
results: &[ResultU16Info],
|
||||
@ -96,8 +97,8 @@ mod tests {
|
||||
// Special solution for this crate because the code generated by a macro will use
|
||||
// satrs_mib::res_code::*
|
||||
use crate as satrs_mib;
|
||||
use satrs_core::res_code::ResultU16;
|
||||
use satrs_mib::resultcode;
|
||||
use satrs_shared::res_code::ResultU16;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
@ -106,12 +107,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[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]
|
||||
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(info = "Not enough data inside the TC application data field")]
|
||||
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] = &[
|
||||
INVALID_PUS_SERVICE_EXT,
|
||||
@ -119,12 +120,12 @@ mod tests {
|
||||
NOT_ENOUGH_APP_DATA_EXT,
|
||||
];
|
||||
|
||||
const CSV_NAME: &'static str = "dummy.csv";
|
||||
const CSV_NAME: &str = "dummy.csv";
|
||||
|
||||
#[test]
|
||||
fn test_printout() {
|
||||
let mut wtrb = csv::WriterBuilder::new();
|
||||
wtrb.delimiter(';' as u8);
|
||||
wtrb.delimiter(b';');
|
||||
print_resultcodes_as_csv(wtrb, TMTC_RESULTS).expect("Priting result codes failed");
|
||||
}
|
||||
|
||||
@ -133,7 +134,7 @@ mod tests {
|
||||
let csvpath = Path::new(CSV_NAME);
|
||||
let mut wtrb = csv::WriterBuilder::new();
|
||||
let file = File::create(csvpath).expect("Creating CSV file failed");
|
||||
wtrb.delimiter(';' as u8);
|
||||
wtrb.delimiter(b';');
|
||||
write_resultcodes_to_csv(wtrb, TMTC_RESULTS, file).expect("CSV export failed");
|
||||
assert!(csvpath.exists());
|
||||
let file = File::open(csvpath).expect("Opening CSV file failed");
|
||||
|
@ -7,3 +7,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.1.2] 2024-02-17
|
||||
|
||||
- Bumped `spacepackets` to v0.10.0 for `UnsignedEnum` trait change.
|
||||
|
||||
# [v0.1.1] 2024-02-12
|
||||
|
||||
- Added missing `#![no_std]` attribute for library
|
||||
- Fixed unit tests
|
||||
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release.
|
28
satrs-shared/Cargo.toml
Normal file
28
satrs-shared/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "satrs-shared"
|
||||
description = "Components shared by multiple sat-rs crates"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
license = "Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.spacepackets]
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "spacepackets/serde"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]
|
5
satrs-shared/README.md
Normal file
5
satrs-shared/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
satrs-shared
|
||||
=========
|
||||
|
||||
Subcrate for some shared sat-rs framework components not expected to change very often.
|
||||
You can find more information on the [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
3
satrs-shared/src/lib.rs
Normal file
3
satrs-shared/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! This crates contains modules shared among other sat-rs framework crates.
|
||||
#![no_std]
|
||||
pub mod res_code;
|
@ -4,6 +4,7 @@ use spacepackets::ecss::{EcssEnumU16, EcssEnumeration};
|
||||
use spacepackets::util::UnsignedEnum;
|
||||
use spacepackets::ByteConversionError;
|
||||
|
||||
/// Simple [u16] based result code type which also allows to group related resultcodes.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ResultU16 {
|
||||
@ -12,7 +13,7 @@ pub struct ResultU16 {
|
||||
}
|
||||
|
||||
impl ResultU16 {
|
||||
pub const fn const_new(group_id: u8, unique_id: u8) -> Self {
|
||||
pub const fn new(group_id: u8, unique_id: u8) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
unique_id,
|
||||
@ -51,6 +52,10 @@ impl UnsignedEnum for ResultU16 {
|
||||
buf[1] = self.unique_id;
|
||||
Ok(self.size())
|
||||
}
|
||||
|
||||
fn value(&self) -> u64 {
|
||||
self.raw() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl EcssEnumeration for ResultU16 {
|
||||
@ -58,3 +63,26 @@ impl EcssEnumeration for ResultU16 {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const RESULT_CODE_CONST: ResultU16 = ResultU16::new(1, 1);
|
||||
|
||||
#[test]
|
||||
pub fn test_basic() {
|
||||
let result_code = ResultU16::new(1, 1);
|
||||
assert_eq!(result_code.unique_id(), 1);
|
||||
assert_eq!(result_code.group_id(), 1);
|
||||
assert_eq!(result_code, RESULT_CODE_CONST);
|
||||
assert_eq!(result_code.raw(), (1_u16 << 8) | 1);
|
||||
assert_eq!(result_code.pfc(), 16);
|
||||
assert_eq!(result_code.size(), 2);
|
||||
let mut buf: [u8; 2] = [0; 2];
|
||||
let written = result_code.write_to_be_bytes(&mut buf).unwrap();
|
||||
assert_eq!(written, 2);
|
||||
assert_eq!(buf[0], 1);
|
||||
assert_eq!(buf[1], 1);
|
||||
}
|
||||
}
|
47
satrs/CHANGELOG.md
Normal file
47
satrs/CHANGELOG.md
Normal file
@ -0,0 +1,47 @@
|
||||
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]
|
||||
|
||||
## Changed
|
||||
|
||||
- Refactored `EventManager` to heavily use generics instead of trait objects.
|
||||
- `SendEventProvider` -> `EventSendProvider`. `id` trait method renamed to `channel_id`.
|
||||
- `ListenerTable` -> `ListenerMapProvider`
|
||||
- `SenderTable` -> `SenderMapProvider`
|
||||
- There is an `EventManagerWithMpsc` and a `EventManagerWithBoundedMpsc` helper type now.
|
||||
- Refactored ECSS TM sender abstractions to be generic over different message queue backends.
|
||||
- Refactored Verification Reporter abstractions and implementation to be generic over the sender
|
||||
instead of using trait objects.
|
||||
|
||||
## Fixed
|
||||
|
||||
- Update deprecated API for `PusScheduler::insert_wrapped_tc_cds_short`
|
||||
and `PusScheduler::insert_wrapped_tc_cds_long`.
|
||||
|
||||
# [v0.2.0-rc.0] 2024-02-21
|
||||
|
||||
## Added
|
||||
|
||||
- New PUS service abstractions for HK (PUS 3) and actions (PUS 8). Introducing new abstractions
|
||||
allows to move some boilerplate code into the framework.
|
||||
- New `VerificationReportingProvider` abstraction to avoid relying on a concrete verification
|
||||
reporting provider.
|
||||
|
||||
## Changed
|
||||
|
||||
- Verification reporter API timestamp arguments are not `Option`al anymore. Empty timestamps
|
||||
can be passed by simply specifying the `&[]` empty slice argument.
|
||||
|
||||
# [v0.1.1] 2024-02-12
|
||||
|
||||
- Minor fixes for crate config `homepage` entries and links in documentation.
|
||||
|
||||
# [v0.1.0] 2024-02-12
|
||||
|
||||
Initial release.
|
@ -1,31 +1,28 @@
|
||||
[package]
|
||||
name = "satrs-core"
|
||||
version = "0.1.0-alpha.3"
|
||||
name = "satrs"
|
||||
version = "0.2.0-rc.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = "Core components of the sat-rs framework to build software for remote systems"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
description = "A framework to build software for remote systems"
|
||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "space", "aerospace"]
|
||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||
|
||||
[dependencies]
|
||||
delegate = ">0.7, <=0.10"
|
||||
paste = "1"
|
||||
|
||||
[dependencies.smallvec]
|
||||
version = "1"
|
||||
smallvec = "1"
|
||||
crc = "3"
|
||||
satrs-shared = "0.1.2"
|
||||
|
||||
[dependencies.num_enum]
|
||||
version = ">0.5, <=0.7"
|
||||
default-features = false
|
||||
|
||||
[dependencies.crc]
|
||||
version = "3"
|
||||
|
||||
[dependencies.dyn-clone]
|
||||
version = "1"
|
||||
optional = true
|
||||
@ -71,11 +68,8 @@ features = ["all"]
|
||||
optional = true
|
||||
|
||||
[dependencies.spacepackets]
|
||||
version = "0.9.0"
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
# git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git"
|
||||
# rev = "297cfad22637d3b07a1b27abe56d9a607b5b82a7"
|
||||
# branch = "main"
|
||||
|
||||
[dependencies.cobs]
|
||||
git = "https://github.com/robamu/cobs.rs.git"
|
||||
@ -115,7 +109,7 @@ alloc = [
|
||||
"dyn-clone",
|
||||
"downcast-rs"
|
||||
]
|
||||
serde = ["dep:serde", "spacepackets/serde"]
|
||||
serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"]
|
||||
crossbeam = ["crossbeam-channel"]
|
||||
heapless = ["dep:heapless"]
|
||||
doc-images = []
|
201
satrs/LICENSE-APACHE
Normal file
201
satrs/LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
1
satrs/NOTICE
Normal file
1
satrs/NOTICE
Normal file
@ -0,0 +1 @@
|
||||
This software contains code developed at the University of Stuttgart's Institute of Space Systems.
|
8
satrs/README.md
Normal file
8
satrs/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
[](https://crates.io/crates/satrs)
|
||||
[](https://docs.rs/satrs)
|
||||
|
||||
sat-rs
|
||||
======
|
||||
|
||||
This crate contains the primary components of the sat-rs framework.
|
||||
You can find more information on the [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
@ -19,7 +19,5 @@ Checklist for new releases
|
||||
|
||||
# Post-Release
|
||||
|
||||
1. Create a new annotaged tag and push it with `git tag -a satrs-core-<version>` and
|
||||
`git push -u origin satrs-core-<version>`
|
||||
2. Create a new release on `EGit` based on the tag.
|
||||
1. Create a new release on `EGit` with the name `satrs-<version>`.
|
||||
|
42
satrs/src/action.rs
Normal file
42
satrs/src/action.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::{pool::StoreAddr, TargetId};
|
||||
|
||||
pub type ActionId = u32;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum ActionRequest {
|
||||
UnsignedIdAndStoreData {
|
||||
action_id: ActionId,
|
||||
data_addr: StoreAddr,
|
||||
},
|
||||
#[cfg(feature = "alloc")]
|
||||
UnsignedIdAndVecData {
|
||||
action_id: ActionId,
|
||||
data: alloc::vec::Vec<u8>,
|
||||
},
|
||||
#[cfg(feature = "alloc")]
|
||||
StringIdAndVecData {
|
||||
action_id: alloc::string::String,
|
||||
data: alloc::vec::Vec<u8>,
|
||||
},
|
||||
#[cfg(feature = "alloc")]
|
||||
StringIdAndStoreData {
|
||||
action_id: alloc::string::String,
|
||||
data: StoreAddr,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TargetedActionRequest {
|
||||
target: TargetId,
|
||||
action_request: ActionRequest,
|
||||
}
|
||||
|
||||
impl TargetedActionRequest {
|
||||
pub fn new(target: TargetId, action_request: ActionRequest) -> Self {
|
||||
Self {
|
||||
target,
|
||||
action_request,
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ use spacepackets::{
|
||||
tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType},
|
||||
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
|
||||
},
|
||||
util::UnsignedByteField,
|
||||
util::{UnsignedByteField, UnsignedEnum},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
@ -7,7 +7,7 @@ use spacepackets::ByteConversionError;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "std")]
|
||||
pub use stdmod::*;
|
||||
pub use std_mod::*;
|
||||
|
||||
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||
|
||||
@ -148,12 +148,11 @@ pub trait VirtualFilestore {
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod stdmod {
|
||||
pub mod std_mod {
|
||||
use super::*;
|
||||
use std::{
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
@ -9,7 +9,7 @@ use spacepackets::{
|
||||
pdu::{FileDirectiveType, PduError, PduHeader},
|
||||
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
|
||||
},
|
||||
util::UnsignedByteField,
|
||||
util::{UnsignedByteField, UnsignedEnum},
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
@ -13,7 +13,7 @@ use cobs::{decode_in_place, encode, max_encoding_length};
|
||||
///
|
||||
/// ```
|
||||
/// use cobs::decode_in_place_report;
|
||||
/// use satrs_core::encoding::{encode_packet_with_cobs};
|
||||
/// use satrs::encoding::{encode_packet_with_cobs};
|
||||
//
|
||||
/// const SIMPLE_PACKET: [u8; 5] = [1, 2, 3, 4, 5];
|
||||
/// const INVERTED_PACKET: [u8; 5] = [5, 4, 3, 2, 1];
|
784
satrs/src/event_man.rs
Normal file
784
satrs/src/event_man.rs
Normal file
@ -0,0 +1,784 @@
|
||||
//! 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 [ListenerMapProvider], which maps
|
||||
//! listener groups identified by [ListenerKey]s to a [sender ID][ChannelId].
|
||||
//! It also contains a sender table abstracted by the [SenderMapProvider] which maps these sender
|
||||
//! IDs to concrete [EventSendProvider]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 [EventReceiveProvider] 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 [EventReceiveProvider]
|
||||
//! implementation so all events are routed to the manager.
|
||||
//! 4. Create the [send event providers][EventSendProvider]s which allow routing events to
|
||||
//! subscribers. You can now use their [sender IDs][EventSendProvider::channel_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/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};
|
||||
use crate::queue::GenericSendError;
|
||||
use core::marker::PhantomData;
|
||||
use core::slice::Iter;
|
||||
|
||||
use crate::ChannelId;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use std_mod::*;
|
||||
|
||||
#[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 EventSendProvider<EV: GenericEvent, AuxDataProvider = Params> {
|
||||
fn channel_id(&self) -> ChannelId;
|
||||
|
||||
fn send_no_data(&self, event: EV) -> Result<(), GenericSendError> {
|
||||
self.send(event, None)
|
||||
}
|
||||
|
||||
fn send(&self, event: EV, aux_data: Option<AuxDataProvider>) -> Result<(), GenericSendError>;
|
||||
}
|
||||
|
||||
/// Generic abstraction for an event receiver.
|
||||
pub trait EventReceiveProvider<Event: GenericEvent, AuxDataProvider = Params> {
|
||||
/// This function has to be provided by any event receiver. A call may or may not return
|
||||
/// an event and optional auxiliary data.
|
||||
fn try_recv_event(&self) -> Option<(Event, Option<AuxDataProvider>)>;
|
||||
}
|
||||
|
||||
pub trait ListenerMapProvider {
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
fn get_listeners(&self) -> alloc::vec::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 SenderMapProvider<
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent = EventU32,
|
||||
AUX = Params,
|
||||
>
|
||||
{
|
||||
fn contains_send_event_provider(&self, id: &ChannelId) -> bool;
|
||||
|
||||
fn get_send_event_provider(&self, id: &ChannelId) -> Option<&SP>;
|
||||
fn add_send_event_provider(&mut self, send_provider: SP) -> bool;
|
||||
}
|
||||
|
||||
/// Generic event manager implementation.
|
||||
///
|
||||
/// # Generics
|
||||
///
|
||||
/// * `ERP`: [EventReceiveProvider] used to receive all events.
|
||||
/// * `SMP`: [SenderMapProvider] which maps channel IDs to send providers.
|
||||
/// * `LTR`: [ListenerMapProvider] which maps listener keys to channel IDs.
|
||||
/// * `SP`: [EventSendProvider] contained within the sender map which sends the events.
|
||||
/// * `EV`: The event type. This type must implement the [GenericEvent]. Currently only [EventU32]
|
||||
/// and [EventU16] are supported.
|
||||
/// * `AUX`: Auxiliary data which is sent with the event to provide optional context information
|
||||
pub struct EventManager<
|
||||
ERP: EventReceiveProvider<EV, AUX>,
|
||||
SMP: SenderMapProvider<SP, EV, AUX>,
|
||||
LTR: ListenerMapProvider,
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent = EventU32,
|
||||
AUX = Params,
|
||||
> {
|
||||
event_receiver: ERP,
|
||||
sender_map: SMP,
|
||||
listener_map: LTR,
|
||||
phantom: core::marker::PhantomData<(SP, EV, AUX)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventRoutingResult<EV: GenericEvent, AUX> {
|
||||
/// No event was received
|
||||
Empty,
|
||||
/// An event was received and routed to listeners.
|
||||
Handled {
|
||||
num_recipients: u32,
|
||||
event: EV,
|
||||
aux_data: Option<AUX>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventRoutingError {
|
||||
Send(GenericSendError),
|
||||
NoSendersForKey(ListenerKey),
|
||||
NoSenderForId(ChannelId),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventRoutingErrorsWithResult<EV: GenericEvent, AUX> {
|
||||
pub result: EventRoutingResult<EV, AUX>,
|
||||
pub errors: [Option<EventRoutingError>; 3],
|
||||
}
|
||||
|
||||
impl<
|
||||
ER: EventReceiveProvider<EV, AUX>,
|
||||
S: SenderMapProvider<SP, EV, AUX>,
|
||||
L: ListenerMapProvider,
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent + Copy,
|
||||
AUX: Clone,
|
||||
> EventManager<ER, S, L, SP, EV, AUX>
|
||||
{
|
||||
pub fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||
self.listener_map.remove_duplicates(key)
|
||||
}
|
||||
|
||||
/// Subscribe for a unique event.
|
||||
pub fn subscribe_single(&mut self, event: &EV, 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<
|
||||
ERP: EventReceiveProvider<EV, AUX>,
|
||||
SMP: SenderMapProvider<SP, EV, AUX>,
|
||||
LTR: ListenerMapProvider,
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent + Copy,
|
||||
AUX: Clone,
|
||||
> EventManager<ERP, SMP, LTR, SP, EV, AUX>
|
||||
{
|
||||
pub fn new_with_custom_maps(event_receiver: ERP, sender_map: SMP, listener_map: LTR) -> Self {
|
||||
EventManager {
|
||||
listener_map,
|
||||
sender_map,
|
||||
event_receiver,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new sender component which can be used to send events to subscribers.
|
||||
pub fn add_sender(&mut self, send_provider: SP) {
|
||||
if !self
|
||||
.sender_map
|
||||
.contains_send_event_provider(&send_provider.channel_id())
|
||||
{
|
||||
self.sender_map.add_send_event_provider(send_provider);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic function to update the event subscribers.
|
||||
fn update_listeners(&mut self, key: ListenerKey, sender_id: ChannelId) {
|
||||
self.listener_map.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<EV, AUX>, EventRoutingErrorsWithResult<EV, AUX>> {
|
||||
let mut err_idx = 0;
|
||||
let mut err_slice = [None, None, None];
|
||||
let mut num_recipients = 0;
|
||||
let mut add_error = |error: EventRoutingError| {
|
||||
if err_idx < 3 {
|
||||
err_slice[err_idx] = Some(error);
|
||||
err_idx += 1;
|
||||
}
|
||||
};
|
||||
let mut send_handler = |key: &ListenerKey, event: EV, aux_data: &Option<AUX>| {
|
||||
if self.listener_map.contains_listener(key) {
|
||||
if let Some(ids) = self.listener_map.get_listener_ids(key) {
|
||||
for id in ids {
|
||||
if let Some(sender) = self.sender_map.get_send_event_provider(id) {
|
||||
if let Err(e) = sender.send(event, aux_data.clone()) {
|
||||
add_error(EventRoutingError::Send(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.try_recv_event() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Helper type which constrains the sender map and listener map generics to the [DefaultSenderMap]
|
||||
/// and the [DefaultListenerMap]. It uses regular mpsc channels as the message queue backend.
|
||||
pub type EventManagerWithMpsc<EV = EventU32, AUX = Params> = EventManager<
|
||||
MpscEventReceiver,
|
||||
DefaultSenderMap<EventSenderMpsc<EV>, EV, AUX>,
|
||||
DefaultListenerMap,
|
||||
EventSenderMpsc<EV>,
|
||||
>;
|
||||
|
||||
/// Helper type which constrains the sender map and listener map generics to the [DefaultSenderMap]
|
||||
/// and the [DefaultListenerMap]. It uses
|
||||
/// [bounded mpsc senders](https://doc.rust-lang.org/std/sync/mpsc/struct.SyncSender.html) as the
|
||||
/// message queue backend.
|
||||
pub type EventManagerWithBoundedMpsc<EV = EventU32, AUX = Params> = EventManager<
|
||||
MpscEventReceiver,
|
||||
DefaultSenderMap<EventSenderMpscBounded<EV>, EV, AUX>,
|
||||
DefaultListenerMap,
|
||||
EventSenderMpscBounded<EV>,
|
||||
>;
|
||||
|
||||
impl<
|
||||
ER: EventReceiveProvider<EV, AUX>,
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent + Copy,
|
||||
AUX: 'static,
|
||||
> EventManager<ER, DefaultSenderMap<SP, EV, AUX>, DefaultListenerMap, SP, EV, AUX>
|
||||
{
|
||||
/// Create an event manager where the sender table will be the [DefaultSenderMap]
|
||||
/// and the listener table will be the [DefaultListenerMap].
|
||||
pub fn new(event_receiver: ER) -> Self {
|
||||
Self {
|
||||
listener_map: DefaultListenerMap::default(),
|
||||
sender_map: DefaultSenderMap::default(),
|
||||
event_receiver,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default listener map.
|
||||
///
|
||||
/// Simple implementation which uses a [HashMap] and a [Vec] internally.
|
||||
#[derive(Default)]
|
||||
pub struct DefaultListenerMap {
|
||||
listeners: HashMap<ListenerKey, Vec<ChannelId>>,
|
||||
}
|
||||
|
||||
impl ListenerMapProvider for DefaultListenerMap {
|
||||
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 = alloc::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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default sender map.
|
||||
///
|
||||
/// Simple implementation which uses a [HashMap] internally.
|
||||
pub struct DefaultSenderMap<
|
||||
SP: EventSendProvider<EV, AUX>,
|
||||
EV: GenericEvent = EventU32,
|
||||
AUX = Params,
|
||||
> {
|
||||
senders: HashMap<ChannelId, SP>,
|
||||
phantom: PhantomData<(EV, AUX)>,
|
||||
}
|
||||
|
||||
impl<SP: EventSendProvider<EV, AUX>, EV: GenericEvent, AUX> Default
|
||||
for DefaultSenderMap<SP, EV, AUX>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
senders: Default::default(),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SP: EventSendProvider<EV, AUX>, EV: GenericEvent, AUX> SenderMapProvider<SP, EV, AUX>
|
||||
for DefaultSenderMap<SP, EV, AUX>
|
||||
{
|
||||
fn contains_send_event_provider(&self, id: &ChannelId) -> bool {
|
||||
self.senders.contains_key(id)
|
||||
}
|
||||
|
||||
fn get_send_event_provider(&self, id: &ChannelId) -> Option<&SP> {
|
||||
self.senders
|
||||
.get(id)
|
||||
.filter(|sender| sender.channel_id() == *id)
|
||||
}
|
||||
|
||||
fn add_send_event_provider(&mut self, send_provider: SP) -> bool {
|
||||
let id = send_provider.channel_id();
|
||||
if self.senders.contains_key(&id) {
|
||||
return false;
|
||||
}
|
||||
self.senders.insert(id, send_provider).is_none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod std_mod {
|
||||
use super::*;
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> {
|
||||
mpsc_receiver: mpsc::Receiver<(Event, Option<Params>)>,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> MpscEventReceiver<Event> {
|
||||
pub fn new(receiver: mpsc::Receiver<(Event, Option<Params>)>) -> Self {
|
||||
Self {
|
||||
mpsc_receiver: receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Event: GenericEvent + Send> EventReceiveProvider<Event> for MpscEventReceiver<Event> {
|
||||
fn try_recv_event(&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>;
|
||||
|
||||
/// Generic event sender which uses a regular [mpsc::Sender] as the messaging backend to
|
||||
/// send events.
|
||||
#[derive(Clone)]
|
||||
pub struct EventSenderMpsc<Event: GenericEvent + Send> {
|
||||
id: u32,
|
||||
sender: mpsc::Sender<(Event, Option<Params>)>,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> EventSenderMpsc<Event> {
|
||||
pub fn new(id: u32, sender: mpsc::Sender<(Event, Option<Params>)>) -> Self {
|
||||
Self { id, sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> EventSendProvider<Event> for EventSenderMpsc<Event> {
|
||||
fn channel_id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), GenericSendError> {
|
||||
self.sender
|
||||
.send((event, aux_data))
|
||||
.map_err(|_| GenericSendError::RxDisconnected)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic event sender which uses the [mpsc::SyncSender] as the messaging backend to send
|
||||
/// events. This has the advantage that the channel is bounded and thus more deterministic.
|
||||
#[derive(Clone)]
|
||||
pub struct EventSenderMpscBounded<Event: GenericEvent + Send> {
|
||||
channel_id: u32,
|
||||
sender: mpsc::SyncSender<(Event, Option<Params>)>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> EventSenderMpscBounded<Event> {
|
||||
pub fn new(
|
||||
channel_id: u32,
|
||||
sender: mpsc::SyncSender<(Event, Option<Params>)>,
|
||||
capacity: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
channel_id,
|
||||
sender,
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> EventSendProvider<Event> for EventSenderMpscBounded<Event> {
|
||||
fn channel_id(&self) -> u32 {
|
||||
self.channel_id
|
||||
}
|
||||
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), GenericSendError> {
|
||||
if let Err(e) = self.sender.try_send((event, aux_data)) {
|
||||
return match e {
|
||||
mpsc::TrySendError::Full(_) => {
|
||||
Err(GenericSendError::QueueFull(Some(self.capacity as u32)))
|
||||
}
|
||||
mpsc::TrySendError::Disconnected(_) => Err(GenericSendError::RxDisconnected),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type EventU32SenderMpsc = EventSenderMpsc<EventU32>;
|
||||
pub type EventU16SenderMpsc = EventSenderMpsc<EventU16>;
|
||||
pub type EventU32SenderMpscBounded = EventSenderMpscBounded<EventU32>;
|
||||
pub type EventU16SenderMpscBounded = EventSenderMpscBounded<EventU16>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::event_man::EventManager;
|
||||
use crate::events::{EventU32, GenericEvent, Severity};
|
||||
use crate::params::ParamsRaw;
|
||||
use std::format;
|
||||
use std::sync::mpsc::{self, channel, Receiver, Sender};
|
||||
|
||||
const TEST_EVENT: EventU32 = EventU32::const_new(Severity::INFO, 0, 5);
|
||||
|
||||
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,
|
||||
..
|
||||
} = res
|
||||
{
|
||||
assert_eq!(event, expected);
|
||||
assert_eq!(num_recipients, expected_num_sent);
|
||||
}
|
||||
}
|
||||
|
||||
fn generic_event_man() -> (Sender<EventU32WithAuxData>, EventManagerWithMpsc) {
|
||||
let (event_sender, manager_queue) = channel();
|
||||
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
||||
(event_sender, EventManager::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 = EventSenderMpsc::new(0, single_event_sender);
|
||||
event_man.subscribe_single(&event_grp_0, single_event_listener.channel_id());
|
||||
event_man.add_sender(single_event_listener);
|
||||
let (group_event_sender_0, group_event_receiver_0) = channel();
|
||||
let group_event_listener = EventU32SenderMpsc::new(1, group_event_sender_0);
|
||||
event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.channel_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 = EventSenderMpsc::new(0, single_event_sender);
|
||||
event_man.subscribe_single(&event_grp_0, single_event_listener.channel_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 = EventU32SenderMpsc::new(0, event_grp_0_sender);
|
||||
event_man.subscribe_group(
|
||||
event_grp_0.group_id(),
|
||||
event_grp_0_and_1_listener.channel_id(),
|
||||
);
|
||||
event_man.subscribe_group(
|
||||
event_grp_1_0.group_id(),
|
||||
event_grp_0_and_1_listener.channel_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 = EventU32SenderMpsc::new(0, event_0_tx_0);
|
||||
let event_listener_1 = EventU32SenderMpsc::new(1, event_0_tx_1);
|
||||
let event_listener_0_sender_id = event_listener_0.channel_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.channel_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 = EventManagerWithMpsc::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 = EventU32SenderMpsc::new(0, event_0_tx_0);
|
||||
event_man.subscribe_all(all_events_listener.channel_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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounded_event_sender_queue_full() {
|
||||
let (event_sender, _event_receiver) = mpsc::sync_channel(3);
|
||||
let event_sender = EventU32SenderMpscBounded::new(1, event_sender, 3);
|
||||
event_sender
|
||||
.send_no_data(TEST_EVENT)
|
||||
.expect("sending test event failed");
|
||||
event_sender
|
||||
.send_no_data(TEST_EVENT)
|
||||
.expect("sending test event failed");
|
||||
event_sender
|
||||
.send_no_data(TEST_EVENT)
|
||||
.expect("sending test event failed");
|
||||
let error = event_sender.send_no_data(TEST_EVENT);
|
||||
if let Err(e) = error {
|
||||
assert!(matches!(e, GenericSendError::QueueFull(Some(3))));
|
||||
} else {
|
||||
panic!("unexpected error {error:?}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_bounded_event_sender_rx_dropped() {
|
||||
let (event_sender, event_receiver) = mpsc::sync_channel(3);
|
||||
let event_sender = EventU32SenderMpscBounded::new(1, event_sender, 3);
|
||||
drop(event_receiver);
|
||||
if let Err(e) = event_sender.send_no_data(TEST_EVENT) {
|
||||
assert!(matches!(e, GenericSendError::RxDisconnected));
|
||||
} else {
|
||||
panic!("Expected error");
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
|
||||
//! use satrs::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
|
||||
//!
|
||||
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(1, 0);
|
||||
//! const MSG_FAILED: EventU32 = EventU32::const_new(Severity::LOW, 1, 1);
|
||||
@ -412,6 +412,10 @@ impl UnsignedEnum for EventU32 {
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
self.base.write_to_bytes(self.raw(), buf, self.size())
|
||||
}
|
||||
|
||||
fn value(&self) -> u64 {
|
||||
self.raw().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl EcssEnumeration for EventU32 {
|
||||
@ -425,6 +429,7 @@ impl<SEVERITY: HasSeverity> UnsignedEnum for EventU32TypedSev<SEVERITY> {
|
||||
delegate!(to self.event {
|
||||
fn size(&self) -> usize;
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||
fn value(&self) -> u64;
|
||||
});
|
||||
}
|
||||
|
||||
@ -560,6 +565,10 @@ impl UnsignedEnum for EventU16 {
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
self.base.write_to_bytes(self.raw(), buf, self.size())
|
||||
}
|
||||
|
||||
fn value(&self) -> u64 {
|
||||
self.raw().into()
|
||||
}
|
||||
}
|
||||
impl EcssEnumeration for EventU16 {
|
||||
#[inline]
|
||||
@ -573,6 +582,7 @@ impl<SEVERITY: HasSeverity> UnsignedEnum for EventU16TypedSev<SEVERITY> {
|
||||
delegate!(to self.event {
|
||||
fn size(&self) -> usize;
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||
fn value(&self) -> u64;
|
||||
});
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs)
|
||||
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
|
||||
/// test also serves as the example application for this module.
|
||||
pub struct TcpTmtcInCobsServer<
|
||||
TmError,
|
@ -88,7 +88,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
|
||||
/// [spacepackets::PacketId]s as part of the server configuration for that purpose.
|
||||
///
|
||||
/// ## Example
|
||||
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs)
|
||||
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
|
||||
/// also serves as the example application for this module.
|
||||
pub struct TcpSpacepacketsServer<
|
||||
TmError,
|
@ -20,8 +20,8 @@ use std::vec::Vec;
|
||||
/// ```
|
||||
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
/// use spacepackets::ecss::WritablePusPacket;
|
||||
/// use satrs_core::hal::std::udp_server::UdpTcServer;
|
||||
/// use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
/// use satrs::hal::std::udp_server::UdpTcServer;
|
||||
/// use satrs::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
/// use spacepackets::SpHeader;
|
||||
/// use spacepackets::ecss::tc::PusTcCreator;
|
||||
///
|
40
satrs/src/hk.rs
Normal file
40
satrs/src/hk.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use crate::{
|
||||
pus::verification::{TcStateAccepted, VerificationToken},
|
||||
TargetId,
|
||||
};
|
||||
|
||||
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 {
|
||||
pub target_id: TargetId,
|
||||
pub hk_request: HkRequest,
|
||||
}
|
||||
|
||||
impl TargetedHkRequest {
|
||||
pub fn new(target_id: TargetId, hk_request: HkRequest) -> Self {
|
||||
Self {
|
||||
target_id,
|
||||
hk_request,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PusHkRequestRouter {
|
||||
type Error;
|
||||
fn route(
|
||||
&self,
|
||||
target_id: TargetId,
|
||||
hk_request: HkRequest,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
//! # Core components of the sat-rs framework
|
||||
//! # sat-rs: A framework to build on-board software for remote systems
|
||||
//!
|
||||
//! You can find more information about the sat-rs framework on the
|
||||
//! [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).
|
||||
//! [homepage](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
|
||||
//! The [satrs-book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/) contains
|
||||
//! high-level information about this framework.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
@ -24,27 +26,31 @@ extern crate std;
|
||||
#[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 queue;
|
||||
pub mod request;
|
||||
pub mod res_code;
|
||||
pub mod seq_count;
|
||||
pub mod tmtc;
|
||||
|
||||
pub mod action;
|
||||
pub mod hk;
|
||||
pub mod mode;
|
||||
pub mod params;
|
||||
|
||||
pub use spacepackets;
|
||||
|
||||
// Generic channel ID type.
|
||||
/// Generic channel ID type.
|
||||
pub type ChannelId = u32;
|
||||
|
||||
/// Generic target ID type.
|
||||
pub type TargetId = u64;
|
@ -1,9 +1,10 @@
|
||||
use crate::tmtc::TargetId;
|
||||
use core::mem::size_of;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacepackets::ByteConversionError;
|
||||
|
||||
use crate::TargetId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ModeAndSubmode {
|
||||
@ -47,12 +48,12 @@ impl ModeAndSubmode {
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ModeCommand {
|
||||
pub struct TargetedModeCommand {
|
||||
pub address: TargetId,
|
||||
pub mode_submode: ModeAndSubmode,
|
||||
}
|
||||
|
||||
impl ModeCommand {
|
||||
impl TargetedModeCommand {
|
||||
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
|
||||
Self {
|
||||
address,
|
@ -10,7 +10,7 @@
|
||||
//! ```rust
|
||||
//! use std::any::Any;
|
||||
//! use std::error::Error;
|
||||
//! use satrs_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||
//! use satrs::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||
//!
|
||||
//! struct ExampleSysObj {
|
||||
//! id: ObjectId,
|
||||
@ -51,7 +51,6 @@
|
||||
//! 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")]
|
||||
@ -63,6 +62,8 @@ use hashbrown::HashMap;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
use crate::TargetId;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
pub struct ObjectId {
|
||||
pub id: TargetId,
|
@ -13,7 +13,7 @@
|
||||
//! ## Example for primitive type wrapper
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::params::{ParamsRaw, ToBeBytes, U32Pair, WritableToBeBytes};
|
||||
//! use satrs::params::{ParamsRaw, ToBeBytes, U32Pair, WritableToBeBytes};
|
||||
//!
|
||||
//! let u32_pair = U32Pair(0x1010, 25);
|
||||
//! assert_eq!(u32_pair.0, 0x1010);
|
||||
@ -43,22 +43,19 @@
|
||||
//! 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};
|
||||
pub use spacepackets::util::ToBeBytes;
|
||||
use spacepackets::util::UnsignedEnum;
|
||||
use spacepackets::ByteConversionError;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
pub use spacepackets::util::ToBeBytes;
|
||||
use alloc::string::{String, ToString};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Generic trait which is used for objects which can be converted into a raw network (big) endian
|
||||
/// byte format.
|
||||
@ -560,56 +557,64 @@ from_conversions_for_raw!(
|
||||
(f64, Self::F64),
|
||||
);
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod alloc_mod {
|
||||
use super::*;
|
||||
/// Generic enumeration for additional parameters, including parameters which rely on heap
|
||||
/// allocations.
|
||||
/// Generic enumeration for additional parameters, including parameters which rely on heap
|
||||
/// allocations.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Params {
|
||||
Heapless(ParamsHeapless),
|
||||
Store(StoreAddr),
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Params {
|
||||
Heapless(ParamsHeapless),
|
||||
Store(StoreAddr),
|
||||
Vec(Vec<u8>),
|
||||
String(String),
|
||||
}
|
||||
Vec(Vec<u8>),
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl From<StoreAddr> for Params {
|
||||
fn from(x: StoreAddr) -> Self {
|
||||
Self::Store(x)
|
||||
}
|
||||
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<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)
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
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())
|
||||
}
|
||||
/// Converts a byte slice into the [Params::Vec] variant
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
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)
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
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(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
/// Converts a string slice into the [Params::String] variant
|
||||
impl From<&str> for Params {
|
||||
fn from(val: &str) -> Self {
|
||||
Self::String(val.to_string())
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
//! # Example for the [StaticMemoryPool]
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::pool::{PoolProvider, StaticMemoryPool, StaticPoolConfig};
|
||||
//! use satrs::pool::{PoolProvider, StaticMemoryPool, StaticPoolConfig};
|
||||
//!
|
||||
//! // 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes
|
||||
//! let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)], false);
|
385
satrs/src/pus/action.rs
Normal file
385
satrs/src/pus/action.rs
Normal file
@ -0,0 +1,385 @@
|
||||
use crate::{action::ActionRequest, TargetId};
|
||||
|
||||
use super::verification::{TcStateAccepted, VerificationToken};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||
pub use std_mod::*;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub use alloc_mod::*;
|
||||
|
||||
/// This trait is an abstraction for the routing of PUS service 8 action requests to a dedicated
|
||||
/// recipient using the generic [TargetId].
|
||||
pub trait PusActionRequestRouter {
|
||||
type Error;
|
||||
fn route(
|
||||
&self,
|
||||
target_id: TargetId,
|
||||
hk_request: ActionRequest,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub mod alloc_mod {
|
||||
use spacepackets::ecss::tc::PusTcReader;
|
||||
|
||||
use crate::pus::verification::VerificationReportingProvider;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// This trait is an abstraction for the conversion of a PUS service 8 action telecommand into
|
||||
/// an [ActionRequest].
|
||||
///
|
||||
/// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
|
||||
/// The only requirement is that a valid [TargetId] and an [ActionRequest] are returned by the
|
||||
/// core conversion function.
|
||||
///
|
||||
/// The user should take care of performing the error handling as well. Some of the following
|
||||
/// aspects might be relevant:
|
||||
///
|
||||
/// - Checking the validity of the APID, service ID, subservice ID.
|
||||
/// - Checking the validity of the user data.
|
||||
///
|
||||
/// A [VerificationReportingProvider] instance is passed to the user to also allow handling
|
||||
/// of the verification process as part of the PUS standard requirements.
|
||||
pub trait PusActionToRequestConverter {
|
||||
type Error;
|
||||
fn convert(
|
||||
&mut self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
time_stamp: &[u8],
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
) -> Result<(TargetId, ActionRequest), Self::Error>;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||
pub mod std_mod {
|
||||
use crate::pus::{
|
||||
verification::VerificationReportingProvider, EcssTcInMemConverter, GenericRoutingError,
|
||||
PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceBase,
|
||||
PusServiceHelper,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// This is a high-level handler for the PUS service 8 action service.
|
||||
///
|
||||
/// It performs the following handling steps:
|
||||
///
|
||||
/// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
|
||||
/// allows to configure the used telecommand memory backend.
|
||||
/// 2. Convert the TC to a targeted action request using the provided
|
||||
/// [PusActionToRequestConverter]. The generic error type is constrained to the
|
||||
/// [PusPacketHandlingError] for the concrete implementation which offers a packet handler.
|
||||
/// 3. Route the action request using the provided [PusActionRequestRouter].
|
||||
/// 4. Handle all routing errors using the provided [PusRoutingErrorHandler].
|
||||
pub struct PusService8ActionHandler<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusActionToRequestConverter,
|
||||
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
|
||||
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
|
||||
RoutingError = GenericRoutingError,
|
||||
> {
|
||||
service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
|
||||
pub request_converter: RequestConverter,
|
||||
pub request_router: RequestRouter,
|
||||
pub routing_error_handler: RoutingErrorHandler,
|
||||
}
|
||||
|
||||
impl<
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusActionToRequestConverter<Error = PusPacketHandlingError>,
|
||||
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
|
||||
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
|
||||
RoutingError: Clone,
|
||||
>
|
||||
PusService8ActionHandler<
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
RequestConverter,
|
||||
RequestRouter,
|
||||
RoutingErrorHandler,
|
||||
RoutingError,
|
||||
>
|
||||
where
|
||||
PusPacketHandlingError: From<RoutingError>,
|
||||
{
|
||||
pub fn new(
|
||||
service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
|
||||
request_converter: RequestConverter,
|
||||
request_router: RequestRouter,
|
||||
routing_error_handler: RoutingErrorHandler,
|
||||
) -> Self {
|
||||
Self {
|
||||
service_helper,
|
||||
request_converter,
|
||||
request_router,
|
||||
routing_error_handler,
|
||||
}
|
||||
}
|
||||
|
||||
/// Core function to poll the next TC packet and try to handle it.
|
||||
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)?;
|
||||
let mut partial_error = None;
|
||||
let time_stamp =
|
||||
PusServiceBase::<VerificationReporter>::get_current_cds_short_timestamp(
|
||||
&mut partial_error,
|
||||
);
|
||||
let (target_id, action_request) = self.request_converter.convert(
|
||||
ecss_tc_and_token.token,
|
||||
&tc,
|
||||
&time_stamp,
|
||||
&self.service_helper.common.verification_handler,
|
||||
)?;
|
||||
if let Err(e) =
|
||||
self.request_router
|
||||
.route(target_id, action_request, ecss_tc_and_token.token)
|
||||
{
|
||||
self.routing_error_handler.handle_error(
|
||||
target_id,
|
||||
ecss_tc_and_token.token,
|
||||
&tc,
|
||||
e.clone(),
|
||||
&time_stamp,
|
||||
&self.service_helper.common.verification_handler,
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
Ok(PusPacketHandlerResult::RequestHandled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use delegate::delegate;
|
||||
|
||||
use spacepackets::{
|
||||
ecss::{
|
||||
tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
|
||||
tm::PusTmReader,
|
||||
PusPacket,
|
||||
},
|
||||
CcsdsPacket, SequenceFlags, SpHeader,
|
||||
};
|
||||
|
||||
use crate::pus::{
|
||||
tests::{
|
||||
PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler, TestConverter,
|
||||
TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
|
||||
},
|
||||
verification::{
|
||||
tests::TestVerificationReporter, FailParams, RequestId, VerificationReportingProvider,
|
||||
},
|
||||
EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl PusActionRequestRouter for TestRouter<ActionRequest> {
|
||||
type Error = GenericRoutingError;
|
||||
|
||||
fn route(
|
||||
&self,
|
||||
target_id: TargetId,
|
||||
hk_request: ActionRequest,
|
||||
_token: VerificationToken<TcStateAccepted>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.routing_requests
|
||||
.borrow_mut()
|
||||
.push_back((target_id, hk_request));
|
||||
self.check_for_injected_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl PusActionToRequestConverter for TestConverter<8> {
|
||||
type Error = PusPacketHandlingError;
|
||||
fn convert(
|
||||
&mut self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
time_stamp: &[u8],
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
) -> Result<(TargetId, ActionRequest), Self::Error> {
|
||||
self.conversion_request.push_back(tc.raw_data().to_vec());
|
||||
self.check_service(tc)?;
|
||||
let target_id = tc.apid();
|
||||
if tc.user_data().len() < 4 {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
token,
|
||||
FailParams::new(
|
||||
time_stamp,
|
||||
&APP_DATA_TOO_SHORT,
|
||||
(tc.user_data().len() as u32).to_be_bytes().as_ref(),
|
||||
),
|
||||
)
|
||||
.expect("start success failure");
|
||||
return Err(PusPacketHandlingError::NotEnoughAppData {
|
||||
expected: 4,
|
||||
found: tc.user_data().len(),
|
||||
});
|
||||
}
|
||||
if tc.subservice() == 1 {
|
||||
verif_reporter
|
||||
.start_success(token, time_stamp)
|
||||
.expect("start success failure");
|
||||
return Ok((
|
||||
target_id.into(),
|
||||
ActionRequest::UnsignedIdAndVecData {
|
||||
action_id: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
|
||||
data: tc.user_data()[4..].to_vec(),
|
||||
},
|
||||
));
|
||||
}
|
||||
Err(PusPacketHandlingError::InvalidAppData(
|
||||
"unexpected app data".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct Pus8HandlerWithVecTester {
|
||||
common: PusServiceHandlerWithVecCommon<TestVerificationReporter>,
|
||||
handler: PusService8ActionHandler<
|
||||
EcssTcInVecConverter,
|
||||
TestVerificationReporter,
|
||||
TestConverter<8>,
|
||||
TestRouter<ActionRequest>,
|
||||
TestRoutingErrorHandler,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Pus8HandlerWithVecTester {
|
||||
pub fn new() -> Self {
|
||||
let (common, srv_handler) =
|
||||
PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
|
||||
Self {
|
||||
common,
|
||||
handler: PusService8ActionHandler::new(
|
||||
srv_handler,
|
||||
TestConverter::default(),
|
||||
TestRouter::default(),
|
||||
TestRoutingErrorHandler::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
delegate! {
|
||||
to self.handler.request_converter {
|
||||
pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
|
||||
}
|
||||
}
|
||||
delegate! {
|
||||
to self.handler.request_router {
|
||||
pub fn retrieve_next_request(&mut self) -> (TargetId, ActionRequest);
|
||||
}
|
||||
}
|
||||
delegate! {
|
||||
to self.handler.routing_error_handler {
|
||||
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PusTestHarness for Pus8HandlerWithVecTester {
|
||||
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 Pus8HandlerWithVecTester {
|
||||
delegate! {
|
||||
to self.handler {
|
||||
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_test() {
|
||||
let mut action_handler = Pus8HandlerWithVecTester::new();
|
||||
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
|
||||
let action_id: u32 = 1;
|
||||
let action_id_raw = action_id.to_be_bytes();
|
||||
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
|
||||
action_handler.send_tc(&tc);
|
||||
let result = action_handler.handle_one_tc();
|
||||
assert!(result.is_ok());
|
||||
action_handler.check_next_conversion(&tc);
|
||||
let (target_id, action_req) = action_handler.retrieve_next_request();
|
||||
assert_eq!(target_id, TEST_APID.into());
|
||||
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
|
||||
assert_eq!(action_id, 1);
|
||||
assert_eq!(data, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_error() {
|
||||
let mut action_handler = Pus8HandlerWithVecTester::new();
|
||||
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
|
||||
let action_id: u32 = 1;
|
||||
let action_id_raw = action_id.to_be_bytes();
|
||||
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
|
||||
let error = GenericRoutingError::UnknownTargetId(25);
|
||||
action_handler
|
||||
.handler
|
||||
.request_router
|
||||
.inject_routing_error(error);
|
||||
action_handler.send_tc(&tc);
|
||||
let result = action_handler.handle_one_tc();
|
||||
assert!(result.is_err());
|
||||
let check_error = |routing_error: GenericRoutingError| {
|
||||
if let GenericRoutingError::UnknownTargetId(id) = routing_error {
|
||||
assert_eq!(id, 25);
|
||||
} else {
|
||||
panic!("unexpected error type");
|
||||
}
|
||||
};
|
||||
if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
|
||||
check_error(routing_error);
|
||||
} else {
|
||||
panic!("unexpected error type");
|
||||
}
|
||||
|
||||
action_handler.check_next_conversion(&tc);
|
||||
let (target_id, action_req) = action_handler.retrieve_next_request();
|
||||
assert_eq!(target_id, TEST_APID.into());
|
||||
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
|
||||
assert_eq!(action_id, 1);
|
||||
assert_eq!(data, &[]);
|
||||
}
|
||||
|
||||
let (target_id, found_error) = action_handler.retrieve_next_error();
|
||||
assert_eq!(target_id, TEST_APID.into());
|
||||
check_error(found_error);
|
||||
}
|
||||
}
|
@ -269,7 +269,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl EcssChannel for TestSender {
|
||||
fn id(&self) -> ChannelId {
|
||||
fn channel_id(&self) -> ChannelId {
|
||||
0
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@ use crate::events::{EventU32, GenericEvent, Severity};
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::events::{EventU32TypedSev, HasSeverity};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::hash::Hash;
|
||||
#[cfg(feature = "alloc")]
|
||||
use hashbrown::HashSet;
|
||||
@ -32,19 +30,19 @@ pub use heapless_mod::*;
|
||||
/// structure to track disabled events. A more primitive and embedded friendly
|
||||
/// solution could track this information in a static or pre-allocated list which contains
|
||||
/// the disabled events.
|
||||
pub trait PusEventMgmtBackendProvider<Provider: GenericEvent> {
|
||||
pub trait PusEventMgmtBackendProvider<Event: GenericEvent> {
|
||||
type Error;
|
||||
|
||||
fn event_enabled(&self, event: &Provider) -> bool;
|
||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>;
|
||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>;
|
||||
fn event_enabled(&self, event: &Event) -> bool;
|
||||
fn enable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
|
||||
fn disable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapless")]
|
||||
pub mod heapless_mod {
|
||||
use super::*;
|
||||
use crate::events::{GenericEvent, LargestEventRaw};
|
||||
use std::marker::PhantomData;
|
||||
use crate::events::LargestEventRaw;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
|
||||
// TODO: After a new version of heapless is released which uses hash32 version 0.3, try using
|
||||
@ -108,6 +106,10 @@ impl From<EcssTmtcError> for EventManError {
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::events::EventU16;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Default backend provider which uses a hash set as the event reporting status container
|
||||
@ -115,14 +117,11 @@ pub mod alloc_mod {
|
||||
///
|
||||
/// This provider is a good option for host systems or larger embedded systems where
|
||||
/// the expected occasional memory allocation performed by the [HashSet] is not an issue.
|
||||
pub struct DefaultPusMgmtBackendProvider<Event: GenericEvent = EventU32> {
|
||||
pub struct DefaultPusEventMgmtBackend<Event: GenericEvent = EventU32> {
|
||||
disabled: HashSet<Event>,
|
||||
}
|
||||
|
||||
/// Safety: All contained field are [Send] as well
|
||||
unsafe impl<Event: GenericEvent + Send> Send for DefaultPusMgmtBackendProvider<Event> {}
|
||||
|
||||
impl<Event: GenericEvent> Default for DefaultPusMgmtBackendProvider<Event> {
|
||||
impl<Event: GenericEvent> Default for DefaultPusEventMgmtBackend<Event> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disabled: HashSet::default(),
|
||||
@ -130,46 +129,50 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider: GenericEvent + PartialEq + Eq + Hash + Copy + Clone>
|
||||
PusEventMgmtBackendProvider<Provider> for DefaultPusMgmtBackendProvider<Provider>
|
||||
impl<EV: GenericEvent + PartialEq + Eq + Hash + Copy + Clone> PusEventMgmtBackendProvider<EV>
|
||||
for DefaultPusEventMgmtBackend<EV>
|
||||
{
|
||||
type Error = ();
|
||||
fn event_enabled(&self, event: &Provider) -> bool {
|
||||
|
||||
fn event_enabled(&self, event: &EV) -> bool {
|
||||
!self.disabled.contains(event)
|
||||
}
|
||||
|
||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
fn enable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
|
||||
Ok(self.disabled.remove(event))
|
||||
}
|
||||
|
||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
fn disable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
|
||||
Ok(self.disabled.insert(*event))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PusEventDispatcher<BackendError, Provider: GenericEvent> {
|
||||
pub struct PusEventDispatcher<
|
||||
B: PusEventMgmtBackendProvider<EV, Error = E>,
|
||||
EV: GenericEvent,
|
||||
E,
|
||||
> {
|
||||
reporter: EventReporter,
|
||||
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>,
|
||||
backend: B,
|
||||
phantom: PhantomData<(E, EV)>,
|
||||
}
|
||||
|
||||
/// Safety: All contained fields are send as well.
|
||||
unsafe impl<E: Send, Event: GenericEvent + Send> Send for PusEventDispatcher<E, Event> {}
|
||||
|
||||
impl<BackendError, Provider: GenericEvent> PusEventDispatcher<BackendError, Provider> {
|
||||
pub fn new(
|
||||
reporter: EventReporter,
|
||||
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>,
|
||||
) -> Self {
|
||||
Self { reporter, backend }
|
||||
impl<B: PusEventMgmtBackendProvider<EV, Error = E>, EV: GenericEvent, E>
|
||||
PusEventDispatcher<B, EV, E>
|
||||
{
|
||||
pub fn new(reporter: EventReporter, backend: B) -> Self {
|
||||
Self {
|
||||
reporter,
|
||||
backend,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendError, Event: GenericEvent> PusEventDispatcher<BackendError, Event> {
|
||||
pub fn enable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> {
|
||||
pub fn enable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
|
||||
self.backend.enable_event_reporting(event)
|
||||
}
|
||||
|
||||
pub fn disable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> {
|
||||
pub fn disable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
|
||||
self.backend.disable_event_reporting(event)
|
||||
}
|
||||
|
||||
@ -177,7 +180,7 @@ pub mod alloc_mod {
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event: Event,
|
||||
event: EV,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<bool, EventManError> {
|
||||
if !self.backend.event_enabled(&event) {
|
||||
@ -208,18 +211,30 @@ pub mod alloc_mod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendError> PusEventDispatcher<BackendError, EventU32> {
|
||||
impl<EV: GenericEvent + Copy + PartialEq + Eq + Hash>
|
||||
PusEventDispatcher<DefaultPusEventMgmtBackend<EV>, EV, ()>
|
||||
{
|
||||
pub fn new_with_default_backend(reporter: EventReporter) -> Self {
|
||||
Self {
|
||||
reporter,
|
||||
backend: DefaultPusEventMgmtBackend::default(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: PusEventMgmtBackendProvider<EventU32, Error = E>, E> PusEventDispatcher<B, EventU32, E> {
|
||||
pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>(
|
||||
&mut self,
|
||||
event: &EventU32TypedSev<Severity>,
|
||||
) -> Result<bool, BackendError> {
|
||||
) -> Result<bool, E> {
|
||||
self.backend.enable_event_reporting(event.as_ref())
|
||||
}
|
||||
|
||||
pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>(
|
||||
&mut self,
|
||||
event: &EventU32TypedSev<Severity>,
|
||||
) -> Result<bool, BackendError> {
|
||||
) -> Result<bool, E> {
|
||||
self.backend.disable_event_reporting(event.as_ref())
|
||||
}
|
||||
|
||||
@ -233,30 +248,38 @@ pub mod alloc_mod {
|
||||
self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data)
|
||||
}
|
||||
}
|
||||
|
||||
pub type DefaultPusEventU16Dispatcher<E> =
|
||||
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU16>, EventU16, E>;
|
||||
pub type DefaultPusEventU32Dispatcher<E> =
|
||||
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU32>, EventU32, E>;
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::events::SeverityInfo;
|
||||
use crate::pus::MpscTmAsVecSender;
|
||||
use std::sync::mpsc::{channel, TryRecvError};
|
||||
use crate::{events::SeverityInfo, pus::TmAsVecSenderWithMpsc};
|
||||
use std::sync::mpsc::{self, TryRecvError};
|
||||
|
||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||
EventU32TypedSev::<SeverityInfo>::const_new(1, 0);
|
||||
const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5);
|
||||
const EMPTY_STAMP: [u8; 7] = [0; 7];
|
||||
|
||||
fn create_basic_man() -> PusEventDispatcher<(), EventU32> {
|
||||
fn create_basic_man_1() -> DefaultPusEventU32Dispatcher<()> {
|
||||
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
|
||||
let backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
|
||||
PusEventDispatcher::new(reporter, Box::new(backend))
|
||||
PusEventDispatcher::new_with_default_backend(reporter)
|
||||
}
|
||||
fn create_basic_man_2() -> DefaultPusEventU32Dispatcher<()> {
|
||||
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
|
||||
let backend = DefaultPusEventMgmtBackend::default();
|
||||
PusEventDispatcher::new(reporter, backend)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = MpscTmAsVecSender::new(0, "test_sender", event_tx);
|
||||
let mut event_man = create_basic_man_1();
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let mut sender = TmAsVecSenderWithMpsc::new(0, "test_sender", event_tx);
|
||||
let event_sent = event_man
|
||||
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
|
||||
.expect("Sending info event failed");
|
||||
@ -268,9 +291,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_disable_event() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = MpscTmAsVecSender::new(0, "test", event_tx);
|
||||
let mut event_man = create_basic_man_2();
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
|
||||
let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
@ -291,9 +314,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_reenable_event() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = MpscTmAsVecSender::new(0, "test", event_tx);
|
||||
let mut event_man = create_basic_man_1();
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
|
||||
let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user