33 Commits

Author SHA1 Message Date
b48b5b8caa Merge pull request 'bump example patch version' (#129) from prepare-example-release into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #129
2024-02-21 13:51:49 +01:00
238c3a8d43 bump example patch version
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-21 11:34:35 +01:00
de8c0bc13e Merge pull request 'Use released package versions again' (#128) from bump-example into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #128
2024-02-21 11:34:07 +01:00
012eb82f42 bump example
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-21 11:22:16 +01:00
d26f6cbe27 Merge pull request 'sat-rs v0.2.0-rc.0' (#127) from prepare-satrs-release-candidate into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #127
2024-02-21 11:13:05 +01:00
82d3215761 changelog
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-21 11:08:22 +01:00
2b80244636 prepare release candidate
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-21 10:06:58 +01:00
f1611cd5b8 Merge pull request 'Custom badges' (#125) from try-custom-badges into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #125
2024-02-20 14:57:10 +01:00
808126ee41 better green
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-02-20 14:55:52 +01:00
05df24447b let's try this
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-20 14:54:58 +01:00
b229360233 try custom badges
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-20 14:52:01 +01:00
52be26829b Merge pull request 'First PUS handler abstractions with request mapping' (#121) from move-some-requests into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #121
2024-02-20 14:42:02 +01:00
ca2c8aa359 update changelog
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-20 14:36:34 +01:00
ba03150178 Added high-level abstraction for some PUS services
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
Introduced high-level abstractions for targetable requests in general.

- PUS Service 3 (HK) abstraction for targetable HK requests
- PUS Service 8 (Action) abstraction for targetable action requests
2024-02-20 14:33:21 +01:00
4e45bfa7e6 Merge pull request 'add mode tree graph' (#124) from add-mode-tree-graph into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #124
2024-02-16 19:21:54 +01:00
93c01c8c22 add mode tree graph
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-16 17:42:44 +01:00
2d062f3010 Merge pull request 'rename Python client' (#123) from rename-pyclient into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #123
2024-02-16 13:27:28 +01:00
01d9a85976 rename Python client
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-16 13:24:16 +01:00
fa7cd39f3e Merge pull request 'added goal graph for example' (#122) from example-goal-graph into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #122
2024-02-16 13:10:53 +01:00
5bb37d8e87 added goal graph for example
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-16 12:54:50 +01:00
813e221030 Merge pull request 'prepare example release v0.1.0' (#120) from example-release into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #120
2024-02-13 11:22:22 +01:00
18cec8bcf0 prepare example release v0.1.0
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-13 11:07:27 +01:00
d4a122e462 Merge pull request 'of course, something is missing..' (#119) from of-course-forgot-a-link into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #119
2024-02-12 18:28:02 +01:00
7af327d077 of course, something is missing..
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-12 18:17:50 +01:00
3a6cd6712d Merge pull request 'MIB docs update' (#118) from mib-docs-update into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #118
2024-02-12 18:11:54 +01:00
c4eba03043 Merge branch 'main' into mib-docs-update
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build queued...
2024-02-12 18:11:05 +01:00
e9e5c999ec better documentation
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-02-12 18:10:33 +01:00
f14a85cb84 add missing links for MIB
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-02-12 18:04:43 +01:00
c3902c2c06 Merge pull request 'Corrections for docs and links' (#117) from links-and-docs-corrections into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #117
2024-02-12 17:56:28 +01:00
24f82d4c5e prep v0.1.1
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build queued...
2024-02-12 17:56:01 +01:00
44ff62e947 more link corrections
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-12 17:34:16 +01:00
9cf80b44ea homepage link corrections
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-12 17:29:48 +01:00
f4bc33aefa link corrections
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-12 17:25:22 +01:00
62 changed files with 5317 additions and 754 deletions

View File

@ -1,5 +1,10 @@
<p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p> <p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p>
[![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
[![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
[![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
sat-rs 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 to write on-board software for remote systems like rovers or satellites. It is specifically written
for the special requirements for these systems. You can find an overview of the project and the for the special requirements for these systems. You can find an overview of the project and the
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/) link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
at the [IRS documentation website](https://absatsw.irs.uni-stuttgart.de/sat-rs.html). at the [IRS software projects website](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/).
A lot of the architecture and general design considerations are based on the A lot of the architecture and general design considerations are based on the
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage [FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage
@ -41,7 +46,7 @@ Each project has its own `CHANGELOG.md`.
* [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS * [`spacepackets`](https://egit.irs.uni-stuttgart.de/rust/spacepackets): Basic ECSS and CCSDS
packet protocol implementations. This repository is re-exported in the packet protocol implementations. This repository is re-exported in the
[`satrs-core`](https://egit.irs.uni-stuttgart.de/rust/satrs-launchpad/src/branch/main/satrs-core) [`satrs`](https://egit.irs.uni-stuttgart.de/rust/satrs/src/branch/main/satrs)
crate. crate.
# Coverage # Coverage

View File

@ -43,8 +43,8 @@ def main():
parser.add_argument( parser.add_argument(
"-p", "-p",
"--package", "--package",
choices=["satrs-core"], choices=["satrs"],
default="satrs-core", default="satrs",
help="Choose project to generate coverage for", help="Choose project to generate coverage for",
) )
parser.add_argument( parser.add_argument(

View 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">&lt;html&gt;
&lt;center&gt;
&lt;h4&gt;ACS Mode Tree&lt;/h4&gt;
&lt;/center&gt;
&lt;table border="1"&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;MGMs&lt;/th&gt;
&lt;th&gt;SUSs&lt;/th&gt;
&lt;th&gt;STR&lt;/th&gt;
&lt;th&gt;MGT&lt;/th&gt;
&lt;th&gt;RWs&lt;/th&gt;
&lt;th&gt;ACS CTRL&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAFE&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;SAFE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/html&gt;<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">&lt;html&gt;
&lt;center&gt;
&lt;h4&gt;ACS IDLE Sequence&lt;/h4&gt;
&lt;/center&gt;
&lt;table border="1"&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;MGMs&lt;/th&gt;
&lt;th&gt;SUS&lt;/th&gt;
&lt;th&gt;STR&lt;/th&gt;
&lt;th&gt;MGT&lt;/th&gt;
&lt;th&gt;RWs&lt;/th&gt;
&lt;th&gt;ACS CTRL&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;NORMAL&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;SAFE&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/html&gt;<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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,14 @@ it is still centered around small packets. `sat-rs` provides support for these E
standards and also attempts to fill the gap to the internet protocol by providing the following standards and also attempts to fill the gap to the internet protocol by providing the following
components. components.
1. [UDP TMTC Server](https://docs.rs/satrs-core/0.1.0-alpha.0/satrs_core/hal/host/udp_server/index.html). 1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/host/udp_server/index.html).
UDP is already packet based which makes it an excellent fit for exchanging space packets. UDP is already packet based which makes it an excellent fit for exchanging space packets.
2. [TCP TMTC Server Components](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/index.html). 2. [TCP TMTC Server Components](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/index.html).
TCP is a stream based protocol, so the framework provides building blocks to parse telemetry TCP is a stream based protocol, so the framework provides building blocks to parse telemetry
from an arbitrary bytestream. Two concrete implementations are provided: from an arbitrary bytestream. Two concrete implementations are provided:
- [TCP spacepackets server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html) - [TCP spacepackets server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
to parse tightly packed CCSDS Spacepackets. to parse tightly packed CCSDS Spacepackets.
- [TCP COBS server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html) - [TCP COBS server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html)
to parse generic frames wrapped with the to parse generic frames wrapped with the
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).

View File

@ -26,15 +26,17 @@ For example, a very small telecommand (TC) pool might look like this:
![Example Pool](images/pools/static-pools.png) ![Example Pool](images/pools/static-pools.png)
The core of the pool abstractions is the The core of the pool abstractions is the
[PoolProvider trait](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/trait.PoolProvider.html). [PoolProvider trait](https://docs.rs/satrs/latest/satrs/pool/trait.PoolProvider.html).
This trait specifies the general API a pool structure should have without making assumption This trait specifies the general API a pool structure should have without making assumption
of how the data is stored. of how the data is stored.
This trait is implemented by a static memory pool implementation. This trait is implemented by a static memory pool implementation.
The code to generate this static pool would look like this: The code to generate this static pool would look like this:
```rust <!-- Would be nice to test this code sample, but need to wait
use satrs_core::pool::{StaticMemoryPool, StaticPoolConfig}; for https://github.com/rust-lang/mdBook/issues/706 to be merged.. -->
```rust, ignore
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![ let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![
(6, 16), (6, 16),
@ -52,8 +54,8 @@ inter-process communication (IPC) data.
You can read You can read
- [`StaticPoolConfig` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticPoolConfig.html) - [`StaticPoolConfig` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticPoolConfig.html)
- [`StaticMemoryPool` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticMemoryPool.html) - [`StaticMemoryPool` API](https://docs.rs/satrs/latest/satrs/pool/struct.StaticMemoryPool.html)
for more details. for more details.

View File

@ -23,11 +23,11 @@ Some additional explanation is provided for the various components.
The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This
might be an optional component for an OBSW which is only used during the development phase on might be an optional component for an OBSW which is only used during the development phase on
ground. The UDP server is strongly based on the ground. The UDP server is strongly based on the
[UDP TC server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/udp_server/struct.UdpTcServer.html). [UDP TC server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/struct.UdpTcServer.html).
This server component is wrapped by a TMTC server which handles all telemetry to the last connected This server component is wrapped by a TMTC server which handles all telemetry to the last connected
client. client.
The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html) The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
class. It parses space packets by using the CCSDS space packet ID as the packet class. It parses space packets by using the CCSDS space packet ID as the packet
start delimiter. All available telemetry will be sent back to a client after having read all start delimiter. All available telemetry will be sent back to a client after having read all
telecommands from the client. telecommands from the client.
@ -51,13 +51,13 @@ services. This currently includes the following services:
- Service 1 for telecommand verification. The verification handling is handled locally: Each - Service 1 for telecommand verification. The verification handling is handled locally: Each
component which generates verification telemetry in some shape or form receives a component which generates verification telemetry in some shape or form receives a
[reporter](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/verification/struct.VerificationReporterWithSender.html) [reporter](https://docs.rs/satrs/latest/satrs/pus/verification/struct.VerificationReporterWithSender.html)
object which can be used to send PUS 1 verification telemetry to the TM funnel. object which can be used to send PUS 1 verification telemetry to the TM funnel.
- Service 3 for housekeeping telemetry handling. - Service 3 for housekeeping telemetry handling.
- Service 5 for management and downlink of on-board events. - Service 5 for management and downlink of on-board events.
- Service 8 for handling on-board actions. - Service 8 for handling on-board actions.
- Service 11 for scheduling telecommands to be released at a specific time. This component - Service 11 for scheduling telecommands to be released at a specific time. This component
uses the [PUS scheduler class](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/scheduler/alloc_mod/struct.PusScheduler.html) uses the [PUS scheduler class](https://docs.rs/satrs/latest/satrs/pus/scheduler/alloc_mod/struct.PusScheduler.html)
which performs the core logic of scheduling telecommands. All telecommands released by the which performs the core logic of scheduling telecommands. All telecommands released by the
scheduler are sent to the central TC source using a message. scheduler are sent to the central TC source using a message.
- Service 17 for test purposes like pings. - Service 17 for test purposes like pings.
@ -65,10 +65,10 @@ services. This currently includes the following services:
### Event Management Component ### Event Management Component
An event manager based on the sat-rs An event manager based on the sat-rs
[event manager component](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/event_man/index.html) [event manager component](https://docs.rs/satrs/latest/satrs/event_man/index.html)
is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5 is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5
telemetry by the telemetry by the
[PUS event dispatcher](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/event_man/alloc_mod/struct.PusEventDispatcher.html). [PUS event dispatcher](https://docs.rs/satrs/latest/satrs/pus/event_man/alloc_mod/struct.PusEventDispatcher.html).
You can read the [events](./events.md) chapter for more in-depth information about event management. You can read the [events](./events.md) chapter for more in-depth information about event management.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "satrs-example" name = "satrs-example"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
default-run = "satrs-example" default-run = "satrs-example"
@ -20,12 +20,12 @@ thiserror = "1"
derive-new = "0.5" derive-new = "0.5"
[dependencies.satrs] [dependencies.satrs]
# version = "0.1.0-alpha.3" version = "0.2.0-rc.0"
path = "../satrs" # path = "../satrs"
[dependencies.satrs-mib] [dependencies.satrs-mib]
# version = "0.1.0-alpha.2" version = "0.1.1"
path = "../satrs-mib" # path = "../satrs-mib"
[features] [features]
dyn_tmtc = [] dyn_tmtc = []

View File

@ -1,8 +1,9 @@
use std::sync::mpsc::{self, TryRecvError}; use std::sync::mpsc::{self, TryRecvError};
use log::{info, warn}; use log::{info, warn};
use satrs::pus::verification::VerificationReporterWithSender; use satrs::pus::verification::{VerificationReporterWithSender, VerificationReportingProvider};
use satrs::pus::{EcssTmSender, PusTmWrapper}; use satrs::pus::{EcssTmSender, PusTmWrapper};
use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::hk::Subservice as HkSubservice; use satrs::spacepackets::ecss::hk::Subservice as HkSubservice;
use satrs::{ use satrs::{
hk::HkRequest, hk::HkRequest,
@ -70,12 +71,12 @@ impl AcsTask {
"ACS thread: Received HK request {:?}", "ACS thread: Received HK request {:?}",
request.targeted_request request.targeted_request
); );
let target_and_apid_id = TargetAndApidId::from(request.targeted_request.target_id);
match request.targeted_request.request { match request.targeted_request.request {
Request::Hk(hk_req) => match hk_req { Request::Hk(hk_req) => match hk_req {
HkRequest::OneShot(unique_id) => self.handle_hk_request( HkRequest::OneShot(unique_id) => {
request.targeted_request.target_id_with_apid.target_id(), self.handle_hk_request(target_and_apid_id.target(), unique_id)
unique_id, }
),
HkRequest::Enable(_) => {} HkRequest::Enable(_) => {}
HkRequest::Disable(_) => {} HkRequest::Disable(_) => {}
HkRequest::ModifyCollectionInterval(_, _) => {} HkRequest::ModifyCollectionInterval(_, _) => {}
@ -89,10 +90,10 @@ impl AcsTask {
} }
let started_token = self let started_token = self
.verif_reporter .verif_reporter
.start_success(request.token, Some(&self.timestamp)) .start_success(request.token, &self.timestamp)
.expect("Sending start success failed"); .expect("Sending start success failed");
self.verif_reporter self.verif_reporter
.completion_success(started_token, Some(&self.timestamp)) .completion_success(started_token, &self.timestamp)
.expect("Sending completion success failed"); .expect("Sending completion success failed");
true true
} }

View File

@ -48,7 +48,11 @@ pub mod tmtc_err {
#[resultcode] #[resultcode]
pub const PUS_SERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2); pub const PUS_SERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2);
#[resultcode] #[resultcode]
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::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( #[resultcode(
info = "Not enough data inside the TC application data field. Optionally includes: \ info = "Not enough data inside the TC application data field. Optionally includes: \
@ -60,6 +64,9 @@ pub mod tmtc_err {
pub const TMTC_RESULTS: &[ResultU16Info] = &[ pub const TMTC_RESULTS: &[ResultU16Info] = &[
INVALID_PUS_SERVICE_EXT, INVALID_PUS_SERVICE_EXT,
INVALID_PUS_SUBSERVICE_EXT, INVALID_PUS_SUBSERVICE_EXT,
PUS_SERVICE_NOT_IMPLEMENTED_EXT,
UNKNOWN_TARGET_ID_EXT,
ROUTING_ERROR_EXT,
NOT_ENOUGH_APP_DATA_EXT, NOT_ENOUGH_APP_DATA_EXT,
]; ];
} }
@ -76,6 +83,13 @@ pub mod hk_err {
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Hk as u8, 2); pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Hk as u8, 2);
#[resultcode] #[resultcode]
pub const COLLECTION_INTERVAL_MISSING: ResultU16 = ResultU16::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)] #[allow(clippy::enum_variant_names)]

View File

@ -12,7 +12,10 @@ use satrs::{
DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken, DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken,
PusEventDispatcher, PusEventDispatcher,
}, },
verification::{TcStateStarted, VerificationReporterWithSender, VerificationToken}, verification::{
TcStateStarted, VerificationReporterWithSender, VerificationReportingProvider,
VerificationToken,
},
EcssTmSender, EcssTmSender,
}, },
spacepackets::time::cds::{self, TimeProvider}, spacepackets::time::cds::{self, TimeProvider},
@ -73,7 +76,7 @@ impl PusEventHandler {
.try_into() .try_into()
.expect("expected start verification token"); .expect("expected start verification token");
self.verif_handler self.verif_handler
.completion_success(started_token, Some(timestamp)) .completion_success(started_token, timestamp)
.expect("Sending completion success failed"); .expect("Sending completion success failed");
}; };
// handle event requests // handle event requests

View File

@ -1,59 +1 @@
use derive_new::new;
use satrs::spacepackets::ecss::tc::IsPusTelecommand;
use satrs::spacepackets::ecss::PusPacket;
use satrs::spacepackets::{ByteConversionError, CcsdsPacket};
use satrs::tmtc::TargetId;
use std::fmt;
use thiserror::Error;
pub mod config; 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()),
})
}
}

View File

@ -17,6 +17,7 @@ use log::info;
use pus::test::create_test_service_dynamic; use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig; use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer; use satrs::hal::std::udp_server::UdpTcServer;
use satrs::request::TargetAndApidId;
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools}; use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{ use satrs_example::config::tasks::{
@ -35,7 +36,7 @@ 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::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
use crate::pus::test::create_test_service_static; use crate::pus::test::create_test_service_static;
use crate::pus::{PusReceiver, PusTcMpscRouter}; use crate::pus::{PusReceiver, PusTcMpscRouter};
use crate::requests::RequestWithToken; use crate::requests::{GenericRequestRouter, RequestWithToken};
use crate::tcp::{SyncTcpTmSource, TcpTask}; use crate::tcp::{SyncTcpTmSource, TcpTask};
use crate::tmtc::{ use crate::tmtc::{
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic, PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
@ -45,10 +46,8 @@ use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender}; use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
use satrs::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInSharedPoolSender}; use satrs::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInSharedPoolSender};
use satrs::spacepackets::{time::cds::TimeProvider, time::TimeWriter}; use satrs::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
use satrs::tmtc::{CcsdsDistributor, TargetId}; use satrs::tmtc::CcsdsDistributor;
use satrs::ChannelId; use satrs::ChannelId;
use satrs_example::TargetIdWithApid;
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::sync::mpsc::{self, channel}; use std::sync::mpsc::{self, channel};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -82,11 +81,11 @@ fn static_tmtc_pool_main() {
tm_funnel_tx.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>(); let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID. // Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = HashMap::new(); let mut request_map = GenericRequestRouter::default();
request_map.insert(acs_target_id, acs_thread_tx); request_map.0.insert(acs_target_id.into(), acs_thread_tx);
// This helper structure is used by all telecommand providers which need to send telecommands // This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source. // to the TC source.
@ -310,11 +309,11 @@ fn dyn_tmtc_pool_main() {
tm_funnel_tx.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>(); let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID. // Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = HashMap::new(); let mut request_map = GenericRequestRouter::default();
request_map.insert(acs_target_id, acs_thread_tx); request_map.0.insert(acs_target_id.into(), acs_thread_tx);
let tc_source = PusTcSourceProviderDynamic(tc_source_tx); let tc_source = PusTcSourceProviderDynamic(tc_source_tx);

View File

@ -1,22 +1,76 @@
use crate::requests::{ActionRequest, Request, RequestWithToken};
use log::{error, warn}; use log::{error, warn};
use satrs::action::ActionRequest;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr}; use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::action::{PusActionToRequestConverter, PusService8ActionHandler};
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationToken, FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationReportingProvider,
VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper, PusPacketHandlingError, PusServiceHelper,
}; };
use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket; use satrs::spacepackets::ecss::PusPacket;
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId; use satrs::{ChannelId, TargetId};
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID}; use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use satrs_example::TargetIdWithApid; use std::sync::mpsc::{self};
use std::collections::HashMap;
use std::sync::mpsc::{self, Sender}; 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( pub fn create_action_service_static(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
@ -24,7 +78,7 @@ pub fn create_action_service_static(
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool, tc_pool: SharedStaticMemoryPool,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>, pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>, action_router: GenericRequestRouter,
) -> Pus8Wrapper<EcssTcInSharedStoreConverter> { ) -> Pus8Wrapper<EcssTcInSharedStoreConverter> {
let action_srv_tm_sender = MpscTmInSharedPoolSender::new( let action_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusAction as ChannelId, TmSenderId::PusAction as ChannelId,
@ -38,12 +92,16 @@ pub fn create_action_service_static(
pus_action_rx, pus_action_rx,
); );
let pus_8_handler = PusService8ActionHandler::new( let pus_8_handler = PusService8ActionHandler::new(
Box::new(action_srv_receiver), PusServiceHelper::new(
Box::new(action_srv_tm_sender), Box::new(action_srv_receiver),
PUS_APID, Box::new(action_srv_tm_sender),
verif_reporter.clone(), PUS_APID,
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048), verif_reporter.clone(),
request_map.clone(), EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
),
ExampleActionRequestConverter::default(),
action_router,
GenericRoutingErrorHandler::<8>::default(),
); );
Pus8Wrapper { pus_8_handler } Pus8Wrapper { pus_8_handler }
} }
@ -52,7 +110,7 @@ pub fn create_action_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>, tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSender,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>, pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>, action_router: GenericRequestRouter,
) -> Pus8Wrapper<EcssTcInVecConverter> { ) -> Pus8Wrapper<EcssTcInVecConverter> {
let action_srv_tm_sender = MpscTmAsVecSender::new( let action_srv_tm_sender = MpscTmAsVecSender::new(
TmSenderId::PusAction as ChannelId, TmSenderId::PusAction as ChannelId,
@ -65,146 +123,28 @@ pub fn create_action_service_dynamic(
pus_action_rx, pus_action_rx,
); );
let pus_8_handler = PusService8ActionHandler::new( let pus_8_handler = PusService8ActionHandler::new(
Box::new(action_srv_receiver), PusServiceHelper::new(
Box::new(action_srv_tm_sender), Box::new(action_srv_receiver),
PUS_APID, Box::new(action_srv_tm_sender),
verif_reporter.clone(), PUS_APID,
EcssTcInVecConverter::default(), verif_reporter.clone(),
request_map.clone(), EcssTcInVecConverter::default(),
),
ExampleActionRequestConverter::default(),
action_router,
GenericRoutingErrorHandler::<8>::default(),
); );
Pus8Wrapper { pus_8_handler } Pus8Wrapper { pus_8_handler }
} }
pub struct PusService8ActionHandler<TcInMemConverter: EcssTcInMemConverter> {
service_helper: PusServiceHelper<TcInMemConverter>,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
}
impl<TcInMemConverter: EcssTcInMemConverter> PusService8ActionHandler<TcInMemConverter> {
pub fn new(
tc_receiver: Box<dyn EcssTcReceiver>,
tm_sender: Box<dyn EcssTmSender>,
tm_apid: u16,
verification_handler: VerificationReporterWithSender,
tc_in_mem_converter: TcInMemConverter,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
) -> Self {
Self {
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 struct Pus8Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub(crate) pus_8_handler: PusService8ActionHandler<TcInMemConverter>, pub(crate) pus_8_handler: PusService8ActionHandler<
TcInMemConverter,
VerificationReporterWithSender,
ExampleActionRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<8>,
>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> { impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> {

View File

@ -76,7 +76,7 @@ pub fn create_event_service_dynamic(
} }
pub struct Pus5Wrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Pus5Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub pus_5_handler: PusService5EventHandler<TcInMemConverter>, pub pus_5_handler: PusService5EventHandler<TcInMemConverter, VerificationReporterWithSender>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> { impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> {

View File

@ -1,22 +1,145 @@
use crate::requests::{Request, RequestWithToken};
use log::{error, warn}; use log::{error, warn};
use satrs::hk::{CollectionIntervalFactor, HkRequest}; use satrs::hk::{CollectionIntervalFactor, HkRequest};
use satrs::pool::{SharedStaticMemoryPool, StoreAddr}; use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::hk::{PusHkToRequestConverter, PusService3HkHandler};
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, StdVerifReporterWithSender, VerificationReporterWithSender, FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationReportingProvider,
VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper, PusPacketHandlingError, PusServiceHelper,
}; };
use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{hk, PusPacket}; use satrs::spacepackets::ecss::{hk, PusPacket};
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId; use satrs::{ChannelId, TargetId};
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID}; use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use satrs_example::TargetIdWithApid; use std::sync::mpsc::{self};
use std::collections::HashMap;
use std::sync::mpsc::{self, Sender}; 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( pub fn create_hk_service_static(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
@ -24,7 +147,7 @@ pub fn create_hk_service_static(
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool, tc_pool: SharedStaticMemoryPool,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>, pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>, request_router: GenericRequestRouter,
) -> Pus3Wrapper<EcssTcInSharedStoreConverter> { ) -> Pus3Wrapper<EcssTcInSharedStoreConverter> {
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new( let hk_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusHk as ChannelId, TmSenderId::PusHk as ChannelId,
@ -35,12 +158,16 @@ pub fn create_hk_service_static(
let hk_srv_receiver = let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx); MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new( let pus_3_handler = PusService3HkHandler::new(
Box::new(hk_srv_receiver), PusServiceHelper::new(
Box::new(hk_srv_tm_sender), Box::new(hk_srv_receiver),
PUS_APID, Box::new(hk_srv_tm_sender),
verif_reporter.clone(), PUS_APID,
EcssTcInSharedStoreConverter::new(tc_pool, 2048), verif_reporter.clone(),
request_map, EcssTcInSharedStoreConverter::new(tc_pool, 2048),
),
ExampleHkRequestConverter::default(),
request_router,
GenericRoutingErrorHandler::default(),
); );
Pus3Wrapper { pus_3_handler } Pus3Wrapper { pus_3_handler }
} }
@ -49,7 +176,7 @@ pub fn create_hk_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>, tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSender,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>, pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>, request_router: GenericRequestRouter,
) -> Pus3Wrapper<EcssTcInVecConverter> { ) -> Pus3Wrapper<EcssTcInVecConverter> {
let hk_srv_tm_sender = MpscTmAsVecSender::new( let hk_srv_tm_sender = MpscTmAsVecSender::new(
TmSenderId::PusHk as ChannelId, TmSenderId::PusHk as ChannelId,
@ -59,154 +186,28 @@ pub fn create_hk_service_dynamic(
let hk_srv_receiver = let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx); MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new( let pus_3_handler = PusService3HkHandler::new(
Box::new(hk_srv_receiver), PusServiceHelper::new(
Box::new(hk_srv_tm_sender), Box::new(hk_srv_receiver),
PUS_APID, Box::new(hk_srv_tm_sender),
verif_reporter.clone(), PUS_APID,
EcssTcInVecConverter::default(), verif_reporter.clone(),
request_map, EcssTcInVecConverter::default(),
),
ExampleHkRequestConverter::default(),
request_router,
GenericRoutingErrorHandler::default(),
); );
Pus3Wrapper { pus_3_handler } Pus3Wrapper { pus_3_handler }
} }
pub struct PusService3HkHandler<TcInMemConverter: EcssTcInMemConverter> {
psb: PusServiceHelper<TcInMemConverter>,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
}
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 struct Pus3Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub(crate) pus_3_handler: PusService3HkHandler<TcInMemConverter>, pub(crate) pus_3_handler: PusService3HkHandler<
TcInMemConverter,
VerificationReporterWithSender,
ExampleHkRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<3>,
>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> { impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> {

View File

@ -1,7 +1,11 @@
use crate::tmtc::MpscStoreAndSendError; use crate::tmtc::MpscStoreAndSendError;
use log::warn; use log::warn;
use satrs::pus::verification::{FailParams, StdVerifReporterWithSender}; use satrs::pus::verification::{
use satrs::pus::{EcssTcAndToken, PusPacketHandlerResult, TcInMemory}; FailParams, StdVerifReporterWithSender, VerificationReportingProvider,
};
use satrs::pus::{
EcssTcAndToken, GenericRoutingError, PusPacketHandlerResult, PusRoutingErrorHandler, TcInMemory,
};
use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusServiceId; use satrs::spacepackets::ecss::PusServiceId;
use satrs::spacepackets::time::cds::TimeProvider; use satrs::spacepackets::time::cds::TimeProvider;
@ -78,7 +82,7 @@ impl PusReceiver {
self.stamp_helper.update_from_now(); self.stamp_helper.update_from_now();
let accepted_token = self let accepted_token = self
.verif_reporter .verif_reporter
.acceptance_success(init_token, Some(self.stamp_helper.stamp())) .acceptance_success(init_token, self.stamp_helper.stamp())
.expect("Acceptance success failure"); .expect("Acceptance success failure");
let service = PusServiceId::try_from(service); let service = PusServiceId::try_from(service);
match service { match service {
@ -115,9 +119,9 @@ impl PusReceiver {
let result = self.verif_reporter.start_failure( let result = self.verif_reporter.start_failure(
accepted_token, accepted_token,
FailParams::new( FailParams::new(
Some(self.stamp_helper.stamp()), self.stamp_helper.stamp(),
&tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED, &tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED,
Some(&[standard_service as u8]), &[standard_service as u8],
), ),
); );
if result.is_err() { if result.is_err() {
@ -139,9 +143,9 @@ impl PusReceiver {
.start_failure( .start_failure(
accepted_token, accepted_token,
FailParams::new( FailParams::new(
Some(self.stamp_helper.stamp()), self.stamp_helper.stamp(),
&tmtc_err::INVALID_PUS_SUBSERVICE, &tmtc_err::INVALID_PUS_SUBSERVICE,
Some(&[e.number]), &[e.number],
), ),
) )
.expect("Start failure verification failed") .expect("Start failure verification failed")
@ -151,3 +155,56 @@ impl PusReceiver {
Ok(PusPacketHandlerResult::RequestHandled) 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");
}
}
}
}

View File

@ -52,7 +52,8 @@ impl TcReleaser for mpsc::Sender<Vec<u8>> {
} }
pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub pus_11_handler: PusService11SchedHandler<TcInMemConverter, PusScheduler>, pub pus_11_handler:
PusService11SchedHandler<TcInMemConverter, VerificationReporterWithSender, PusScheduler>,
pub sched_tc_pool: StaticMemoryPool, pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096], pub releaser_buf: [u8; 4096],
pub tc_releaser: Box<dyn TcReleaser + Send>, pub tc_releaser: Box<dyn TcReleaser + Send>,

View File

@ -2,7 +2,9 @@ use log::{info, warn};
use satrs::params::Params; use satrs::params::Params;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr}; use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::test::PusService17TestHandler; use satrs::pus::test::PusService17TestHandler;
use satrs::pus::verification::{FailParams, VerificationReporterWithSender}; use satrs::pus::verification::{
FailParams, VerificationReporterWithSender, VerificationReportingProvider,
};
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper, MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper,
@ -79,7 +81,7 @@ pub fn create_test_service_dynamic(
} }
pub struct Service17CustomWrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Service17CustomWrapper<TcInMemConverter: EcssTcInMemConverter> {
pub pus17_handler: PusService17TestHandler<TcInMemConverter>, pub pus17_handler: PusService17TestHandler<TcInMemConverter, VerificationReporterWithSender>,
pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>, pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>,
} }
@ -125,15 +127,13 @@ impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConve
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(token, &stamp_buf)
.start_success(token, Some(&stamp_buf))
.expect("Error sending start success"); .expect("Error sending start success");
self.pus17_handler self.pus17_handler
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &stamp_buf)
.completion_success(start_token, Some(&stamp_buf))
.expect("Error sending completion success"); .expect("Error sending completion success");
} else { } else {
let fail_data = [tc.subservice()]; let fail_data = [tc.subservice()];
@ -141,13 +141,12 @@ impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConve
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut()
.start_failure( .start_failure(
token, token,
FailParams::new( FailParams::new(
Some(&stamp_buf), &stamp_buf,
&tmtc_err::INVALID_PUS_SUBSERVICE, &tmtc_err::INVALID_PUS_SUBSERVICE,
Some(&fail_data), &fail_data,
), ),
) )
.expect("Sending start failure verification failed"); .expect("Sending start failure verification failed");

View 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 {}

View File

@ -1,15 +1,16 @@
use std::collections::HashMap;
use std::sync::mpsc;
use derive_new::new; use derive_new::new;
use satrs::action::ActionRequest;
use satrs::hk::HkRequest; use satrs::hk::HkRequest;
use satrs::mode::ModeRequest; use satrs::mode::ModeRequest;
use satrs::pus::action::PusActionRequestRouter;
use satrs::pus::hk::PusHkRequestRouter;
use satrs::pus::verification::{TcStateAccepted, VerificationToken}; use satrs::pus::verification::{TcStateAccepted, VerificationToken};
use satrs_example::TargetIdWithApid; use satrs::pus::GenericRoutingError;
use satrs::queue::GenericSendError;
#[allow(dead_code)] use satrs::TargetId;
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum ActionRequest {
CmdWithU32Id((u32, Vec<u8>)),
CmdWithStringId((String, Vec<u8>)),
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
@ -22,7 +23,7 @@ pub enum Request {
#[derive(Clone, Eq, PartialEq, Debug, new)] #[derive(Clone, Eq, PartialEq, Debug, new)]
pub struct TargetedRequest { pub struct TargetedRequest {
pub(crate) target_id_with_apid: TargetIdWithApid, pub(crate) target_id: TargetId,
pub(crate) request: Request, pub(crate) request: Request,
} }
@ -34,7 +35,7 @@ pub struct RequestWithToken {
impl RequestWithToken { impl RequestWithToken {
pub fn new( pub fn new(
target_id: TargetIdWithApid, target_id: TargetId,
request: Request, request: Request,
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
) -> Self { ) -> 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(())
}
}

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.1.1] 2024-02-17
- Bumped `spacepackets` to v0.10.0
# [v0.1.0] 2024-02-12 # [v0.1.0] 2024-02-12
Initial release containing the `resultcode` macro. Initial release containing the `resultcode` macro.

View File

@ -1,13 +1,13 @@
[package] [package]
name = "satrs-mib" name = "satrs-mib"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
rust-version = "1.61" rust-version = "1.61"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = """ description = """
Helper crate of the sat-rs framework to build a mission information base (MIB) from the Helper crate of the sat-rs framework to build a mission information base (MIB) from the
On-Board Software (OBSW) code directly.""" On-Board Software (OBSW) code directly."""
homepage = "https://absatsw.uni-stuttgart.de/projects/sat-rs" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["no-std", "space", "aerospace"] keywords = ["no-std", "space", "aerospace"]
@ -23,12 +23,12 @@ version = "1"
optional = true optional = true
[dependencies.satrs-shared] [dependencies.satrs-shared]
version = "0.1.1" version = "0.1.2"
features = ["serde"] features = ["serde"]
[dependencies.satrs-mib-codegen] [dependencies.satrs-mib-codegen]
path = "codegen" path = "codegen"
version = "0.1.0" version = "0.1.1"
[dependencies.serde] [dependencies.serde]
version = "1" version = "1"

View File

@ -1,3 +1,6 @@
[![Crates.io](https://img.shields.io/crates/v/satrs-mib)](https://crates.io/crates/satrs-mib)
[![docs.rs](https://img.shields.io/docsrs/satrs-mib)](https://docs.rs/satrs-mib)
satrs-mib satrs-mib
========= =========

View File

@ -1,6 +1,6 @@
[package] [package]
name = "satrs-mib-codegen" name = "satrs-mib-codegen"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
description = "satrs-mib proc macro implementation" description = "satrs-mib proc macro implementation"
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
@ -26,9 +26,7 @@ features = ["full"]
[dev-dependencies] [dev-dependencies]
trybuild = { version = "1", features = ["diff"] } trybuild = { version = "1", features = ["diff"] }
satrs-shared = "0.1.2"
[dev-dependencies.satrs-mib] [dev-dependencies.satrs-mib]
path = ".." path = ".."
[dev-dependencies.satrs-shared]
version = "0.1.1"

View File

@ -1,6 +1,14 @@
use quote::{format_ident, quote, ToTokens}; use quote::{format_ident, quote, ToTokens};
use syn::{parse_macro_input, ItemConst, LitStr}; 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] #[proc_macro_attribute]
pub fn resultcode( pub fn resultcode(
args: proc_macro::TokenStream, args: proc_macro::TokenStream,

View File

@ -73,6 +73,7 @@ pub mod stdmod {
Ok(()) Ok(())
} }
/// This function exports a slice of result code information objects to a CSV file.
pub fn write_resultcodes_to_csv( pub fn write_resultcodes_to_csv(
writer_builder: csv::WriterBuilder, writer_builder: csv::WriterBuilder,
results: &[ResultU16Info], results: &[ResultU16Info],

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.1.2] 2024-02-17
- Bumped `spacepackets` to v0.10.0 for `UnsignedEnum` trait change.
# [v0.1.1] 2024-02-12 # [v0.1.1] 2024-02-12
- Added missing `#![no_std]` attribute for library - Added missing `#![no_std]` attribute for library

View File

@ -1,10 +1,10 @@
[package] [package]
name = "satrs-shared" name = "satrs-shared"
description = "Components shared by multiple sat-rs crates" description = "Components shared by multiple sat-rs crates"
version = "0.1.1" version = "0.1.2"
edition = "2021" edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
homepage = "https://absatsw.uni-stuttgart.de/projects/sat-rs" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
license = "Apache-2.0" license = "Apache-2.0"
@ -18,7 +18,7 @@ default-features = false
optional = true optional = true
[dependencies.spacepackets] [dependencies.spacepackets]
version = "0.9" version = "0.10"
default-features = false default-features = false
[features] [features]

View File

@ -52,6 +52,10 @@ impl UnsignedEnum for ResultU16 {
buf[1] = self.unique_id; buf[1] = self.unique_id;
Ok(self.size()) Ok(self.size())
} }
fn value(&self) -> u64 {
self.raw() as u64
}
} }
impl EcssEnumeration for ResultU16 { impl EcssEnumeration for ResultU16 {

View File

@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [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 # [v0.1.0] 2024-02-12
Initial release. Initial release.

View File

@ -1,11 +1,11 @@
[package] [package]
name = "satrs" name = "satrs"
version = "0.1.0" version = "0.2.0-rc.0"
edition = "2021" edition = "2021"
rust-version = "1.61" rust-version = "1.61"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "A framework to build software for remote systems" description = "A framework to build software for remote systems"
homepage = "https://absatsw.uni-stuttgart.de/projects/sat-rs" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs" repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["no-std", "space", "aerospace"] keywords = ["no-std", "space", "aerospace"]
@ -17,7 +17,7 @@ delegate = ">0.7, <=0.10"
paste = "1" paste = "1"
smallvec = "1" smallvec = "1"
crc = "3" crc = "3"
satrs-shared = "0.1.1" satrs-shared = "0.1.2"
[dependencies.num_enum] [dependencies.num_enum]
version = ">0.5, <=0.7" version = ">0.5, <=0.7"
@ -68,7 +68,7 @@ features = ["all"]
optional = true optional = true
[dependencies.spacepackets] [dependencies.spacepackets]
version = "0.9" version = "0.10"
default-features = false default-features = false
[dependencies.cobs] [dependencies.cobs]

View File

@ -1,8 +1,8 @@
[![Crates.io](https://img.shields.io/crates/v/satrs-core)](https://crates.io/crates/satrs-core) [![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
[![docs.rs](https://img.shields.io/docsrs/satrs-core)](https://docs.rs/satrs-core) [![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
satrs-core sat-rs
====== ======
This crate contains the core components of the sat-rs framework. 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). You can find more information on the [homepage](https://egit.irs.uni-stuttgart.de/rust/sat-rs).

42
satrs/src/action.rs Normal file
View 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,
}
}
}

View File

@ -23,7 +23,7 @@ use spacepackets::{
tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType}, tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
}, },
util::UnsignedByteField, util::{UnsignedByteField, UnsignedEnum},
}; };
use thiserror::Error; use thiserror::Error;

View File

@ -9,7 +9,7 @@ use spacepackets::{
pdu::{FileDirectiveType, PduError, PduHeader}, pdu::{FileDirectiveType, PduError, PduHeader},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
}, },
util::UnsignedByteField, util::{UnsignedByteField, UnsignedEnum},
}; };
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]

View File

@ -412,6 +412,10 @@ impl UnsignedEnum for EventU32 {
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.base.write_to_bytes(self.raw(), buf, self.size()) self.base.write_to_bytes(self.raw(), buf, self.size())
} }
fn value(&self) -> u64 {
self.raw().into()
}
} }
impl EcssEnumeration for EventU32 { impl EcssEnumeration for EventU32 {
@ -425,6 +429,7 @@ impl<SEVERITY: HasSeverity> UnsignedEnum for EventU32TypedSev<SEVERITY> {
delegate!(to self.event { delegate!(to self.event {
fn size(&self) -> usize; fn size(&self) -> usize;
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; 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> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.base.write_to_bytes(self.raw(), buf, self.size()) self.base.write_to_bytes(self.raw(), buf, self.size())
} }
fn value(&self) -> u64 {
self.raw().into()
}
} }
impl EcssEnumeration for EventU16 { impl EcssEnumeration for EventU16 {
#[inline] #[inline]
@ -573,6 +582,7 @@ impl<SEVERITY: HasSeverity> UnsignedEnum for EventU16TypedSev<SEVERITY> {
delegate!(to self.event { delegate!(to self.event {
fn size(&self) -> usize; fn size(&self) -> usize;
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
fn value(&self) -> u64;
}); });
} }

View File

@ -1,3 +1,8 @@
use crate::{
pus::verification::{TcStateAccepted, VerificationToken},
TargetId,
};
pub type CollectionIntervalFactor = u32; pub type CollectionIntervalFactor = u32;
pub type UniqueId = u32; pub type UniqueId = u32;
@ -11,6 +16,25 @@ pub enum HkRequest {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TargetedHkRequest { pub struct TargetedHkRequest {
target: u32, pub target_id: TargetId,
hk_request: HkRequest, 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>;
} }

View File

@ -1,7 +1,9 @@
//! # sat-rs: A framework to build on-board software for remote systems //! # sat-rs: A framework to build on-board software for remote systems
//! //!
//! You can find more information about the sat-rs framework on the //! 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 //! ## Overview
//! //!
@ -32,19 +34,25 @@ pub mod events;
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod executable; pub mod executable;
pub mod hal; pub mod hal;
pub mod hk;
pub mod mode;
pub mod objects; pub mod objects;
pub mod params;
pub mod pool; pub mod pool;
pub mod power; pub mod power;
pub mod pus; pub mod pus;
pub mod queue;
pub mod request; pub mod request;
pub mod res_code; pub mod res_code;
pub mod seq_count; pub mod seq_count;
pub mod tmtc; pub mod tmtc;
pub mod action;
pub mod hk;
pub mod mode;
pub mod params;
pub use spacepackets; pub use spacepackets;
// Generic channel ID type. /// Generic channel ID type.
pub type ChannelId = u32; pub type ChannelId = u32;
/// Generic target ID type.
pub type TargetId = u64;

View File

@ -1,9 +1,10 @@
use crate::tmtc::TargetId;
use core::mem::size_of; use core::mem::size_of;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use spacepackets::ByteConversionError; use spacepackets::ByteConversionError;
use crate::TargetId;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModeAndSubmode { pub struct ModeAndSubmode {
@ -47,12 +48,12 @@ impl ModeAndSubmode {
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModeCommand { pub struct TargetedModeCommand {
pub address: TargetId, pub address: TargetId,
pub mode_submode: ModeAndSubmode, pub mode_submode: ModeAndSubmode,
} }
impl ModeCommand { impl TargetedModeCommand {
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self { pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
Self { Self {
address, address,

View File

@ -51,7 +51,6 @@
//! assert_eq!(example_obj.id, obj_id); //! assert_eq!(example_obj.id, obj_id);
//! assert_eq!(example_obj.dummy, 42); //! assert_eq!(example_obj.dummy, 42);
//! ``` //! ```
use crate::tmtc::TargetId;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::boxed::Box; use alloc::boxed::Box;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@ -63,6 +62,8 @@ use hashbrown::HashMap;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error::Error; use std::error::Error;
use crate::TargetId;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct ObjectId { pub struct ObjectId {
pub id: TargetId, pub id: TargetId,

385
satrs/src/pus/action.rs Normal file
View 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 [VerificationReporterWithSender] 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);
}
}

View File

@ -6,16 +6,24 @@ use spacepackets::ecss::event::Subservice;
use spacepackets::ecss::PusPacket; use spacepackets::ecss::PusPacket;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use super::verification::VerificationReportingProvider;
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper}; use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
pub struct PusService5EventHandler<TcInMemConverter: EcssTcInMemConverter> { pub struct PusService5EventHandler<
pub service_helper: PusServiceHelper<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
event_request_tx: Sender<EventRequestWithToken>, event_request_tx: Sender<EventRequestWithToken>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusService5EventHandler<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusService5EventHandler<TcInMemConverter, VerificationReporter>
{
pub fn new( pub fn new(
service_handler: PusServiceHelper<TcInMemConverter>, service_handler: PusServiceHelper<TcInMemConverter, VerificationReporter>,
event_request_tx: Sender<EventRequestWithToken>, event_request_tx: Sender<EventRequestWithToken>,
) -> Self { ) -> Self {
Self { Self {
@ -44,9 +52,10 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService5EventHandler<TcInMemConv
} }
let handle_enable_disable_request = |enable: bool, stamp: [u8; 7]| { let handle_enable_disable_request = |enable: bool, stamp: [u8; 7]| {
if tc.user_data().len() < 4 { if tc.user_data().len() < 4 {
return Err(PusPacketHandlingError::NotEnoughAppData( return Err(PusPacketHandlingError::NotEnoughAppData {
"at least 4 bytes event ID expected".into(), expected: 4,
)); found: tc.user_data().len(),
});
} }
let user_data = tc.user_data(); let user_data = tc.user_data();
let event_u32 = EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap())); let event_u32 = EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap()));
@ -54,8 +63,7 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService5EventHandler<TcInMemConv
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.borrow_mut() .start_success(ecss_tc_and_token.token, &stamp)
.start_success(ecss_tc_and_token.token, Some(&stamp))
.map_err(|_| PartialPusHandlingError::Verification); .map_err(|_| PartialPusHandlingError::Verification);
let partial_error = start_token.clone().err(); let partial_error = start_token.clone().err();
let mut token: TcStateToken = ecss_tc_and_token.token.into(); let mut token: TcStateToken = ecss_tc_and_token.token.into();
@ -86,7 +94,9 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService5EventHandler<TcInMemConv
Ok(PusPacketHandlerResult::RequestHandled) Ok(PusPacketHandlerResult::RequestHandled)
}; };
let mut partial_error = None; let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error); let time_stamp = PusServiceBase::<VerificationReporter>::get_current_cds_short_timestamp(
&mut partial_error,
);
match srv.unwrap() { match srv.unwrap() {
Subservice::TmInfoReport Subservice::TmInfoReport
| Subservice::TmLowSeverityReport | Subservice::TmLowSeverityReport
@ -128,7 +138,7 @@ mod tests {
use crate::pus::event_man::EventRequest; use crate::pus::event_man::EventRequest;
use crate::pus::tests::SimplePusPacketHandler; use crate::pus::tests::SimplePusPacketHandler;
use crate::pus::verification::RequestId; use crate::pus::verification::{RequestId, VerificationReporterWithSender};
use crate::{ use crate::{
events::EventU32, events::EventU32,
pus::{ pus::{
@ -145,7 +155,8 @@ mod tests {
struct Pus5HandlerWithStoreTester { struct Pus5HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon, common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService5EventHandler<EcssTcInSharedStoreConverter>, handler:
PusService5EventHandler<EcssTcInSharedStoreConverter, VerificationReporterWithSender>,
} }
impl Pus5HandlerWithStoreTester { impl Pus5HandlerWithStoreTester {
@ -271,8 +282,9 @@ mod tests {
let result = test_harness.handle_one_tc(); let result = test_harness.handle_one_tc();
assert!(result.is_err()); assert!(result.is_err());
let result = result.unwrap_err(); let result = result.unwrap_err();
if let PusPacketHandlingError::NotEnoughAppData(string) = result { if let PusPacketHandlingError::NotEnoughAppData { expected, found } = result {
assert_eq!(string, "at least 4 bytes event ID expected"); assert_eq!(expected, 4);
assert_eq!(found, 3);
} else { } else {
panic!("unexpected result type {result:?}") panic!("unexpected result type {result:?}")
} }

View File

@ -1 +1,394 @@
pub use spacepackets::ecss::hk::*; pub use spacepackets::ecss::hk::*;
#[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::*;
use crate::{hk::HkRequest, TargetId};
use super::verification::{TcStateAccepted, VerificationToken};
/// This trait is an abstraction for the routing of PUS service 3 housekeeping requests to a
/// dedicated recipient using the generic [TargetId].
pub trait PusHkRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
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
/// a [HkRequest].
///
/// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
/// The only requirement is that a valid [TargetId] and a [HkRequest] 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 [VerificationReporterWithSender] instance is passed to the user to also allow handling
/// of the verification process as part of the PUS standard requirements.
pub trait PusHkToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, HkRequest), 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 generic high-level handler for the PUS service 3 housekeeping 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
/// [PusPacketHandlerResult] for the concrete implementation which offers a packet handler.
/// 3. Route the action request using the provided [PusActionRequestRouter]. The generic error
/// type is constrained to the [GenericRoutingError] for the concrete implementation.
/// 4. Handle all routing errors using the provided [PusRoutingErrorHandler]. The generic error
/// type is constrained to the [GenericRoutingError] for the concrete implementation.
pub struct PusService3HkHandler<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusHkToRequestConverter,
RequestRouter: PusHkRequestRouter<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: PusHkToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusHkRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError: Clone,
>
PusService3HkHandler<
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,
}
}
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, hk_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, hk_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::hk::Subservice;
use spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
tm::PusTmReader,
PusPacket,
},
CcsdsPacket, SequenceFlags, SpHeader,
};
use crate::{
hk::HkRequest,
pus::{
tests::{
PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler,
TestConverter, TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
},
verification::{
tests::TestVerificationReporter, FailParams, RequestId, TcStateAccepted,
VerificationReportingProvider, VerificationToken,
},
EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult,
PusPacketHandlingError,
},
TargetId,
};
use super::{PusHkRequestRouter, PusHkToRequestConverter, PusService3HkHandler};
impl PusHkRequestRouter for TestRouter<HkRequest> {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
_token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
self.routing_requests
.borrow_mut()
.push_back((target_id, hk_request));
self.check_for_injected_error()
}
}
impl PusHkToRequestConverter for TestConverter<3> {
type Error = PusPacketHandlingError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, HkRequest), 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() == Subservice::TcGenerateOneShotHk as u8 {
verif_reporter
.start_success(token, time_stamp)
.expect("start success failure");
return Ok((
target_id.into(),
HkRequest::OneShot(u32::from_be_bytes(
tc.user_data()[0..4].try_into().unwrap(),
)),
));
}
Err(PusPacketHandlingError::InvalidAppData(
"unexpected app data".into(),
))
}
}
struct Pus3HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon<TestVerificationReporter>,
handler: PusService3HkHandler<
EcssTcInVecConverter,
TestVerificationReporter,
TestConverter<3>,
TestRouter<HkRequest>,
TestRoutingErrorHandler,
>,
}
impl Pus3HandlerWithVecTester {
pub fn new() -> Self {
let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
Self {
common,
handler: PusService3HkHandler::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, HkRequest);
}
}
delegate! {
to self.handler.routing_error_handler {
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
}
}
}
impl PusTestHarness for Pus3HandlerWithVecTester {
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 Pus3HandlerWithVecTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
}
}
#[test]
fn basic_test() {
let mut hk_handler = Pus3HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
let unique_id: u32 = 1;
let unique_id_raw = unique_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
hk_handler.send_tc(&tc);
let result = hk_handler.handle_one_tc();
assert!(result.is_ok());
hk_handler.check_next_conversion(&tc);
let (target_id, hk_request) = hk_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let HkRequest::OneShot(id) = hk_request {
assert_eq!(id, unique_id);
} else {
panic!("unexpected request");
}
}
#[test]
fn test_routing_error() {
let mut hk_handler = Pus3HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
let unique_id: u32 = 1;
let unique_id_raw = unique_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
let error = GenericRoutingError::UnknownTargetId(25);
hk_handler
.handler
.request_router
.inject_routing_error(error);
hk_handler.send_tc(&tc);
let result = hk_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");
}
hk_handler.check_next_conversion(&tc);
let (target_id, hk_req) = hk_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let HkRequest::OneShot(unique_id) = hk_req {
assert_eq!(unique_id, 1);
}
let (target_id, found_error) = hk_handler.retrieve_next_error();
assert_eq!(target_id, TEST_APID.into());
check_error(found_error);
}
}

View File

@ -2,6 +2,7 @@
//! //!
//! This module contains structures to make working with the PUS C standard easier. //! This module contains structures to make working with the PUS C standard easier.
//! The satrs-example application contains various usage examples of these components. //! The satrs-example application contains various usage examples of these components.
use crate::queue::{GenericRecvError, GenericSendError};
use crate::ChannelId; use crate::ChannelId;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@ -16,6 +17,7 @@ use spacepackets::ecss::tm::PusTmCreator;
use spacepackets::ecss::PusError; use spacepackets::ecss::PusError;
use spacepackets::{ByteConversionError, SpHeader}; use spacepackets::{ByteConversionError, SpHeader};
pub mod action;
pub mod event; pub mod event;
pub mod event_man; pub mod event_man;
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -55,52 +57,6 @@ impl<'tm> From<PusTmCreator<'tm>> for PusTmWrapper<'tm> {
} }
} }
/// 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 {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum EcssTmtcError { pub enum EcssTmtcError {
StoreLock, StoreLock,
@ -304,8 +260,12 @@ pub trait ReceivesEcssPusTc {
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
mod alloc_mod { mod alloc_mod {
use crate::TargetId;
use super::*; use super::*;
use crate::pus::verification::VerificationReportingProvider;
/// Extension trait for [EcssTmSenderCore]. /// Extension trait for [EcssTmSenderCore].
/// ///
/// It provides additional functionality, for example by implementing the [Downcast] trait /// It provides additional functionality, for example by implementing the [Downcast] trait
@ -385,22 +345,33 @@ mod alloc_mod {
impl<T> EcssTcReceiver for T where T: EcssTcReceiverCore + 'static {} impl<T> EcssTcReceiver for T where T: EcssTcReceiverCore + 'static {}
impl_downcast!(EcssTcReceiver); impl_downcast!(EcssTcReceiver);
pub trait PusRoutingErrorHandler {
type Error;
fn handle_error(
&self,
target_id: TargetId,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
error: Self::Error,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
);
}
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod { pub mod std_mod {
use crate::pool::{PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr}; use crate::pool::{PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr};
use crate::pus::verification::{ use crate::pus::verification::{TcStateAccepted, VerificationToken};
StdVerifReporterWithSender, TcStateAccepted, VerificationToken,
};
use crate::pus::{ use crate::pus::{
EcssChannel, EcssTcAndToken, EcssTcReceiver, EcssTcReceiverCore, EcssTmSender, EcssChannel, EcssTcAndToken, EcssTcReceiver, EcssTcReceiverCore, EcssTmSender,
EcssTmSenderCore, EcssTmtcError, GenericRecvError, GenericSendError, PusTmWrapper, EcssTmSenderCore, EcssTmtcError, GenericRecvError, GenericSendError, PusTmWrapper,
TryRecvTmtcError, TryRecvTmtcError,
}; };
use crate::tmtc::tm_helper::SharedTmPool; use crate::tmtc::tm_helper::SharedTmPool;
use crate::ChannelId; use crate::{ChannelId, TargetId};
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::vec::Vec; use alloc::vec::Vec;
use crossbeam_channel as cb; use crossbeam_channel as cb;
@ -410,13 +381,12 @@ pub mod std_mod {
use spacepackets::time::cds::TimeProvider; use spacepackets::time::cds::TimeProvider;
use spacepackets::time::StdTimestampError; use spacepackets::time::StdTimestampError;
use spacepackets::time::TimeWriter; use spacepackets::time::TimeWriter;
use std::cell::RefCell;
use std::string::String; use std::string::String;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::TryRecvError; use std::sync::mpsc::TryRecvError;
use thiserror::Error; use thiserror::Error;
use super::verification::VerificationReporterWithSender; use super::verification::VerificationReportingProvider;
use super::{AcceptedEcssTcAndToken, TcInMemory}; use super::{AcceptedEcssTcAndToken, TcInMemory};
impl From<mpsc::SendError<StoreAddr>> for EcssTmtcError { impl From<mpsc::SendError<StoreAddr>> for EcssTmtcError {
@ -662,6 +632,20 @@ pub mod std_mod {
} }
} }
// TODO: All these types could probably be no_std if we implemented error handling ourselves..
// but thiserror is really nice, so keep it like this for simplicity for now. Maybe thiserror
// will be no_std soon, see https://github.com/rust-lang/rust/issues/103765 .
#[derive(Debug, Clone, Error)]
pub enum GenericRoutingError {
#[error("not enough application data, expected at least {expected}, found {found}")]
NotEnoughAppData { expected: usize, found: usize },
#[error("Unknown target ID {0}")]
UnknownTargetId(TargetId),
#[error("Sending action request failed: {0}")]
SendError(GenericSendError),
}
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum PusPacketHandlingError { pub enum PusPacketHandlingError {
#[error("generic PUS error: {0}")] #[error("generic PUS error: {0}")]
@ -670,8 +654,8 @@ pub mod std_mod {
WrongService(u8), WrongService(u8),
#[error("invalid subservice {0}")] #[error("invalid subservice {0}")]
InvalidSubservice(u8), InvalidSubservice(u8),
#[error("not enough application data available: {0}")] #[error("not enough application data, expected at least {expected}, found {found}")]
NotEnoughAppData(String), NotEnoughAppData { expected: usize, found: usize },
#[error("PUS packet too large, does not fit in buffer: {0}")] #[error("PUS packet too large, does not fit in buffer: {0}")]
PusPacketTooLarge(usize), PusPacketTooLarge(usize),
#[error("invalid application data")] #[error("invalid application data")]
@ -682,6 +666,8 @@ pub mod std_mod {
EcssTmtc(#[from] EcssTmtcError), EcssTmtc(#[from] EcssTmtcError),
#[error("invalid verification token")] #[error("invalid verification token")]
InvalidVerificationToken, InvalidVerificationToken,
#[error("request routing error: {0}")]
RequestRoutingError(#[from] GenericRoutingError),
#[error("other error {0}")] #[error("other error {0}")]
Other(String), Other(String),
} }
@ -825,18 +811,18 @@ pub mod std_mod {
} }
} }
pub struct PusServiceBase { pub struct PusServiceBase<VerificationReporter: VerificationReportingProvider> {
pub tc_receiver: Box<dyn EcssTcReceiver>, pub tc_receiver: Box<dyn EcssTcReceiver>,
pub tm_sender: Box<dyn EcssTmSender>, pub tm_sender: Box<dyn EcssTmSender>,
pub tm_apid: u16, pub tm_apid: u16,
/// The verification handler is wrapped in a [RefCell] to allow the interior mutability /// The verification handler is wrapped in a [RefCell] to allow the interior mutability
/// pattern. This makes writing methods which are not mutable a lot easier. /// pattern. This makes writing methods which are not mutable a lot easier.
pub verification_handler: RefCell<StdVerifReporterWithSender>, pub verification_handler: VerificationReporter,
} }
impl PusServiceBase { impl<VerificationReporter: VerificationReportingProvider> PusServiceBase<VerificationReporter> {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn get_current_timestamp( pub fn get_current_cds_short_timestamp(
partial_error: &mut Option<PartialPusHandlingError>, partial_error: &mut Option<PartialPusHandlingError>,
) -> [u8; 7] { ) -> [u8; 7] {
let mut time_stamp: [u8; 7] = [0; 7]; let mut time_stamp: [u8; 7] = [0; 7];
@ -850,11 +836,10 @@ pub mod std_mod {
} }
time_stamp time_stamp
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn get_current_timestamp_ignore_error() -> [u8; 7] { pub fn get_current_timestamp_ignore_error() -> [u8; 7] {
let mut dummy = None; let mut dummy = None;
Self::get_current_timestamp(&mut dummy) Self::get_current_cds_short_timestamp(&mut dummy)
} }
} }
@ -867,17 +852,24 @@ pub mod std_mod {
/// This base class can handle PUS telecommands backed by different memory storage machanisms /// This base class can handle PUS telecommands backed by different memory storage machanisms
/// by using the [EcssTcInMemConverter] abstraction. This object provides some convenience /// by using the [EcssTcInMemConverter] abstraction. This object provides some convenience
/// methods to make the generic parts of TC handling easier. /// methods to make the generic parts of TC handling easier.
pub struct PusServiceHelper<TcInMemConverter: EcssTcInMemConverter> { pub struct PusServiceHelper<
pub common: PusServiceBase, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub common: PusServiceBase<VerificationReporter>,
pub tc_in_mem_converter: TcInMemConverter, pub tc_in_mem_converter: TcInMemConverter,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusServiceHelper<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusServiceHelper<TcInMemConverter, VerificationReporter>
{
pub fn new( pub fn new(
tc_receiver: Box<dyn EcssTcReceiver>, tc_receiver: Box<dyn EcssTcReceiver>,
tm_sender: Box<dyn EcssTmSender>, tm_sender: Box<dyn EcssTmSender>,
tm_apid: u16, tm_apid: u16,
verification_handler: VerificationReporterWithSender, verification_handler: VerificationReporter,
tc_in_mem_converter: TcInMemConverter, tc_in_mem_converter: TcInMemConverter,
) -> Self { ) -> Self {
Self { Self {
@ -885,7 +877,7 @@ pub mod std_mod {
tc_receiver, tc_receiver,
tm_sender, tm_sender,
tm_apid, tm_apid,
verification_handler: RefCell::new(verification_handler), verification_handler,
}, },
tc_in_mem_converter, tc_in_mem_converter,
} }
@ -939,12 +931,15 @@ pub(crate) fn source_buffer_large_enough(cap: usize, len: usize) -> Result<(), E
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use core::cell::RefCell;
use std::sync::mpsc::TryRecvError; use std::sync::mpsc::TryRecvError;
use std::sync::{mpsc, RwLock}; use std::sync::{mpsc, RwLock};
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::vec; use alloc::collections::VecDeque;
use spacepackets::ecss::tc::PusTcCreator; use alloc::vec::Vec;
use satrs_shared::res_code::ResultU16;
use spacepackets::ecss::tc::{PusTcCreator, PusTcReader};
use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader}; use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader};
use spacepackets::ecss::{PusPacket, WritablePusPacket}; use spacepackets::ecss::{PusPacket, WritablePusPacket};
use spacepackets::CcsdsPacket; use spacepackets::CcsdsPacket;
@ -954,14 +949,17 @@ pub mod tests {
}; };
use crate::pus::verification::RequestId; use crate::pus::verification::RequestId;
use crate::tmtc::tm_helper::SharedTmPool; use crate::tmtc::tm_helper::SharedTmPool;
use crate::TargetId;
use super::verification::tests::{SharedVerificationMap, TestVerificationReporter};
use super::verification::{ use super::verification::{
TcStateAccepted, VerificationReporterCfg, VerificationReporterWithSender, VerificationToken, TcStateAccepted, VerificationReporterCfg, VerificationReporterWithSender,
VerificationReportingProvider, VerificationToken,
}; };
use super::{ use super::{
EcssTcAndToken, EcssTcInSharedStoreConverter, EcssTcInVecConverter, MpscTcReceiver, EcssTcAndToken, EcssTcInSharedStoreConverter, EcssTcInVecConverter, GenericRoutingError,
MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusPacketHandlingError, PusServiceHelper, TcInMemory, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceHelper, TcInMemory,
}; };
pub const TEST_APID: u16 = 0x101; pub const TEST_APID: u16 = 0x101;
@ -1016,8 +1014,11 @@ pub mod tests {
/// [PusServiceHandler] which might be required for a specific PUS service handler. /// [PusServiceHandler] which might be required for a specific PUS service handler.
/// ///
/// The PUS service handler is instantiated with a [EcssTcInStoreConverter]. /// The PUS service handler is instantiated with a [EcssTcInStoreConverter].
pub fn new() -> (Self, PusServiceHelper<EcssTcInSharedStoreConverter>) { pub fn new() -> (
let pool_cfg = StaticPoolConfig::new(vec![(16, 16), (8, 32), (4, 64)], false); Self,
PusServiceHelper<EcssTcInSharedStoreConverter, VerificationReporterWithSender>,
) {
let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false);
let tc_pool = StaticMemoryPool::new(pool_cfg.clone()); let tc_pool = StaticMemoryPool::new(pool_cfg.clone());
let tm_pool = StaticMemoryPool::new(pool_cfg); let tm_pool = StaticMemoryPool::new(pool_cfg);
let shared_tc_pool = SharedStaticMemoryPool::new(RwLock::new(tc_pool)); let shared_tc_pool = SharedStaticMemoryPool::new(RwLock::new(tc_pool));
@ -1062,7 +1063,7 @@ pub mod tests {
let token = self.verification_handler.add_tc(tc); let token = self.verification_handler.add_tc(tc);
let token = self let token = self
.verification_handler .verification_handler
.acceptance_success(token, Some(&[0; 7])) .acceptance_success(token, &[0; 7])
.unwrap(); .unwrap();
let tc_size = tc.write_to_bytes(&mut self.pus_buf).unwrap(); let tc_size = tc.write_to_bytes(&mut self.pus_buf).unwrap();
let mut tc_pool = self.tc_pool.write().unwrap(); let mut tc_pool = self.tc_pool.write().unwrap();
@ -1109,15 +1110,18 @@ pub mod tests {
} }
} }
pub struct PusServiceHandlerWithVecCommon { pub struct PusServiceHandlerWithVecCommon<VerificationReporter: VerificationReportingProvider> {
current_tm: Option<alloc::vec::Vec<u8>>, current_tm: Option<alloc::vec::Vec<u8>>,
tc_sender: mpsc::Sender<EcssTcAndToken>, tc_sender: mpsc::Sender<EcssTcAndToken>,
tm_receiver: mpsc::Receiver<alloc::vec::Vec<u8>>, tm_receiver: mpsc::Receiver<alloc::vec::Vec<u8>>,
verification_handler: VerificationReporterWithSender, pub verification_handler: VerificationReporter,
} }
impl PusServiceHandlerWithVecCommon { impl PusServiceHandlerWithVecCommon<VerificationReporterWithSender> {
pub fn new() -> (Self, PusServiceHelper<EcssTcInVecConverter>) { pub fn new_with_standard_verif_reporter() -> (
Self,
PusServiceHelper<EcssTcInVecConverter, VerificationReporterWithSender>,
) {
let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel(); let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel(); let (tm_tx, tm_rx) = mpsc::channel();
@ -1125,6 +1129,7 @@ pub mod tests {
let verif_cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap(); let verif_cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
let verification_handler = let verification_handler =
VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender)); VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender));
let test_srv_tm_sender = MpscTmAsVecSender::new(0, "test-sender", tm_tx); let test_srv_tm_sender = MpscTmAsVecSender::new(0, "test-sender", tm_tx);
let test_srv_tc_receiver = MpscTcReceiver::new(0, "test-receiver", test_srv_tc_rx); let test_srv_tc_receiver = MpscTcReceiver::new(0, "test-receiver", test_srv_tc_rx);
let in_store_converter = EcssTcInVecConverter::default(); let in_store_converter = EcssTcInVecConverter::default();
@ -1144,12 +1149,47 @@ pub mod tests {
), ),
) )
} }
}
impl PusServiceHandlerWithVecCommon<TestVerificationReporter> {
pub fn new_with_test_verif_sender() -> (
Self,
PusServiceHelper<EcssTcInVecConverter, TestVerificationReporter>,
) {
let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel();
let test_srv_tm_sender = MpscTmAsVecSender::new(0, "test-sender", tm_tx);
let test_srv_tc_receiver = MpscTcReceiver::new(0, "test-receiver", test_srv_tc_rx);
let in_store_converter = EcssTcInVecConverter::default();
let shared_verif_map = SharedVerificationMap::default();
let verification_handler = TestVerificationReporter::new(shared_verif_map);
(
Self {
current_tm: None,
tc_sender: test_srv_tc_tx,
tm_receiver: tm_rx,
verification_handler: verification_handler.clone(),
},
PusServiceHelper::new(
Box::new(test_srv_tc_receiver),
Box::new(test_srv_tm_sender),
TEST_APID,
verification_handler,
in_store_converter,
),
)
}
}
impl<VerificationReporter: VerificationReportingProvider>
PusServiceHandlerWithVecCommon<VerificationReporter>
{
pub fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> { pub fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let token = self.verification_handler.add_tc(tc); let token = self.verification_handler.add_tc(tc);
let token = self let token = self
.verification_handler .verification_handler
.acceptance_success(token, Some(&[0; 7])) .acceptance_success(token, &[0; 7])
.unwrap(); .unwrap();
// Send accepted TC to test service handler. // Send accepted TC to test service handler.
self.tc_sender self.tc_sender
@ -1191,4 +1231,106 @@ pub mod tests {
assert_eq!(req_id, expected_request_id); assert_eq!(req_id, expected_request_id);
} }
} }
pub const APP_DATA_TOO_SHORT: ResultU16 = ResultU16::new(1, 1);
#[derive(Default)]
pub struct TestConverter<const SERVICE: u8> {
pub conversion_request: VecDeque<Vec<u8>>,
}
impl<const SERVICE: u8> TestConverter<SERVICE> {
pub fn check_service(&self, tc: &PusTcReader) -> Result<(), PusPacketHandlingError> {
if tc.service() != SERVICE {
return Err(PusPacketHandlingError::WrongService(tc.service()));
}
Ok(())
}
pub fn is_empty(&self) {
self.conversion_request.is_empty();
}
pub fn check_next_conversion(&mut self, tc: &PusTcCreator) {
assert!(!self.conversion_request.is_empty());
assert_eq!(
self.conversion_request.pop_front().unwrap(),
tc.to_vec().unwrap()
);
}
}
#[derive(Default)]
pub struct TestRoutingErrorHandler {
pub routing_errors: RefCell<VecDeque<(TargetId, GenericRoutingError)>>,
}
impl PusRoutingErrorHandler for TestRoutingErrorHandler {
type Error = GenericRoutingError;
fn handle_error(
&self,
target_id: TargetId,
_token: VerificationToken<TcStateAccepted>,
_tc: &PusTcReader,
error: Self::Error,
_time_stamp: &[u8],
_verif_reporter: &impl VerificationReportingProvider,
) {
self.routing_errors
.borrow_mut()
.push_back((target_id, error));
}
}
impl TestRoutingErrorHandler {
pub fn is_empty(&self) -> bool {
self.routing_errors.borrow().is_empty()
}
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError) {
if self.routing_errors.borrow().is_empty() {
panic!("no routing request available");
}
self.routing_errors.borrow_mut().pop_front().unwrap()
}
}
pub struct TestRouter<REQUEST> {
pub routing_requests: RefCell<VecDeque<(TargetId, REQUEST)>>,
pub injected_routing_failure: RefCell<Option<GenericRoutingError>>,
}
impl<REQUEST> Default for TestRouter<REQUEST> {
fn default() -> Self {
Self {
routing_requests: Default::default(),
injected_routing_failure: Default::default(),
}
}
}
impl<REQUEST> TestRouter<REQUEST> {
pub fn check_for_injected_error(&self) -> Result<(), GenericRoutingError> {
if self.injected_routing_failure.borrow().is_some() {
return Err(self.injected_routing_failure.borrow_mut().take().unwrap());
}
Ok(())
}
pub fn inject_routing_error(&mut self, error: GenericRoutingError) {
*self.injected_routing_failure.borrow_mut() = Some(error);
}
pub fn is_empty(&self) -> bool {
self.routing_requests.borrow().is_empty()
}
pub fn retrieve_next_request(&mut self) -> (TargetId, REQUEST) {
if self.routing_requests.borrow().is_empty() {
panic!("no routing request available");
}
self.routing_requests.borrow_mut().pop_front().unwrap()
}
}
} }

View File

@ -1,4 +1,5 @@
use super::scheduler::PusSchedulerProvider; use super::scheduler::PusSchedulerProvider;
use super::verification::VerificationReportingProvider;
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper}; use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
use crate::pool::PoolProvider; use crate::pool::PoolProvider;
use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError}; use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError};
@ -16,16 +17,23 @@ use spacepackets::time::cds::TimeProvider;
/// telecommands when applicable. /// telecommands when applicable.
pub struct PusService11SchedHandler< pub struct PusService11SchedHandler<
TcInMemConverter: EcssTcInMemConverter, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
PusScheduler: PusSchedulerProvider, PusScheduler: PusSchedulerProvider,
> { > {
pub service_helper: PusServiceHelper<TcInMemConverter>, pub service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
scheduler: PusScheduler, scheduler: PusScheduler,
} }
impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider> impl<
PusService11SchedHandler<TcInMemConverter, Scheduler> TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
Scheduler: PusSchedulerProvider,
> PusService11SchedHandler<TcInMemConverter, VerificationReporter, Scheduler>
{ {
pub fn new(service_helper: PusServiceHelper<TcInMemConverter>, scheduler: Scheduler) -> Self { pub fn new(
service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
scheduler: Scheduler,
) -> Self {
Self { Self {
service_helper, service_helper,
scheduler, scheduler,
@ -62,15 +70,16 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
)); ));
} }
let mut partial_error = None; let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error); let time_stamp = PusServiceBase::<VerificationReporter>::get_current_cds_short_timestamp(
&mut partial_error,
);
match standard_subservice.unwrap() { match standard_subservice.unwrap() {
scheduling::Subservice::TcEnableScheduling => { scheduling::Subservice::TcEnableScheduling => {
let start_token = self let start_token = self
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(ecss_tc_and_token.token, &time_stamp)
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
.expect("Error sending start success"); .expect("Error sending start success");
self.scheduler.enable(); self.scheduler.enable();
@ -78,8 +87,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
self.service_helper self.service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &time_stamp)
.completion_success(start_token, Some(&time_stamp))
.expect("Error sending completion success"); .expect("Error sending completion success");
} else { } else {
return Err(PusPacketHandlingError::Other( return Err(PusPacketHandlingError::Other(
@ -92,8 +100,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(ecss_tc_and_token.token, &time_stamp)
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
.expect("Error sending start success"); .expect("Error sending start success");
self.scheduler.disable(); self.scheduler.disable();
@ -101,8 +108,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
self.service_helper self.service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &time_stamp)
.completion_success(start_token, Some(&time_stamp))
.expect("Error sending completion success"); .expect("Error sending completion success");
} else { } else {
return Err(PusPacketHandlingError::Other( return Err(PusPacketHandlingError::Other(
@ -115,8 +121,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(ecss_tc_and_token.token, &time_stamp)
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
.expect("Error sending start success"); .expect("Error sending start success");
self.scheduler self.scheduler
@ -126,8 +131,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
self.service_helper self.service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &time_stamp)
.completion_success(start_token, Some(&time_stamp))
.expect("Error sending completion success"); .expect("Error sending completion success");
} }
scheduling::Subservice::TcInsertActivity => { scheduling::Subservice::TcInsertActivity => {
@ -135,8 +139,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(ecss_tc_and_token.token, &time_stamp)
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
.expect("error sending start success"); .expect("error sending start success");
// let mut pool = self.sched_tc_pool.write().expect("locking pool failed"); // let mut pool = self.sched_tc_pool.write().expect("locking pool failed");
@ -147,8 +150,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
self.service_helper self.service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &time_stamp)
.completion_success(start_token, Some(&time_stamp))
.expect("sending completion success failed"); .expect("sending completion success failed");
} }
_ => { _ => {
@ -172,6 +174,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
mod tests { mod tests {
use crate::pool::{StaticMemoryPool, StaticPoolConfig}; use crate::pool::{StaticMemoryPool, StaticPoolConfig};
use crate::pus::tests::TEST_APID; use crate::pus::tests::TEST_APID;
use crate::pus::verification::VerificationReporterWithSender;
use crate::pus::{ use crate::pus::{
scheduler::{self, PusSchedulerProvider, TcInfo}, scheduler::{self, PusSchedulerProvider, TcInfo},
tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness}, tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness},
@ -194,7 +197,11 @@ mod tests {
struct Pus11HandlerWithStoreTester { struct Pus11HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon, common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService11SchedHandler<EcssTcInSharedStoreConverter, TestScheduler>, handler: PusService11SchedHandler<
EcssTcInSharedStoreConverter,
VerificationReporterWithSender,
TestScheduler,
>,
sched_tc_pool: StaticMemoryPool, sched_tc_pool: StaticMemoryPool,
} }

View File

@ -5,16 +5,24 @@ use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use spacepackets::ecss::PusPacket; use spacepackets::ecss::PusPacket;
use spacepackets::SpHeader; use spacepackets::SpHeader;
use super::verification::VerificationReportingProvider;
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper}; use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
/// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets. /// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets.
/// This handler only processes ping requests and generates a ping reply for them accordingly. /// This handler only processes ping requests and generates a ping reply for them accordingly.
pub struct PusService17TestHandler<TcInMemConverter: EcssTcInMemConverter> { pub struct PusService17TestHandler<
pub service_helper: PusServiceHelper<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusService17TestHandler<TcInMemConverter> { impl<
pub fn new(service_helper: PusServiceHelper<TcInMemConverter>) -> Self { TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusService17TestHandler<TcInMemConverter, VerificationReporter>
{
pub fn new(service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>) -> Self {
Self { service_helper } Self { service_helper }
} }
@ -33,13 +41,15 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService17TestHandler<TcInMemConv
} }
if tc.subservice() == 1 { if tc.subservice() == 1 {
let mut partial_error = None; let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error); let time_stamp =
PusServiceBase::<VerificationReporter>::get_current_cds_short_timestamp(
&mut partial_error,
);
let result = self let result = self
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .start_success(ecss_tc_and_token.token, &time_stamp)
.start_success(ecss_tc_and_token.token, Some(&time_stamp))
.map_err(|_| PartialPusHandlingError::Verification); .map_err(|_| PartialPusHandlingError::Verification);
let start_token = if let Ok(result) = result { let start_token = if let Ok(result) = result {
Some(result) Some(result)
@ -67,8 +77,7 @@ impl<TcInMemConverter: EcssTcInMemConverter> PusService17TestHandler<TcInMemConv
.service_helper .service_helper
.common .common
.verification_handler .verification_handler
.get_mut() .completion_success(start_token, &time_stamp)
.completion_success(start_token, Some(&time_stamp))
.is_err() .is_err()
{ {
partial_error = Some(PartialPusHandlingError::Verification) partial_error = Some(PartialPusHandlingError::Verification)
@ -95,7 +104,7 @@ mod tests {
PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness, PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness,
SimplePusPacketHandler, TEST_APID, SimplePusPacketHandler, TEST_APID,
}; };
use crate::pus::verification::RequestId; use crate::pus::verification::{RequestId, VerificationReporterWithSender};
use crate::pus::verification::{TcStateAccepted, VerificationToken}; use crate::pus::verification::{TcStateAccepted, VerificationToken};
use crate::pus::{ use crate::pus::{
EcssTcInSharedStoreConverter, EcssTcInVecConverter, PusPacketHandlerResult, EcssTcInSharedStoreConverter, EcssTcInVecConverter, PusPacketHandlerResult,
@ -111,7 +120,8 @@ mod tests {
struct Pus17HandlerWithStoreTester { struct Pus17HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon, common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService17TestHandler<EcssTcInSharedStoreConverter>, handler:
PusService17TestHandler<EcssTcInSharedStoreConverter, VerificationReporterWithSender>,
} }
impl Pus17HandlerWithStoreTester { impl Pus17HandlerWithStoreTester {
@ -148,13 +158,14 @@ mod tests {
} }
struct Pus17HandlerWithVecTester { struct Pus17HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon, common: PusServiceHandlerWithVecCommon<VerificationReporterWithSender>,
handler: PusService17TestHandler<EcssTcInVecConverter>, handler: PusService17TestHandler<EcssTcInVecConverter, VerificationReporterWithSender>,
} }
impl Pus17HandlerWithVecTester { impl Pus17HandlerWithVecTester {
pub fn new() -> Self { pub fn new() -> Self {
let (common, srv_handler) = PusServiceHandlerWithVecCommon::new(); let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_standard_verif_reporter();
Self { Self {
common, common,
handler: PusService17TestHandler::new(srv_handler), handler: PusService17TestHandler::new(srv_handler),

View File

@ -16,7 +16,9 @@
//! use std::sync::{Arc, mpsc, RwLock}; //! use std::sync::{Arc, mpsc, RwLock};
//! use std::time::Duration; //! use std::time::Duration;
//! use satrs::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; //! use satrs::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
//! use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender}; //! use satrs::pus::verification::{
//! VerificationReportingProvider, VerificationReporterCfg, VerificationReporterWithSender
//! };
//! use satrs::seq_count::SeqCountProviderSimple; //! use satrs::seq_count::SeqCountProviderSimple;
//! use satrs::pus::MpscTmInSharedPoolSender; //! use satrs::pus::MpscTmInSharedPoolSender;
//! use satrs::tmtc::tm_helper::SharedTmPool; //! use satrs::tmtc::tm_helper::SharedTmPool;
@ -43,9 +45,9 @@
//! let init_token = reporter.add_tc(&pus_tc_0); //! let init_token = reporter.add_tc(&pus_tc_0);
//! //!
//! // Complete success sequence for a telecommand //! // Complete success sequence for a telecommand
//! let accepted_token = reporter.acceptance_success(init_token, Some(&EMPTY_STAMP)).unwrap(); //! let accepted_token = reporter.acceptance_success(init_token, &EMPTY_STAMP).unwrap();
//! let started_token = reporter.start_success(accepted_token, Some(&EMPTY_STAMP)).unwrap(); //! let started_token = reporter.start_success(accepted_token, &EMPTY_STAMP).unwrap();
//! reporter.completion_success(started_token, Some(&EMPTY_STAMP)).unwrap(); //! reporter.completion_success(started_token, &EMPTY_STAMP).unwrap();
//! //!
//! // Verify it arrives correctly on receiver end //! // Verify it arrives correctly on receiver end
//! let mut tm_buf: [u8; 1024] = [0; 1024]; //! let mut tm_buf: [u8; 1024] = [0; 1024];
@ -279,16 +281,16 @@ impl<STATE> VerificationToken<STATE> {
/// Composite helper struct to pass failure parameters to the [VerificationReporter] /// Composite helper struct to pass failure parameters to the [VerificationReporter]
pub struct FailParams<'stamp, 'fargs> { pub struct FailParams<'stamp, 'fargs> {
time_stamp: Option<&'stamp [u8]>, time_stamp: &'stamp [u8],
failure_code: &'fargs dyn EcssEnumeration, failure_code: &'fargs dyn EcssEnumeration,
failure_data: Option<&'fargs [u8]>, failure_data: &'fargs [u8],
} }
impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> {
pub fn new( pub fn new(
time_stamp: Option<&'stamp [u8]>, time_stamp: &'stamp [u8],
failure_code: &'fargs impl EcssEnumeration, failure_code: &'fargs impl EcssEnumeration,
failure_data: Option<&'fargs [u8]>, failure_data: &'fargs [u8],
) -> Self { ) -> Self {
Self { Self {
time_stamp, time_stamp,
@ -296,6 +298,13 @@ impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> {
failure_data, failure_data,
} }
} }
pub fn new_no_fail_data(
time_stamp: &'stamp [u8],
failure_code: &'fargs impl EcssEnumeration,
) -> Self {
Self::new(time_stamp, failure_code, &[])
}
} }
/// Composite helper struct to pass step failure parameters to the [VerificationReporter] /// Composite helper struct to pass step failure parameters to the [VerificationReporter]
@ -306,10 +315,10 @@ pub struct FailParamsWithStep<'stamp, 'fargs> {
impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> { impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> {
pub fn new( pub fn new(
time_stamp: Option<&'stamp [u8]>, time_stamp: &'stamp [u8],
step: &'fargs impl EcssEnumeration, step: &'fargs impl EcssEnumeration,
failure_code: &'fargs impl EcssEnumeration, failure_code: &'fargs impl EcssEnumeration,
failure_data: Option<&'fargs [u8]>, failure_data: &'fargs [u8],
) -> Self { ) -> Self {
Self { Self {
bp: FailParams::new(time_stamp, failure_code, failure_data), bp: FailParams::new(time_stamp, failure_code, failure_data),
@ -399,6 +408,66 @@ impl<'src_data, TcState: WasAtLeastAccepted + Copy>
pub fn send_success_step_or_completion_success(self) {} pub fn send_success_step_or_completion_success(self) {}
} }
pub trait VerificationReportingProvider {
fn add_tc(
&mut self,
pus_tc: &(impl CcsdsPacket + IsPusTelecommand),
) -> VerificationToken<TcStateNone> {
self.add_tc_with_req_id(RequestId::new(pus_tc))
}
fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken<TcStateNone>;
fn acceptance_success(
&self,
token: VerificationToken<TcStateNone>,
time_stamp: &[u8],
) -> Result<VerificationToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateNone>>;
fn acceptance_failure(
&self,
token: VerificationToken<TcStateNone>,
params: FailParams,
) -> Result<(), VerificationOrSendErrorWithToken<TcStateNone>>;
fn start_success(
&self,
token: VerificationToken<TcStateAccepted>,
time_stamp: &[u8],
) -> Result<VerificationToken<TcStateStarted>, VerificationOrSendErrorWithToken<TcStateAccepted>>;
fn start_failure(
&self,
token: VerificationToken<TcStateAccepted>,
params: FailParams,
) -> Result<(), VerificationOrSendErrorWithToken<TcStateAccepted>>;
fn step_success(
&self,
token: &VerificationToken<TcStateStarted>,
time_stamp: &[u8],
step: impl EcssEnumeration,
) -> Result<(), EcssTmtcError>;
fn step_failure(
&self,
token: VerificationToken<TcStateStarted>,
params: FailParamsWithStep,
) -> Result<(), VerificationOrSendErrorWithToken<TcStateStarted>>;
fn completion_success<TcState: WasAtLeastAccepted + Copy>(
&self,
token: VerificationToken<TcState>,
time_stamp: &[u8],
) -> Result<(), VerificationOrSendErrorWithToken<TcState>>;
fn completion_failure<TcState: WasAtLeastAccepted + Copy>(
&self,
token: VerificationToken<TcState>,
params: FailParams,
) -> Result<(), VerificationOrSendErrorWithToken<TcState>>;
}
/// Primary verification handler. It provides an API to send PUS 1 verification telemetry packets /// Primary verification handler. It provides an API to send PUS 1 verification telemetry packets
/// and verify the various steps of telecommand handling as specified in the PUS standard. /// and verify the various steps of telecommand handling as specified in the PUS standard.
/// ///
@ -456,7 +525,7 @@ impl VerificationReporterCore {
token: VerificationToken<State>, token: VerificationToken<State>,
seq_count: u16, seq_count: u16,
msg_count: u16, msg_count: u16,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
) -> Result< ) -> Result<
VerificationSendable<'src_data, State, VerifSuccess>, VerificationSendable<'src_data, State, VerifSuccess>,
VerificationErrorWithToken<State>, VerificationErrorWithToken<State>,
@ -513,7 +582,7 @@ impl VerificationReporterCore {
token: VerificationToken<TcStateNone>, token: VerificationToken<TcStateNone>,
seq_count: u16, seq_count: u16,
msg_count: u16, msg_count: u16,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
) -> Result< ) -> Result<
VerificationSendable<'src_data, TcStateNone, VerifSuccess>, VerificationSendable<'src_data, TcStateNone, VerifSuccess>,
VerificationErrorWithToken<TcStateNone>, VerificationErrorWithToken<TcStateNone>,
@ -584,7 +653,7 @@ impl VerificationReporterCore {
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
seq_count: u16, seq_count: u16,
msg_count: u16, msg_count: u16,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
) -> Result< ) -> Result<
VerificationSendable<'src_data, TcStateAccepted, VerifSuccess>, VerificationSendable<'src_data, TcStateAccepted, VerifSuccess>,
VerificationErrorWithToken<TcStateAccepted>, VerificationErrorWithToken<TcStateAccepted>,
@ -658,7 +727,7 @@ impl VerificationReporterCore {
token: &VerificationToken<TcStateStarted>, token: &VerificationToken<TcStateStarted>,
seq_count: u16, seq_count: u16,
msg_count: u16, msg_count: u16,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
step: impl EcssEnumeration, step: impl EcssEnumeration,
) -> Result<VerificationSendable<'src_data, TcStateStarted, VerifSuccess>, EcssTmtcError> { ) -> Result<VerificationSendable<'src_data, TcStateStarted, VerifSuccess>, EcssTmtcError> {
Ok(VerificationSendable::new_no_token( Ok(VerificationSendable::new_no_token(
@ -714,7 +783,7 @@ impl VerificationReporterCore {
token: VerificationToken<TcState>, token: VerificationToken<TcState>,
seq_counter: u16, seq_counter: u16,
msg_counter: u16, msg_counter: u16,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
) -> Result< ) -> Result<
VerificationSendable<'src_data, TcState, VerifSuccess>, VerificationSendable<'src_data, TcState, VerifSuccess>,
VerificationErrorWithToken<TcState>, VerificationErrorWithToken<TcState>,
@ -788,7 +857,7 @@ impl VerificationReporterCore {
seq_count: u16, seq_count: u16,
msg_counter: u16, msg_counter: u16,
req_id: &RequestId, req_id: &RequestId,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
step: Option<&(impl EcssEnumeration + ?Sized)>, step: Option<&(impl EcssEnumeration + ?Sized)>,
) -> Result<PusTmCreator<'src_data>, EcssTmtcError> { ) -> Result<PusTmCreator<'src_data>, EcssTmtcError> {
let mut source_data_len = size_of::<u32>(); let mut source_data_len = size_of::<u32>();
@ -832,9 +901,7 @@ impl VerificationReporterCore {
if let Some(step) = step { if let Some(step) = step {
source_data_len += step.size(); source_data_len += step.size();
} }
if let Some(failure_data) = params.failure_data { source_data_len += params.failure_data.len();
source_data_len += failure_data.len();
}
source_buffer_large_enough(src_data_buf.len(), source_data_len)?; source_buffer_large_enough(src_data_buf.len(), source_data_len)?;
req_id.to_bytes(&mut src_data_buf[0..RequestId::SIZE_AS_BYTES]); req_id.to_bytes(&mut src_data_buf[0..RequestId::SIZE_AS_BYTES]);
idx += RequestId::SIZE_AS_BYTES; idx += RequestId::SIZE_AS_BYTES;
@ -849,9 +916,7 @@ impl VerificationReporterCore {
.write_to_be_bytes(&mut src_data_buf[idx..idx + params.failure_code.size()]) .write_to_be_bytes(&mut src_data_buf[idx..idx + params.failure_code.size()])
.map_err(PusError::ByteConversion)?; .map_err(PusError::ByteConversion)?;
idx += params.failure_code.size(); idx += params.failure_code.size();
if let Some(failure_data) = params.failure_data { src_data_buf[idx..idx + params.failure_data.len()].copy_from_slice(params.failure_data);
src_data_buf[idx..idx + failure_data.len()].copy_from_slice(failure_data);
}
let mut sp_header = SpHeader::tm_unseg(self.apid(), seq_count, 0).unwrap(); let mut sp_header = SpHeader::tm_unseg(self.apid(), seq_count, 0).unwrap();
Ok(self.create_pus_verif_tm_base( Ok(self.create_pus_verif_tm_base(
src_data_buf, src_data_buf,
@ -869,11 +934,11 @@ impl VerificationReporterCore {
subservice: u8, subservice: u8,
msg_counter: u16, msg_counter: u16,
sp_header: &mut SpHeader, sp_header: &mut SpHeader,
time_stamp: Option<&'src_data [u8]>, time_stamp: &'src_data [u8],
source_data_len: usize, source_data_len: usize,
) -> PusTmCreator<'src_data> { ) -> PusTmCreator<'src_data> {
let tm_sec_header = let tm_sec_header =
PusTmSecondaryHeader::new(1, subservice, msg_counter, self.dest_id, time_stamp); PusTmSecondaryHeader::new(1, subservice, msg_counter, self.dest_id, Some(time_stamp));
PusTmCreator::new( PusTmCreator::new(
sp_header, sp_header,
tm_sec_header, tm_sec_header,
@ -970,7 +1035,7 @@ mod alloc_mod {
&self, &self,
token: VerificationToken<TcStateNone>, token: VerificationToken<TcStateNone>,
sender: &(impl EcssTmSenderCore + ?Sized), sender: &(impl EcssTmSenderCore + ?Sized),
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result<VerificationToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateNone>> ) -> Result<VerificationToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateNone>>
{ {
let seq_count = self let seq_count = self
@ -1025,7 +1090,7 @@ mod alloc_mod {
&self, &self,
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
sender: &(impl EcssTmSenderCore + ?Sized), sender: &(impl EcssTmSenderCore + ?Sized),
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result< ) -> Result<
VerificationToken<TcStateStarted>, VerificationToken<TcStateStarted>,
VerificationOrSendErrorWithToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateAccepted>,
@ -1085,7 +1150,7 @@ mod alloc_mod {
&self, &self,
token: &VerificationToken<TcStateStarted>, token: &VerificationToken<TcStateStarted>,
sender: &(impl EcssTmSenderCore + ?Sized), sender: &(impl EcssTmSenderCore + ?Sized),
time_stamp: Option<&[u8]>, time_stamp: &[u8],
step: impl EcssEnumeration, step: impl EcssEnumeration,
) -> Result<(), EcssTmtcError> { ) -> Result<(), EcssTmtcError> {
let seq_count = self let seq_count = self
@ -1148,7 +1213,7 @@ mod alloc_mod {
&self, &self,
token: VerificationToken<TcState>, token: VerificationToken<TcState>,
sender: &(impl EcssTmSenderCore + ?Sized), sender: &(impl EcssTmSenderCore + ?Sized),
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result<(), VerificationOrSendErrorWithToken<TcState>> { ) -> Result<(), VerificationOrSendErrorWithToken<TcState>> {
let seq_count = self let seq_count = self
.seq_count_provider .seq_count_provider
@ -1226,24 +1291,34 @@ mod alloc_mod {
to self.reporter { to self.reporter {
pub fn set_apid(&mut self, apid: u16) -> bool; pub fn set_apid(&mut self, apid: u16) -> bool;
pub fn apid(&self) -> u16; pub fn apid(&self) -> u16;
pub fn add_tc(&mut self, pus_tc: &(impl CcsdsPacket + IsPusTelecommand)) -> VerificationToken<TcStateNone>;
pub fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken<TcStateNone>;
pub fn dest_id(&self) -> u16; pub fn dest_id(&self) -> u16;
pub fn set_dest_id(&mut self, dest_id: u16); pub fn set_dest_id(&mut self, dest_id: u16);
} }
} }
}
pub fn acceptance_success( impl VerificationReportingProvider for VerificationReporterWithSender {
delegate! {
to self.reporter {
fn add_tc(
&mut self,
pus_tc: &(impl CcsdsPacket + IsPusTelecommand),
) -> VerificationToken<TcStateNone>;
fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken<TcStateNone>;
}
}
fn acceptance_success(
&self, &self,
token: VerificationToken<TcStateNone>, token: VerificationToken<TcStateNone>,
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result<VerificationToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateNone>> ) -> Result<VerificationToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateNone>>
{ {
self.reporter self.reporter
.acceptance_success(token, self.sender.as_ref(), time_stamp) .acceptance_success(token, self.sender.as_ref(), time_stamp)
} }
pub fn acceptance_failure( fn acceptance_failure(
&self, &self,
token: VerificationToken<TcStateNone>, token: VerificationToken<TcStateNone>,
params: FailParams, params: FailParams,
@ -1252,10 +1327,10 @@ mod alloc_mod {
.acceptance_failure(token, self.sender.as_ref(), params) .acceptance_failure(token, self.sender.as_ref(), params)
} }
pub fn start_success( fn start_success(
&self, &self,
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result< ) -> Result<
VerificationToken<TcStateStarted>, VerificationToken<TcStateStarted>,
VerificationOrSendErrorWithToken<TcStateAccepted>, VerificationOrSendErrorWithToken<TcStateAccepted>,
@ -1264,7 +1339,7 @@ mod alloc_mod {
.start_success(token, self.sender.as_ref(), time_stamp) .start_success(token, self.sender.as_ref(), time_stamp)
} }
pub fn start_failure( fn start_failure(
&self, &self,
token: VerificationToken<TcStateAccepted>, token: VerificationToken<TcStateAccepted>,
params: FailParams, params: FailParams,
@ -1273,17 +1348,17 @@ mod alloc_mod {
.start_failure(token, self.sender.as_ref(), params) .start_failure(token, self.sender.as_ref(), params)
} }
pub fn step_success( fn step_success(
&self, &self,
token: &VerificationToken<TcStateStarted>, token: &VerificationToken<TcStateStarted>,
time_stamp: Option<&[u8]>, time_stamp: &[u8],
step: impl EcssEnumeration, step: impl EcssEnumeration,
) -> Result<(), EcssTmtcError> { ) -> Result<(), EcssTmtcError> {
self.reporter self.reporter
.step_success(token, self.sender.as_ref(), time_stamp, step) .step_success(token, self.sender.as_ref(), time_stamp, step)
} }
pub fn step_failure( fn step_failure(
&self, &self,
token: VerificationToken<TcStateStarted>, token: VerificationToken<TcStateStarted>,
params: FailParamsWithStep, params: FailParamsWithStep,
@ -1292,16 +1367,16 @@ mod alloc_mod {
.step_failure(token, self.sender.as_ref(), params) .step_failure(token, self.sender.as_ref(), params)
} }
pub fn completion_success<TcState: WasAtLeastAccepted + Copy>( fn completion_success<TcState: WasAtLeastAccepted + Copy>(
&self, &self,
token: VerificationToken<TcState>, token: VerificationToken<TcState>,
time_stamp: Option<&[u8]>, time_stamp: &[u8],
) -> Result<(), VerificationOrSendErrorWithToken<TcState>> { ) -> Result<(), VerificationOrSendErrorWithToken<TcState>> {
self.reporter self.reporter
.completion_success(token, self.sender.as_ref(), time_stamp) .completion_success(token, self.sender.as_ref(), time_stamp)
} }
pub fn completion_failure<TcState: WasAtLeastAccepted + Copy>( fn completion_failure<TcState: WasAtLeastAccepted + Copy>(
&self, &self,
token: VerificationToken<TcState>, token: VerificationToken<TcState>,
params: FailParams, params: FailParams,
@ -1322,7 +1397,7 @@ mod std_mod {
} }
#[cfg(test)] #[cfg(test)]
mod tests { pub mod tests {
use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
use crate::pus::tests::CommonTmInfo; use crate::pus::tests::CommonTmInfo;
use crate::pus::verification::{ use crate::pus::verification::{
@ -1335,6 +1410,8 @@ mod tests {
use crate::ChannelId; use crate::ChannelId;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::format; use alloc::format;
use alloc::sync::Arc;
use hashbrown::HashMap;
use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader}; use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader};
use spacepackets::ecss::tm::PusTmReader; use spacepackets::ecss::tm::PusTmReader;
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, PusError, PusPacket}; use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, PusError, PusPacket};
@ -1342,15 +1419,208 @@ mod tests {
use spacepackets::{ByteConversionError, CcsdsPacket, SpHeader}; use spacepackets::{ByteConversionError, CcsdsPacket, SpHeader};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::mpsc; use std::sync::{mpsc, Mutex};
use std::time::Duration; use std::time::Duration;
use std::vec; use std::vec;
use std::vec::Vec; use std::vec::Vec;
use super::VerificationReportingProvider;
fn is_send<T: Send>(_: &T) {} fn is_send<T: Send>(_: &T) {}
#[allow(dead_code)] #[allow(dead_code)]
fn is_sync<T: Sync>(_: &T) {} fn is_sync<T: Sync>(_: &T) {}
pub struct VerificationStatus {
pub accepted: Option<bool>,
pub started: Option<bool>,
pub step: u64,
pub step_status: Option<bool>,
pub completed: Option<bool>,
pub failure_data: Option<Vec<u8>>,
pub fail_enum: Option<u64>,
}
pub type SharedVerificationMap = Arc<Mutex<RefCell<HashMap<RequestId, VerificationStatus>>>>;
#[derive(Clone)]
pub struct TestVerificationReporter {
pub verification_map: SharedVerificationMap,
}
impl TestVerificationReporter {
pub fn new(verification_map: SharedVerificationMap) -> Self {
Self { verification_map }
}
}
impl VerificationReportingProvider for TestVerificationReporter {
fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken<TcStateNone> {
let verif_map = self.verification_map.lock().unwrap();
verif_map.borrow_mut().insert(
req_id,
VerificationStatus {
accepted: None,
started: None,
step: 0,
step_status: None,
completed: None,
failure_data: None,
fail_enum: None,
},
);
VerificationToken {
state: core::marker::PhantomData,
req_id,
}
}
fn acceptance_success(
&self,
token: VerificationToken<TcStateNone>,
_time_stamp: &[u8],
) -> Result<
VerificationToken<super::TcStateAccepted>,
super::VerificationOrSendErrorWithToken<TcStateNone>,
> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => entry.accepted = Some(true),
None => panic!(
"unexpected acceptance success for request ID {}",
token.req_id()
),
};
Ok(VerificationToken {
state: core::marker::PhantomData,
req_id: token.req_id,
})
}
fn acceptance_failure(
&self,
token: VerificationToken<TcStateNone>,
params: FailParams,
) -> Result<(), super::VerificationOrSendErrorWithToken<TcStateNone>> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => {
entry.accepted = Some(false);
entry.failure_data = Some(params.failure_data.to_vec());
entry.fail_enum = Some(params.failure_code.value());
}
None => panic!(
"unexpected acceptance failure for request ID {}",
token.req_id()
),
};
Ok(())
}
fn start_success(
&self,
token: VerificationToken<super::TcStateAccepted>,
_time_stamp: &[u8],
) -> Result<
VerificationToken<super::TcStateStarted>,
super::VerificationOrSendErrorWithToken<super::TcStateAccepted>,
> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => entry.started = Some(true),
None => panic!("unexpected start success for request ID {}", token.req_id()),
};
Ok(VerificationToken {
state: core::marker::PhantomData,
req_id: token.req_id,
})
}
fn start_failure(
&self,
token: VerificationToken<super::TcStateAccepted>,
params: FailParams,
) -> Result<(), super::VerificationOrSendErrorWithToken<super::TcStateAccepted>> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => {
entry.started = Some(false);
entry.failure_data = Some(params.failure_data.to_vec());
entry.fail_enum = Some(params.failure_code.value());
}
None => panic!("unexpected start failure for request ID {}", token.req_id()),
};
Ok(())
}
fn step_success(
&self,
token: &VerificationToken<super::TcStateStarted>,
_time_stamp: &[u8],
step: impl spacepackets::ecss::EcssEnumeration,
) -> Result<(), EcssTmtcError> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => {
entry.step = step.value();
entry.step_status = Some(true);
}
None => panic!("unexpected start success for request ID {}", token.req_id()),
};
Ok(())
}
fn step_failure(
&self,
token: VerificationToken<super::TcStateStarted>,
_params: FailParamsWithStep,
) -> Result<(), super::VerificationOrSendErrorWithToken<super::TcStateStarted>> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => {
entry.step_status = Some(false);
}
None => panic!("unexpected start success for request ID {}", token.req_id()),
};
Ok(())
}
fn completion_success<TcState: super::WasAtLeastAccepted + Copy>(
&self,
token: VerificationToken<TcState>,
_time_stamp: &[u8],
) -> Result<(), super::VerificationOrSendErrorWithToken<TcState>> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => entry.completed = Some(true),
None => panic!(
"unexpected acceptance success for request ID {}",
token.req_id()
),
};
Ok(())
}
fn completion_failure<TcState: super::WasAtLeastAccepted + Copy>(
&self,
token: VerificationToken<TcState>,
params: FailParams,
) -> Result<(), super::VerificationOrSendErrorWithToken<TcState>> {
let verif_map = self.verification_map.lock().unwrap();
match verif_map.borrow_mut().get_mut(&token.req_id) {
Some(entry) => {
entry.completed = Some(false);
entry.failure_data = Some(params.failure_data.to_vec());
entry.fail_enum = Some(params.failure_code.value());
}
None => panic!(
"unexpected acceptance success for request ID {}",
token.req_id()
),
};
Ok(())
}
}
const TEST_APID: u16 = 0x02; const TEST_APID: u16 = 0x02;
const EMPTY_STAMP: [u8; 7] = [0; 7]; const EMPTY_STAMP: [u8; 7] = [0; 7];
@ -1504,7 +1774,7 @@ mod tests {
fn test_basic_acceptance_success() { fn test_basic_acceptance_success() {
let (b, tok) = base_init(false); let (b, tok) = base_init(false);
let mut sender = TestSender::default(); let mut sender = TestSender::default();
b.vr.acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
acceptance_check(&mut sender, &tok.req_id); acceptance_check(&mut sender, &tok.req_id);
} }
@ -1513,7 +1783,7 @@ mod tests {
fn test_basic_acceptance_success_with_helper() { fn test_basic_acceptance_success_with_helper() {
let (mut b, tok) = base_with_helper_init(); let (mut b, tok) = base_with_helper_init();
b.helper b.helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
acceptance_check(sender, &tok.req_id); acceptance_check(sender, &tok.req_id);
@ -1544,7 +1814,7 @@ mod tests {
let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; let stamp_buf = [1, 2, 3, 4, 5, 6, 7];
let mut sender = TestSender::default(); let mut sender = TestSender::default();
let fail_code = EcssEnumU16::new(2); let fail_code = EcssEnumU16::new(2);
let fail_params = FailParams::new(Some(stamp_buf.as_slice()), &fail_code, None); let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code);
b.vr.acceptance_failure(tok, &sender, fail_params) b.vr.acceptance_failure(tok, &sender, fail_params)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
acceptance_fail_check(&mut sender, tok.req_id, stamp_buf); acceptance_fail_check(&mut sender, tok.req_id, stamp_buf);
@ -1556,7 +1826,7 @@ mod tests {
b.rep().reporter.dest_id = 5; b.rep().reporter.dest_id = 5;
let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; let stamp_buf = [1, 2, 3, 4, 5, 6, 7];
let fail_code = EcssEnumU16::new(2); let fail_code = EcssEnumU16::new(2);
let fail_params = FailParams::new(Some(stamp_buf.as_slice()), &fail_code, None); let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code);
b.helper b.helper
.acceptance_failure(tok, fail_params) .acceptance_failure(tok, fail_params)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
@ -1573,11 +1843,7 @@ mod tests {
let fail_data: [u8; 16] = [0; 16]; let fail_data: [u8; 16] = [0; 16];
// 4 req ID + 1 byte step + 2 byte error code + 8 byte fail data // 4 req ID + 1 byte step + 2 byte error code + 8 byte fail data
assert_eq!(b.rep().allowed_source_data_len(), 15); assert_eq!(b.rep().allowed_source_data_len(), 15);
let fail_params = FailParams::new( let fail_params = FailParams::new(stamp_buf.as_slice(), &fail_code, fail_data.as_slice());
Some(stamp_buf.as_slice()),
&fail_code,
Some(fail_data.as_slice()),
);
let res = b.helper.acceptance_failure(tok, fail_params); let res = b.helper.acceptance_failure(tok, fail_params);
assert!(res.is_err()); assert!(res.is_err());
let err_with_token = res.unwrap_err(); let err_with_token = res.unwrap_err();
@ -1609,11 +1875,7 @@ mod tests {
let fail_data = EcssEnumU32::new(12); let fail_data = EcssEnumU32::new(12);
let mut fail_data_raw = [0; 4]; let mut fail_data_raw = [0; 4];
fail_data.write_to_be_bytes(&mut fail_data_raw).unwrap(); fail_data.write_to_be_bytes(&mut fail_data_raw).unwrap();
let fail_params = FailParams::new( let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice());
Some(&EMPTY_STAMP),
&fail_code,
Some(fail_data_raw.as_slice()),
);
b.vr.acceptance_failure(tok, &sender, fail_params) b.vr.acceptance_failure(tok, &sender, fail_params)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let cmp_info = TmInfo { let cmp_info = TmInfo {
@ -1673,16 +1935,12 @@ mod tests {
let fail_data: i32 = -12; let fail_data: i32 = -12;
let mut fail_data_raw = [0; 4]; let mut fail_data_raw = [0; 4];
fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice());
let fail_params = FailParams::new( let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice());
Some(&EMPTY_STAMP),
&fail_code,
Some(fail_data_raw.as_slice()),
);
let accepted_token = let accepted_token =
b.vr.acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
b.vr.start_failure(accepted_token, &mut sender, fail_params) b.vr.start_failure(accepted_token, &sender, fail_params)
.expect("Start failure failure"); .expect("Start failure failure");
start_fail_check(&mut sender, tok.req_id, fail_data_raw); start_fail_check(&mut sender, tok.req_id, fail_data_raw);
} }
@ -1694,21 +1952,15 @@ mod tests {
let fail_data: i32 = -12; let fail_data: i32 = -12;
let mut fail_data_raw = [0; 4]; let mut fail_data_raw = [0; 4];
fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice());
let fail_params = FailParams::new( let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice());
Some(&EMPTY_STAMP),
&fail_code,
Some(fail_data_raw.as_slice()),
);
let accepted_token = b let accepted_token = b
.helper .helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let empty = b b.helper
.helper
.start_failure(accepted_token, fail_params) .start_failure(accepted_token, fail_params)
.expect("Start failure failure"); .expect("Start failure failure");
assert_eq!(empty, ());
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
start_fail_check(sender, tok.req_id, fail_data_raw); start_fail_check(sender, tok.req_id, fail_data_raw);
} }
@ -1775,27 +2027,17 @@ mod tests {
let mut sender = TestSender::default(); let mut sender = TestSender::default();
let accepted_token = b let accepted_token = b
.rep() .rep()
.acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) .acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = b let started_token = b
.rep() .rep()
.start_success(accepted_token, &sender, Some(&[0, 1, 0, 1, 0, 1, 0])) .start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
b.rep() b.rep()
.step_success( .step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(0))
&started_token,
&sender,
Some(&EMPTY_STAMP),
EcssEnumU8::new(0),
)
.expect("Sending step 0 success failed"); .expect("Sending step 0 success failed");
b.vr.step_success( b.vr.step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(1))
&started_token, .expect("Sending step 1 success failed");
&sender,
Some(&EMPTY_STAMP),
EcssEnumU8::new(1),
)
.expect("Sending step 1 success failed");
assert_eq!(sender.service_queue.borrow().len(), 4); assert_eq!(sender.service_queue.borrow().len(), 4);
step_success_check(&mut sender, tok.req_id); step_success_check(&mut sender, tok.req_id);
} }
@ -1805,17 +2047,17 @@ mod tests {
let (mut b, tok) = base_with_helper_init(); let (mut b, tok) = base_with_helper_init();
let accepted_token = b let accepted_token = b
.helper .helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = b let started_token = b
.helper .helper
.start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
b.helper b.helper
.step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(0)) .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(0))
.expect("Sending step 0 success failed"); .expect("Sending step 0 success failed");
b.helper b.helper
.step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(1)) .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(1))
.expect("Sending step 1 success failed"); .expect("Sending step 1 success failed");
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
assert_eq!(sender.service_queue.borrow().len(), 4); assert_eq!(sender.service_queue.borrow().len(), 4);
@ -1900,31 +2142,22 @@ mod tests {
fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice());
let fail_step = EcssEnumU8::new(1); let fail_step = EcssEnumU8::new(1);
let fail_params = FailParamsWithStep::new( let fail_params = FailParamsWithStep::new(
Some(&EMPTY_STAMP), &EMPTY_STAMP,
&fail_step, &fail_step,
&fail_code, &fail_code,
Some(fail_data_raw.as_slice()), fail_data_raw.as_slice(),
); );
let accepted_token = let accepted_token =
b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = let started_token =
b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
let mut empty = b.vr.step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(0))
b.vr.step_success(
&started_token,
&mut sender,
Some(&EMPTY_STAMP),
EcssEnumU8::new(0),
)
.expect("Sending completion success failed"); .expect("Sending completion success failed");
assert_eq!(empty, ()); b.vr.step_failure(started_token, &sender, fail_params)
empty = .expect("Step failure failed");
b.vr.step_failure(started_token, &mut sender, fail_params)
.expect("Step failure failed");
assert_eq!(empty, ());
check_step_failure(&mut sender, req_id, fail_data_raw); check_step_failure(&mut sender, req_id, fail_data_raw);
} }
@ -1938,30 +2171,26 @@ mod tests {
fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice());
let fail_step = EcssEnumU8::new(1); let fail_step = EcssEnumU8::new(1);
let fail_params = FailParamsWithStep::new( let fail_params = FailParamsWithStep::new(
Some(&EMPTY_STAMP), &EMPTY_STAMP,
&fail_step, &fail_step,
&fail_code, &fail_code,
Some(fail_data_raw.as_slice()), fail_data_raw.as_slice(),
); );
let accepted_token = b let accepted_token = b
.helper .helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = b let started_token = b
.helper .helper
.start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
let mut empty = b b.helper
.helper .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(0))
.step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(0))
.expect("Sending completion success failed"); .expect("Sending completion success failed");
assert_eq!(empty, ()); b.helper
empty = b
.helper
.step_failure(started_token, fail_params) .step_failure(started_token, fail_params)
.expect("Step failure failed"); .expect("Step failure failed");
assert_eq!(empty, ());
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
check_step_failure(sender, req_id, fail_data_raw); check_step_failure(sender, req_id, fail_data_raw);
} }
@ -2018,18 +2247,16 @@ mod tests {
let mut sender = TestSender::default(); let mut sender = TestSender::default();
let req_id = tok.req_id; let req_id = tok.req_id;
let fail_code = EcssEnumU32::new(0x1020); let fail_code = EcssEnumU32::new(0x1020);
let fail_params = FailParams::new(Some(&EMPTY_STAMP), &fail_code, None); let fail_params = FailParams::new_no_fail_data(&EMPTY_STAMP, &fail_code);
let accepted_token = let accepted_token =
b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = let started_token =
b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
let empty = b.vr.completion_failure(started_token, &sender, fail_params)
b.vr.completion_failure(started_token, &mut sender, fail_params) .expect("Completion failure");
.expect("Completion failure");
assert_eq!(empty, ());
completion_fail_check(&mut sender, req_id); completion_fail_check(&mut sender, req_id);
} }
@ -2038,21 +2265,19 @@ mod tests {
let (mut b, tok) = base_with_helper_init(); let (mut b, tok) = base_with_helper_init();
let req_id = tok.req_id; let req_id = tok.req_id;
let fail_code = EcssEnumU32::new(0x1020); let fail_code = EcssEnumU32::new(0x1020);
let fail_params = FailParams::new(Some(&EMPTY_STAMP), &fail_code, None); let fail_params = FailParams::new_no_fail_data(&EMPTY_STAMP, &fail_code);
let accepted_token = b let accepted_token = b
.helper .helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = b let started_token = b
.helper .helper
.start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
let empty = b b.helper
.helper
.completion_failure(started_token, fail_params) .completion_failure(started_token, fail_params)
.expect("Completion failure"); .expect("Completion failure");
assert_eq!(empty, ());
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
completion_fail_check(sender, req_id); completion_fail_check(sender, req_id);
} }
@ -2106,12 +2331,12 @@ mod tests {
let (b, tok) = base_init(false); let (b, tok) = base_init(false);
let mut sender = TestSender::default(); let mut sender = TestSender::default();
let accepted_token = let accepted_token =
b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = let started_token =
b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
b.vr.completion_success(started_token, &mut sender, Some(&EMPTY_STAMP)) b.vr.completion_success(started_token, &sender, &EMPTY_STAMP)
.expect("Sending completion success failed"); .expect("Sending completion success failed");
completion_success_check(&mut sender, tok.req_id); completion_success_check(&mut sender, tok.req_id);
} }
@ -2121,14 +2346,14 @@ mod tests {
let (mut b, tok) = base_with_helper_init(); let (mut b, tok) = base_with_helper_init();
let accepted_token = b let accepted_token = b
.helper .helper
.acceptance_success(tok, Some(&EMPTY_STAMP)) .acceptance_success(tok, &EMPTY_STAMP)
.expect("Sending acceptance success failed"); .expect("Sending acceptance success failed");
let started_token = b let started_token = b
.helper .helper
.start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0])
.expect("Sending start success failed"); .expect("Sending start success failed");
b.helper b.helper
.completion_success(started_token, Some(&EMPTY_STAMP)) .completion_success(started_token, &EMPTY_STAMP)
.expect("Sending completion success failed"); .expect("Sending completion success failed");
let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap();
completion_success_check(sender, tok.req_id); completion_success_check(sender, tok.req_id);
@ -2154,13 +2379,13 @@ mod tests {
// Complete success sequence for a telecommand // Complete success sequence for a telecommand
let accepted_token = reporter let accepted_token = reporter
.acceptance_success(init_token, Some(&EMPTY_STAMP)) .acceptance_success(init_token, &EMPTY_STAMP)
.unwrap(); .unwrap();
let started_token = reporter let started_token = reporter
.start_success(accepted_token, Some(&EMPTY_STAMP)) .start_success(accepted_token, &EMPTY_STAMP)
.unwrap(); .unwrap();
reporter reporter
.completion_success(started_token, Some(&EMPTY_STAMP)) .completion_success(started_token, &EMPTY_STAMP)
.unwrap(); .unwrap();
// Verify it arrives correctly on receiver end // Verify it arrives correctly on receiver end

49
satrs/src/queue.rs Normal file
View File

@ -0,0 +1,49 @@
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
/// 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 {}

View File

@ -1 +1,110 @@
use core::fmt;
#[cfg(feature = "std")]
use std::error::Error;
use spacepackets::{
ecss::{tc::IsPusTelecommand, PusPacket},
ByteConversionError, CcsdsPacket,
};
use crate::TargetId;
pub type Apid = u16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TargetIdCreationError {
ByteConversion(ByteConversionError),
NotEnoughAppData(usize),
}
impl From<ByteConversionError> for TargetIdCreationError {
fn from(e: ByteConversionError) -> Self {
Self::ByteConversion(e)
}
}
impl fmt::Display for TargetIdCreationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ByteConversion(e) => write!(f, "target ID creation: {}", e),
Self::NotEnoughAppData(len) => {
write!(f, "not enough app data to generate target ID: {}", len)
}
}
}
}
#[cfg(feature = "std")]
impl Error for TargetIdCreationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::ByteConversion(e) = self {
return Some(e);
}
None
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct TargetAndApidId {
pub apid: Apid,
pub target: u32,
}
impl TargetAndApidId {
pub fn new(apid: Apid, target: u32) -> Self {
Self { apid, target }
}
pub fn apid(&self) -> Apid {
self.apid
}
pub fn target(&self) -> u32 {
self.target
}
pub fn raw(&self) -> TargetId {
((self.apid as u64) << 32) | (self.target as u64)
}
pub fn target_id(&self) -> TargetId {
self.raw()
}
pub fn from_pus_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()),
})
}
}
impl From<u64> for TargetAndApidId {
fn from(raw: u64) -> Self {
Self {
apid: (raw >> 32) as u16,
target: raw as u32,
}
}
}
impl From<TargetAndApidId> for u64 {
fn from(target_and_apid_id: TargetAndApidId) -> Self {
target_and_apid_id.raw()
}
}
impl fmt::Display for TargetAndApidId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}, {}", self.apid, self.target)
}
}

View File

@ -20,8 +20,6 @@ pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use pus_distrib::{PusDistributor, PusServiceProvider}; pub use pus_distrib::{PusDistributor, PusServiceProvider};
pub type TargetId = u32;
/// Generic trait for object which can receive any telecommands in form of a raw bytestream, with /// Generic trait for object which can receive any telecommands in form of a raw bytestream, with
/// no assumptions about the received protocol. /// no assumptions about the received protocol.
/// ///

View File

@ -1,9 +1,10 @@
//#[cfg(feature = "crossbeam")] #[cfg(feature = "crossbeam")]
pub mod crossbeam_test { pub mod crossbeam_test {
use hashbrown::HashMap; use hashbrown::HashMap;
use satrs::pool::{PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; use satrs::pool::{PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, RequestId, VerificationReporterCfg, VerificationReporterWithSender, FailParams, RequestId, VerificationReporterCfg, VerificationReporterWithSender,
VerificationReportingProvider,
}; };
use satrs::pus::CrossbeamTmInStoreSender; use satrs::pus::CrossbeamTmInStoreSender;
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
@ -89,24 +90,24 @@ pub mod crossbeam_test {
let token = reporter_with_sender_0.add_tc_with_req_id(req_id_0); let token = reporter_with_sender_0.add_tc_with_req_id(req_id_0);
let accepted_token = reporter_with_sender_0 let accepted_token = reporter_with_sender_0
.acceptance_success(token, Some(&FIXED_STAMP)) .acceptance_success(token, &FIXED_STAMP)
.expect("Acceptance success failed"); .expect("Acceptance success failed");
// Do some start handling here // Do some start handling here
let started_token = reporter_with_sender_0 let started_token = reporter_with_sender_0
.start_success(accepted_token, Some(&FIXED_STAMP)) .start_success(accepted_token, &FIXED_STAMP)
.expect("Start success failed"); .expect("Start success failed");
// Do some step handling here // Do some step handling here
reporter_with_sender_0 reporter_with_sender_0
.step_success(&started_token, Some(&FIXED_STAMP), EcssEnumU8::new(0)) .step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(0))
.expect("Start success failed"); .expect("Start success failed");
// Finish up // Finish up
reporter_with_sender_0 reporter_with_sender_0
.step_success(&started_token, Some(&FIXED_STAMP), EcssEnumU8::new(1)) .step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(1))
.expect("Start success failed"); .expect("Start success failed");
reporter_with_sender_0 reporter_with_sender_0
.completion_success(started_token, Some(&FIXED_STAMP)) .completion_success(started_token, &FIXED_STAMP)
.expect("Completion success failed"); .expect("Completion success failed");
}); });
@ -124,13 +125,13 @@ pub mod crossbeam_test {
let (tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap(); let (tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap();
let token = reporter_with_sender_1.add_tc(&tc); let token = reporter_with_sender_1.add_tc(&tc);
let accepted_token = reporter_with_sender_1 let accepted_token = reporter_with_sender_1
.acceptance_success(token, Some(&FIXED_STAMP)) .acceptance_success(token, &FIXED_STAMP)
.expect("Acceptance success failed"); .expect("Acceptance success failed");
let started_token = reporter_with_sender_1 let started_token = reporter_with_sender_1
.start_success(accepted_token, Some(&FIXED_STAMP)) .start_success(accepted_token, &FIXED_STAMP)
.expect("Start success failed"); .expect("Start success failed");
let fail_code = EcssEnumU16::new(2); let fail_code = EcssEnumU16::new(2);
let params = FailParams::new(Some(&FIXED_STAMP), &fail_code, None); let params = FailParams::new_no_fail_data(&FIXED_STAMP, &fail_code);
reporter_with_sender_1 reporter_with_sender_1
.completion_failure(started_token, params) .completion_failure(started_token, params)
.expect("Completion success failed"); .expect("Completion success failed");