35 Commits

Author SHA1 Message Date
f3f928f79f fix docs
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-02-26 11:29:59 +01:00
249eb12bb4 Merge branch 'main' into refactor-verification-mod
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-02-26 11:28:07 +01:00
f21ab0017e Merge pull request 'fixed for scheduler' (#133) from scheduler-fixes into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #133
2024-02-26 11:15:50 +01:00
a7ca00317f cargo fmt
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-26 11:00:48 +01:00
75fda42f4f fixed for scheduler
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-02-26 10:53:33 +01:00
7380a3b24c some doc improvements
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-26 10:46:27 +01:00
97a8035333 that might fix docs 2024-02-26 10:35:12 +01:00
f91075a598 another small fix
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-02-26 10:31:09 +01:00
1d0460a63f import improvement
Some checks failed
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-02-26 10:28:47 +01:00
85b9f6a002 introduce crossbeam module 2024-02-26 10:20:21 +01:00
75c687feed changelog
Some checks failed
Rust/sat-rs/pipeline/head Build started...
Rust/sat-rs/pipeline/pr-main There was a failure building this commit
2024-02-23 17:42:45 +01:00
b5b5e92863 improve docs
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-23 17:41:36 +01:00
0d5f06c589 refactored verification reporter 2024-02-23 17:39:51 +01:00
faf0f6f6c6 Merge pull request 'refactored event manager' (#131) from refactor-event-man into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #131
2024-02-23 14:31:48 +01:00
a690c7720d Refactored event manager
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-23 14:19:30 +01:00
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
68 changed files with 6353 additions and 1533 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
========= =========

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

@ -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,11 +20,11 @@ thiserror = "1"
derive-new = "0.5" derive-new = "0.5"
[dependencies.satrs] [dependencies.satrs]
version = "0.1.1" # version = "0.2.0-rc.0"
# path = "../satrs" path = "../satrs"
[dependencies.satrs-mib] [dependencies.satrs-mib]
version = "0.1.0" version = "0.1.1"
# path = "../satrs-mib" # path = "../satrs-mib"
[features] [features]

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::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,
@ -20,19 +21,19 @@ use crate::{
update_time, update_time,
}; };
pub struct AcsTask { pub struct AcsTask<VerificationReporter: VerificationReportingProvider> {
timestamp: [u8; 7], timestamp: [u8; 7],
time_provider: TimeProvider<DaysLen16Bits>, time_provider: TimeProvider<DaysLen16Bits>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporter,
tm_sender: Box<dyn EcssTmSender>, tm_sender: Box<dyn EcssTmSender>,
request_rx: mpsc::Receiver<RequestWithToken>, request_rx: mpsc::Receiver<RequestWithToken>,
} }
impl AcsTask { impl<VerificationReporter: VerificationReportingProvider> AcsTask<VerificationReporter> {
pub fn new( pub fn new(
tm_sender: impl EcssTmSender, tm_sender: impl EcssTmSender,
request_rx: mpsc::Receiver<RequestWithToken>, request_rx: mpsc::Receiver<RequestWithToken>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporter,
) -> Self { ) -> Self {
Self { Self {
timestamp: [0; 7], timestamp: [0; 7],
@ -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

@ -1,18 +1,17 @@
use std::sync::mpsc::{self, SendError}; use std::sync::mpsc::{self};
use satrs::{ use satrs::{
event_man::{ event_man::{
EventManager, EventManagerWithMpscQueue, MpscEventReceiver, MpscEventU32SendProvider, EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded,
SendEventProvider, MpscEventReceiver,
}, },
events::EventU32, events::EventU32,
params::Params, params::Params,
pus::{ pus::{
event_man::{ event_man::{
DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken, DefaultPusEventU32Dispatcher, EventReporter, EventRequest, EventRequestWithToken,
PusEventDispatcher,
}, },
verification::{TcStateStarted, VerificationReporterWithSender, VerificationToken}, verification::{TcStateStarted, VerificationReportingProvider, VerificationToken},
EcssTmSender, EcssTmSender,
}, },
spacepackets::time::cds::{self, TimeProvider}, spacepackets::time::cds::{self, TimeProvider},
@ -21,38 +20,37 @@ use satrs_example::config::PUS_APID;
use crate::update_time; use crate::update_time;
pub type MpscEventManager = EventManager<SendError<(EventU32, Option<Params>)>>; pub struct PusEventHandler<VerificationReporter: VerificationReportingProvider> {
pub struct PusEventHandler {
event_request_rx: mpsc::Receiver<EventRequestWithToken>, event_request_rx: mpsc::Receiver<EventRequestWithToken>,
pus_event_dispatcher: PusEventDispatcher<(), EventU32>, pus_event_dispatcher: DefaultPusEventU32Dispatcher<()>,
pus_event_man_rx: mpsc::Receiver<(EventU32, Option<Params>)>, pus_event_man_rx: mpsc::Receiver<(EventU32, Option<Params>)>,
tm_sender: Box<dyn EcssTmSender>, tm_sender: Box<dyn EcssTmSender>,
time_provider: TimeProvider, time_provider: TimeProvider,
timestamp: [u8; 7], timestamp: [u8; 7],
verif_handler: VerificationReporterWithSender, verif_handler: VerificationReporter,
} }
/* /*
*/ */
impl PusEventHandler { impl<VerificationReporter: VerificationReportingProvider> PusEventHandler<VerificationReporter> {
pub fn new( pub fn new(
verif_handler: VerificationReporterWithSender, verif_handler: VerificationReporter,
event_manager: &mut MpscEventManager, event_manager: &mut EventManagerWithBoundedMpsc,
event_request_rx: mpsc::Receiver<EventRequestWithToken>, event_request_rx: mpsc::Receiver<EventRequestWithToken>,
tm_sender: impl EcssTmSender, tm_sender: impl EcssTmSender,
) -> Self { ) -> Self {
let (pus_event_man_tx, pus_event_man_rx) = mpsc::channel(); let event_queue_cap = 30;
let (pus_event_man_tx, pus_event_man_rx) = mpsc::sync_channel(event_queue_cap);
// All events sent to the manager are routed to the PUS event manager, which generates PUS event // All events sent to the manager are routed to the PUS event manager, which generates PUS event
// telemetry for each event. // telemetry for each event.
let event_reporter = EventReporter::new(PUS_APID, 128).unwrap(); let event_reporter = EventReporter::new(PUS_APID, 128).unwrap();
let pus_tm_backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
let pus_event_dispatcher = let pus_event_dispatcher =
PusEventDispatcher::new(event_reporter, Box::new(pus_tm_backend)); DefaultPusEventU32Dispatcher::new_with_default_backend(event_reporter);
let pus_event_man_send_provider = MpscEventU32SendProvider::new(1, pus_event_man_tx); let pus_event_man_send_provider =
EventU32SenderMpscBounded::new(1, pus_event_man_tx, event_queue_cap);
event_manager.subscribe_all(pus_event_man_send_provider.id()); event_manager.subscribe_all(pus_event_man_send_provider.channel_id());
event_manager.add_sender(pus_event_man_send_provider); event_manager.add_sender(pus_event_man_send_provider);
Self { Self {
@ -73,7 +71,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
@ -114,7 +112,7 @@ impl PusEventHandler {
} }
pub struct EventManagerWrapper { pub struct EventManagerWrapper {
event_manager: MpscEventManager, event_manager: EventManagerWithBoundedMpsc,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>, event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
} }
@ -125,7 +123,7 @@ impl EventManagerWrapper {
let (event_sender, event_man_rx) = mpsc::channel(); let (event_sender, event_man_rx) = mpsc::channel();
let event_recv = MpscEventReceiver::<EventU32>::new(event_man_rx); let event_recv = MpscEventReceiver::<EventU32>::new(event_man_rx);
Self { Self {
event_manager: EventManagerWithMpscQueue::new(Box::new(event_recv)), event_manager: EventManagerWithBoundedMpsc::new(event_recv),
event_sender, event_sender,
} }
} }
@ -134,7 +132,7 @@ impl EventManagerWrapper {
self.event_sender.clone() self.event_sender.clone()
} }
pub fn event_manager(&mut self) -> &mut MpscEventManager { pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
&mut self.event_manager &mut self.event_manager
} }
@ -146,15 +144,15 @@ impl EventManagerWrapper {
} }
} }
pub struct EventHandler { pub struct EventHandler<VerificationReporter: VerificationReportingProvider> {
pub event_man_wrapper: EventManagerWrapper, pub event_man_wrapper: EventManagerWrapper,
pub pus_event_handler: PusEventHandler, pub pus_event_handler: PusEventHandler<VerificationReporter>,
} }
impl EventHandler { impl<VerificationReporter: VerificationReportingProvider> EventHandler<VerificationReporter> {
pub fn new( pub fn new(
tm_sender: impl EcssTmSender, tm_sender: impl EcssTmSender,
verif_handler: VerificationReporterWithSender, verif_handler: VerificationReporter,
event_request_rx: mpsc::Receiver<EventRequestWithToken>, event_request_rx: mpsc::Receiver<EventRequestWithToken>,
) -> Self { ) -> Self {
let mut event_man_wrapper = EventManagerWrapper::new(); let mut event_man_wrapper = EventManagerWrapper::new();
@ -175,7 +173,7 @@ impl EventHandler {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn event_manager(&mut self) -> &mut MpscEventManager { pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
self.event_man_wrapper.event_manager() self.event_man_wrapper.event_manager()
} }

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,
@ -43,23 +44,23 @@ use crate::tmtc::{
use crate::udp::{StaticUdpTmHandler, UdpTmtcServer}; use crate::udp::{StaticUdpTmHandler, UdpTmtcServer};
use satrs::pus::event_man::EventRequestWithToken; 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, TmAsVecSenderWithId, TmInSharedPoolSenderWithId};
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};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
fn create_verification_reporter(verif_sender: impl EcssTmSender) -> VerificationReporterWithSender { fn create_verification_reporter<Sender: EcssTmSender + Clone>(
verif_sender: Sender,
) -> VerificationReporterWithSender<Sender> {
let verif_cfg = VerificationReporterCfg::new(PUS_APID, 1, 2, 8).unwrap(); let verif_cfg = VerificationReporterCfg::new(PUS_APID, 1, 2, 8).unwrap();
// Every software component which needs to generate verification telemetry, gets a cloned // Every software component which needs to generate verification telemetry, gets a cloned
// verification reporter. // verification reporter.
VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender)) VerificationReporterWithSender::new(&verif_cfg, verif_sender)
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -69,24 +70,24 @@ fn static_tmtc_pool_main() {
let shared_tc_pool = SharedTcPool { let shared_tc_pool = SharedTcPool {
pool: Arc::new(RwLock::new(tc_pool)), pool: Arc::new(RwLock::new(tc_pool)),
}; };
let (tc_source_tx, tc_source_rx) = channel(); let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
let (tm_funnel_tx, tm_funnel_rx) = channel(); let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(50);
let (tm_server_tx, tm_server_rx) = channel(); let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
// Every software component which needs to generate verification telemetry, receives a cloned // Every software component which needs to generate verification telemetry, receives a cloned
// verification reporter. // verification reporter.
let verif_reporter = create_verification_reporter(MpscTmInSharedPoolSender::new( let verif_reporter = create_verification_reporter(TmInSharedPoolSenderWithId::new(
TmSenderId::PusVerification as ChannelId, TmSenderId::PusVerification as ChannelId,
"verif_sender", "verif_sender",
shared_tm_pool.clone(), shared_tm_pool.clone(),
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.
@ -103,7 +104,7 @@ fn static_tmtc_pool_main() {
// The event task is the core handler to perform the event routing and TM handling as specified // The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation. // in the sat-rs documentation.
let mut event_handler = EventHandler::new( let mut event_handler = EventHandler::new(
MpscTmInSharedPoolSender::new( TmInSharedPoolSenderWithId::new(
TmSenderId::AllEvents as ChannelId, TmSenderId::AllEvents as ChannelId,
"ALL_EVENTS_TX", "ALL_EVENTS_TX",
shared_tm_pool.clone(), shared_tm_pool.clone(),
@ -203,7 +204,7 @@ fn static_tmtc_pool_main() {
.expect("tcp server creation failed"); .expect("tcp server creation failed");
let mut acs_task = AcsTask::new( let mut acs_task = AcsTask::new(
MpscTmInSharedPoolSender::new( TmInSharedPoolSenderWithId::new(
TmSenderId::AcsSubsystem as ChannelId, TmSenderId::AcsSubsystem as ChannelId,
"ACS_TASK_SENDER", "ACS_TASK_SENDER",
shared_tm_pool.clone(), shared_tm_pool.clone(),
@ -304,17 +305,17 @@ fn dyn_tmtc_pool_main() {
let (tm_server_tx, tm_server_rx) = channel(); let (tm_server_tx, tm_server_rx) = channel();
// Every software component which needs to generate verification telemetry, gets a cloned // Every software component which needs to generate verification telemetry, gets a cloned
// verification reporter. // verification reporter.
let verif_reporter = create_verification_reporter(MpscTmAsVecSender::new( let verif_reporter = create_verification_reporter(TmAsVecSenderWithId::new(
TmSenderId::PusVerification as ChannelId, TmSenderId::PusVerification as ChannelId,
"verif_sender", "verif_sender",
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);
@ -325,7 +326,7 @@ fn dyn_tmtc_pool_main() {
// The event task is the core handler to perform the event routing and TM handling as specified // The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation. // in the sat-rs documentation.
let mut event_handler = EventHandler::new( let mut event_handler = EventHandler::new(
MpscTmAsVecSender::new( TmAsVecSenderWithId::new(
TmSenderId::AllEvents as ChannelId, TmSenderId::AllEvents as ChannelId,
"ALL_EVENTS_TX", "ALL_EVENTS_TX",
tm_funnel_tx.clone(), tm_funnel_tx.clone(),
@ -416,7 +417,7 @@ fn dyn_tmtc_pool_main() {
.expect("tcp server creation failed"); .expect("tcp server creation failed");
let mut acs_task = AcsTask::new( let mut acs_task = AcsTask::new(
MpscTmAsVecSender::new( TmAsVecSenderWithId::new(
TmSenderId::AcsSubsystem as ChannelId, TmSenderId::AcsSubsystem as ChannelId,
"ACS_TASK_SENDER", "ACS_TASK_SENDER",
tm_funnel_tx.clone(), tm_funnel_tx.clone(),

View File

@ -1,32 +1,89 @@
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::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationToken, FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, MpscTcReceiver, PusPacketHandlerResult, PusPacketHandlingError, PusServiceHelper,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper, TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
}; };
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,
tm_funnel_tx: mpsc::Sender<StoreAddr>, tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
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, VerificationReporterWithSharedPoolMpscBoundedSender>
let action_srv_tm_sender = MpscTmInSharedPoolSender::new( {
let action_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusAction as ChannelId, TmSenderId::PusAction as ChannelId,
"PUS_8_TM_SENDER", "PUS_8_TM_SENDER",
shared_tm_store.clone(), shared_tm_store.clone(),
@ -38,23 +95,27 @@ 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 }
} }
pub fn create_action_service_dynamic( 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: VerificationReporterWithVecMpscSender,
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, VerificationReporterWithVecMpscSender> {
let action_srv_tm_sender = MpscTmAsVecSender::new( let action_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusAction as ChannelId, TmSenderId::PusAction as ChannelId,
"PUS_8_TM_SENDER", "PUS_8_TM_SENDER",
tm_funnel_tx.clone(), tm_funnel_tx.clone(),
@ -65,149 +126,38 @@ 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> { pub struct Pus8Wrapper<
service_helper: PusServiceHelper<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>, VerificationReporter: VerificationReportingProvider,
> {
pub(crate) pus_8_handler: PusService8ActionHandler<
TcInMemConverter,
VerificationReporter,
ExampleActionRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<8>,
>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusService8ActionHandler<TcInMemConverter> { impl<
pub fn new( TcInMemConverter: EcssTcInMemConverter,
tc_receiver: Box<dyn EcssTcReceiver>, VerificationReporter: VerificationReportingProvider,
tm_sender: Box<dyn EcssTmSender>, > Pus8Wrapper<TcInMemConverter, VerificationReporter>
tm_apid: u16, {
verification_handler: VerificationReporterWithSender,
tc_in_mem_converter: TcInMemConverter,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
) -> Self {
Self {
service_helper: PusServiceHelper::new(
tc_receiver,
tm_sender,
tm_apid,
verification_handler,
tc_in_mem_converter,
),
request_handlers,
}
}
fn handle_action_request_with_id(
&self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
) -> Result<(), PusPacketHandlingError> {
let user_data = tc.user_data();
if user_data.len() < 8 {
self.service_helper
.common
.verification_handler
.borrow_mut()
.start_failure(
token,
FailParams::new(Some(time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
)
.expect("Sending start failure failed");
return Err(PusPacketHandlingError::NotEnoughAppData(
"Expected at least 4 bytes".into(),
));
}
//let target_id = u32::from_be_bytes(user_data[0..4].try_into().unwrap());
let target_id = TargetIdWithApid::from_tc(tc).unwrap();
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
if let Some(sender) = self.request_handlers.get(&target_id) {
sender
.send(RequestWithToken::new(
target_id,
Request::Action(ActionRequest::CmdWithU32Id((
action_id,
Vec::from(&user_data[8..]),
))),
token,
))
.expect("Forwarding action request failed");
} else {
let mut fail_data: [u8; 4] = [0; 4];
fail_data.copy_from_slice(&target_id.target.to_be_bytes());
self.service_helper
.common
.verification_handler
.borrow_mut()
.start_failure(
token,
FailParams::new(
Some(time_stamp),
&tmtc_err::UNKNOWN_TARGET_ID,
Some(&fail_data),
),
)
.expect("Sending start failure failed");
return Err(PusPacketHandlingError::Other(format!(
"Unknown target ID {target_id}"
)));
}
Ok(())
}
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
self.service_helper
.tc_in_mem_converter
.cache_ecss_tc_in_memory(&ecss_tc_and_token.tc_in_memory)?;
let tc = PusTcReader::new(self.service_helper.tc_in_mem_converter.tc_slice_raw())?.0;
let subservice = tc.subservice();
let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
match subservice {
128 => {
self.handle_action_request_with_id(ecss_tc_and_token.token, &tc, &time_stamp)?;
}
_ => {
let fail_data = [subservice];
self.service_helper
.common
.verification_handler
.get_mut()
.start_failure(
ecss_tc_and_token.token,
FailParams::new(
Some(&time_stamp),
&tmtc_err::INVALID_PUS_SUBSERVICE,
Some(&fail_data),
),
)
.expect("Sending start failure failed");
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
}
}
if let Some(partial_error) = partial_error {
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
partial_error,
));
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
pub struct Pus8Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub(crate) pus_8_handler: PusService8ActionHandler<TcInMemConverter>,
}
impl<TcInMemConverter: EcssTcInMemConverter> Pus8Wrapper<TcInMemConverter> {
pub fn handle_next_packet(&mut self) -> bool { pub fn handle_next_packet(&mut self) -> bool {
match self.pus_8_handler.handle_one_tc() { match self.pus_8_handler.handle_one_tc() {
Ok(result) => match result { Ok(result) => match result {

View File

@ -4,11 +4,14 @@ use log::{error, warn};
use satrs::pool::{SharedStaticMemoryPool, StoreAddr}; use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::event_man::EventRequestWithToken; use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::event_srv::PusService5EventHandler; use satrs::pus::event_srv::PusService5EventHandler;
use satrs::pus::verification::VerificationReporterWithSender; use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::VerificationReportingProvider;
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult, MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId,
PusServiceHelper, TmInSharedPoolSenderWithId,
}; };
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId; use satrs::ChannelId;
@ -16,13 +19,14 @@ use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
pub fn create_event_service_static( pub fn create_event_service_static(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>, tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tc_pool: SharedStaticMemoryPool, tc_pool: SharedStaticMemoryPool,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>, pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>, event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> Pus5Wrapper<EcssTcInSharedStoreConverter> { ) -> Pus5Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
let event_srv_tm_sender = MpscTmInSharedPoolSender::new( {
let event_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusEvent as ChannelId, TmSenderId::PusEvent as ChannelId,
"PUS_5_TM_SENDER", "PUS_5_TM_SENDER",
shared_tm_store.clone(), shared_tm_store.clone(),
@ -48,11 +52,11 @@ pub fn create_event_service_static(
pub fn create_event_service_dynamic( pub fn create_event_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>, tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithVecMpscSender,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>, pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>, event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> Pus5Wrapper<EcssTcInVecConverter> { ) -> Pus5Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
let event_srv_tm_sender = MpscTmAsVecSender::new( let event_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusEvent as ChannelId, TmSenderId::PusEvent as ChannelId,
"PUS_5_TM_SENDER", "PUS_5_TM_SENDER",
tm_funnel_tx, tm_funnel_tx,
@ -75,11 +79,18 @@ pub fn create_event_service_dynamic(
Pus5Wrapper { pus_5_handler } Pus5Wrapper { pus_5_handler }
} }
pub struct Pus5Wrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Pus5Wrapper<
pub pus_5_handler: PusService5EventHandler<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus_5_handler: PusService5EventHandler<TcInMemConverter, VerificationReporter>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Pus5Wrapper<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus5Wrapper<TcInMemConverter, VerificationReporter>
{
pub fn handle_next_packet(&mut self) -> bool { pub fn handle_next_packet(&mut self) -> bool {
match self.pus_5_handler.handle_one_tc() { match self.pus_5_handler.handle_one_tc() {
Ok(result) => match result { Ok(result) => match result {

View File

@ -1,32 +1,158 @@
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::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::{ use satrs::pus::verification::{
FailParams, StdVerifReporterWithSender, VerificationReporterWithSender, FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
}; };
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, MpscTcReceiver, PusPacketHandlerResult, PusPacketHandlingError, PusServiceHelper,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper, TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
}; };
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,
tm_funnel_tx: mpsc::Sender<StoreAddr>, tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
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, VerificationReporterWithSharedPoolMpscBoundedSender>
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new( {
let hk_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusHk as ChannelId, TmSenderId::PusHk as ChannelId,
"PUS_3_TM_SENDER", "PUS_3_TM_SENDER",
shared_tm_store.clone(), shared_tm_store.clone(),
@ -35,23 +161,27 @@ 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 }
} }
pub fn create_hk_service_dynamic( 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: VerificationReporterWithVecMpscSender,
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, VerificationReporterWithVecMpscSender> {
let hk_srv_tm_sender = MpscTmAsVecSender::new( let hk_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusHk as ChannelId, TmSenderId::PusHk as ChannelId,
"PUS_3_TM_SENDER", "PUS_3_TM_SENDER",
tm_funnel_tx.clone(), tm_funnel_tx.clone(),
@ -59,157 +189,38 @@ 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> { pub struct Pus3Wrapper<
psb: PusServiceHelper<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>, VerificationReporter: VerificationReportingProvider,
> {
pub(crate) pus_3_handler: PusService3HkHandler<
TcInMemConverter,
VerificationReporter,
ExampleHkRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<3>,
>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusService3HkHandler<TcInMemConverter> { impl<
pub fn new( TcInMemConverter: EcssTcInMemConverter,
tc_receiver: Box<dyn EcssTcReceiver>, VerificationReporter: VerificationReportingProvider,
tm_sender: Box<dyn EcssTmSender>, > Pus3Wrapper<TcInMemConverter, VerificationReporter>
tm_apid: u16, {
verification_handler: StdVerifReporterWithSender,
tc_in_mem_converter: TcInMemConverter,
request_handlers: HashMap<TargetIdWithApid, Sender<RequestWithToken>>,
) -> Self {
Self {
psb: PusServiceHelper::new(
tc_receiver,
tm_sender,
tm_apid,
verification_handler,
tc_in_mem_converter,
),
request_handlers,
}
}
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.psb.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.psb
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let subservice = tc.subservice();
let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
let user_data = tc.user_data();
if user_data.is_empty() {
self.psb
.common
.verification_handler
.borrow_mut()
.start_failure(
ecss_tc_and_token.token,
FailParams::new(Some(&time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData(
"Expected at least 8 bytes of app data".into(),
));
}
if user_data.len() < 8 {
let err = if user_data.len() < 4 {
&hk_err::TARGET_ID_MISSING
} else {
&hk_err::UNIQUE_ID_MISSING
};
self.psb
.common
.verification_handler
.borrow_mut()
.start_failure(
ecss_tc_and_token.token,
FailParams::new(Some(&time_stamp), err, None),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData(
"Expected at least 8 bytes of app data".into(),
));
}
let target_id = TargetIdWithApid::from_tc(&tc).expect("invalid tc format");
let unique_id = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
if !self.request_handlers.contains_key(&target_id) {
self.psb
.common
.verification_handler
.borrow_mut()
.start_failure(
ecss_tc_and_token.token,
FailParams::new(Some(&time_stamp), &hk_err::UNKNOWN_TARGET_ID, None),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData(format!(
"Unknown target ID {target_id}"
)));
}
let send_request = |target: TargetIdWithApid, request: HkRequest| {
let sender = self.request_handlers.get(&target).unwrap();
sender
.send(RequestWithToken::new(
target,
Request::Hk(request),
ecss_tc_and_token.token,
))
.unwrap_or_else(|_| panic!("Sending HK request {request:?} failed"));
};
if subservice == hk::Subservice::TcEnableHkGeneration as u8 {
send_request(target_id, HkRequest::Enable(unique_id));
} else if subservice == hk::Subservice::TcDisableHkGeneration as u8 {
send_request(target_id, HkRequest::Disable(unique_id));
} else if subservice == hk::Subservice::TcGenerateOneShotHk as u8 {
send_request(target_id, HkRequest::OneShot(unique_id));
} else if subservice == hk::Subservice::TcModifyHkCollectionInterval as u8 {
if user_data.len() < 12 {
self.psb
.common
.verification_handler
.borrow_mut()
.start_failure(
ecss_tc_and_token.token,
FailParams::new(
Some(&time_stamp),
&hk_err::COLLECTION_INTERVAL_MISSING,
None,
),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData(
"Collection interval missing".into(),
));
}
send_request(
target_id,
HkRequest::ModifyCollectionInterval(
unique_id,
CollectionIntervalFactor::from_be_bytes(user_data[8..12].try_into().unwrap()),
),
);
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
pub struct Pus3Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub(crate) pus_3_handler: PusService3HkHandler<TcInMemConverter>,
}
impl<TcInMemConverter: EcssTcInMemConverter> Pus3Wrapper<TcInMemConverter> {
pub fn handle_next_packet(&mut self) -> bool { pub fn handle_next_packet(&mut self) -> bool {
match self.pus_3_handler.handle_one_tc() { match self.pus_3_handler.handle_one_tc() {
Ok(result) => match result { Ok(result) => match result {

View File

@ -1,7 +1,9 @@
use crate::tmtc::MpscStoreAndSendError; use crate::tmtc::MpscStoreAndSendError;
use log::warn; use log::warn;
use satrs::pus::verification::{FailParams, StdVerifReporterWithSender}; use satrs::pus::verification::{FailParams, VerificationReportingProvider};
use satrs::pus::{EcssTcAndToken, PusPacketHandlerResult, TcInMemory}; 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;
@ -24,8 +26,8 @@ pub struct PusTcMpscRouter {
pub action_service_receiver: Sender<EcssTcAndToken>, pub action_service_receiver: Sender<EcssTcAndToken>,
} }
pub struct PusReceiver { pub struct PusReceiver<VerificationReporter: VerificationReportingProvider> {
pub verif_reporter: StdVerifReporterWithSender, pub verif_reporter: VerificationReporter,
pub pus_router: PusTcMpscRouter, pub pus_router: PusTcMpscRouter,
stamp_helper: TimeStampHelper, stamp_helper: TimeStampHelper,
} }
@ -57,8 +59,8 @@ impl TimeStampHelper {
} }
} }
impl PusReceiver { impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
pub fn new(verif_reporter: StdVerifReporterWithSender, pus_router: PusTcMpscRouter) -> Self { pub fn new(verif_reporter: VerificationReporter, pus_router: PusTcMpscRouter) -> Self {
Self { Self {
verif_reporter, verif_reporter,
pus_router, pus_router,
@ -67,7 +69,7 @@ impl PusReceiver {
} }
} }
impl PusReceiver { impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
pub fn handle_tc_packet( pub fn handle_tc_packet(
&mut self, &mut self,
tc_in_memory: TcInMemory, tc_in_memory: TcInMemory,
@ -78,7 +80,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 +117,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 +141,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 +153,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

@ -5,11 +5,14 @@ use log::{error, info, warn};
use satrs::pool::{PoolProvider, StaticMemoryPool, StoreAddr}; use satrs::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
use satrs::pus::scheduler::{PusScheduler, TcInfo}; use satrs::pus::scheduler::{PusScheduler, TcInfo};
use satrs::pus::scheduler_srv::PusService11SchedHandler; use satrs::pus::scheduler_srv::PusService11SchedHandler;
use satrs::pus::verification::VerificationReporterWithSender; use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::VerificationReportingProvider;
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult, MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId,
PusServiceHelper, TmInSharedPoolSenderWithId,
}; };
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId; use satrs::ChannelId;
@ -51,14 +54,22 @@ impl TcReleaser for mpsc::Sender<Vec<u8>> {
} }
} }
pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Pus11Wrapper<
pub pus_11_handler: PusService11SchedHandler<TcInMemConverter, PusScheduler>, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus_11_handler:
PusService11SchedHandler<TcInMemConverter, VerificationReporter, PusScheduler>,
pub sched_tc_pool: StaticMemoryPool, pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096], pub releaser_buf: [u8; 4096],
pub tc_releaser: Box<dyn TcReleaser + Send>, pub tc_releaser: Box<dyn TcReleaser + Send>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus11Wrapper<TcInMemConverter, VerificationReporter>
{
pub fn release_tcs(&mut self) { pub fn release_tcs(&mut self) {
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool { let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
self.tc_releaser.release(enabled, info, tc) self.tc_releaser.release(enabled, info, tc)
@ -109,13 +120,14 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
pub fn create_scheduler_service_static( pub fn create_scheduler_service_static(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>, tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tc_releaser: PusTcSourceProviderSharedPool, tc_releaser: PusTcSourceProviderSharedPool,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>, pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool, sched_tc_pool: StaticMemoryPool,
) -> Pus11Wrapper<EcssTcInSharedStoreConverter> { ) -> Pus11Wrapper<EcssTcInSharedStoreConverter, VerificationReporterWithSharedPoolMpscBoundedSender>
let sched_srv_tm_sender = MpscTmInSharedPoolSender::new( {
let sched_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusSched as ChannelId, TmSenderId::PusSched as ChannelId,
"PUS_11_TM_SENDER", "PUS_11_TM_SENDER",
shared_tm_store.clone(), shared_tm_store.clone(),
@ -148,12 +160,12 @@ pub fn create_scheduler_service_static(
pub fn create_scheduler_service_dynamic( pub fn create_scheduler_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>, tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithVecMpscSender,
tc_source_sender: mpsc::Sender<Vec<u8>>, tc_source_sender: mpsc::Sender<Vec<u8>>,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>, pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool, sched_tc_pool: StaticMemoryPool,
) -> Pus11Wrapper<EcssTcInVecConverter> { ) -> Pus11Wrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
let sched_srv_tm_sender = MpscTmAsVecSender::new( let sched_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusSched as ChannelId, TmSenderId::PusSched as ChannelId,
"PUS_11_TM_SENDER", "PUS_11_TM_SENDER",
tm_funnel_tx, tm_funnel_tx,

View File

@ -1,25 +1,32 @@
use satrs::pus::EcssTcInMemConverter; use satrs::pus::{verification::VerificationReportingProvider, EcssTcInMemConverter};
use super::{ use super::{
action::Pus8Wrapper, event::Pus5Wrapper, hk::Pus3Wrapper, scheduler::Pus11Wrapper, action::Pus8Wrapper, event::Pus5Wrapper, hk::Pus3Wrapper, scheduler::Pus11Wrapper,
test::Service17CustomWrapper, test::Service17CustomWrapper,
}; };
pub struct PusStack<TcInMemConverter: EcssTcInMemConverter> { pub struct PusStack<
event_srv: Pus5Wrapper<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
hk_srv: Pus3Wrapper<TcInMemConverter>, VerificationReporter: VerificationReportingProvider,
action_srv: Pus8Wrapper<TcInMemConverter>, > {
schedule_srv: Pus11Wrapper<TcInMemConverter>, event_srv: Pus5Wrapper<TcInMemConverter, VerificationReporter>,
test_srv: Service17CustomWrapper<TcInMemConverter>, hk_srv: Pus3Wrapper<TcInMemConverter, VerificationReporter>,
action_srv: Pus8Wrapper<TcInMemConverter, VerificationReporter>,
schedule_srv: Pus11Wrapper<TcInMemConverter, VerificationReporter>,
test_srv: Service17CustomWrapper<TcInMemConverter, VerificationReporter>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> PusStack<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusStack<TcInMemConverter, VerificationReporter>
{
pub fn new( pub fn new(
hk_srv: Pus3Wrapper<TcInMemConverter>, hk_srv: Pus3Wrapper<TcInMemConverter, VerificationReporter>,
event_srv: Pus5Wrapper<TcInMemConverter>, event_srv: Pus5Wrapper<TcInMemConverter, VerificationReporter>,
action_srv: Pus8Wrapper<TcInMemConverter>, action_srv: Pus8Wrapper<TcInMemConverter, VerificationReporter>,
schedule_srv: Pus11Wrapper<TcInMemConverter>, schedule_srv: Pus11Wrapper<TcInMemConverter, VerificationReporter>,
test_srv: Service17CustomWrapper<TcInMemConverter>, test_srv: Service17CustomWrapper<TcInMemConverter, VerificationReporter>,
) -> Self { ) -> Self {
Self { Self {
event_srv, event_srv,

View File

@ -2,10 +2,13 @@ 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, VerificationReportingProvider};
use satrs::pus::verification::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::{ use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver,
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper, PusPacketHandlerResult, PusServiceHelper, TmAsVecSenderWithId, TmInSharedPoolSenderWithId,
}; };
use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket; use satrs::spacepackets::ecss::PusPacket;
@ -19,13 +22,16 @@ use std::sync::mpsc::{self, Sender};
pub fn create_test_service_static( pub fn create_test_service_static(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>, tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tc_pool: SharedStaticMemoryPool, tc_pool: SharedStaticMemoryPool,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>, event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>, pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> Service17CustomWrapper<EcssTcInSharedStoreConverter> { ) -> Service17CustomWrapper<
let test_srv_tm_sender = MpscTmInSharedPoolSender::new( EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let test_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusTest as ChannelId, TmSenderId::PusTest as ChannelId,
"PUS_17_TM_SENDER", "PUS_17_TM_SENDER",
shared_tm_store.clone(), shared_tm_store.clone(),
@ -51,11 +57,11 @@ pub fn create_test_service_static(
pub fn create_test_service_dynamic( pub fn create_test_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>, tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithSender, verif_reporter: VerificationReporterWithVecMpscSender,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>, event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>, pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> Service17CustomWrapper<EcssTcInVecConverter> { ) -> Service17CustomWrapper<EcssTcInVecConverter, VerificationReporterWithVecMpscSender> {
let test_srv_tm_sender = MpscTmAsVecSender::new( let test_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusTest as ChannelId, TmSenderId::PusTest as ChannelId,
"PUS_17_TM_SENDER", "PUS_17_TM_SENDER",
tm_funnel_tx.clone(), tm_funnel_tx.clone(),
@ -78,12 +84,19 @@ pub fn create_test_service_dynamic(
} }
} }
pub struct Service17CustomWrapper<TcInMemConverter: EcssTcInMemConverter> { pub struct Service17CustomWrapper<
pub pus17_handler: PusService17TestHandler<TcInMemConverter>, TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus17_handler: PusService17TestHandler<TcInMemConverter, VerificationReporter>,
pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>, pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>,
} }
impl<TcInMemConverter: EcssTcInMemConverter> Service17CustomWrapper<TcInMemConverter> { impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Service17CustomWrapper<TcInMemConverter, VerificationReporter>
{
pub fn handle_next_packet(&mut self) -> bool { pub fn handle_next_packet(&mut self) -> bool {
let res = self.pus17_handler.handle_one_tc(); let res = self.pus17_handler.handle_one_tc();
if res.is_err() { if res.is_err() {
@ -125,15 +138,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 +152,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

@ -1,6 +1,6 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::mpsc::{Receiver, Sender}, sync::mpsc::{self},
}; };
use log::info; use log::info;
@ -77,16 +77,16 @@ impl TmFunnelCommon {
pub struct TmFunnelStatic { pub struct TmFunnelStatic {
common: TmFunnelCommon, common: TmFunnelCommon,
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
tm_funnel_rx: Receiver<StoreAddr>, tm_funnel_rx: mpsc::Receiver<StoreAddr>,
tm_server_tx: Sender<StoreAddr>, tm_server_tx: mpsc::SyncSender<StoreAddr>,
} }
impl TmFunnelStatic { impl TmFunnelStatic {
pub fn new( pub fn new(
shared_tm_store: SharedTmPool, shared_tm_store: SharedTmPool,
sync_tm_tcp_source: SyncTcpTmSource, sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: Receiver<StoreAddr>, tm_funnel_rx: mpsc::Receiver<StoreAddr>,
tm_server_tx: Sender<StoreAddr>, tm_server_tx: mpsc::SyncSender<StoreAddr>,
) -> Self { ) -> Self {
Self { Self {
common: TmFunnelCommon::new(sync_tm_tcp_source), common: TmFunnelCommon::new(sync_tm_tcp_source),
@ -123,15 +123,15 @@ impl TmFunnelStatic {
pub struct TmFunnelDynamic { pub struct TmFunnelDynamic {
common: TmFunnelCommon, common: TmFunnelCommon,
tm_funnel_rx: Receiver<Vec<u8>>, tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
tm_server_tx: Sender<Vec<u8>>, tm_server_tx: mpsc::Sender<Vec<u8>>,
} }
impl TmFunnelDynamic { impl TmFunnelDynamic {
pub fn new( pub fn new(
sync_tm_tcp_source: SyncTcpTmSource, sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: Receiver<Vec<u8>>, tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
tm_server_tx: Sender<Vec<u8>>, tm_server_tx: mpsc::Sender<Vec<u8>>,
) -> Self { ) -> Self {
Self { Self {
common: TmFunnelCommon::new(sync_tm_tcp_source), common: TmFunnelCommon::new(sync_tm_tcp_source),

View File

@ -1,7 +1,10 @@
use log::warn; use log::warn;
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::{EcssTcAndToken, ReceivesEcssPusTc}; use satrs::pus::{EcssTcAndToken, ReceivesEcssPusTc};
use satrs::spacepackets::SpHeader; use satrs::spacepackets::SpHeader;
use std::sync::mpsc::{self, Receiver, SendError, Sender, TryRecvError}; use std::sync::mpsc::{self, Receiver, SendError, Sender, SyncSender, TryRecvError};
use thiserror::Error; use thiserror::Error;
use crate::pus::PusReceiver; use crate::pus::PusReceiver;
@ -37,7 +40,7 @@ impl SharedTcPool {
#[derive(Clone)] #[derive(Clone)]
pub struct PusTcSourceProviderSharedPool { pub struct PusTcSourceProviderSharedPool {
pub tc_source: Sender<StoreAddr>, pub tc_source: SyncSender<StoreAddr>,
pub shared_pool: SharedTcPool, pub shared_pool: SharedTcPool,
} }
@ -97,14 +100,14 @@ pub struct TcSourceTaskStatic {
shared_tc_pool: SharedTcPool, shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>, tc_receiver: Receiver<StoreAddr>,
tc_buf: [u8; 4096], tc_buf: [u8; 4096],
pus_receiver: PusReceiver, pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
} }
impl TcSourceTaskStatic { impl TcSourceTaskStatic {
pub fn new( pub fn new(
shared_tc_pool: SharedTcPool, shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>, tc_receiver: Receiver<StoreAddr>,
pus_receiver: PusReceiver, pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
) -> Self { ) -> Self {
Self { Self {
shared_tc_pool, shared_tc_pool,
@ -161,11 +164,14 @@ impl TcSourceTaskStatic {
// TC source components where the heap is the backing memory of the received telecommands. // TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic { pub struct TcSourceTaskDynamic {
pub tc_receiver: Receiver<Vec<u8>>, pub tc_receiver: Receiver<Vec<u8>>,
pus_receiver: PusReceiver, pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
} }
impl TcSourceTaskDynamic { impl TcSourceTaskDynamic {
pub fn new(tc_receiver: Receiver<Vec<u8>>, pus_receiver: PusReceiver) -> Self { pub fn new(
tc_receiver: Receiver<Vec<u8>>,
pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
) -> Self {
Self { Self {
tc_receiver, tc_receiver,
pus_receiver, pus_receiver,

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,6 +1,6 @@
[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>"]
@ -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,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

@ -3,10 +3,12 @@ use syn::{parse_macro_input, ItemConst, LitStr};
/// This macro can be used to automatically generate introspection information for return codes. /// This macro can be used to automatically generate introspection information for return codes.
/// ///
/// For example, it can be applied to types like the [satrs_shared::res_code::ResultU16] type /// For example, it can be applied to types like the
/// to automatically generate [satrs_mib::res_code::ResultU16Info] instances. These instances /// [`satrs_mib::res_code::ResultU16`](https://docs.rs/satrs-mib/latest/satrs_mib/res_code/struct.ResultU16.html#) type
/// can then be used for tasks like generating CSVs or YAML files with the list of all result /// to automatically generate
/// codes. This information is valuable for both operators and developers. /// [`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

@ -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,7 +1,7 @@
[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.irs.uni-stuttgart.de/projects/sat-rs/" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@ -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,36 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
## Changed
- Refactored `EventManager` to heavily use generics instead of trait objects.
- `SendEventProvider` -> `EventSendProvider`. `id` trait method renamed to `channel_id`.
- `ListenerTable` -> `ListenerMapProvider`
- `SenderTable` -> `SenderMapProvider`
- There is an `EventManagerWithMpsc` and a `EventManagerWithBoundedMpsc` helper type now.
- Refactored ECSS TM sender abstractions to be generic over different message queue backends.
- Refactored Verification Reporter abstractions and implementation to be generic over the sender
instead of using trait objects.
## Fixed
- Update deprecated API for `PusScheduler::insert_wrapped_tc_cds_short`
and `PusScheduler::insert_wrapped_tc_cds_long`.
# [v0.2.0-rc.0] 2024-02-21
## Added
- New PUS service abstractions for HK (PUS 3) and actions (PUS 8). Introducing new abstractions
allows to move some boilerplate code into the framework.
- New `VerificationReportingProvider` abstraction to avoid relying on a concrete verification
reporting provider.
## Changed
- Verification reporter API timestamp arguments are not `Option`al anymore. Empty timestamps
can be passed by simply specifying the `&[]` empty slice argument.
# [v0.1.1] 2024-02-12 # [v0.1.1] 2024-02-12
- Minor fixes for crate config `homepage` entries and links in documentation. - Minor fixes for crate config `homepage` entries and links in documentation.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "satrs" name = "satrs"
version = "0.1.1" 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>"]
@ -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]

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

@ -7,7 +7,7 @@ use spacepackets::ByteConversionError;
use std::error::Error; use std::error::Error;
use std::path::Path; use std::path::Path;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use stdmod::*; pub use std_mod::*;
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM); pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
@ -148,12 +148,11 @@ pub trait VirtualFilestore {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod stdmod { pub mod std_mod {
use super::*; use super::*;
use std::{ use std::{
fs::{self, File, OpenOptions}, fs::{self, File, OpenOptions},
io::{BufReader, Read, Seek, SeekFrom, Write}, io::{BufReader, Read, Seek, SeekFrom, Write},
path::Path,
}; };
#[derive(Default)] #[derive(Default)]

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

@ -10,27 +10,27 @@
//! [sat-rs book chapter](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/events.html) //! [sat-rs book chapter](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/events.html)
//! about events first: //! about events first:
//! //!
//! The event manager has a listener table abstracted by the [ListenerTable], which maps //! The event manager has a listener table abstracted by the [ListenerMapProvider], which maps
//! listener groups identified by [ListenerKey]s to a [sender ID][ChannelId]. //! listener groups identified by [ListenerKey]s to a [sender ID][ChannelId].
//! It also contains a sender table abstracted by the [SenderTable] which maps these sender IDs //! It also contains a sender table abstracted by the [SenderMapProvider] which maps these sender
//! to a concrete [SendEventProvider]s. A simple approach would be to use one send event provider //! IDs to concrete [EventSendProvider]s. A simple approach would be to use one send event provider
//! for each OBSW thread and then subscribe for all interesting events for a particular thread //! for each OBSW thread and then subscribe for all interesting events for a particular thread
//! using the send event provider ID. //! using the send event provider ID.
//! //!
//! This can be done with the [EventManager] like this: //! This can be done with the [EventManager] like this:
//! //!
//! 1. Provide a concrete [EventReceiver] implementation. This abstraction allow to use different //! 1. Provide a concrete [EventReceiveProvider] implementation. This abstraction allow to use different
//! message queue backends. A straightforward implementation where dynamic memory allocation is //! message queue backends. A straightforward implementation where dynamic memory allocation is
//! not a big concern could use [std::sync::mpsc::channel] to do this and is provided in //! not a big concern could use [std::sync::mpsc::channel] to do this and is provided in
//! form of the [MpscEventReceiver]. //! form of the [MpscEventReceiver].
//! 2. To set up event creators, create channel pairs using some message queue implementation. //! 2. To set up event creators, create channel pairs using some message queue implementation.
//! Each event creator gets a (cloned) sender component which allows it to send events to the //! Each event creator gets a (cloned) sender component which allows it to send events to the
//! manager. //! manager.
//! 3. The event manager receives the receiver component as part of a [EventReceiver] //! 3. The event manager receives the receiver component as part of a [EventReceiveProvider]
//! implementation so all events are routed to the manager. //! implementation so all events are routed to the manager.
//! 4. Create the [send event providers][SendEventProvider]s which allow routing events to //! 4. Create the [send event providers][EventSendProvider]s which allow routing events to
//! subscribers. You can now use their [sender IDs][SendEventProvider::id] to subscribe for //! subscribers. You can now use their [sender IDs][EventSendProvider::channel_id] to subscribe
//! event groups, for example by using the [EventManager::subscribe_single] method. //! for event groups, for example by using the [EventManager::subscribe_single] method.
//! 5. Add the send provider as well using the [EventManager::add_sender] call so the event //! 5. Add the send provider as well using the [EventManager::add_sender] call so the event
//! manager can route listener groups to a the send provider. //! manager can route listener groups to a the send provider.
//! //!
@ -41,24 +41,22 @@
//! //!
//! # Examples //! # Examples
//! //!
//! You can check [integration test](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/pus_events.rs) //! You can check [integration test](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/pus_events.rs)
//! for a concrete example using multi-threading where events are routed to //! for a concrete example using multi-threading where events are routed to
//! different threads. //! different threads.
use crate::events::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw}; use crate::events::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
use crate::params::{Params, ParamsHeapless}; use crate::params::{Params, ParamsHeapless};
#[cfg(feature = "alloc")] use crate::queue::GenericSendError;
use alloc::boxed::Box; use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::slice::Iter; use core::slice::Iter;
#[cfg(feature = "alloc")]
use hashbrown::HashMap;
use crate::ChannelId; use crate::ChannelId;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use stdmod::*; pub use std_mod::*;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum ListenerKey { pub enum ListenerKey {
@ -75,108 +73,110 @@ pub type EventWithAuxData<Event> = (Event, Option<Params>);
pub type EventU32WithAuxData = EventWithAuxData<EventU32>; pub type EventU32WithAuxData = EventWithAuxData<EventU32>;
pub type EventU16WithAuxData = EventWithAuxData<EventU16>; pub type EventU16WithAuxData = EventWithAuxData<EventU16>;
pub trait SendEventProvider<Provider: GenericEvent, AuxDataProvider = Params> { pub trait EventSendProvider<EV: GenericEvent, AuxDataProvider = Params> {
type Error; fn channel_id(&self) -> ChannelId;
fn id(&self) -> ChannelId; fn send_no_data(&self, event: EV) -> Result<(), GenericSendError> {
fn send_no_data(&self, event: Provider) -> Result<(), Self::Error> {
self.send(event, None) self.send(event, None)
} }
fn send(&self, event: Provider, aux_data: Option<AuxDataProvider>) -> Result<(), Self::Error>;
fn send(&self, event: EV, aux_data: Option<AuxDataProvider>) -> Result<(), GenericSendError>;
} }
/// Generic abstraction for an event receiver. /// Generic abstraction for an event receiver.
pub trait EventReceiver<Event: GenericEvent, AuxDataProvider = Params> { pub trait EventReceiveProvider<Event: GenericEvent, AuxDataProvider = Params> {
/// This function has to be provided by any event receiver. A receive call may or may not return /// This function has to be provided by any event receiver. A call may or may not return
/// an event. /// an event and optional auxiliary data.
/// fn try_recv_event(&self) -> Option<(Event, Option<AuxDataProvider>)>;
/// To allow returning arbitrary additional auxiliary data, a mutable slice is passed to the
/// [Self::receive] call as well. Receivers can write data to this slice, but care must be taken
/// to avoid panics due to size missmatches or out of bound writes.
fn receive(&self) -> Option<(Event, Option<AuxDataProvider>)>;
} }
pub trait ListenerTable { pub trait ListenerMapProvider {
fn get_listeners(&self) -> Vec<ListenerKey>; #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
fn get_listeners(&self) -> alloc::vec::Vec<ListenerKey>;
fn contains_listener(&self, key: &ListenerKey) -> bool; fn contains_listener(&self, key: &ListenerKey) -> bool;
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>>; fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>>;
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool; fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool;
fn remove_duplicates(&mut self, key: &ListenerKey); fn remove_duplicates(&mut self, key: &ListenerKey);
} }
pub trait SenderTable<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params> { pub trait SenderMapProvider<
SP: EventSendProvider<EV, AUX>,
EV: GenericEvent = EventU32,
AUX = Params,
>
{
fn contains_send_event_provider(&self, id: &ChannelId) -> bool; fn contains_send_event_provider(&self, id: &ChannelId) -> bool;
fn get_send_event_provider(
&self, fn get_send_event_provider(&self, id: &ChannelId) -> Option<&SP>;
id: &ChannelId, fn add_send_event_provider(&mut self, send_provider: SP) -> bool;
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>;
fn add_send_event_provider(
&mut self,
send_provider: Box<
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
>,
) -> bool;
} }
/// Generic event manager implementation. /// Generic event manager implementation.
/// ///
/// # Generics /// # Generics
/// ///
/// * `SendProviderError`: [SendEventProvider] error type /// * `ERP`: [EventReceiveProvider] used to receive all events.
/// * `Event`: Concrete event provider, currently either [EventU32] or [EventU16] /// * `SMP`: [SenderMapProvider] which maps channel IDs to send providers.
/// * `AuxDataProvider`: Concrete auxiliary data provider, currently either [Params] or /// * `LTR`: [ListenerMapProvider] which maps listener keys to channel IDs.
/// [ParamsHeapless] /// * `SP`: [EventSendProvider] contained within the sender map which sends the events.
pub struct EventManager<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params> /// * `EV`: The event type. This type must implement the [GenericEvent]. Currently only [EventU32]
{ /// and [EventU16] are supported.
listener_table: Box<dyn ListenerTable>, /// * `AUX`: Auxiliary data which is sent with the event to provide optional context information
sender_table: Box<dyn SenderTable<SendProviderError, Event, AuxDataProvider>>, pub struct EventManager<
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>, ERP: EventReceiveProvider<EV, AUX>,
SMP: SenderMapProvider<SP, EV, AUX>,
LTR: ListenerMapProvider,
SP: EventSendProvider<EV, AUX>,
EV: GenericEvent = EventU32,
AUX = Params,
> {
event_receiver: ERP,
sender_map: SMP,
listener_map: LTR,
phantom: core::marker::PhantomData<(SP, EV, AUX)>,
} }
/// Safety: It is safe to implement [Send] because all fields in the [EventManager] are [Send]
/// as well
#[cfg(feature = "std")]
unsafe impl<E, Event: GenericEvent + Send, AuxDataProvider: Send> Send
for EventManager<E, Event, AuxDataProvider>
{
}
#[cfg(feature = "std")]
pub type EventManagerWithMpscQueue<Event, AuxDataProvider> = EventManager<
std::sync::mpsc::SendError<(Event, Option<AuxDataProvider>)>,
Event,
AuxDataProvider,
>;
#[derive(Debug)] #[derive(Debug)]
pub enum EventRoutingResult<Event: GenericEvent, AuxDataProvider> { pub enum EventRoutingResult<EV: GenericEvent, AUX> {
/// No event was received /// No event was received
Empty, Empty,
/// An event was received and routed. /// An event was received and routed to listeners.
/// The first tuple entry will contain the number of recipients. Handled {
Handled(u32, Event, Option<AuxDataProvider>), num_recipients: u32,
event: EV,
aux_data: Option<AUX>,
},
} }
#[derive(Debug)] #[derive(Debug)]
pub enum EventRoutingError<E> { pub enum EventRoutingError {
SendError(E), Send(GenericSendError),
NoSendersForKey(ListenerKey), NoSendersForKey(ListenerKey),
NoSenderForId(ChannelId), NoSenderForId(ChannelId),
} }
#[derive(Debug)] #[derive(Debug)]
pub struct EventRoutingErrorsWithResult<Event: GenericEvent, AuxDataProvider, E> { pub struct EventRoutingErrorsWithResult<EV: GenericEvent, AUX> {
pub result: EventRoutingResult<Event, AuxDataProvider>, pub result: EventRoutingResult<EV, AUX>,
pub errors: [Option<EventRoutingError<E>>; 3], pub errors: [Option<EventRoutingError>; 3],
} }
impl<E, Event: GenericEvent + Copy> EventManager<E, Event> { impl<
ER: EventReceiveProvider<EV, AUX>,
S: SenderMapProvider<SP, EV, AUX>,
L: ListenerMapProvider,
SP: EventSendProvider<EV, AUX>,
EV: GenericEvent + Copy,
AUX: Clone,
> EventManager<ER, S, L, SP, EV, AUX>
{
pub fn remove_duplicates(&mut self, key: &ListenerKey) { pub fn remove_duplicates(&mut self, key: &ListenerKey) {
self.listener_table.remove_duplicates(key) self.listener_map.remove_duplicates(key)
} }
/// Subscribe for a unique event. /// Subscribe for a unique event.
pub fn subscribe_single(&mut self, event: &Event, sender_id: ChannelId) { pub fn subscribe_single(&mut self, event: &EV, sender_id: ChannelId) {
self.update_listeners(ListenerKey::Single(event.raw_as_largest_type()), sender_id); self.update_listeners(ListenerKey::Single(event.raw_as_largest_type()), sender_id);
} }
@ -194,49 +194,37 @@ impl<E, Event: GenericEvent + Copy> EventManager<E, Event> {
} }
} }
impl<E: 'static, Event: GenericEvent + Copy + 'static, AuxDataProvider: Clone + 'static> impl<
EventManager<E, Event, AuxDataProvider> ERP: EventReceiveProvider<EV, AUX>,
SMP: SenderMapProvider<SP, EV, AUX>,
LTR: ListenerMapProvider,
SP: EventSendProvider<EV, AUX>,
EV: GenericEvent + Copy,
AUX: Clone,
> EventManager<ERP, SMP, LTR, SP, EV, AUX>
{ {
/// Create an event manager where the sender table will be the [DefaultSenderTableProvider] pub fn new_with_custom_maps(event_receiver: ERP, sender_map: SMP, listener_map: LTR) -> Self {
/// and the listener table will be the [DefaultListenerTableProvider].
pub fn new(event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>) -> Self {
let listener_table: Box<DefaultListenerTableProvider> = Box::default();
let sender_table: Box<DefaultSenderTableProvider<E, Event, AuxDataProvider>> =
Box::default();
Self::new_custom_tables(listener_table, sender_table, event_receiver)
}
}
impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
EventManager<E, Event, AuxDataProvider>
{
pub fn new_custom_tables(
listener_table: Box<dyn ListenerTable>,
sender_table: Box<dyn SenderTable<E, Event, AuxDataProvider>>,
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
) -> Self {
EventManager { EventManager {
listener_table, listener_map,
sender_table, sender_map,
event_receiver, event_receiver,
phantom: PhantomData,
} }
} }
pub fn add_sender( /// Add a new sender component which can be used to send events to subscribers.
&mut self, pub fn add_sender(&mut self, send_provider: SP) {
send_provider: impl SendEventProvider<Event, AuxDataProvider, Error = E> + 'static,
) {
if !self if !self
.sender_table .sender_map
.contains_send_event_provider(&send_provider.id()) .contains_send_event_provider(&send_provider.channel_id())
{ {
self.sender_table self.sender_map.add_send_event_provider(send_provider);
.add_send_event_provider(Box::new(send_provider));
} }
} }
/// Generic function to update the event subscribers.
fn update_listeners(&mut self, key: ListenerKey, sender_id: ChannelId) { fn update_listeners(&mut self, key: ListenerKey, sender_id: ChannelId) {
self.listener_table.add_listener(key, sender_id); self.listener_map.add_listener(key, sender_id);
} }
/// This function will use the cached event receiver and try to receive one event. /// This function will use the cached event receiver and try to receive one event.
@ -248,40 +236,36 @@ impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
/// [EventRoutingErrorsWithResult] error struct. /// [EventRoutingErrorsWithResult] error struct.
pub fn try_event_handling( pub fn try_event_handling(
&self, &self,
) -> Result< ) -> Result<EventRoutingResult<EV, AUX>, EventRoutingErrorsWithResult<EV, AUX>> {
EventRoutingResult<Event, AuxDataProvider>,
EventRoutingErrorsWithResult<Event, AuxDataProvider, E>,
> {
let mut err_idx = 0; let mut err_idx = 0;
let mut err_slice = [None, None, None]; let mut err_slice = [None, None, None];
let mut num_recipients = 0; let mut num_recipients = 0;
let mut add_error = |error: EventRoutingError<E>| { let mut add_error = |error: EventRoutingError| {
if err_idx < 3 { if err_idx < 3 {
err_slice[err_idx] = Some(error); err_slice[err_idx] = Some(error);
err_idx += 1; err_idx += 1;
} }
}; };
let mut send_handler = let mut send_handler = |key: &ListenerKey, event: EV, aux_data: &Option<AUX>| {
|key: &ListenerKey, event: Event, aux_data: &Option<AuxDataProvider>| { if self.listener_map.contains_listener(key) {
if self.listener_table.contains_listener(key) { if let Some(ids) = self.listener_map.get_listener_ids(key) {
if let Some(ids) = self.listener_table.get_listener_ids(key) { for id in ids {
for id in ids { if let Some(sender) = self.sender_map.get_send_event_provider(id) {
if let Some(sender) = self.sender_table.get_send_event_provider(id) { if let Err(e) = sender.send(event, aux_data.clone()) {
if let Err(e) = sender.send(event, aux_data.clone()) { add_error(EventRoutingError::Send(e));
add_error(EventRoutingError::SendError(e));
} else {
num_recipients += 1;
}
} else { } else {
add_error(EventRoutingError::NoSenderForId(*id)); num_recipients += 1;
} }
} else {
add_error(EventRoutingError::NoSenderForId(*id));
} }
} else {
add_error(EventRoutingError::NoSendersForKey(*key));
} }
} else {
add_error(EventRoutingError::NoSendersForKey(*key));
} }
}; }
if let Some((event, aux_data)) = self.event_receiver.receive() { };
if let Some((event, aux_data)) = self.event_receiver.try_recv_event() {
let single_key = ListenerKey::Single(event.raw_as_largest_type()); let single_key = ListenerKey::Single(event.raw_as_largest_type());
send_handler(&single_key, event, &aux_data); send_handler(&single_key, event, &aux_data);
let group_key = ListenerKey::Group(event.group_id_as_largest_type()); let group_key = ListenerKey::Group(event.group_id_as_largest_type());
@ -289,130 +273,177 @@ impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
send_handler(&ListenerKey::All, event, &aux_data); send_handler(&ListenerKey::All, event, &aux_data);
if err_idx > 0 { if err_idx > 0 {
return Err(EventRoutingErrorsWithResult { return Err(EventRoutingErrorsWithResult {
result: EventRoutingResult::Handled(num_recipients, event, aux_data), result: EventRoutingResult::Handled {
num_recipients,
event,
aux_data,
},
errors: err_slice, errors: err_slice,
}); });
} }
return Ok(EventRoutingResult::Handled(num_recipients, event, aux_data)); return Ok(EventRoutingResult::Handled {
num_recipients,
event,
aux_data,
});
} }
Ok(EventRoutingResult::Empty) Ok(EventRoutingResult::Empty)
} }
} }
#[derive(Default)] #[cfg(feature = "alloc")]
pub struct DefaultListenerTableProvider { pub mod alloc_mod {
listeners: HashMap<ListenerKey, Vec<ChannelId>>, use alloc::vec::Vec;
} use hashbrown::HashMap;
pub struct DefaultSenderTableProvider< use super::*;
SendProviderError,
Event: GenericEvent = EventU32,
AuxDataProvider = Params,
> {
senders: HashMap<
ChannelId,
Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>,
>,
}
impl<SendProviderError, Event: GenericEvent, AuxDataProvider> Default /// Helper type which constrains the sender map and listener map generics to the [DefaultSenderMap]
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider> /// and the [DefaultListenerMap]. It uses regular mpsc channels as the message queue backend.
{ pub type EventManagerWithMpsc<EV = EventU32, AUX = Params> = EventManager<
fn default() -> Self { MpscEventReceiver,
Self { DefaultSenderMap<EventSenderMpsc<EV>, EV, AUX>,
senders: HashMap::new(), DefaultListenerMap,
EventSenderMpsc<EV>,
>;
/// Helper type which constrains the sender map and listener map generics to the [DefaultSenderMap]
/// and the [DefaultListenerMap]. It uses
/// [bounded mpsc senders](https://doc.rust-lang.org/std/sync/mpsc/struct.SyncSender.html) as the
/// message queue backend.
pub type EventManagerWithBoundedMpsc<EV = EventU32, AUX = Params> = EventManager<
MpscEventReceiver,
DefaultSenderMap<EventSenderMpscBounded<EV>, EV, AUX>,
DefaultListenerMap,
EventSenderMpscBounded<EV>,
>;
impl<
ER: EventReceiveProvider<EV, AUX>,
SP: EventSendProvider<EV, AUX>,
EV: GenericEvent + Copy,
AUX: 'static,
> EventManager<ER, DefaultSenderMap<SP, EV, AUX>, DefaultListenerMap, SP, EV, AUX>
{
/// Create an event manager where the sender table will be the [DefaultSenderMap]
/// and the listener table will be the [DefaultListenerMap].
pub fn new(event_receiver: ER) -> Self {
Self {
listener_map: DefaultListenerMap::default(),
sender_map: DefaultSenderMap::default(),
event_receiver,
phantom: PhantomData,
}
} }
} }
}
impl ListenerTable for DefaultListenerTableProvider { /// Default listener map.
fn get_listeners(&self) -> Vec<ListenerKey> { ///
let mut key_list = Vec::new(); /// Simple implementation which uses a [HashMap] and a [Vec] internally.
for key in self.listeners.keys() { #[derive(Default)]
key_list.push(*key); pub struct DefaultListenerMap {
listeners: HashMap<ListenerKey, Vec<ChannelId>>,
}
impl ListenerMapProvider for DefaultListenerMap {
fn get_listeners(&self) -> Vec<ListenerKey> {
let mut key_list = Vec::new();
for key in self.listeners.keys() {
key_list.push(*key);
}
key_list
} }
key_list
}
fn contains_listener(&self, key: &ListenerKey) -> bool { fn contains_listener(&self, key: &ListenerKey) -> bool {
self.listeners.contains_key(key) self.listeners.contains_key(key)
}
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>> {
self.listeners.get(key).map(|vec| vec.iter())
}
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool {
if let Some(existing_list) = self.listeners.get_mut(&key) {
existing_list.push(sender_id);
} else {
let new_list = vec![sender_id];
self.listeners.insert(key, new_list);
} }
true
}
fn remove_duplicates(&mut self, key: &ListenerKey) { fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<ChannelId>> {
if let Some(list) = self.listeners.get_mut(key) { self.listeners.get(key).map(|vec| vec.iter())
list.sort_unstable(); }
list.dedup();
fn add_listener(&mut self, key: ListenerKey, sender_id: ChannelId) -> bool {
if let Some(existing_list) = self.listeners.get_mut(&key) {
existing_list.push(sender_id);
} else {
let new_list = alloc::vec![sender_id];
self.listeners.insert(key, new_list);
}
true
}
fn remove_duplicates(&mut self, key: &ListenerKey) {
if let Some(list) = self.listeners.get_mut(key) {
list.sort_unstable();
list.dedup();
}
} }
} }
}
impl<SendProviderError, Event: GenericEvent, AuxDataProvider> /// Default sender map.
SenderTable<SendProviderError, Event, AuxDataProvider> ///
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider> /// Simple implementation which uses a [HashMap] internally.
{ pub struct DefaultSenderMap<
fn contains_send_event_provider(&self, id: &ChannelId) -> bool { SP: EventSendProvider<EV, AUX>,
self.senders.contains_key(id) EV: GenericEvent = EventU32,
AUX = Params,
> {
senders: HashMap<ChannelId, SP>,
phantom: PhantomData<(EV, AUX)>,
} }
fn get_send_event_provider( impl<SP: EventSendProvider<EV, AUX>, EV: GenericEvent, AUX> Default
&self, for DefaultSenderMap<SP, EV, AUX>
id: &ChannelId, {
) -> Option<&dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>> { fn default() -> Self {
self.senders Self {
.get(id) senders: Default::default(),
.filter(|sender| sender.id() == *id) phantom: Default::default(),
.map(|v| v.as_ref()) }
} }
}
fn add_send_event_provider(
&mut self, impl<SP: EventSendProvider<EV, AUX>, EV: GenericEvent, AUX> SenderMapProvider<SP, EV, AUX>
send_provider: Box< for DefaultSenderMap<SP, EV, AUX>
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>, {
>, fn contains_send_event_provider(&self, id: &ChannelId) -> bool {
) -> bool { self.senders.contains_key(id)
let id = send_provider.id(); }
if self.senders.contains_key(&id) {
return false; fn get_send_event_provider(&self, id: &ChannelId) -> Option<&SP> {
self.senders
.get(id)
.filter(|sender| sender.channel_id() == *id)
}
fn add_send_event_provider(&mut self, send_provider: SP) -> bool {
let id = send_provider.channel_id();
if self.senders.contains_key(&id) {
return false;
}
self.senders.insert(id, send_provider).is_none()
} }
self.senders.insert(id, send_provider).is_none()
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod stdmod { pub mod std_mod {
use super::*; use super::*;
use crate::event_man::{EventReceiver, EventWithAuxData}; use std::sync::mpsc;
use crate::events::{EventU16, EventU32, GenericEvent};
use crate::params::Params;
use std::sync::mpsc::{Receiver, SendError, Sender};
pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> { pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> {
mpsc_receiver: Receiver<(Event, Option<Params>)>, mpsc_receiver: mpsc::Receiver<(Event, Option<Params>)>,
} }
impl<Event: GenericEvent + Send> MpscEventReceiver<Event> { impl<Event: GenericEvent + Send> MpscEventReceiver<Event> {
pub fn new(receiver: Receiver<(Event, Option<Params>)>) -> Self { pub fn new(receiver: mpsc::Receiver<(Event, Option<Params>)>) -> Self {
Self { Self {
mpsc_receiver: receiver, mpsc_receiver: receiver,
} }
} }
} }
impl<Event: GenericEvent + Send> EventReceiver<Event> for MpscEventReceiver<Event> { impl<Event: GenericEvent + Send> EventReceiveProvider<Event> for MpscEventReceiver<Event> {
fn receive(&self) -> Option<EventWithAuxData<Event>> { fn try_recv_event(&self) -> Option<EventWithAuxData<Event>> {
if let Ok(event_and_data) = self.mpsc_receiver.try_recv() { if let Ok(event_and_data) = self.mpsc_receiver.try_recv() {
return Some(event_and_data); return Some(event_and_data);
} }
@ -423,31 +454,75 @@ pub mod stdmod {
pub type MpscEventU32Receiver = MpscEventReceiver<EventU32>; pub type MpscEventU32Receiver = MpscEventReceiver<EventU32>;
pub type MpscEventU16Receiver = MpscEventReceiver<EventU16>; pub type MpscEventU16Receiver = MpscEventReceiver<EventU16>;
/// Generic event sender which uses a regular [mpsc::Sender] as the messaging backend to
/// send events.
#[derive(Clone)] #[derive(Clone)]
pub struct MpscEventSendProvider<Event: GenericEvent + Send> { pub struct EventSenderMpsc<Event: GenericEvent + Send> {
id: u32, id: u32,
sender: Sender<(Event, Option<Params>)>, sender: mpsc::Sender<(Event, Option<Params>)>,
} }
impl<Event: GenericEvent + Send> MpscEventSendProvider<Event> { impl<Event: GenericEvent + Send> EventSenderMpsc<Event> {
pub fn new(id: u32, sender: Sender<(Event, Option<Params>)>) -> Self { pub fn new(id: u32, sender: mpsc::Sender<(Event, Option<Params>)>) -> Self {
Self { id, sender } Self { id, sender }
} }
} }
impl<Event: GenericEvent + Send> SendEventProvider<Event> for MpscEventSendProvider<Event> { impl<Event: GenericEvent + Send> EventSendProvider<Event> for EventSenderMpsc<Event> {
type Error = SendError<(Event, Option<Params>)>; fn channel_id(&self) -> u32 {
fn id(&self) -> u32 {
self.id self.id
} }
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), Self::Error> { fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), GenericSendError> {
self.sender.send((event, aux_data)) self.sender
.send((event, aux_data))
.map_err(|_| GenericSendError::RxDisconnected)
} }
} }
pub type MpscEventU32SendProvider = MpscEventSendProvider<EventU32>; /// Generic event sender which uses the [mpsc::SyncSender] as the messaging backend to send
pub type MpscEventU16SendProvider = MpscEventSendProvider<EventU16>; /// events. This has the advantage that the channel is bounded and thus more deterministic.
#[derive(Clone)]
pub struct EventSenderMpscBounded<Event: GenericEvent + Send> {
channel_id: u32,
sender: mpsc::SyncSender<(Event, Option<Params>)>,
capacity: usize,
}
impl<Event: GenericEvent + Send> EventSenderMpscBounded<Event> {
pub fn new(
channel_id: u32,
sender: mpsc::SyncSender<(Event, Option<Params>)>,
capacity: usize,
) -> Self {
Self {
channel_id,
sender,
capacity,
}
}
}
impl<Event: GenericEvent + Send> EventSendProvider<Event> for EventSenderMpscBounded<Event> {
fn channel_id(&self) -> u32 {
self.channel_id
}
fn send(&self, event: Event, aux_data: Option<Params>) -> Result<(), GenericSendError> {
if let Err(e) = self.sender.try_send((event, aux_data)) {
return match e {
mpsc::TrySendError::Full(_) => {
Err(GenericSendError::QueueFull(Some(self.capacity as u32)))
}
mpsc::TrySendError::Disconnected(_) => Err(GenericSendError::RxDisconnected),
};
}
Ok(())
}
}
pub type EventU32SenderMpsc = EventSenderMpsc<EventU32>;
pub type EventU16SenderMpsc = EventSenderMpsc<EventU16>;
pub type EventU32SenderMpscBounded = EventSenderMpscBounded<EventU32>;
pub type EventU16SenderMpscBounded = EventSenderMpscBounded<EventU16>;
} }
#[cfg(test)] #[cfg(test)]
@ -456,32 +531,10 @@ mod tests {
use crate::event_man::EventManager; use crate::event_man::EventManager;
use crate::events::{EventU32, GenericEvent, Severity}; use crate::events::{EventU32, GenericEvent, Severity};
use crate::params::ParamsRaw; use crate::params::ParamsRaw;
use alloc::boxed::Box;
use std::format; use std::format;
use std::sync::mpsc::{channel, Receiver, SendError, Sender}; use std::sync::mpsc::{self, channel, Receiver, Sender};
#[derive(Clone)] const TEST_EVENT: EventU32 = EventU32::const_new(Severity::INFO, 0, 5);
struct MpscEventSenderQueue {
id: u32,
mpsc_sender: Sender<EventU32WithAuxData>,
}
impl MpscEventSenderQueue {
fn new(id: u32, mpsc_sender: Sender<EventU32WithAuxData>) -> Self {
Self { id, mpsc_sender }
}
}
impl SendEventProvider<EventU32> for MpscEventSenderQueue {
type Error = SendError<EventU32WithAuxData>;
fn id(&self) -> u32 {
self.id
}
fn send(&self, event: EventU32, aux_data: Option<Params>) -> Result<(), Self::Error> {
self.mpsc_sender.send((event, aux_data))
}
}
fn check_next_event( fn check_next_event(
expected: EventU32, expected: EventU32,
@ -500,22 +553,21 @@ mod tests {
expected_num_sent: u32, expected_num_sent: u32,
) { ) {
assert!(matches!(res, EventRoutingResult::Handled { .. })); assert!(matches!(res, EventRoutingResult::Handled { .. }));
if let EventRoutingResult::Handled(num_recipients, event, _aux_data) = res { if let EventRoutingResult::Handled {
num_recipients,
event,
..
} = res
{
assert_eq!(event, expected); assert_eq!(event, expected);
assert_eq!(num_recipients, expected_num_sent); assert_eq!(num_recipients, expected_num_sent);
} }
} }
fn generic_event_man() -> ( fn generic_event_man() -> (Sender<EventU32WithAuxData>, EventManagerWithMpsc) {
Sender<EventU32WithAuxData>,
EventManager<SendError<EventU32WithAuxData>>,
) {
let (event_sender, manager_queue) = channel(); let (event_sender, manager_queue) = channel();
let event_man_receiver = MpscEventReceiver::new(manager_queue); let event_man_receiver = MpscEventReceiver::new(manager_queue);
( (event_sender, EventManager::new(event_man_receiver))
event_sender,
EventManager::new(Box::new(event_man_receiver)),
)
} }
#[test] #[test]
@ -524,15 +576,12 @@ mod tests {
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap(); let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap(); let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
let (single_event_sender, single_event_receiver) = channel(); let (single_event_sender, single_event_receiver) = channel();
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender); let single_event_listener = EventSenderMpsc::new(0, single_event_sender);
event_man.subscribe_single(&event_grp_0, single_event_listener.id()); event_man.subscribe_single(&event_grp_0, single_event_listener.channel_id());
event_man.add_sender(single_event_listener); event_man.add_sender(single_event_listener);
let (group_event_sender_0, group_event_receiver_0) = channel(); let (group_event_sender_0, group_event_receiver_0) = channel();
let group_event_listener = MpscEventSenderQueue { let group_event_listener = EventU32SenderMpsc::new(1, group_event_sender_0);
id: 1, event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.channel_id());
mpsc_sender: group_event_sender_0,
};
event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.id());
event_man.add_sender(group_event_listener); event_man.add_sender(group_event_listener);
// Test event with one listener // Test event with one listener
@ -559,8 +608,8 @@ mod tests {
let (event_sender, mut event_man) = generic_event_man(); let (event_sender, mut event_man) = generic_event_man();
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap(); let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
let (single_event_sender, single_event_receiver) = channel(); let (single_event_sender, single_event_receiver) = channel();
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender); let single_event_listener = EventSenderMpsc::new(0, single_event_sender);
event_man.subscribe_single(&event_grp_0, single_event_listener.id()); event_man.subscribe_single(&event_grp_0, single_event_listener.channel_id());
event_man.add_sender(single_event_listener); event_man.add_sender(single_event_listener);
event_sender event_sender
.send((event_grp_0, Some(Params::Heapless((2_u32, 3_u32).into())))) .send((event_grp_0, Some(Params::Heapless((2_u32, 3_u32).into()))))
@ -591,12 +640,15 @@ mod tests {
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap(); let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap(); let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
let (event_grp_0_sender, event_grp_0_receiver) = channel(); let (event_grp_0_sender, event_grp_0_receiver) = channel();
let event_grp_0_and_1_listener = MpscEventSenderQueue { let event_grp_0_and_1_listener = EventU32SenderMpsc::new(0, event_grp_0_sender);
id: 0, event_man.subscribe_group(
mpsc_sender: event_grp_0_sender, event_grp_0.group_id(),
}; event_grp_0_and_1_listener.channel_id(),
event_man.subscribe_group(event_grp_0.group_id(), event_grp_0_and_1_listener.id()); );
event_man.subscribe_group(event_grp_1_0.group_id(), event_grp_0_and_1_listener.id()); event_man.subscribe_group(
event_grp_1_0.group_id(),
event_grp_0_and_1_listener.channel_id(),
);
event_man.add_sender(event_grp_0_and_1_listener); event_man.add_sender(event_grp_0_and_1_listener);
event_sender event_sender
@ -625,18 +677,12 @@ mod tests {
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap(); let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
let (event_0_tx_0, event_0_rx_0) = channel(); let (event_0_tx_0, event_0_rx_0) = channel();
let (event_0_tx_1, event_0_rx_1) = channel(); let (event_0_tx_1, event_0_rx_1) = channel();
let event_listener_0 = MpscEventSenderQueue { let event_listener_0 = EventU32SenderMpsc::new(0, event_0_tx_0);
id: 0, let event_listener_1 = EventU32SenderMpsc::new(1, event_0_tx_1);
mpsc_sender: event_0_tx_0, let event_listener_0_sender_id = event_listener_0.channel_id();
};
let event_listener_1 = MpscEventSenderQueue {
id: 1,
mpsc_sender: event_0_tx_1,
};
let event_listener_0_sender_id = event_listener_0.id();
event_man.subscribe_single(&event_0, event_listener_0_sender_id); event_man.subscribe_single(&event_0, event_listener_0_sender_id);
event_man.add_sender(event_listener_0); event_man.add_sender(event_listener_0);
let event_listener_1_sender_id = event_listener_1.id(); let event_listener_1_sender_id = event_listener_1.channel_id();
event_man.subscribe_single(&event_0, event_listener_1_sender_id); event_man.subscribe_single(&event_0, event_listener_1_sender_id);
event_man.add_sender(event_listener_1); event_man.add_sender(event_listener_1);
event_sender event_sender
@ -681,16 +727,12 @@ mod tests {
fn test_all_events_listener() { fn test_all_events_listener() {
let (event_sender, manager_queue) = channel(); let (event_sender, manager_queue) = channel();
let event_man_receiver = MpscEventReceiver::new(manager_queue); let event_man_receiver = MpscEventReceiver::new(manager_queue);
let mut event_man: EventManager<SendError<EventU32WithAuxData>> = let mut event_man = EventManagerWithMpsc::new(event_man_receiver);
EventManager::new(Box::new(event_man_receiver));
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap(); let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap(); let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
let (event_0_tx_0, all_events_rx) = channel(); let (event_0_tx_0, all_events_rx) = channel();
let all_events_listener = MpscEventSenderQueue { let all_events_listener = EventU32SenderMpsc::new(0, event_0_tx_0);
id: 0, event_man.subscribe_all(all_events_listener.channel_id());
mpsc_sender: event_0_tx_0,
};
event_man.subscribe_all(all_events_listener.id());
event_man.add_sender(all_events_listener); event_man.add_sender(all_events_listener);
event_sender event_sender
.send((event_0, None)) .send((event_0, None))
@ -707,4 +749,36 @@ mod tests {
check_next_event(event_0, &all_events_rx); check_next_event(event_0, &all_events_rx);
check_next_event(event_1, &all_events_rx); check_next_event(event_1, &all_events_rx);
} }
#[test]
fn test_bounded_event_sender_queue_full() {
let (event_sender, _event_receiver) = mpsc::sync_channel(3);
let event_sender = EventU32SenderMpscBounded::new(1, event_sender, 3);
event_sender
.send_no_data(TEST_EVENT)
.expect("sending test event failed");
event_sender
.send_no_data(TEST_EVENT)
.expect("sending test event failed");
event_sender
.send_no_data(TEST_EVENT)
.expect("sending test event failed");
let error = event_sender.send_no_data(TEST_EVENT);
if let Err(e) = error {
assert!(matches!(e, GenericSendError::QueueFull(Some(3))));
} else {
panic!("unexpected error {error:?}");
}
}
#[test]
fn test_bounded_event_sender_rx_dropped() {
let (event_sender, event_receiver) = mpsc::sync_channel(3);
let event_sender = EventU32SenderMpscBounded::new(1, event_sender, 3);
drop(event_receiver);
if let Err(e) = event_sender.send_no_data(TEST_EVENT) {
assert!(matches!(e, GenericSendError::RxDisconnected));
} else {
panic!("Expected error");
}
}
} }

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

@ -107,7 +107,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
/// ///
/// ## Example /// ## Example
/// ///
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs) /// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
/// test also serves as the example application for this module. /// test also serves as the example application for this module.
pub struct TcpTmtcInCobsServer< pub struct TcpTmtcInCobsServer<
TmError, TmError,

View File

@ -88,7 +88,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
/// [spacepackets::PacketId]s as part of the server configuration for that purpose. /// [spacepackets::PacketId]s as part of the server configuration for that purpose.
/// ///
/// ## Example /// ## Example
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-core/tests/tcp_servers.rs) /// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
/// also serves as the example application for this module. /// also serves as the example application for this module.
pub struct TcpSpacepacketsServer< pub struct TcpSpacepacketsServer<
TmError, TmError,

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

@ -26,27 +26,31 @@ extern crate std;
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod cfdp; pub mod cfdp;
pub mod encoding; pub mod encoding;
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod event_man; pub mod event_man;
pub mod events; pub mod events;
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[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,

View File

@ -43,22 +43,19 @@
//! This includes the [ParamsHeapless] enumeration for contained values which do not require heap //! This includes the [ParamsHeapless] enumeration for contained values which do not require heap
//! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which //! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which
//! require [alloc] support but allow for more flexbility. //! require [alloc] support but allow for more flexbility.
#[cfg(feature = "alloc")]
use crate::pool::StoreAddr; use crate::pool::StoreAddr;
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt::Debug; use core::fmt::Debug;
use core::mem::size_of; use core::mem::size_of;
use paste::paste; use paste::paste;
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8}; use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8};
pub use spacepackets::util::ToBeBytes;
use spacepackets::util::UnsignedEnum; use spacepackets::util::UnsignedEnum;
use spacepackets::ByteConversionError; use spacepackets::ByteConversionError;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use alloc_mod::*; use alloc::string::{String, ToString};
pub use spacepackets::util::ToBeBytes; #[cfg(feature = "alloc")]
use alloc::vec::Vec;
/// Generic trait which is used for objects which can be converted into a raw network (big) endian /// Generic trait which is used for objects which can be converted into a raw network (big) endian
/// byte format. /// byte format.
@ -560,56 +557,64 @@ from_conversions_for_raw!(
(f64, Self::F64), (f64, Self::F64),
); );
#[cfg(feature = "alloc")] /// Generic enumeration for additional parameters, including parameters which rely on heap
mod alloc_mod { /// allocations.
use super::*; #[derive(Debug, Clone)]
/// Generic enumeration for additional parameters, including parameters which rely on heap #[non_exhaustive]
/// allocations. pub enum Params {
Heapless(ParamsHeapless),
Store(StoreAddr),
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone)] Vec(Vec<u8>),
pub enum Params { #[cfg(feature = "alloc")]
Heapless(ParamsHeapless), #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
Store(StoreAddr), String(String),
Vec(Vec<u8>), }
String(String),
}
impl From<StoreAddr> for Params { impl From<StoreAddr> for Params {
fn from(x: StoreAddr) -> Self { fn from(x: StoreAddr) -> Self {
Self::Store(x) Self::Store(x)
}
} }
}
impl From<ParamsHeapless> for Params { impl From<ParamsHeapless> for Params {
fn from(x: ParamsHeapless) -> Self { fn from(x: ParamsHeapless) -> Self {
Self::Heapless(x) Self::Heapless(x)
}
} }
}
impl From<Vec<u8>> for Params { #[cfg(feature = "alloc")]
fn from(val: Vec<u8>) -> Self { #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
Self::Vec(val) impl From<Vec<u8>> for Params {
} fn from(val: Vec<u8>) -> Self {
Self::Vec(val)
} }
}
/// Converts a byte slice into the [Params::Vec] variant /// Converts a byte slice into the [Params::Vec] variant
impl From<&[u8]> for Params { #[cfg(feature = "alloc")]
fn from(val: &[u8]) -> Self { #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
Self::Vec(val.to_vec()) impl From<&[u8]> for Params {
} fn from(val: &[u8]) -> Self {
Self::Vec(val.to_vec())
} }
}
impl From<String> for Params { #[cfg(feature = "alloc")]
fn from(val: String) -> Self { #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
Self::String(val) impl From<String> for Params {
} fn from(val: String) -> Self {
Self::String(val)
} }
}
/// Converts a string slice into the [Params::String] variant #[cfg(feature = "alloc")]
impl From<&str> for Params { #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
fn from(val: &str) -> Self { /// Converts a string slice into the [Params::String] variant
Self::String(val.to_string()) impl From<&str> for Params {
} fn from(val: &str) -> Self {
Self::String(val.to_string())
} }
} }

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 [VerificationReportingProvider] instance is passed to the user to also allow handling
/// of the verification process as part of the PUS standard requirements.
pub trait PusActionToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, ActionRequest), Self::Error>;
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pus::{
verification::VerificationReportingProvider, EcssTcInMemConverter, GenericRoutingError,
PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceBase,
PusServiceHelper,
};
use super::*;
/// This is a high-level handler for the PUS service 8 action service.
///
/// It performs the following handling steps:
///
/// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
/// allows to configure the used telecommand memory backend.
/// 2. Convert the TC to a targeted action request using the provided
/// [PusActionToRequestConverter]. The generic error type is constrained to the
/// [PusPacketHandlingError] for the concrete implementation which offers a packet handler.
/// 3. Route the action request using the provided [PusActionRequestRouter].
/// 4. Handle all routing errors using the provided [PusRoutingErrorHandler].
pub struct PusService8ActionHandler<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusActionToRequestConverter,
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError = GenericRoutingError,
> {
service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
pub request_converter: RequestConverter,
pub request_router: RequestRouter,
pub routing_error_handler: RoutingErrorHandler,
}
impl<
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusActionToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError: Clone,
>
PusService8ActionHandler<
TcInMemConverter,
VerificationReporter,
RequestConverter,
RequestRouter,
RoutingErrorHandler,
RoutingError,
>
where
PusPacketHandlingError: From<RoutingError>,
{
pub fn new(
service_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
) -> Self {
Self {
service_helper,
request_converter,
request_router,
routing_error_handler,
}
}
/// Core function to poll the next TC packet and try to handle it.
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let mut partial_error = None;
let time_stamp =
PusServiceBase::<VerificationReporter>::get_current_cds_short_timestamp(
&mut partial_error,
);
let (target_id, action_request) = self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
&time_stamp,
&self.service_helper.common.verification_handler,
)?;
if let Err(e) =
self.request_router
.route(target_id, action_request, ecss_tc_and_token.token)
{
self.routing_error_handler.handle_error(
target_id,
ecss_tc_and_token.token,
&tc,
e.clone(),
&time_stamp,
&self.service_helper.common.verification_handler,
);
return Err(e.into());
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
}
#[cfg(test)]
mod tests {
use delegate::delegate;
use spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
tm::PusTmReader,
PusPacket,
},
CcsdsPacket, SequenceFlags, SpHeader,
};
use crate::pus::{
tests::{
PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler, TestConverter,
TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
},
verification::{
tests::TestVerificationReporter, FailParams, RequestId, VerificationReportingProvider,
},
EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError,
};
use super::*;
impl PusActionRequestRouter for TestRouter<ActionRequest> {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
hk_request: ActionRequest,
_token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
self.routing_requests
.borrow_mut()
.push_back((target_id, hk_request));
self.check_for_injected_error()
}
}
impl PusActionToRequestConverter for TestConverter<8> {
type Error = PusPacketHandlingError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, ActionRequest), Self::Error> {
self.conversion_request.push_back(tc.raw_data().to_vec());
self.check_service(tc)?;
let target_id = tc.apid();
if tc.user_data().len() < 4 {
verif_reporter
.start_failure(
token,
FailParams::new(
time_stamp,
&APP_DATA_TOO_SHORT,
(tc.user_data().len() as u32).to_be_bytes().as_ref(),
),
)
.expect("start success failure");
return Err(PusPacketHandlingError::NotEnoughAppData {
expected: 4,
found: tc.user_data().len(),
});
}
if tc.subservice() == 1 {
verif_reporter
.start_success(token, time_stamp)
.expect("start success failure");
return Ok((
target_id.into(),
ActionRequest::UnsignedIdAndVecData {
action_id: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
data: tc.user_data()[4..].to_vec(),
},
));
}
Err(PusPacketHandlingError::InvalidAppData(
"unexpected app data".into(),
))
}
}
struct Pus8HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon<TestVerificationReporter>,
handler: PusService8ActionHandler<
EcssTcInVecConverter,
TestVerificationReporter,
TestConverter<8>,
TestRouter<ActionRequest>,
TestRoutingErrorHandler,
>,
}
impl Pus8HandlerWithVecTester {
pub fn new() -> Self {
let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
Self {
common,
handler: PusService8ActionHandler::new(
srv_handler,
TestConverter::default(),
TestRouter::default(),
TestRoutingErrorHandler::default(),
),
}
}
delegate! {
to self.handler.request_converter {
pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
}
}
delegate! {
to self.handler.request_router {
pub fn retrieve_next_request(&mut self) -> (TargetId, ActionRequest);
}
}
delegate! {
to self.handler.routing_error_handler {
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
}
}
}
impl PusTestHarness for Pus8HandlerWithVecTester {
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(
&self,
subservice: u8,
expected_request_id: RequestId,
);
}
}
}
impl SimplePusPacketHandler for Pus8HandlerWithVecTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
}
}
#[test]
fn basic_test() {
let mut action_handler = Pus8HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
let action_id: u32 = 1;
let action_id_raw = action_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
action_handler.send_tc(&tc);
let result = action_handler.handle_one_tc();
assert!(result.is_ok());
action_handler.check_next_conversion(&tc);
let (target_id, action_req) = action_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
assert_eq!(action_id, 1);
assert_eq!(data, &[]);
}
}
#[test]
fn test_routing_error() {
let mut action_handler = Pus8HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
let action_id: u32 = 1;
let action_id_raw = action_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
let error = GenericRoutingError::UnknownTargetId(25);
action_handler
.handler
.request_router
.inject_routing_error(error);
action_handler.send_tc(&tc);
let result = action_handler.handle_one_tc();
assert!(result.is_err());
let check_error = |routing_error: GenericRoutingError| {
if let GenericRoutingError::UnknownTargetId(id) = routing_error {
assert_eq!(id, 25);
} else {
panic!("unexpected error type");
}
};
if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
check_error(routing_error);
} else {
panic!("unexpected error type");
}
action_handler.check_next_conversion(&tc);
let (target_id, action_req) = action_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
assert_eq!(action_id, 1);
assert_eq!(data, &[]);
}
let (target_id, found_error) = action_handler.retrieve_next_error();
assert_eq!(target_id, TEST_APID.into());
check_error(found_error);
}
}

View File

@ -269,7 +269,7 @@ mod tests {
} }
impl EcssChannel for TestSender { impl EcssChannel for TestSender {
fn id(&self) -> ChannelId { fn channel_id(&self) -> ChannelId {
0 0
} }
} }

View File

@ -2,8 +2,6 @@ use crate::events::{EventU32, GenericEvent, Severity};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use crate::events::{EventU32TypedSev, HasSeverity}; use crate::events::{EventU32TypedSev, HasSeverity};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use core::hash::Hash; use core::hash::Hash;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use hashbrown::HashSet; use hashbrown::HashSet;
@ -32,19 +30,19 @@ pub use heapless_mod::*;
/// structure to track disabled events. A more primitive and embedded friendly /// structure to track disabled events. A more primitive and embedded friendly
/// solution could track this information in a static or pre-allocated list which contains /// solution could track this information in a static or pre-allocated list which contains
/// the disabled events. /// the disabled events.
pub trait PusEventMgmtBackendProvider<Provider: GenericEvent> { pub trait PusEventMgmtBackendProvider<Event: GenericEvent> {
type Error; type Error;
fn event_enabled(&self, event: &Provider) -> bool; fn event_enabled(&self, event: &Event) -> bool;
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>; fn enable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>; fn disable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error>;
} }
#[cfg(feature = "heapless")] #[cfg(feature = "heapless")]
pub mod heapless_mod { pub mod heapless_mod {
use super::*; use super::*;
use crate::events::{GenericEvent, LargestEventRaw}; use crate::events::LargestEventRaw;
use std::marker::PhantomData; use core::marker::PhantomData;
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
// TODO: After a new version of heapless is released which uses hash32 version 0.3, try using // TODO: After a new version of heapless is released which uses hash32 version 0.3, try using
@ -108,6 +106,10 @@ impl From<EcssTmtcError> for EventManError {
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod alloc_mod { pub mod alloc_mod {
use core::marker::PhantomData;
use crate::events::EventU16;
use super::*; use super::*;
/// Default backend provider which uses a hash set as the event reporting status container /// Default backend provider which uses a hash set as the event reporting status container
@ -115,14 +117,11 @@ pub mod alloc_mod {
/// ///
/// This provider is a good option for host systems or larger embedded systems where /// This provider is a good option for host systems or larger embedded systems where
/// the expected occasional memory allocation performed by the [HashSet] is not an issue. /// the expected occasional memory allocation performed by the [HashSet] is not an issue.
pub struct DefaultPusMgmtBackendProvider<Event: GenericEvent = EventU32> { pub struct DefaultPusEventMgmtBackend<Event: GenericEvent = EventU32> {
disabled: HashSet<Event>, disabled: HashSet<Event>,
} }
/// Safety: All contained field are [Send] as well impl<Event: GenericEvent> Default for DefaultPusEventMgmtBackend<Event> {
unsafe impl<Event: GenericEvent + Send> Send for DefaultPusMgmtBackendProvider<Event> {}
impl<Event: GenericEvent> Default for DefaultPusMgmtBackendProvider<Event> {
fn default() -> Self { fn default() -> Self {
Self { Self {
disabled: HashSet::default(), disabled: HashSet::default(),
@ -130,46 +129,50 @@ pub mod alloc_mod {
} }
} }
impl<Provider: GenericEvent + PartialEq + Eq + Hash + Copy + Clone> impl<EV: GenericEvent + PartialEq + Eq + Hash + Copy + Clone> PusEventMgmtBackendProvider<EV>
PusEventMgmtBackendProvider<Provider> for DefaultPusMgmtBackendProvider<Provider> for DefaultPusEventMgmtBackend<EV>
{ {
type Error = (); type Error = ();
fn event_enabled(&self, event: &Provider) -> bool {
fn event_enabled(&self, event: &EV) -> bool {
!self.disabled.contains(event) !self.disabled.contains(event)
} }
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> { fn enable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
Ok(self.disabled.remove(event)) Ok(self.disabled.remove(event))
} }
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> { fn disable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
Ok(self.disabled.insert(*event)) Ok(self.disabled.insert(*event))
} }
} }
pub struct PusEventDispatcher<BackendError, Provider: GenericEvent> { pub struct PusEventDispatcher<
B: PusEventMgmtBackendProvider<EV, Error = E>,
EV: GenericEvent,
E,
> {
reporter: EventReporter, reporter: EventReporter,
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>, backend: B,
phantom: PhantomData<(E, EV)>,
} }
/// Safety: All contained fields are send as well. impl<B: PusEventMgmtBackendProvider<EV, Error = E>, EV: GenericEvent, E>
unsafe impl<E: Send, Event: GenericEvent + Send> Send for PusEventDispatcher<E, Event> {} PusEventDispatcher<B, EV, E>
{
impl<BackendError, Provider: GenericEvent> PusEventDispatcher<BackendError, Provider> { pub fn new(reporter: EventReporter, backend: B) -> Self {
pub fn new( Self {
reporter: EventReporter, reporter,
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>, backend,
) -> Self { phantom: PhantomData,
Self { reporter, backend } }
} }
}
impl<BackendError, Event: GenericEvent> PusEventDispatcher<BackendError, Event> { pub fn enable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
pub fn enable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> {
self.backend.enable_event_reporting(event) self.backend.enable_event_reporting(event)
} }
pub fn disable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> { pub fn disable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
self.backend.disable_event_reporting(event) self.backend.disable_event_reporting(event)
} }
@ -177,7 +180,7 @@ pub mod alloc_mod {
&mut self, &mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized), sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8], time_stamp: &[u8],
event: Event, event: EV,
aux_data: Option<&[u8]>, aux_data: Option<&[u8]>,
) -> Result<bool, EventManError> { ) -> Result<bool, EventManError> {
if !self.backend.event_enabled(&event) { if !self.backend.event_enabled(&event) {
@ -208,18 +211,30 @@ pub mod alloc_mod {
} }
} }
impl<BackendError> PusEventDispatcher<BackendError, EventU32> { impl<EV: GenericEvent + Copy + PartialEq + Eq + Hash>
PusEventDispatcher<DefaultPusEventMgmtBackend<EV>, EV, ()>
{
pub fn new_with_default_backend(reporter: EventReporter) -> Self {
Self {
reporter,
backend: DefaultPusEventMgmtBackend::default(),
phantom: PhantomData,
}
}
}
impl<B: PusEventMgmtBackendProvider<EventU32, Error = E>, E> PusEventDispatcher<B, EventU32, E> {
pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>( pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>(
&mut self, &mut self,
event: &EventU32TypedSev<Severity>, event: &EventU32TypedSev<Severity>,
) -> Result<bool, BackendError> { ) -> Result<bool, E> {
self.backend.enable_event_reporting(event.as_ref()) self.backend.enable_event_reporting(event.as_ref())
} }
pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>( pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>(
&mut self, &mut self,
event: &EventU32TypedSev<Severity>, event: &EventU32TypedSev<Severity>,
) -> Result<bool, BackendError> { ) -> Result<bool, E> {
self.backend.disable_event_reporting(event.as_ref()) self.backend.disable_event_reporting(event.as_ref())
} }
@ -233,30 +248,38 @@ pub mod alloc_mod {
self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data) self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data)
} }
} }
pub type DefaultPusEventU16Dispatcher<E> =
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU16>, EventU16, E>;
pub type DefaultPusEventU32Dispatcher<E> =
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU32>, EventU32, E>;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::events::SeverityInfo; use crate::{events::SeverityInfo, pus::TmAsVecSenderWithMpsc};
use crate::pus::MpscTmAsVecSender; use std::sync::mpsc::{self, TryRecvError};
use std::sync::mpsc::{channel, TryRecvError};
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
EventU32TypedSev::<SeverityInfo>::const_new(1, 0); EventU32TypedSev::<SeverityInfo>::const_new(1, 0);
const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5); const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5);
const EMPTY_STAMP: [u8; 7] = [0; 7]; const EMPTY_STAMP: [u8; 7] = [0; 7];
fn create_basic_man() -> PusEventDispatcher<(), EventU32> { fn create_basic_man_1() -> DefaultPusEventU32Dispatcher<()> {
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed"); let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
let backend = DefaultPusMgmtBackendProvider::<EventU32>::default(); PusEventDispatcher::new_with_default_backend(reporter)
PusEventDispatcher::new(reporter, Box::new(backend)) }
fn create_basic_man_2() -> DefaultPusEventU32Dispatcher<()> {
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
let backend = DefaultPusEventMgmtBackend::default();
PusEventDispatcher::new(reporter, backend)
} }
#[test] #[test]
fn test_basic() { fn test_basic() {
let mut event_man = create_basic_man(); let mut event_man = create_basic_man_1();
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = mpsc::channel();
let mut sender = MpscTmAsVecSender::new(0, "test_sender", event_tx); let mut sender = TmAsVecSenderWithMpsc::new(0, "test_sender", event_tx);
let event_sent = event_man let event_sent = event_man
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None) .generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
.expect("Sending info event failed"); .expect("Sending info event failed");
@ -268,9 +291,9 @@ mod tests {
#[test] #[test]
fn test_disable_event() { fn test_disable_event() {
let mut event_man = create_basic_man(); let mut event_man = create_basic_man_2();
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = mpsc::channel();
let mut sender = MpscTmAsVecSender::new(0, "test", event_tx); let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT); let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT);
assert!(res.is_ok()); assert!(res.is_ok());
assert!(res.unwrap()); assert!(res.unwrap());
@ -291,9 +314,9 @@ mod tests {
#[test] #[test]
fn test_reenable_event() { fn test_reenable_event() {
let mut event_man = create_basic_man(); let mut event_man = create_basic_man_1();
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = mpsc::channel();
let mut sender = MpscTmAsVecSender::new(0, "test", event_tx); let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT); let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT);
assert!(res.is_ok()); assert!(res.is_ok());
assert!(res.unwrap()); assert!(res.unwrap());

View File

@ -6,20 +6,28 @@ 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_helper: PusServiceHelper<TcInMemConverter, VerificationReporter>,
event_request_tx: Sender<EventRequestWithToken>, event_request_tx: Sender<EventRequestWithToken>,
) -> Self { ) -> Self {
Self { Self {
service_helper: service_handler, service_helper,
event_request_tx, event_request_tx,
} }
} }
@ -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,9 @@ 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, VerificationReporterWithSharedPoolMpscBoundedSender,
};
use crate::{ use crate::{
events::EventU32, events::EventU32,
pus::{ pus::{
@ -145,7 +157,10 @@ mod tests {
struct Pus5HandlerWithStoreTester { struct Pus5HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon, common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService5EventHandler<EcssTcInSharedStoreConverter>, handler: PusService5EventHandler<
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
>,
} }
impl Pus5HandlerWithStoreTester { impl Pus5HandlerWithStoreTester {
@ -271,8 +286,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 [VerificationReportingProvider] 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
/// [PusHkToRequestConverter]. 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 [PusHkRequestRouter]. 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -340,18 +340,18 @@ pub fn generate_insert_telecommand_app_data(
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod alloc_mod { pub mod alloc_mod {
use alloc::{
collections::{
btree_map::{Entry, Range},
BTreeMap,
},
vec::Vec,
};
use spacepackets::time::cds::{self, DaysLen24Bits};
use crate::pool::StoreAddr;
use super::*; use super::*;
use crate::pool::{PoolProvider, StoreAddr, StoreError};
use alloc::collections::btree_map::{Entry, Range};
use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;
use spacepackets::ecss::scheduling::TimeWindowType;
use spacepackets::ecss::tc::{PusTc, PusTcReader};
use spacepackets::ecss::PusPacket;
use spacepackets::time::cds::DaysLen24Bits;
use spacepackets::time::{cds, CcsdsTimeProvider, UnixTimestamp};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::SystemTimeError; use std::time::SystemTimeError;
@ -461,7 +461,7 @@ pub mod alloc_mod {
} }
match self.tc_map.entry(time_stamp) { match self.tc_map.entry(time_stamp) {
Entry::Vacant(e) => { Entry::Vacant(e) => {
e.insert(vec![info]); e.insert(alloc::vec![info]);
} }
Entry::Occupied(mut v) => { Entry::Occupied(mut v) => {
v.get_mut().push(info); v.get_mut().push(info);
@ -498,7 +498,7 @@ pub mod alloc_mod {
/// short timestamp with 16-bit length of days field. /// short timestamp with 16-bit length of days field.
pub fn insert_wrapped_tc_cds_short( pub fn insert_wrapped_tc_cds_short(
&mut self, &mut self,
pus_tc: &PusTc, pus_tc: &(impl IsPusTelecommand + PusPacket + GenericPusTcSecondaryHeader),
pool: &mut (impl PoolProvider + ?Sized), pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> { ) -> Result<TcInfo, ScheduleError> {
self.insert_wrapped_tc::<cds::TimeProvider>(pus_tc, pool) self.insert_wrapped_tc::<cds::TimeProvider>(pus_tc, pool)
@ -508,7 +508,7 @@ pub mod alloc_mod {
/// long timestamp with a 24-bit length of days field. /// long timestamp with a 24-bit length of days field.
pub fn insert_wrapped_tc_cds_long( pub fn insert_wrapped_tc_cds_long(
&mut self, &mut self,
pus_tc: &PusTc, pus_tc: &(impl IsPusTelecommand + PusPacket + GenericPusTcSecondaryHeader),
pool: &mut (impl PoolProvider + ?Sized), pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> { ) -> Result<TcInfo, ScheduleError> {
self.insert_wrapped_tc::<cds::TimeProvider<DaysLen24Bits>>(pus_tc, pool) self.insert_wrapped_tc::<cds::TimeProvider<DaysLen24Bits>>(pus_tc, pool)
@ -530,7 +530,7 @@ pub mod alloc_mod {
let range = self.retrieve_by_time_filter(time_window); let range = self.retrieve_by_time_filter(time_window);
let mut del_packets = 0; let mut del_packets = 0;
let mut res_if_fails = None; let mut res_if_fails = None;
let mut keys_to_delete = Vec::new(); let mut keys_to_delete = alloc::vec::Vec::new();
for time_bucket in range { for time_bucket in range {
for tc in time_bucket.1 { for tc in time_bucket.1 {
match pool.delete(tc.addr) { match pool.delete(tc.addr) {
@ -561,7 +561,10 @@ pub mod alloc_mod {
} }
/// Retrieve a range over all scheduled commands. /// Retrieve a range over all scheduled commands.
pub fn retrieve_all(&mut self) -> Range<'_, UnixTimestamp, Vec<TcInfo>> { pub fn retrieve_all(
&mut self,
) -> alloc::collections::btree_map::Range<'_, UnixTimestamp, alloc::vec::Vec<TcInfo>>
{
self.tc_map.range(..) self.tc_map.range(..)
} }
@ -572,7 +575,7 @@ pub mod alloc_mod {
pub fn retrieve_by_time_filter<TimeProvider: CcsdsTimeProvider>( pub fn retrieve_by_time_filter<TimeProvider: CcsdsTimeProvider>(
&mut self, &mut self,
time_window: TimeWindow<TimeProvider>, time_window: TimeWindow<TimeProvider>,
) -> Range<'_, UnixTimestamp, Vec<TcInfo>> { ) -> Range<'_, UnixTimestamp, alloc::vec::Vec<TcInfo>> {
match time_window.time_window_type() { match time_window.time_window_type() {
TimeWindowType::SelectAll => self.tc_map.range(..), TimeWindowType::SelectAll => self.tc_map.range(..),
TimeWindowType::TimeTagToTimeTag => { TimeWindowType::TimeTagToTimeTag => {
@ -761,9 +764,9 @@ pub mod alloc_mod {
mut releaser: R, mut releaser: R,
tc_store: &(impl PoolProvider + ?Sized), tc_store: &(impl PoolProvider + ?Sized),
tc_buf: &mut [u8], tc_buf: &mut [u8],
) -> Result<Vec<TcInfo>, (Vec<TcInfo>, StoreError)> { ) -> Result<alloc::vec::Vec<TcInfo>, (alloc::vec::Vec<TcInfo>, StoreError)> {
let tcs_to_release = self.telecommands_to_release(); let tcs_to_release = self.telecommands_to_release();
let mut released_tcs = Vec::new(); let mut released_tcs = alloc::vec::Vec::new();
for tc in tcs_to_release { for tc in tcs_to_release {
for info in tc.1 { for info in tc.1 {
tc_store tc_store
@ -835,7 +838,7 @@ pub mod alloc_mod {
} }
match self.tc_map.entry(time_stamp) { match self.tc_map.entry(time_stamp) {
Entry::Vacant(e) => { Entry::Vacant(e) => {
e.insert(vec![info]); e.insert(alloc::vec![info]);
} }
Entry::Occupied(mut v) => { Entry::Occupied(mut v) => {
v.get_mut().push(info); v.get_mut().push(info);

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::VerificationReporterWithSharedPoolMpscBoundedSender;
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,
VerificationReporterWithSharedPoolMpscBoundedSender,
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,6 +104,9 @@ mod tests {
PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness, PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness,
SimplePusPacketHandler, TEST_APID, SimplePusPacketHandler, TEST_APID,
}; };
use crate::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use crate::pus::verification::RequestId; use crate::pus::verification::RequestId;
use crate::pus::verification::{TcStateAccepted, VerificationToken}; use crate::pus::verification::{TcStateAccepted, VerificationToken};
use crate::pus::{ use crate::pus::{
@ -111,7 +123,10 @@ mod tests {
struct Pus17HandlerWithStoreTester { struct Pus17HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon, common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService17TestHandler<EcssTcInSharedStoreConverter>, handler: PusService17TestHandler<
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
>,
} }
impl Pus17HandlerWithStoreTester { impl Pus17HandlerWithStoreTester {
@ -148,13 +163,15 @@ mod tests {
} }
struct Pus17HandlerWithVecTester { struct Pus17HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon, common: PusServiceHandlerWithVecCommon<VerificationReporterWithVecMpscSender>,
handler: PusService17TestHandler<EcssTcInVecConverter>, handler:
PusService17TestHandler<EcssTcInVecConverter, VerificationReporterWithVecMpscSender>,
} }
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),

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,85 @@
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::sync::mpsc;
/// 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 {}
#[cfg(feature = "std")]
impl<T> From<mpsc::SendError<T>> for GenericSendError {
fn from(_: mpsc::SendError<T>) -> Self {
GenericSendError::RxDisconnected
}
}
#[cfg(feature = "std")]
impl<T> From<mpsc::TrySendError<T>> for GenericSendError {
fn from(err: mpsc::TrySendError<T>) -> Self {
match err {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),
mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
}
}
}
#[cfg(feature = "crossbeam")]
impl<T> From<crossbeam_channel::SendError<T>> for GenericSendError {
fn from(_: crossbeam_channel::SendError<T>) -> Self {
GenericSendError::RxDisconnected
}
}
#[cfg(feature = "crossbeam")]
impl<T> From<crossbeam_channel::TrySendError<T>> for GenericSendError {
fn from(err: crossbeam_channel::TrySendError<T>) -> Self {
match err {
crossbeam_channel::TrySendError::Full(_) => GenericSendError::QueueFull(None),
crossbeam_channel::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
}
}
}

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,14 +1,14 @@
use satrs::event_man::{ use satrs::event_man::{
EventManagerWithMpscQueue, MpscEventU32Receiver, MpscEventU32SendProvider, SendEventProvider, EventManagerWithMpsc, EventSendProvider, EventU32SenderMpsc, MpscEventU32Receiver,
}; };
use satrs::events::{EventU32, EventU32TypedSev, Severity, SeverityInfo}; use satrs::events::{EventU32, EventU32TypedSev, Severity, SeverityInfo};
use satrs::params::U32Pair; use satrs::params::U32Pair;
use satrs::params::{Params, ParamsHeapless, WritableToBeBytes}; use satrs::params::{Params, ParamsHeapless, WritableToBeBytes};
use satrs::pus::event_man::{DefaultPusMgmtBackendProvider, EventReporter, PusEventDispatcher}; use satrs::pus::event_man::{DefaultPusEventMgmtBackend, EventReporter, PusEventDispatcher};
use satrs::pus::MpscTmAsVecSender; use satrs::pus::TmAsVecSenderWithMpsc;
use spacepackets::ecss::tm::PusTmReader; use spacepackets::ecss::tm::PusTmReader;
use spacepackets::ecss::{PusError, PusPacket}; use spacepackets::ecss::{PusError, PusPacket};
use std::sync::mpsc::{channel, SendError, TryRecvError}; use std::sync::mpsc::{self, SendError, TryRecvError};
use std::thread; use std::thread;
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
@ -24,21 +24,21 @@ pub enum CustomTmSenderError {
#[test] #[test]
fn test_threaded_usage() { fn test_threaded_usage() {
let (event_sender, event_man_receiver) = channel(); let (event_sender, event_man_receiver) = mpsc::channel();
let event_receiver = MpscEventU32Receiver::new(event_man_receiver); let event_receiver = MpscEventU32Receiver::new(event_man_receiver);
let mut event_man = EventManagerWithMpscQueue::new(Box::new(event_receiver)); let mut event_man = EventManagerWithMpsc::new(event_receiver);
let (pus_event_man_tx, pus_event_man_rx) = channel(); let (pus_event_man_tx, pus_event_man_rx) = mpsc::channel();
let pus_event_man_send_provider = MpscEventU32SendProvider::new(1, pus_event_man_tx); let pus_event_man_send_provider = EventU32SenderMpsc::new(1, pus_event_man_tx);
event_man.subscribe_all(pus_event_man_send_provider.id()); event_man.subscribe_all(pus_event_man_send_provider.channel_id());
event_man.add_sender(pus_event_man_send_provider); event_man.add_sender(pus_event_man_send_provider);
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = mpsc::channel();
let reporter = EventReporter::new(0x02, 128).expect("Creating event reporter failed"); let reporter = EventReporter::new(0x02, 128).expect("Creating event reporter failed");
let backend = DefaultPusMgmtBackendProvider::<EventU32>::default(); let mut pus_event_man =
let mut pus_event_man = PusEventDispatcher::new(reporter, Box::new(backend)); PusEventDispatcher::new(reporter, DefaultPusEventMgmtBackend::default());
// PUS + Generic event manager thread // PUS + Generic event manager thread
let jh0 = thread::spawn(move || { let jh0 = thread::spawn(move || {
let mut sender = MpscTmAsVecSender::new(0, "event_sender", event_tx); let mut sender = TmAsVecSenderWithMpsc::new(0, "event_sender", event_tx);
let mut event_cnt = 0; let mut event_cnt = 0;
let mut params_array: [u8; 128] = [0; 128]; let mut params_array: [u8; 128] = [0; 128];
loop { loop {
@ -71,6 +71,7 @@ fn test_threaded_usage() {
Params::Vec(vec) => gen_event(Some(vec.as_slice())), Params::Vec(vec) => gen_event(Some(vec.as_slice())),
Params::String(str) => gen_event(Some(str.as_bytes())), Params::String(str) => gen_event(Some(str.as_bytes())),
Params::Store(_) => gen_event(None), Params::Store(_) => gen_event(None),
_ => panic!("unsupported parameter type"),
} }
} else { } else {
gen_event(None) gen_event(None)
@ -120,10 +121,7 @@ fn test_threaded_usage() {
} }
} }
event_sender event_sender
.send(( .send((LOW_SEV_EVENT, Some(Params::Heapless((2_u32, 3_u32).into()))))
LOW_SEV_EVENT.into(),
Some(Params::Heapless((2_u32, 3_u32).into())),
))
.expect("Sending low severity event failed"); .expect("Sending low severity event failed");
loop { loop {
match event_rx.try_recv() { match event_rx.try_recv() {

View File

@ -1,11 +1,12 @@
//#[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::TmInSharedPoolSenderWithCrossbeam;
use satrs::tmtc::tm_helper::SharedTmPool; use satrs::tmtc::tm_helper::SharedTmPool;
use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader}; use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};
use spacepackets::ecss::tm::PusTmReader; use spacepackets::ecss::tm::PusTmReader;
@ -39,10 +40,13 @@ pub mod crossbeam_test {
let shared_tc_pool_0 = Arc::new(RwLock::new(StaticMemoryPool::new(pool_cfg))); let shared_tc_pool_0 = Arc::new(RwLock::new(StaticMemoryPool::new(pool_cfg)));
let shared_tc_pool_1 = shared_tc_pool_0.clone(); let shared_tc_pool_1 = shared_tc_pool_0.clone();
let (tx, rx) = crossbeam_channel::bounded(10); let (tx, rx) = crossbeam_channel::bounded(10);
let sender = let sender = TmInSharedPoolSenderWithCrossbeam::new(
CrossbeamTmInStoreSender::new(0, "verif_sender", shared_tm_pool.clone(), tx.clone()); 0,
let mut reporter_with_sender_0 = "verif_sender",
VerificationReporterWithSender::new(&cfg, Box::new(sender)); shared_tm_pool.clone(),
tx.clone(),
);
let mut reporter_with_sender_0 = VerificationReporterWithSender::new(&cfg, sender);
let mut reporter_with_sender_1 = reporter_with_sender_0.clone(); let mut reporter_with_sender_1 = reporter_with_sender_0.clone();
// For test purposes, we retrieve the request ID from the TCs and pass them to the receiver // For test purposes, we retrieve the request ID from the TCs and pass them to the receiver
// tread. // tread.
@ -89,24 +93,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 +128,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");