re-add satrs-core without git submoudule
This commit is contained in:
parent
eeccc66f0a
commit
a665b04844
5
satrs-core/.gitignore
vendored
Normal file
5
satrs-core/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/target
|
||||
|
||||
# CLion
|
||||
/.idea/*
|
||||
!/.idea/runConfigurations
|
9
satrs-core/CHANGELOG.md
Normal file
9
satrs-core/CHANGELOG.md
Normal file
@ -0,0 +1,9 @@
|
||||
Change Log
|
||||
=======
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
94
satrs-core/Cargo.toml
Normal file
94
satrs-core/Cargo.toml
Normal file
@ -0,0 +1,94 @@
|
||||
[package]
|
||||
name = "satrs-core"
|
||||
version = "0.1.0-alpha.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = "Core components of the sat-rs framework to build software for remote systems"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/satrs-core"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/satrs-core"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "space", "aerospace"]
|
||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
delegate = "0.8"
|
||||
paste = "1.0"
|
||||
embed-doc-image = "0.1"
|
||||
|
||||
[dependencies.dyn-clone]
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.hashbrown]
|
||||
version = "0.13"
|
||||
optional = true
|
||||
|
||||
[dependencies.heapless]
|
||||
version = "0.7"
|
||||
optional = true
|
||||
|
||||
[dependencies.num-traits]
|
||||
version = "0.2"
|
||||
default-features = false
|
||||
|
||||
[dependencies.downcast-rs]
|
||||
version = "1.2"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.bus]
|
||||
version = "2.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.crossbeam-channel]
|
||||
version= "0.5"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.spacepackets]
|
||||
# git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git"
|
||||
version = "0.4.0"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1"
|
||||
zerocopy = "0.6"
|
||||
once_cell = "1.13"
|
||||
serde_json = "1"
|
||||
|
||||
[dev-dependencies.postcard]
|
||||
version = "1.0"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"downcast-rs/std",
|
||||
"alloc",
|
||||
"bus",
|
||||
"postcard/use-std",
|
||||
"crossbeam-channel/std",
|
||||
"serde/std",
|
||||
"spacepackets/std"
|
||||
]
|
||||
alloc = [
|
||||
"serde/alloc",
|
||||
"spacepackets/alloc",
|
||||
"hashbrown",
|
||||
"dyn-clone",
|
||||
"downcast-rs"
|
||||
]
|
||||
serde = ["dep:serde", "spacepackets/serde"]
|
||||
crossbeam = ["crossbeam-channel"]
|
||||
heapless = ["dep:heapless"]
|
||||
doc-images = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "doc_cfg"]
|
201
satrs-core/LICENSE-APACHE
Normal file
201
satrs-core/LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
1
satrs-core/NOTICE
Normal file
1
satrs-core/NOTICE
Normal file
@ -0,0 +1 @@
|
||||
This software contains code developed at the University of Stuttgart.
|
4
satrs-core/README.md
Normal file
4
satrs-core/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
satrs-core
|
||||
======
|
||||
|
||||
This crate contains the core components of the satrs framework.
|
259
satrs-core/images/event_man_arch.graphml
Normal file
259
satrs-core/images/event_man_arch.graphml
Normal file
@ -0,0 +1,259 @@
|
||||
<?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.22-->
|
||||
<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="509.9999999999999" width="768.7000000000003" x="579.3105418719211" y="304.7"/>
|
||||
<y:Fill hasColor="false" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.936037063598633" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="150.1282958984375" x="26.197490701913352" xml:space="preserve" y="24.234711021505348">Example Event Flow<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.46591974671274444" nodeRatioY="-0.452480958781362" 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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="203.0" x="814.0" y="506.6799999999999"/>
|
||||
<y:Fill color="#FFFF00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.21258544921875" x="58.393707275390625" xml:space="preserve" y="21.27395296096796">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">
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="82.0" x="617.6" y="413.23"/>
|
||||
<y:Fill color="#FF9900" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="13.4398193359375" xml:space="preserve" y="14.547905921936035">Event
|
||||
Creator 0<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="76.55999999999995" x="988.5" y="335.62999999999994"/>
|
||||
<y:Fill color="#FF9900" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="10.719819335937473" xml:space="preserve" y="14.547905921936035">Event
|
||||
Creator 2<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="72.55999999999983" x="860.6610837438426" y="335.62999999999994"/>
|
||||
<y:Fill color="#FF9900" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="8.719819335937359" xml:space="preserve" y="14.547905921936035">Event
|
||||
Creator 1<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="87.27999999999997" x="1112.52" y="335.62999999999994"/>
|
||||
<y:Fill color="#FF9900" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.120361328125" x="16.079819335937373" xml:space="preserve" y="14.547905921936035">Event
|
||||
Creator 3<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="126.0" x="781.0" y="620.26"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.78865051269531" x="16.605674743652344" xml:space="preserve" y="14.547905921936035">PUS Service 5
|
||||
Event Reporting
|
||||
<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="118.63999999999987" x="928.2" y="620.26"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.08859252929688" x="17.2757037353515" xml:space="preserve" y="14.547905921936035">PUS Service 19
|
||||
Event Action<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="60.0" width="87.27999999999997" x="792.1260377358491" y="733.8400000000001"/>
|
||||
<y:Fill color="#FFCC99" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="59.932403564453125" x="13.673798217773424" xml:space="preserve" y="14.547905921936035">Telemetry
|
||||
Sink<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="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="170.79999999999995" width="210.80000000000018" x="1076.84" y="601.88"/>
|
||||
<y:Fill hasColor="false" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="left" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="143.6875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="181.591796875" x="8.373079774614325" xml:space="preserve" y="7.444138124199753">Subscriptions
|
||||
|
||||
1. Event Creator 0 subscribes
|
||||
for event 0
|
||||
2. Event Creator 1 subscribes
|
||||
for event group 2
|
||||
3. PUS Service 5 handler
|
||||
subscribes for all events
|
||||
4. PUS Service 19 handler
|
||||
subscribes for all events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4602795077105583" nodeRatioY="-0.45641605313700395" 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="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="8.058916256157545" sy="0.0" tx="-10.5" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="8.639817810058275" xml:space="preserve" y="29.00100609374465">event 1
|
||||
(group 1)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="35.59999999999969" distanceToCenter="true" position="left" ratio="0.34252387409930674" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e1" source="n2" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="11.93999999999994" tx="-83.5" ty="0.0">
|
||||
<y:Point x="832.0" y="455.16999999999996"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="25.334655000000453" xml:space="preserve" y="-40.972107505798476">event 0
|
||||
(group 0)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="25.520000000000095" distanceToCenter="true" position="left" ratio="0.20267159489379444" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n3" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-23.719999999999914" sy="5.5" tx="87.56000000000006" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.92036437988281" x="5.6761352539062955" xml:space="preserve" y="27.551854405966765">event 2
|
||||
(group 3)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="5.676132812499983" distanceToCenter="false" position="left" ratio="0.3219761157957032" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e3" source="n5" target="n1">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-6.275467980295616" sy="0.0" tx="57.5" ty="8.5">
|
||||
<y:Point x="1149.8845320197042" y="545.1799999999998"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.38468933105469" x="26.667665869801795" xml:space="preserve" y="43.287014528669715">event 3 (group 2)
|
||||
event 4 (group 2)<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="75.3599999999999" distanceToCenter="true" position="left" ratio="0.2967848459873102" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e4" source="n1" target="n6">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-65.0" sy="0.0" tx="6.5" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="-98.78228302001958" xml:space="preserve" y="16.63042580701972"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="57.20000000000004" distanceToCenter="true" position="right" ratio="0.4441995640590947" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e5" source="n1" target="n7">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="42.660000000000196" sy="0.0" tx="-29.359999999999786" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.16456604003906" x="20.4177438354493" xml:space="preserve" y="17.885881494816203"><<all events>><y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="62.0" distanceToCenter="true" position="left" ratio="0.492249939452652" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e6" source="n1" target="n2">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
|
||||
<y:Point x="658.6" y="536.6799999999998"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.69230651855469" x="-131.99129340961497" xml:space="preserve" y="-45.45208675384538">event 1
|
||||
event 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6426904695623505" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e7" source="n1" target="n4">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-35.69940886699487" sy="0.0" tx="-17.140492610837327" ty="1.5"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.452094078063965" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.14430236816406" x="-54.352158195608126" xml:space="preserve" y="-79.29459128622307">group 2<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="31.279999999999973" distanceToCenter="true" position="left" ratio="0.6800790648728832" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e8" source="n6" target="n8">
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="8.233962264150945" ty="-21.42352238805968"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Ubuntu" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="30.90418815612793" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="87.40060424804688" x="-100.50030212402339" xml:space="preserve" y="11.337896156311103">enabled Events
|
||||
as PUS 5 TM<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="56.79999999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources/>
|
||||
</data>
|
||||
</graphml>
|
BIN
satrs-core/images/event_man_arch.png
Normal file
BIN
satrs-core/images/event_man_arch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
49
satrs-core/src/error.rs
Normal file
49
satrs-core/src/error.rs
Normal file
@ -0,0 +1,49 @@
|
||||
pub enum FsrcGroupIds {
|
||||
Tmtc = 0,
|
||||
}
|
||||
|
||||
pub struct FsrcErrorRaw {
|
||||
pub group_id: u8,
|
||||
pub unique_id: u8,
|
||||
pub group_name: &'static str,
|
||||
pub info: &'static str,
|
||||
}
|
||||
|
||||
pub trait FsrcErrorHandler {
|
||||
fn error(&mut self, e: FsrcErrorRaw);
|
||||
fn error_with_one_param(&mut self, e: FsrcErrorRaw, _p1: u32) {
|
||||
self.error(e);
|
||||
}
|
||||
fn error_with_two_params(&mut self, e: FsrcErrorRaw, _p1: u32, _p2: u32) {
|
||||
self.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
impl FsrcErrorRaw {
|
||||
pub const fn new(
|
||||
group_id: u8,
|
||||
unique_id: u8,
|
||||
group_name: &'static str,
|
||||
info: &'static str,
|
||||
) -> Self {
|
||||
FsrcErrorRaw {
|
||||
group_id,
|
||||
unique_id,
|
||||
group_name,
|
||||
info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct SimpleStdErrorHandler {}
|
||||
|
||||
#[cfg(feature = "use_std")]
|
||||
impl FsrcErrorHandler for SimpleStdErrorHandler {
|
||||
fn error(&mut self, e: FsrcErrorRaw) {
|
||||
println!(
|
||||
"Received error from group {} with ID ({},{}): {}",
|
||||
e.group_name, e.group_id, e.unique_id, e.info
|
||||
);
|
||||
}
|
||||
}
|
723
satrs-core/src/event_man.rs
Normal file
723
satrs-core/src/event_man.rs
Normal file
@ -0,0 +1,723 @@
|
||||
//! Event management and forwarding
|
||||
//!
|
||||
//! This module provides components to perform event routing. The most important component for this
|
||||
//! task is the [EventManager]. It receives all events and then routes them to event subscribers
|
||||
//! where appropriate.
|
||||
#![cfg_attr(feature = "doc-images",
|
||||
cfg_attr(all(),
|
||||
doc = ::embed_doc_image::embed_image!("event_man_arch", "images/event_man_arch.png"
|
||||
)))]
|
||||
#![cfg_attr(
|
||||
not(feature = "doc-images"),
|
||||
doc = "**Doc images not enabled**. Compile with feature `doc-images` and Rust version >= 1.54 \
|
||||
to enable."
|
||||
)]
|
||||
//! One common use case for satellite systems is to offer a light-weight publish-subscribe mechanism
|
||||
//! and IPC mechanism for software and hardware events which are also packaged as telemetry (TM) or
|
||||
//! can trigger a system response.
|
||||
//!
|
||||
//! The following graph shows how the event flow for such a setup could look like:
|
||||
//!
|
||||
//! ![Event flow][event_man_arch]
|
||||
//!
|
||||
//! The event manager has a listener table abstracted by the [ListenerTable], which maps
|
||||
//! listener groups identified by [ListenerKey]s to a [sender ID][SenderId].
|
||||
//! It also contains a sender table abstracted by the [SenderTable] which maps these sender IDs
|
||||
//! to a concrete [SendEventProvider]s. A simple approach would be to use one send event provider
|
||||
//! for each OBSW thread and then subscribe for all interesting events for a particular thread
|
||||
//! using the send event provider ID.
|
||||
//!
|
||||
//! This can be done with the [EventManager] like this:
|
||||
//!
|
||||
//! 1. Provide a concrete [EventReceiver] implementation. This abstraction allow to use different
|
||||
//! message queue backends. A straightforward implementation where dynamic memory allocation is
|
||||
//! not a big concern could use [std::sync::mpsc::channel] to do this and is provided in
|
||||
//! form of the [MpscEventReceiver].
|
||||
//! 2. To set up event creators, create channel pairs using some message queue implementation.
|
||||
//! Each event creator gets a (cloned) sender component which allows it to send events to the
|
||||
//! manager.
|
||||
//! 3. The event manager receives the receiver component as part of a [EventReceiver]
|
||||
//! implementation so all events are routed to the manager.
|
||||
//! 4. Create the [send event providers][SendEventProvider]s which allow routing events to
|
||||
//! subscribers. You can now use their [sender IDs][SendEventProvider::id] to subscribe for
|
||||
//! event groups, for example by using the [EventManager::subscribe_single] method.
|
||||
//! 5. Add the send provider as well using the [EventManager::add_sender] call so the event
|
||||
//! manager can route listener groups to a the send provider.
|
||||
//!
|
||||
//! Some components like a PUS Event Service or PUS Event Action Service might require all
|
||||
//! events to package them as telemetry or start actions where applicable.
|
||||
//! Other components might only be interested in certain events. For example, a thermal system
|
||||
//! handler might only be interested in temperature events generated by a thermal sensor component.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! You can check [integration test](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-core/tests/pus_events.rs)
|
||||
//! for a concrete example using multi-threading where events are routed to
|
||||
//! different threads.
|
||||
use crate::events::{EventU16, EventU32, GenericEvent, LargestEventRaw, LargestGroupIdRaw};
|
||||
use crate::params::{Params, ParamsHeapless};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
use core::slice::Iter;
|
||||
#[cfg(feature = "alloc")]
|
||||
use hashbrown::HashMap;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use stdmod::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
pub enum ListenerKey {
|
||||
Single(LargestEventRaw),
|
||||
Group(LargestGroupIdRaw),
|
||||
All,
|
||||
}
|
||||
|
||||
pub type EventWithHeaplessAuxData<Event> = (Event, Option<ParamsHeapless>);
|
||||
pub type EventU32WithHeaplessAuxData = EventWithHeaplessAuxData<EventU32>;
|
||||
pub type EventU16WithHeaplessAuxData = EventWithHeaplessAuxData<EventU16>;
|
||||
|
||||
pub type EventWithAuxData<Event> = (Event, Option<Params>);
|
||||
pub type EventU32WithAuxData = EventWithAuxData<EventU32>;
|
||||
pub type EventU16WithAuxData = EventWithAuxData<EventU16>;
|
||||
|
||||
pub type SenderId = u32;
|
||||
|
||||
pub trait SendEventProvider<Provider: GenericEvent, AuxDataProvider = Params> {
|
||||
type Error;
|
||||
|
||||
fn id(&self) -> SenderId;
|
||||
fn send_no_data(&mut self, event: Provider) -> Result<(), Self::Error> {
|
||||
self.send(event, None)
|
||||
}
|
||||
fn send(
|
||||
&mut self,
|
||||
event: Provider,
|
||||
aux_data: Option<AuxDataProvider>,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Generic abstraction for an event receiver.
|
||||
pub trait EventReceiver<Event: GenericEvent, AuxDataProvider = Params> {
|
||||
/// This function has to be provided by any event receiver. A receive call may or may not return
|
||||
/// an event.
|
||||
///
|
||||
/// To allow returning arbitrary additional auxiliary data, a mutable slice is passed to the
|
||||
/// [Self::receive] call as well. Receivers can write data to this slice, but care must be taken
|
||||
/// to avoid panics due to size missmatches or out of bound writes.
|
||||
fn receive(&mut self) -> Option<(Event, Option<AuxDataProvider>)>;
|
||||
}
|
||||
|
||||
pub trait ListenerTable {
|
||||
fn get_listeners(&self) -> Vec<ListenerKey>;
|
||||
fn contains_listener(&self, key: &ListenerKey) -> bool;
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<SenderId>>;
|
||||
fn add_listener(&mut self, key: ListenerKey, sender_id: SenderId) -> bool;
|
||||
fn remove_duplicates(&mut self, key: &ListenerKey);
|
||||
}
|
||||
|
||||
pub trait SenderTable<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params> {
|
||||
fn contains_send_event_provider(&self, id: &SenderId) -> bool;
|
||||
fn get_send_event_provider(
|
||||
&mut self,
|
||||
id: &SenderId,
|
||||
) -> Option<&mut Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>>;
|
||||
fn add_send_event_provider(
|
||||
&mut self,
|
||||
send_provider: Box<
|
||||
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
||||
>,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// Generic event manager implementation.
|
||||
///
|
||||
/// # Generics
|
||||
///
|
||||
/// * `SendProviderError`: [SendEventProvider] error type
|
||||
/// * `Event`: Concrete event provider, currently either [EventU32] or [EventU16]
|
||||
/// * `AuxDataProvider`: Concrete auxiliary data provider, currently either [Params] or
|
||||
/// [ParamsHeapless]
|
||||
pub struct EventManager<SendProviderError, Event: GenericEvent = EventU32, AuxDataProvider = Params>
|
||||
{
|
||||
listener_table: Box<dyn ListenerTable>,
|
||||
sender_table: Box<dyn SenderTable<SendProviderError, Event, AuxDataProvider>>,
|
||||
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
||||
}
|
||||
|
||||
/// Safety: It is safe to implement [Send] because all fields in the [EventManager] are [Send]
|
||||
/// as well
|
||||
#[cfg(feature = "std")]
|
||||
unsafe impl<E, Event: GenericEvent + Send, AuxDataProvider: Send> Send
|
||||
for EventManager<E, Event, AuxDataProvider>
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub type EventManagerWithMpscQueue<Event, AuxDataProvider> = EventManager<
|
||||
std::sync::mpsc::SendError<(Event, Option<AuxDataProvider>)>,
|
||||
Event,
|
||||
AuxDataProvider,
|
||||
>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventRoutingResult<Event: GenericEvent, AuxDataProvider> {
|
||||
/// No event was received
|
||||
Empty,
|
||||
/// An event was received and routed.
|
||||
/// The first tuple entry will contain the number of recipients.
|
||||
Handled(u32, Event, Option<AuxDataProvider>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventRoutingError<E> {
|
||||
SendError(E),
|
||||
NoSendersForKey(ListenerKey),
|
||||
NoSenderForId(SenderId),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventRoutingErrorsWithResult<Event: GenericEvent, AuxDataProvider, E> {
|
||||
pub result: EventRoutingResult<Event, AuxDataProvider>,
|
||||
pub errors: [Option<EventRoutingError<E>>; 3],
|
||||
}
|
||||
|
||||
impl<E, Event: GenericEvent + Copy> EventManager<E, Event> {
|
||||
pub fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||
self.listener_table.remove_duplicates(key)
|
||||
}
|
||||
|
||||
/// Subscribe for a unique event.
|
||||
pub fn subscribe_single(&mut self, event: &Event, sender_id: SenderId) {
|
||||
self.update_listeners(ListenerKey::Single(event.raw_as_largest_type()), sender_id);
|
||||
}
|
||||
|
||||
/// Subscribe for an event group.
|
||||
pub fn subscribe_group(&mut self, group_id: LargestGroupIdRaw, sender_id: SenderId) {
|
||||
self.update_listeners(ListenerKey::Group(group_id), sender_id);
|
||||
}
|
||||
|
||||
/// Subscribe for all events received by the manager.
|
||||
///
|
||||
/// For example, this can be useful for a handler component which sends every event as
|
||||
/// a telemetry packet.
|
||||
pub fn subscribe_all(&mut self, sender_id: SenderId) {
|
||||
self.update_listeners(ListenerKey::All, sender_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static, Event: GenericEvent + Copy + 'static, AuxDataProvider: Clone + 'static>
|
||||
EventManager<E, Event, AuxDataProvider>
|
||||
{
|
||||
/// Create an event manager where the sender table will be the [DefaultSenderTableProvider]
|
||||
/// and the listener table will be the [DefaultListenerTableProvider].
|
||||
pub fn new(event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>) -> Self {
|
||||
let listener_table: Box<DefaultListenerTableProvider> = Box::default();
|
||||
let sender_table: Box<DefaultSenderTableProvider<E, Event, AuxDataProvider>> =
|
||||
Box::default();
|
||||
Self::new_custom_tables(listener_table, sender_table, event_receiver)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, Event: GenericEvent + Copy, AuxDataProvider: Clone>
|
||||
EventManager<E, Event, AuxDataProvider>
|
||||
{
|
||||
pub fn new_custom_tables(
|
||||
listener_table: Box<dyn ListenerTable>,
|
||||
sender_table: Box<dyn SenderTable<E, Event, AuxDataProvider>>,
|
||||
event_receiver: Box<dyn EventReceiver<Event, AuxDataProvider>>,
|
||||
) -> Self {
|
||||
EventManager {
|
||||
listener_table,
|
||||
sender_table,
|
||||
event_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sender(
|
||||
&mut self,
|
||||
send_provider: impl SendEventProvider<Event, AuxDataProvider, Error = E> + 'static,
|
||||
) {
|
||||
if !self
|
||||
.sender_table
|
||||
.contains_send_event_provider(&send_provider.id())
|
||||
{
|
||||
self.sender_table
|
||||
.add_send_event_provider(Box::new(send_provider));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_listeners(&mut self, key: ListenerKey, sender_id: SenderId) {
|
||||
self.listener_table.add_listener(key, sender_id);
|
||||
}
|
||||
|
||||
/// This function will use the cached event receiver and try to receive one event.
|
||||
/// If an event was received, it will try to route that event to all subscribed event listeners.
|
||||
/// If this works without any issues, the [EventRoutingResult] will contain context information
|
||||
/// about the routed event.
|
||||
///
|
||||
/// This function will track up to 3 errors returned as part of the
|
||||
/// [EventRoutingErrorsWithResult] error struct.
|
||||
pub fn try_event_handling(
|
||||
&mut self,
|
||||
) -> Result<
|
||||
EventRoutingResult<Event, AuxDataProvider>,
|
||||
EventRoutingErrorsWithResult<Event, AuxDataProvider, E>,
|
||||
> {
|
||||
let mut err_idx = 0;
|
||||
let mut err_slice = [None, None, None];
|
||||
let mut num_recipients = 0;
|
||||
let mut add_error = |error: EventRoutingError<E>| {
|
||||
if err_idx < 3 {
|
||||
err_slice[err_idx] = Some(error);
|
||||
err_idx += 1;
|
||||
}
|
||||
};
|
||||
let mut send_handler =
|
||||
|key: &ListenerKey, event: Event, aux_data: &Option<AuxDataProvider>| {
|
||||
if self.listener_table.contains_listener(key) {
|
||||
if let Some(ids) = self.listener_table.get_listener_ids(key) {
|
||||
for id in ids {
|
||||
if let Some(sender) = self.sender_table.get_send_event_provider(id) {
|
||||
if let Err(e) = sender.send(event, aux_data.clone()) {
|
||||
add_error(EventRoutingError::SendError(e));
|
||||
} else {
|
||||
num_recipients += 1;
|
||||
}
|
||||
} else {
|
||||
add_error(EventRoutingError::NoSenderForId(*id));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add_error(EventRoutingError::NoSendersForKey(*key));
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some((event, aux_data)) = self.event_receiver.receive() {
|
||||
let single_key = ListenerKey::Single(event.raw_as_largest_type());
|
||||
send_handler(&single_key, event, &aux_data);
|
||||
let group_key = ListenerKey::Group(event.group_id_as_largest_type());
|
||||
send_handler(&group_key, event, &aux_data);
|
||||
send_handler(&ListenerKey::All, event, &aux_data);
|
||||
if err_idx > 0 {
|
||||
return Err(EventRoutingErrorsWithResult {
|
||||
result: EventRoutingResult::Handled(num_recipients, event, aux_data),
|
||||
errors: err_slice,
|
||||
});
|
||||
}
|
||||
return Ok(EventRoutingResult::Handled(num_recipients, event, aux_data));
|
||||
}
|
||||
Ok(EventRoutingResult::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DefaultListenerTableProvider {
|
||||
listeners: HashMap<ListenerKey, Vec<SenderId>>,
|
||||
}
|
||||
|
||||
pub struct DefaultSenderTableProvider<
|
||||
SendProviderError,
|
||||
Event: GenericEvent = EventU32,
|
||||
AuxDataProvider = Params,
|
||||
> {
|
||||
senders: HashMap<
|
||||
SenderId,
|
||||
Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<SendProviderError, Event: GenericEvent, AuxDataProvider> Default
|
||||
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
senders: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListenerTable for DefaultListenerTableProvider {
|
||||
fn get_listeners(&self) -> Vec<ListenerKey> {
|
||||
let mut key_list = Vec::new();
|
||||
for key in self.listeners.keys() {
|
||||
key_list.push(*key);
|
||||
}
|
||||
key_list
|
||||
}
|
||||
|
||||
fn contains_listener(&self, key: &ListenerKey) -> bool {
|
||||
self.listeners.contains_key(key)
|
||||
}
|
||||
|
||||
fn get_listener_ids(&self, key: &ListenerKey) -> Option<Iter<SenderId>> {
|
||||
self.listeners.get(key).map(|vec| vec.iter())
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, key: ListenerKey, sender_id: SenderId) -> bool {
|
||||
if let Some(existing_list) = self.listeners.get_mut(&key) {
|
||||
existing_list.push(sender_id);
|
||||
} else {
|
||||
let new_list = vec![sender_id];
|
||||
self.listeners.insert(key, new_list);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn remove_duplicates(&mut self, key: &ListenerKey) {
|
||||
if let Some(list) = self.listeners.get_mut(key) {
|
||||
list.sort_unstable();
|
||||
list.dedup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SendProviderError, Event: GenericEvent, AuxDataProvider>
|
||||
SenderTable<SendProviderError, Event, AuxDataProvider>
|
||||
for DefaultSenderTableProvider<SendProviderError, Event, AuxDataProvider>
|
||||
{
|
||||
fn contains_send_event_provider(&self, id: &SenderId) -> bool {
|
||||
self.senders.contains_key(id)
|
||||
}
|
||||
|
||||
fn get_send_event_provider(
|
||||
&mut self,
|
||||
id: &SenderId,
|
||||
) -> Option<&mut Box<dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>>>
|
||||
{
|
||||
self.senders.get_mut(id).filter(|sender| sender.id() == *id)
|
||||
}
|
||||
|
||||
fn add_send_event_provider(
|
||||
&mut self,
|
||||
send_provider: Box<
|
||||
dyn SendEventProvider<Event, AuxDataProvider, Error = SendProviderError>,
|
||||
>,
|
||||
) -> bool {
|
||||
let id = send_provider.id();
|
||||
if self.senders.contains_key(&id) {
|
||||
return false;
|
||||
}
|
||||
self.senders.insert(id, send_provider).is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod stdmod {
|
||||
use super::*;
|
||||
use crate::event_man::{EventReceiver, EventWithAuxData};
|
||||
use crate::events::{EventU16, EventU32, GenericEvent};
|
||||
use crate::params::Params;
|
||||
use std::sync::mpsc::{Receiver, SendError, Sender};
|
||||
|
||||
pub struct MpscEventReceiver<Event: GenericEvent + Send = EventU32> {
|
||||
mpsc_receiver: Receiver<(Event, Option<Params>)>,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> MpscEventReceiver<Event> {
|
||||
pub fn new(receiver: Receiver<(Event, Option<Params>)>) -> Self {
|
||||
Self {
|
||||
mpsc_receiver: receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Event: GenericEvent + Send> EventReceiver<Event> for MpscEventReceiver<Event> {
|
||||
fn receive(&mut self) -> Option<EventWithAuxData<Event>> {
|
||||
if let Ok(event_and_data) = self.mpsc_receiver.try_recv() {
|
||||
return Some(event_and_data);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub type MpscEventU32Receiver = MpscEventReceiver<EventU32>;
|
||||
pub type MpscEventU16Receiver = MpscEventReceiver<EventU16>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MpscEventSendProvider<Event: GenericEvent + Send> {
|
||||
id: u32,
|
||||
sender: Sender<(Event, Option<Params>)>,
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> MpscEventSendProvider<Event> {
|
||||
pub fn new(id: u32, sender: Sender<(Event, Option<Params>)>) -> Self {
|
||||
Self { id, sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Event: GenericEvent + Send> SendEventProvider<Event> for MpscEventSendProvider<Event> {
|
||||
type Error = SendError<(Event, Option<Params>)>;
|
||||
|
||||
fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
fn send(&mut self, event: Event, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
||||
self.sender.send((event, aux_data))
|
||||
}
|
||||
}
|
||||
|
||||
pub type MpscEventU32SendProvider = MpscEventSendProvider<EventU32>;
|
||||
pub type MpscEventU16SendProvider = MpscEventSendProvider<EventU16>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::event_man::EventManager;
|
||||
use crate::events::{EventU32, GenericEvent, Severity};
|
||||
use crate::params::ParamsRaw;
|
||||
use alloc::boxed::Box;
|
||||
use std::format;
|
||||
use std::sync::mpsc::{channel, Receiver, SendError, Sender};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MpscEventSenderQueue {
|
||||
id: u32,
|
||||
mpsc_sender: Sender<EventU32WithAuxData>,
|
||||
}
|
||||
|
||||
impl MpscEventSenderQueue {
|
||||
fn new(id: u32, mpsc_sender: Sender<EventU32WithAuxData>) -> Self {
|
||||
Self { id, mpsc_sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl SendEventProvider<EventU32> for MpscEventSenderQueue {
|
||||
type Error = SendError<EventU32WithAuxData>;
|
||||
|
||||
fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
fn send(&mut self, event: EventU32, aux_data: Option<Params>) -> Result<(), Self::Error> {
|
||||
self.mpsc_sender.send((event, aux_data))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_next_event(
|
||||
expected: EventU32,
|
||||
receiver: &Receiver<EventU32WithAuxData>,
|
||||
) -> Option<Params> {
|
||||
if let Ok(event) = receiver.try_recv() {
|
||||
assert_eq!(event.0, expected);
|
||||
return event.1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn check_handled_event(
|
||||
res: EventRoutingResult<EventU32, Params>,
|
||||
expected: EventU32,
|
||||
expected_num_sent: u32,
|
||||
) {
|
||||
assert!(matches!(res, EventRoutingResult::Handled { .. }));
|
||||
if let EventRoutingResult::Handled(num_recipients, event, _aux_data) = res {
|
||||
assert_eq!(event, expected);
|
||||
assert_eq!(num_recipients, expected_num_sent);
|
||||
}
|
||||
}
|
||||
|
||||
fn generic_event_man() -> (
|
||||
Sender<EventU32WithAuxData>,
|
||||
EventManager<SendError<EventU32WithAuxData>>,
|
||||
) {
|
||||
let (event_sender, manager_queue) = channel();
|
||||
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
||||
(
|
||||
event_sender,
|
||||
EventManager::new(Box::new(event_man_receiver)),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let (event_sender, mut event_man) = generic_event_man();
|
||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||
let (single_event_sender, single_event_receiver) = channel();
|
||||
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
||||
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
||||
event_man.add_sender(single_event_listener);
|
||||
let (group_event_sender_0, group_event_receiver_0) = channel();
|
||||
let group_event_listener = MpscEventSenderQueue {
|
||||
id: 1,
|
||||
mpsc_sender: group_event_sender_0,
|
||||
};
|
||||
event_man.subscribe_group(event_grp_1_0.group_id(), group_event_listener.id());
|
||||
event_man.add_sender(group_event_listener);
|
||||
|
||||
// Test event with one listener
|
||||
event_sender
|
||||
.send((event_grp_0, None))
|
||||
.expect("Sending single error failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||
check_next_event(event_grp_0, &single_event_receiver);
|
||||
|
||||
// Test event which is sent to all group listeners
|
||||
event_sender
|
||||
.send((event_grp_1_0, None))
|
||||
.expect("Sending group error failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
||||
check_next_event(event_grp_1_0, &group_event_receiver_0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_basic_aux_data() {
|
||||
let (event_sender, mut event_man) = generic_event_man();
|
||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||
let (single_event_sender, single_event_receiver) = channel();
|
||||
let single_event_listener = MpscEventSenderQueue::new(0, single_event_sender);
|
||||
event_man.subscribe_single(&event_grp_0, single_event_listener.id());
|
||||
event_man.add_sender(single_event_listener);
|
||||
event_sender
|
||||
.send((event_grp_0, Some(Params::Heapless((2_u32, 3_u32).into()))))
|
||||
.expect("Sending group error failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||
let aux = check_next_event(event_grp_0, &single_event_receiver);
|
||||
assert!(aux.is_some());
|
||||
let aux = aux.unwrap();
|
||||
if let Params::Heapless(ParamsHeapless::Raw(ParamsRaw::U32Pair(pair))) = aux {
|
||||
assert_eq!(pair.0, 2);
|
||||
assert_eq!(pair.1, 3);
|
||||
} else {
|
||||
panic!("{}", format!("Unexpected auxiliary value type {:?}", aux));
|
||||
}
|
||||
}
|
||||
|
||||
/// Test listening for multiple groups
|
||||
#[test]
|
||||
fn test_multi_group() {
|
||||
let (event_sender, mut event_man) = generic_event_man();
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
let hres = res.unwrap();
|
||||
assert!(matches!(hres, EventRoutingResult::Empty));
|
||||
|
||||
let event_grp_0 = EventU32::new(Severity::INFO, 0, 0).unwrap();
|
||||
let event_grp_1_0 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||
let (event_grp_0_sender, event_grp_0_receiver) = channel();
|
||||
let event_grp_0_and_1_listener = MpscEventSenderQueue {
|
||||
id: 0,
|
||||
mpsc_sender: event_grp_0_sender,
|
||||
};
|
||||
event_man.subscribe_group(event_grp_0.group_id(), event_grp_0_and_1_listener.id());
|
||||
event_man.subscribe_group(event_grp_1_0.group_id(), event_grp_0_and_1_listener.id());
|
||||
event_man.add_sender(event_grp_0_and_1_listener);
|
||||
|
||||
event_sender
|
||||
.send((event_grp_0, None))
|
||||
.expect("Sending Event Group 0 failed");
|
||||
event_sender
|
||||
.send((event_grp_1_0, None))
|
||||
.expect("Sendign Event Group 1 failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_grp_0, 1);
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_grp_1_0, 1);
|
||||
|
||||
check_next_event(event_grp_0, &event_grp_0_receiver);
|
||||
check_next_event(event_grp_1_0, &event_grp_0_receiver);
|
||||
}
|
||||
|
||||
/// Test listening to the same event from multiple listeners. Also test listening
|
||||
/// to both group and single events from one listener
|
||||
#[test]
|
||||
fn test_listening_to_same_event_and_multi_type() {
|
||||
let (event_sender, mut event_man) = generic_event_man();
|
||||
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
||||
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||
let (event_0_tx_0, event_0_rx_0) = channel();
|
||||
let (event_0_tx_1, event_0_rx_1) = channel();
|
||||
let event_listener_0 = MpscEventSenderQueue {
|
||||
id: 0,
|
||||
mpsc_sender: event_0_tx_0,
|
||||
};
|
||||
let event_listener_1 = MpscEventSenderQueue {
|
||||
id: 1,
|
||||
mpsc_sender: event_0_tx_1,
|
||||
};
|
||||
let event_listener_0_sender_id = event_listener_0.id();
|
||||
event_man.subscribe_single(&event_0, event_listener_0_sender_id);
|
||||
event_man.add_sender(event_listener_0);
|
||||
let event_listener_1_sender_id = event_listener_1.id();
|
||||
event_man.subscribe_single(&event_0, event_listener_1_sender_id);
|
||||
event_man.add_sender(event_listener_1);
|
||||
event_sender
|
||||
.send((event_0, None))
|
||||
.expect("Triggering Event 0 failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_0, 2);
|
||||
check_next_event(event_0, &event_0_rx_0);
|
||||
check_next_event(event_0, &event_0_rx_1);
|
||||
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
||||
event_sender
|
||||
.send((event_0, None))
|
||||
.expect("Triggering Event 0 failed");
|
||||
event_sender
|
||||
.send((event_1, None))
|
||||
.expect("Triggering Event 1 failed");
|
||||
|
||||
// 3 Events messages will be sent now
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_0, 2);
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_1, 1);
|
||||
// Both the single event and the group event should arrive now
|
||||
check_next_event(event_0, &event_0_rx_0);
|
||||
check_next_event(event_1, &event_0_rx_0);
|
||||
|
||||
// Do double insertion and then remove duplicates
|
||||
event_man.subscribe_group(event_1.group_id(), event_listener_0_sender_id);
|
||||
event_man.remove_duplicates(&ListenerKey::Group(event_1.group_id()));
|
||||
event_sender
|
||||
.send((event_1, None))
|
||||
.expect("Triggering Event 1 failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_events_listener() {
|
||||
let (event_sender, manager_queue) = channel();
|
||||
let event_man_receiver = MpscEventReceiver::new(manager_queue);
|
||||
let mut event_man: EventManager<SendError<EventU32WithAuxData>> =
|
||||
EventManager::new(Box::new(event_man_receiver));
|
||||
let event_0 = EventU32::new(Severity::INFO, 0, 5).unwrap();
|
||||
let event_1 = EventU32::new(Severity::HIGH, 1, 0).unwrap();
|
||||
let (event_0_tx_0, all_events_rx) = channel();
|
||||
let all_events_listener = MpscEventSenderQueue {
|
||||
id: 0,
|
||||
mpsc_sender: event_0_tx_0,
|
||||
};
|
||||
event_man.subscribe_all(all_events_listener.id());
|
||||
event_man.add_sender(all_events_listener);
|
||||
event_sender
|
||||
.send((event_0, None))
|
||||
.expect("Triggering event 0 failed");
|
||||
event_sender
|
||||
.send((event_1, None))
|
||||
.expect("Triggering event 1 failed");
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_0, 1);
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
check_handled_event(res.unwrap(), event_1, 1);
|
||||
check_next_event(event_0, &all_events_rx);
|
||||
check_next_event(event_1, &all_events_rx);
|
||||
}
|
||||
}
|
801
satrs-core/src/events.rs
Normal file
801
satrs-core/src/events.rs
Normal file
@ -0,0 +1,801 @@
|
||||
//! Event support module
|
||||
//!
|
||||
//! This module includes the basic event structs [EventU32] and [EventU16] and versions with the
|
||||
//! ECSS severity levels as a type parameter. These structs are simple abstractions on top of the
|
||||
//! [u32] and [u16] types where the raw value is the unique identifier for a particular event.
|
||||
//! The abstraction also allows to group related events using a group ID, and the severity
|
||||
//! of an event is encoded inside the raw value itself with four possible [Severity] levels:
|
||||
//!
|
||||
//! - INFO
|
||||
//! - LOW
|
||||
//! - MEDIUM
|
||||
//! - HIGH
|
||||
//!
|
||||
//! All event structs implement the [EcssEnumeration] trait and can be created as constants.
|
||||
//! This allows to easily create a static list of constant events which can then be used to generate
|
||||
//! event telemetry using the PUS event manager modules.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
|
||||
//!
|
||||
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(1, 0);
|
||||
//! const MSG_FAILED: EventU32 = EventU32::const_new(Severity::LOW, 1, 1);
|
||||
//!
|
||||
//! const TEMPERATURE_HIGH: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::const_new(2, 0);
|
||||
//!
|
||||
//! let small_event = EventU16::new(Severity::INFO, 3, 0);
|
||||
//! ```
|
||||
use core::fmt::Debug;
|
||||
use core::hash::Hash;
|
||||
use core::marker::PhantomData;
|
||||
use delegate::delegate;
|
||||
use spacepackets::ecss::{EcssEnumeration, ToBeBytes};
|
||||
use spacepackets::{ByteConversionError, SizeMissmatch};
|
||||
|
||||
/// Using a type definition allows to change this to u64 in the future more easily
|
||||
pub type LargestEventRaw = u32;
|
||||
/// Using a type definition allows to change this to u32 in the future more easily
|
||||
pub type LargestGroupIdRaw = u16;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Severity {
|
||||
INFO = 0,
|
||||
LOW = 1,
|
||||
MEDIUM = 2,
|
||||
HIGH = 3,
|
||||
}
|
||||
|
||||
pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
|
||||
const SEVERITY: Severity;
|
||||
}
|
||||
|
||||
/// Type level support struct
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct SeverityInfo {}
|
||||
impl HasSeverity for SeverityInfo {
|
||||
const SEVERITY: Severity = Severity::INFO;
|
||||
}
|
||||
|
||||
/// Type level support struct
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct SeverityLow {}
|
||||
impl HasSeverity for SeverityLow {
|
||||
const SEVERITY: Severity = Severity::LOW;
|
||||
}
|
||||
|
||||
/// Type level support struct
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct SeverityMedium {}
|
||||
impl HasSeverity for SeverityMedium {
|
||||
const SEVERITY: Severity = Severity::MEDIUM;
|
||||
}
|
||||
|
||||
/// Type level support struct
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct SeverityHigh {}
|
||||
impl HasSeverity for SeverityHigh {
|
||||
const SEVERITY: Severity = Severity::HIGH;
|
||||
}
|
||||
|
||||
pub trait GenericEvent: EcssEnumeration {
|
||||
type Raw;
|
||||
type GroupId;
|
||||
type UniqueId;
|
||||
|
||||
fn raw(&self) -> Self::Raw;
|
||||
fn severity(&self) -> Severity;
|
||||
fn group_id(&self) -> Self::GroupId;
|
||||
fn unique_id(&self) -> Self::UniqueId;
|
||||
|
||||
fn raw_as_largest_type(&self) -> LargestEventRaw;
|
||||
fn group_id_as_largest_type(&self) -> LargestGroupIdRaw;
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Severity {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Severity::INFO as u8 => Ok(Severity::INFO),
|
||||
x if x == Severity::LOW as u8 => Ok(Severity::LOW),
|
||||
x if x == Severity::MEDIUM as u8 => Ok(Severity::MEDIUM),
|
||||
x if x == Severity::HIGH as u8 => Ok(Severity::HIGH),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct EventBase<RAW, GID, UID> {
|
||||
severity: Severity,
|
||||
group_id: GID,
|
||||
unique_id: UID,
|
||||
phantom: PhantomData<RAW>,
|
||||
}
|
||||
|
||||
impl<RAW: ToBeBytes, GID, UID> EventBase<RAW, GID, UID> {
|
||||
fn write_to_bytes(
|
||||
&self,
|
||||
raw: RAW,
|
||||
buf: &mut [u8],
|
||||
width: usize,
|
||||
) -> Result<(), ByteConversionError> {
|
||||
if buf.len() < width {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: buf.len(),
|
||||
expected: width,
|
||||
}));
|
||||
}
|
||||
buf.copy_from_slice(raw.to_be_bytes().as_ref());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventBase<u32, u16, u16> {
|
||||
#[inline]
|
||||
fn raw(&self) -> u32 {
|
||||
((self.severity as u32) << 30) | ((self.group_id as u32) << 16) | self.unique_id as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl EventBase<u16, u8, u8> {
|
||||
#[inline]
|
||||
fn raw(&self) -> u16 {
|
||||
((self.severity as u16) << 14) | ((self.group_id as u16) << 8) | self.unique_id as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl<RAW, GID, UID> EventBase<RAW, GID, UID> {
|
||||
#[inline]
|
||||
pub fn severity(&self) -> Severity {
|
||||
self.severity
|
||||
}
|
||||
}
|
||||
|
||||
impl<RAW, GID> EventBase<RAW, GID, u16> {
|
||||
#[inline]
|
||||
pub fn unique_id(&self) -> u16 {
|
||||
self.unique_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<RAW, GID> EventBase<RAW, GID, u8> {
|
||||
#[inline]
|
||||
pub fn unique_id(&self) -> u8 {
|
||||
self.unique_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<RAW, UID> EventBase<RAW, u16, UID> {
|
||||
#[inline]
|
||||
pub fn group_id(&self) -> u16 {
|
||||
self.group_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<RAW, UID> EventBase<RAW, u8, UID> {
|
||||
#[inline]
|
||||
pub fn group_id(&self) -> u8 {
|
||||
self.group_id
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! event_provider_impl {
|
||||
() => {
|
||||
#[inline]
|
||||
fn raw(&self) -> Self::Raw {
|
||||
self.base.raw()
|
||||
}
|
||||
|
||||
/// Retrieve the severity of an event. Returns None if that severity bit field of the raw event
|
||||
/// ID is invalid
|
||||
#[inline]
|
||||
fn severity(&self) -> Severity {
|
||||
self.base.severity()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn group_id(&self) -> Self::GroupId {
|
||||
self.base.group_id()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unique_id(&self) -> Self::UniqueId {
|
||||
self.base.unique_id()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_event_provider {
|
||||
($BaseIdent: ident, $TypedIdent: ident, $raw: ty, $gid: ty, $uid: ty) => {
|
||||
impl GenericEvent for $BaseIdent {
|
||||
type Raw = $raw;
|
||||
type GroupId = $gid;
|
||||
type UniqueId = $uid;
|
||||
|
||||
event_provider_impl!();
|
||||
|
||||
fn raw_as_largest_type(&self) -> LargestEventRaw {
|
||||
self.raw().into()
|
||||
}
|
||||
|
||||
fn group_id_as_largest_type(&self) -> LargestGroupIdRaw {
|
||||
self.group_id().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<SEVERITY: HasSeverity> GenericEvent for $TypedIdent<SEVERITY> {
|
||||
type Raw = $raw;
|
||||
type GroupId = $gid;
|
||||
type UniqueId = $uid;
|
||||
|
||||
delegate!(to self.event {
|
||||
fn raw(&self) -> Self::Raw;
|
||||
fn severity(&self) -> Severity;
|
||||
fn group_id(&self) -> Self::GroupId;
|
||||
fn unique_id(&self) -> Self::UniqueId;
|
||||
fn raw_as_largest_type(&self) -> LargestEventRaw;
|
||||
fn group_id_as_largest_type(&self) -> LargestGroupIdRaw;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! try_from_impls {
|
||||
($SevIdent: ident, $severity: path, $raw: ty, $TypedSevIdent: ident) => {
|
||||
impl TryFrom<$raw> for $TypedSevIdent<$SevIdent> {
|
||||
type Error = Severity;
|
||||
|
||||
fn try_from(raw: $raw) -> Result<Self, Self::Error> {
|
||||
Self::try_from_generic($severity, raw)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! const_from_fn {
|
||||
($from_fn_name: ident, $TypedIdent: ident, $SevIdent: ident) => {
|
||||
pub const fn $from_fn_name(event: $TypedIdent<$SevIdent>) -> Self {
|
||||
Self {
|
||||
base: event.event.base,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventU32 {
|
||||
base: EventBase<u32, u16, u16>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventU32TypedSev<SEVERITY> {
|
||||
event: EventU32,
|
||||
phantom: PhantomData<SEVERITY>,
|
||||
}
|
||||
|
||||
impl<SEVERITY: HasSeverity> From<EventU32TypedSev<SEVERITY>> for EventU32 {
|
||||
fn from(e: EventU32TypedSev<SEVERITY>) -> Self {
|
||||
Self { base: e.event.base }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> AsRef<EventU32> for EventU32TypedSev<Severity> {
|
||||
fn as_ref(&self) -> &EventU32 {
|
||||
&self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> AsMut<EventU32> for EventU32TypedSev<Severity> {
|
||||
fn as_mut(&mut self) -> &mut EventU32 {
|
||||
&mut self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl_event_provider!(EventU32, EventU32TypedSev, u32, u16, u16);
|
||||
|
||||
impl EventU32 {
|
||||
/// Generate an event. The raw representation of an event has 32 bits.
|
||||
/// If the passed group ID is invalid (too large), None wil be returned
|
||||
///
|
||||
/// # Parameter
|
||||
///
|
||||
/// * `severity`: Each event has a [severity][Severity]. The raw value of the severity will
|
||||
/// be stored inside the uppermost 2 bits of the raw event ID
|
||||
/// * `group_id`: Related events can be grouped using a group ID. The group ID will occupy the
|
||||
/// next 14 bits after the severity. Therefore, the size is limited by dec 16383 hex 0x3FFF.
|
||||
/// * `unique_id`: Each event has a unique 16 bit ID occupying the last 16 bits of the
|
||||
/// raw event ID
|
||||
pub fn new(
|
||||
severity: Severity,
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Option<Self> {
|
||||
if group_id > (2u16.pow(14) - 1) {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
base: EventBase {
|
||||
severity,
|
||||
group_id,
|
||||
unique_id,
|
||||
phantom: PhantomData,
|
||||
},
|
||||
})
|
||||
}
|
||||
pub const fn const_new(
|
||||
severity: Severity,
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Self {
|
||||
if group_id > (2u16.pow(14) - 1) {
|
||||
panic!("Group ID too large");
|
||||
}
|
||||
Self {
|
||||
base: EventBase {
|
||||
severity,
|
||||
group_id,
|
||||
unique_id,
|
||||
phantom: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const_from_fn!(const_from_info, EventU32TypedSev, SeverityInfo);
|
||||
const_from_fn!(const_from_low, EventU32TypedSev, SeverityLow);
|
||||
const_from_fn!(const_from_medium, EventU32TypedSev, SeverityMedium);
|
||||
const_from_fn!(const_from_high, EventU32TypedSev, SeverityHigh);
|
||||
}
|
||||
|
||||
impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
|
||||
/// This is similar to [EventU32::new] but the severity is a type generic, which allows to
|
||||
/// have distinct types for events with different severities
|
||||
pub fn new(
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Option<Self> {
|
||||
let event = EventU32::new(SEVERITY::SEVERITY, group_id, unique_id)?;
|
||||
Some(Self {
|
||||
event,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||
pub const fn const_new(
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Self {
|
||||
let event = EventU32::const_new(SEVERITY::SEVERITY, group_id, unique_id);
|
||||
Self {
|
||||
event,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_generic(expected: Severity, raw: u32) -> Result<Self, Severity> {
|
||||
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
|
||||
if severity != expected {
|
||||
return Err(severity);
|
||||
}
|
||||
Ok(Self::const_new(
|
||||
((raw >> 16) & 0x3FFF) as u16,
|
||||
(raw & 0xFFFF) as u16,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for EventU32 {
|
||||
fn from(raw: u32) -> Self {
|
||||
// Severity conversion from u8 should never fail
|
||||
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
|
||||
let group_id = ((raw >> 16) & 0x3FFF) as u16;
|
||||
let unique_id = (raw & 0xFFFF) as u16;
|
||||
// Sanitized input, should never fail
|
||||
Self::const_new(severity, group_id, unique_id)
|
||||
}
|
||||
}
|
||||
|
||||
try_from_impls!(SeverityInfo, Severity::INFO, u32, EventU32TypedSev);
|
||||
try_from_impls!(SeverityLow, Severity::LOW, u32, EventU32TypedSev);
|
||||
try_from_impls!(SeverityMedium, Severity::MEDIUM, u32, EventU32TypedSev);
|
||||
try_from_impls!(SeverityHigh, Severity::HIGH, u32, EventU32TypedSev);
|
||||
|
||||
impl EcssEnumeration for EventU32 {
|
||||
fn pfc(&self) -> u8 {
|
||||
32
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
|
||||
self.base.write_to_bytes(self.raw(), buf, self.byte_width())
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl<SEVERITY: HasSeverity> EcssEnumeration for EventU32TypedSev<SEVERITY> {
|
||||
delegate!(to self.event {
|
||||
fn pfc(&self) -> u8;
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>;
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventU16 {
|
||||
base: EventBase<u16, u8, u8>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventU16TypedSev<SEVERITY> {
|
||||
event: EventU16,
|
||||
phantom: PhantomData<SEVERITY>,
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> AsRef<EventU16> for EventU16TypedSev<Severity> {
|
||||
fn as_ref(&self) -> &EventU16 {
|
||||
&self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> AsMut<EventU16> for EventU16TypedSev<Severity> {
|
||||
fn as_mut(&mut self) -> &mut EventU16 {
|
||||
&mut self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl EventU16 {
|
||||
/// Generate a small event. The raw representation of a small event has 16 bits.
|
||||
/// If the passed group ID is invalid (too large), [None] wil be returned
|
||||
///
|
||||
/// # Parameter
|
||||
///
|
||||
/// * `severity`: Each event has a [severity][Severity]. The raw value of the severity will
|
||||
/// be stored inside the uppermost 2 bits of the raw event ID
|
||||
/// * `group_id`: Related events can be grouped using a group ID. The group ID will occupy the
|
||||
/// next 6 bits after the severity. Therefore, the size is limited by dec 63 hex 0x3F.
|
||||
/// * `unique_id`: Each event has a unique 8 bit ID occupying the last 8 bits of the
|
||||
/// raw event ID
|
||||
pub fn new(
|
||||
severity: Severity,
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Option<Self> {
|
||||
if group_id > (2u8.pow(6) - 1) {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
base: EventBase {
|
||||
severity,
|
||||
group_id,
|
||||
unique_id,
|
||||
phantom: Default::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||
pub const fn const_new(
|
||||
severity: Severity,
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Self {
|
||||
if group_id > (2u8.pow(6) - 1) {
|
||||
panic!("Group ID too large");
|
||||
}
|
||||
Self {
|
||||
base: EventBase {
|
||||
severity,
|
||||
group_id,
|
||||
unique_id,
|
||||
phantom: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
const_from_fn!(const_from_info, EventU16TypedSev, SeverityInfo);
|
||||
const_from_fn!(const_from_low, EventU16TypedSev, SeverityLow);
|
||||
const_from_fn!(const_from_medium, EventU16TypedSev, SeverityMedium);
|
||||
const_from_fn!(const_from_high, EventU16TypedSev, SeverityHigh);
|
||||
}
|
||||
|
||||
impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
|
||||
/// This is similar to [EventU16::new] but the severity is a type generic, which allows to
|
||||
/// have distinct types for events with different severities
|
||||
pub fn new(
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Option<Self> {
|
||||
let event = EventU16::new(SEVERITY::SEVERITY, group_id, unique_id)?;
|
||||
Some(Self {
|
||||
event,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Const version of [Self::new], but panics on invalid group ID input values.
|
||||
pub const fn const_new(
|
||||
group_id: <Self as GenericEvent>::GroupId,
|
||||
unique_id: <Self as GenericEvent>::UniqueId,
|
||||
) -> Self {
|
||||
let event = EventU16::const_new(SEVERITY::SEVERITY, group_id, unique_id);
|
||||
Self {
|
||||
event,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_generic(expected: Severity, raw: u16) -> Result<Self, Severity> {
|
||||
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
|
||||
if severity != expected {
|
||||
return Err(severity);
|
||||
}
|
||||
Ok(Self::const_new(
|
||||
((raw >> 8) & 0x3F) as u8,
|
||||
(raw & 0xFF) as u8,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl_event_provider!(EventU16, EventU16TypedSev, u16, u8, u8);
|
||||
|
||||
impl EcssEnumeration for EventU16 {
|
||||
#[inline]
|
||||
fn pfc(&self) -> u8 {
|
||||
16
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
|
||||
self.base.write_to_bytes(self.raw(), buf, self.byte_width())
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl<SEVERITY: HasSeverity> EcssEnumeration for EventU16TypedSev<SEVERITY> {
|
||||
delegate!(to self.event {
|
||||
fn pfc(&self) -> u8;
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>;
|
||||
});
|
||||
}
|
||||
|
||||
impl From<u16> for EventU16 {
|
||||
fn from(raw: <Self as GenericEvent>::Raw) -> Self {
|
||||
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
|
||||
let group_id = ((raw >> 8) & 0x3F) as u8;
|
||||
let unique_id = (raw & 0xFF) as u8;
|
||||
// Sanitized input, new call should never fail
|
||||
Self::const_new(severity, group_id, unique_id)
|
||||
}
|
||||
}
|
||||
|
||||
try_from_impls!(SeverityInfo, Severity::INFO, u16, EventU16TypedSev);
|
||||
try_from_impls!(SeverityLow, Severity::LOW, u16, EventU16TypedSev);
|
||||
try_from_impls!(SeverityMedium, Severity::MEDIUM, u16, EventU16TypedSev);
|
||||
try_from_impls!(SeverityHigh, Severity::HIGH, u16, EventU16TypedSev);
|
||||
|
||||
impl<Severity: HasSeverity> PartialEq<EventU32> for EventU32TypedSev<Severity> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &EventU32) -> bool {
|
||||
self.raw() == other.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> PartialEq<EventU32TypedSev<Severity>> for EventU32 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &EventU32TypedSev<Severity>) -> bool {
|
||||
self.raw() == other.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> PartialEq<EventU16> for EventU16TypedSev<Severity> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &EventU16) -> bool {
|
||||
self.raw() == other.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Severity: HasSeverity> PartialEq<EventU16TypedSev<Severity>> for EventU16 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &EventU16TypedSev<Severity>) -> bool {
|
||||
self.raw() == other.raw()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EventU32TypedSev;
|
||||
use super::*;
|
||||
use spacepackets::ecss::EcssEnumeration;
|
||||
use spacepackets::ByteConversionError;
|
||||
use std::mem::size_of;
|
||||
|
||||
fn assert_size<T>(_: T, val: usize) {
|
||||
assert_eq!(size_of::<T>(), val);
|
||||
}
|
||||
|
||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(0, 0);
|
||||
const INFO_EVENT_SMALL: EventU16TypedSev<SeverityInfo> = EventU16TypedSev::const_new(0, 0);
|
||||
const HIGH_SEV_EVENT: EventU32TypedSev<SeverityHigh> =
|
||||
EventU32TypedSev::const_new(0x3FFF, 0xFFFF);
|
||||
const HIGH_SEV_EVENT_SMALL: EventU16TypedSev<SeverityHigh> =
|
||||
EventU16TypedSev::const_new(0x3F, 0xff);
|
||||
|
||||
/// This working is a test in itself.
|
||||
const INFO_REDUCED: EventU32 = EventU32::const_from_info(INFO_EVENT);
|
||||
|
||||
#[test]
|
||||
fn test_normal_from_raw_conversion() {
|
||||
let conv_from_raw = EventU32TypedSev::<SeverityInfo>::try_from(INFO_EVENT.raw())
|
||||
.expect("Creating typed EventU32 failed");
|
||||
assert_eq!(conv_from_raw, INFO_EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_from_raw_conversion() {
|
||||
let conv_from_raw = EventU16TypedSev::<SeverityInfo>::try_from(INFO_EVENT_SMALL.raw())
|
||||
.expect("Creating typed EventU16 failed");
|
||||
assert_eq!(conv_from_raw, INFO_EVENT_SMALL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_normal_size() {
|
||||
assert_size(INFO_EVENT.raw(), 4)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_small_size() {
|
||||
assert_size(INFO_EVENT_SMALL.raw(), 2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_event_getters() {
|
||||
assert_eq!(INFO_EVENT.severity(), Severity::INFO);
|
||||
assert_eq!(INFO_EVENT.unique_id(), 0);
|
||||
assert_eq!(INFO_EVENT.group_id(), 0);
|
||||
let raw_event = INFO_EVENT.raw();
|
||||
assert_eq!(raw_event, 0x00000000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_event_getters() {
|
||||
assert_eq!(INFO_EVENT_SMALL.severity(), Severity::INFO);
|
||||
assert_eq!(INFO_EVENT_SMALL.unique_id(), 0);
|
||||
assert_eq!(INFO_EVENT_SMALL.group_id(), 0);
|
||||
let raw_event = INFO_EVENT_SMALL.raw();
|
||||
assert_eq!(raw_event, 0x00000000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_ones_event_regular() {
|
||||
assert_eq!(HIGH_SEV_EVENT.severity(), Severity::HIGH);
|
||||
assert_eq!(HIGH_SEV_EVENT.group_id(), 0x3FFF);
|
||||
assert_eq!(HIGH_SEV_EVENT.unique_id(), 0xFFFF);
|
||||
let raw_event = HIGH_SEV_EVENT.raw();
|
||||
assert_eq!(raw_event, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_ones_event_small() {
|
||||
assert_eq!(HIGH_SEV_EVENT_SMALL.severity(), Severity::HIGH);
|
||||
assert_eq!(HIGH_SEV_EVENT_SMALL.group_id(), 0x3F);
|
||||
assert_eq!(HIGH_SEV_EVENT_SMALL.unique_id(), 0xFF);
|
||||
let raw_event = HIGH_SEV_EVENT_SMALL.raw();
|
||||
assert_eq!(raw_event, 0xFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_group_id_normal() {
|
||||
assert!(EventU32TypedSev::<SeverityMedium>::new(2_u16.pow(14), 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_group_id_small() {
|
||||
assert!(EventU16TypedSev::<SeverityMedium>::new(2_u8.pow(6), 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_new() {
|
||||
assert_eq!(
|
||||
EventU32TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
|
||||
INFO_EVENT
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_new() {
|
||||
assert_eq!(
|
||||
EventU16TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
|
||||
INFO_EVENT_SMALL
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_largest_type() {
|
||||
let event_raw = HIGH_SEV_EVENT.raw_as_largest_type();
|
||||
assert_size(event_raw, 4);
|
||||
assert_eq!(event_raw, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_largest_type_for_small_event() {
|
||||
let event_raw = HIGH_SEV_EVENT_SMALL.raw_as_largest_type();
|
||||
assert_size(event_raw, 4);
|
||||
assert_eq!(event_raw, 0xFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_largest_group_id() {
|
||||
let group_id = HIGH_SEV_EVENT.group_id_as_largest_type();
|
||||
assert_size(group_id, 2);
|
||||
assert_eq!(group_id, 0x3FFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_largest_group_id_small_event() {
|
||||
let group_id = HIGH_SEV_EVENT_SMALL.group_id_as_largest_type();
|
||||
assert_size(group_id, 2);
|
||||
assert_eq!(group_id, 0x3F);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_to_buf() {
|
||||
let mut buf: [u8; 4] = [0; 4];
|
||||
assert!(HIGH_SEV_EVENT.write_to_be_bytes(&mut buf).is_ok());
|
||||
let val_from_raw = u32::from_be_bytes(buf);
|
||||
assert_eq!(val_from_raw, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_to_buf_small() {
|
||||
let mut buf: [u8; 2] = [0; 2];
|
||||
assert!(HIGH_SEV_EVENT_SMALL.write_to_be_bytes(&mut buf).is_ok());
|
||||
let val_from_raw = u16::from_be_bytes(buf);
|
||||
assert_eq!(val_from_raw, 0xFFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_to_buf_insufficient_buf() {
|
||||
let mut buf: [u8; 3] = [0; 3];
|
||||
let err = HIGH_SEV_EVENT.write_to_be_bytes(&mut buf);
|
||||
assert!(err.is_err());
|
||||
let err = err.unwrap_err();
|
||||
if let ByteConversionError::ToSliceTooSmall(missmatch) = err {
|
||||
assert_eq!(missmatch.expected, 4);
|
||||
assert_eq!(missmatch.found, 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_to_buf_small_insufficient_buf() {
|
||||
let mut buf: [u8; 1] = [0; 1];
|
||||
let err = HIGH_SEV_EVENT_SMALL.write_to_be_bytes(&mut buf);
|
||||
assert!(err.is_err());
|
||||
let err = err.unwrap_err();
|
||||
if let ByteConversionError::ToSliceTooSmall(missmatch) = err {
|
||||
assert_eq!(missmatch.expected, 2);
|
||||
assert_eq!(missmatch.found, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn severity_from_invalid_raw_val() {
|
||||
let invalid = 0xFF;
|
||||
assert!(Severity::try_from(invalid).is_err());
|
||||
let invalid = Severity::HIGH as u8 + 1;
|
||||
assert!(Severity::try_from(invalid).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reduction() {
|
||||
let event = EventU32TypedSev::<SeverityInfo>::const_new(1, 1);
|
||||
let raw = event.raw();
|
||||
let reduced: EventU32 = event.into();
|
||||
assert_eq!(reduced.group_id(), 1);
|
||||
assert_eq!(reduced.unique_id(), 1);
|
||||
assert_eq!(raw, reduced.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_reducation() {
|
||||
assert_eq!(INFO_REDUCED.raw(), INFO_EVENT.raw());
|
||||
}
|
||||
}
|
503
satrs-core/src/executable.rs
Normal file
503
satrs-core/src/executable.rs
Normal file
@ -0,0 +1,503 @@
|
||||
//! Task scheduling module
|
||||
use bus::BusReader;
|
||||
use std::boxed::Box;
|
||||
use std::error::Error;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OpResult {
|
||||
Ok,
|
||||
TerminationRequested,
|
||||
}
|
||||
|
||||
pub enum ExecutionType {
|
||||
Infinite,
|
||||
Cycles(u32),
|
||||
OneShot,
|
||||
}
|
||||
|
||||
pub trait Executable: Send {
|
||||
type Error;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType;
|
||||
fn task_name(&self) -> &'static str;
|
||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error>;
|
||||
}
|
||||
|
||||
/// This function allows executing one task which implements the [Executable][Executable] trait
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `executable`: Executable task
|
||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||
pub fn exec_sched_single<
|
||||
T: Executable<Error = E> + Send + 'static + ?Sized,
|
||||
E: Error + Send + 'static,
|
||||
>(
|
||||
mut executable: Box<T>,
|
||||
task_freq: Option<Duration>,
|
||||
op_code: i32,
|
||||
mut termination: Option<BusReader<()>>,
|
||||
) -> JoinHandle<Result<OpResult, E>> {
|
||||
let mut cycle_count = 0;
|
||||
thread::spawn(move || loop {
|
||||
if let Some(ref mut terminator) = termination {
|
||||
match terminator.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||
return Ok(OpResult::Ok);
|
||||
}
|
||||
Err(TryRecvError::Empty) => (),
|
||||
}
|
||||
}
|
||||
match executable.exec_type() {
|
||||
ExecutionType::OneShot => {
|
||||
executable.periodic_op(op_code)?;
|
||||
return Ok(OpResult::Ok);
|
||||
}
|
||||
ExecutionType::Infinite => {
|
||||
executable.periodic_op(op_code)?;
|
||||
}
|
||||
ExecutionType::Cycles(cycles) => {
|
||||
executable.periodic_op(op_code)?;
|
||||
cycle_count += 1;
|
||||
if cycle_count == cycles {
|
||||
return Ok(OpResult::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
||||
thread::sleep(freq);
|
||||
})
|
||||
}
|
||||
|
||||
/// This function allows executing multiple tasks as long as the tasks implement the
|
||||
/// [Executable][Executable] trait
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `executable_vec`: Vector of executable objects
|
||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||
pub fn exec_sched_multi<
|
||||
T: Executable<Error = E> + Send + 'static + ?Sized,
|
||||
E: Error + Send + 'static,
|
||||
>(
|
||||
mut executable_vec: Vec<Box<T>>,
|
||||
task_freq: Option<Duration>,
|
||||
op_code: i32,
|
||||
mut termination: Option<BusReader<()>>,
|
||||
) -> JoinHandle<Result<OpResult, E>> {
|
||||
let mut cycle_counts = vec![0; executable_vec.len()];
|
||||
let mut removal_flags = vec![false; executable_vec.len()];
|
||||
thread::spawn(move || loop {
|
||||
if let Some(ref mut terminator) = termination {
|
||||
match terminator.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => {
|
||||
removal_flags.iter_mut().for_each(|x| *x = true);
|
||||
}
|
||||
Err(TryRecvError::Empty) => (),
|
||||
}
|
||||
}
|
||||
for (idx, executable) in executable_vec.iter_mut().enumerate() {
|
||||
match executable.exec_type() {
|
||||
ExecutionType::OneShot => {
|
||||
executable.periodic_op(op_code)?;
|
||||
removal_flags[idx] = true;
|
||||
}
|
||||
ExecutionType::Infinite => {
|
||||
executable.periodic_op(op_code)?;
|
||||
}
|
||||
ExecutionType::Cycles(cycles) => {
|
||||
executable.periodic_op(op_code)?;
|
||||
cycle_counts[idx] += 1;
|
||||
if cycle_counts[idx] == cycles {
|
||||
removal_flags[idx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut removal_iter = removal_flags.iter();
|
||||
executable_vec.retain(|_| !*removal_iter.next().unwrap());
|
||||
removal_iter = removal_flags.iter();
|
||||
cycle_counts.retain(|_| !*removal_iter.next().unwrap());
|
||||
removal_flags.retain(|&i| !i);
|
||||
if executable_vec.is_empty() {
|
||||
return Ok(OpResult::Ok);
|
||||
}
|
||||
let freq = task_freq.unwrap_or_else(|| panic!("No task frequency specified"));
|
||||
thread::sleep(freq);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{exec_sched_multi, exec_sched_single, Executable, ExecutionType, OpResult};
|
||||
use bus::Bus;
|
||||
use std::boxed::Box;
|
||||
use std::error::Error;
|
||||
use std::string::{String, ToString};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::vec::Vec;
|
||||
use std::{fmt, thread, vec};
|
||||
|
||||
struct TestInfo {
|
||||
exec_num: u32,
|
||||
op_code: i32,
|
||||
}
|
||||
struct OneShotTask {
|
||||
exec_num: Arc<Mutex<TestInfo>>,
|
||||
}
|
||||
struct FixedCyclesTask {
|
||||
cycles: u32,
|
||||
exec_num: Arc<Mutex<TestInfo>>,
|
||||
}
|
||||
struct PeriodicTask {
|
||||
exec_num: Arc<Mutex<TestInfo>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ExampleError {
|
||||
kind: ErrorKind,
|
||||
}
|
||||
|
||||
/// The kind of an error that can occur.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ErrorKind {
|
||||
Generic(String, i32),
|
||||
}
|
||||
|
||||
impl ExampleError {
|
||||
fn new(msg: &str, code: i32) -> ExampleError {
|
||||
ExampleError {
|
||||
kind: ErrorKind::Generic(msg.to_string(), code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the kind of this error.
|
||||
pub fn kind(&self) -> &ErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExampleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.kind() {
|
||||
ErrorKind::Generic(str, code) => {
|
||||
write!(f, "{str} with code {code}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ExampleError {}
|
||||
|
||||
const ONE_SHOT_TASK_NAME: &str = "One Shot Task";
|
||||
|
||||
impl Executable for OneShotTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::OneShot
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
ONE_SHOT_TASK_NAME
|
||||
}
|
||||
|
||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, ExampleError> {
|
||||
let mut data = self.exec_num.lock().expect("Locking Mutex failed");
|
||||
data.exec_num += 1;
|
||||
data.op_code = op_code;
|
||||
std::mem::drop(data);
|
||||
if op_code >= 0 {
|
||||
Ok(OpResult::Ok)
|
||||
} else {
|
||||
Err(ExampleError::new("One Shot Task Failure", op_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CYCLE_TASK_NAME: &str = "Fixed Cycles Task";
|
||||
|
||||
impl Executable for FixedCyclesTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Cycles(self.cycles)
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
CYCLE_TASK_NAME
|
||||
}
|
||||
|
||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, ExampleError> {
|
||||
let mut data = self.exec_num.lock().expect("Locking Mutex failed");
|
||||
data.exec_num += 1;
|
||||
data.op_code = op_code;
|
||||
std::mem::drop(data);
|
||||
if op_code >= 0 {
|
||||
Ok(OpResult::Ok)
|
||||
} else {
|
||||
Err(ExampleError::new("Fixed Cycle Task Failure", op_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PERIODIC_TASK_NAME: &str = "Periodic Task";
|
||||
|
||||
impl Executable for PeriodicTask {
|
||||
type Error = ExampleError;
|
||||
|
||||
fn exec_type(&self) -> ExecutionType {
|
||||
ExecutionType::Infinite
|
||||
}
|
||||
|
||||
fn task_name(&self) -> &'static str {
|
||||
PERIODIC_TASK_NAME
|
||||
}
|
||||
|
||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, ExampleError> {
|
||||
let mut data = self.exec_num.lock().expect("Locking Mutex failed");
|
||||
data.exec_num += 1;
|
||||
data.op_code = op_code;
|
||||
std::mem::drop(data);
|
||||
if op_code >= 0 {
|
||||
Ok(OpResult::Ok)
|
||||
} else {
|
||||
Err(ExampleError::new("Example Task Failure", op_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_one_shot() {
|
||||
let expected_op_code = 42;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let exec_task = OneShotTask {
|
||||
exec_num: shared.clone(),
|
||||
};
|
||||
let task = Box::new(exec_task);
|
||||
let jhandle = exec_sched_single(
|
||||
task,
|
||||
Some(Duration::from_millis(100)),
|
||||
expected_op_code,
|
||||
None,
|
||||
);
|
||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(data.exec_num, 1);
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failed_one_shot() {
|
||||
let op_code_inducing_failure = -1;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let exec_task = OneShotTask {
|
||||
exec_num: shared.clone(),
|
||||
};
|
||||
let task = Box::new(exec_task);
|
||||
let jhandle = exec_sched_single(
|
||||
task,
|
||||
Some(Duration::from_millis(100)),
|
||||
op_code_inducing_failure,
|
||||
None,
|
||||
);
|
||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||
assert!(thread_res.is_err());
|
||||
let error = thread_res.unwrap_err();
|
||||
let err = error.kind();
|
||||
assert!(matches!(err, &ErrorKind::Generic { .. }));
|
||||
match err {
|
||||
ErrorKind::Generic(str, op_code) => {
|
||||
assert_eq!(str, &String::from("One Shot Task Failure"));
|
||||
assert_eq!(op_code, &op_code_inducing_failure);
|
||||
}
|
||||
}
|
||||
let error_display = error.to_string();
|
||||
assert_eq!(error_display, "One Shot Task Failure with code -1");
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(data.exec_num, 1);
|
||||
assert_eq!(data.op_code, op_code_inducing_failure);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_multi_one_shot() {
|
||||
let expected_op_code = 43;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let exec_task_0 = OneShotTask {
|
||||
exec_num: shared.clone(),
|
||||
};
|
||||
let exec_task_1 = OneShotTask {
|
||||
exec_num: shared.clone(),
|
||||
};
|
||||
let task_vec = vec![Box::new(exec_task_0), Box::new(exec_task_1)];
|
||||
for task in task_vec.iter() {
|
||||
assert_eq!(task.task_name(), ONE_SHOT_TASK_NAME);
|
||||
}
|
||||
let jhandle = exec_sched_multi(
|
||||
task_vec,
|
||||
Some(Duration::from_millis(100)),
|
||||
expected_op_code,
|
||||
None,
|
||||
);
|
||||
let thread_res = jhandle.join().expect("One Shot Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(data.exec_num, 2);
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cycles_single() {
|
||||
let expected_op_code = 44;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let cycled_task = Box::new(FixedCyclesTask {
|
||||
exec_num: shared.clone(),
|
||||
cycles: 1,
|
||||
});
|
||||
assert_eq!(cycled_task.task_name(), CYCLE_TASK_NAME);
|
||||
let jh = exec_sched_single(
|
||||
cycled_task,
|
||||
Some(Duration::from_millis(100)),
|
||||
expected_op_code,
|
||||
None,
|
||||
);
|
||||
let thread_res = jh.join().expect("Cycles Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
assert_eq!(data.exec_num, 1);
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_and_cycles() {
|
||||
let expected_op_code = 50;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let one_shot_task = Box::new(OneShotTask {
|
||||
exec_num: shared.clone(),
|
||||
});
|
||||
let cycled_task_0 = Box::new(FixedCyclesTask {
|
||||
exec_num: shared.clone(),
|
||||
cycles: 1,
|
||||
});
|
||||
let cycled_task_1 = Box::new(FixedCyclesTask {
|
||||
exec_num: shared.clone(),
|
||||
cycles: 1,
|
||||
});
|
||||
assert_eq!(cycled_task_0.task_name(), CYCLE_TASK_NAME);
|
||||
assert_eq!(one_shot_task.task_name(), ONE_SHOT_TASK_NAME);
|
||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
||||
let jh = exec_sched_multi(
|
||||
task_vec,
|
||||
Some(Duration::from_millis(100)),
|
||||
expected_op_code,
|
||||
None,
|
||||
);
|
||||
let thread_res = jh.join().expect("Cycles Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
assert_eq!(data.exec_num, 3);
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_periodic_single() {
|
||||
let mut terminator = Bus::new(5);
|
||||
let expected_op_code = 45;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let periodic_task = Box::new(PeriodicTask {
|
||||
exec_num: shared.clone(),
|
||||
});
|
||||
assert_eq!(periodic_task.task_name(), PERIODIC_TASK_NAME);
|
||||
let jh = exec_sched_single(
|
||||
periodic_task,
|
||||
Some(Duration::from_millis(20)),
|
||||
expected_op_code,
|
||||
Some(terminator.add_rx()),
|
||||
);
|
||||
thread::sleep(Duration::from_millis(40));
|
||||
terminator.broadcast(());
|
||||
let thread_res = jh.join().expect("Periodic Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
let range = 2..4;
|
||||
assert!(range.contains(&data.exec_num));
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_periodic_multi() {
|
||||
let mut terminator = Bus::new(5);
|
||||
let expected_op_code = 46;
|
||||
let shared = Arc::new(Mutex::new(TestInfo {
|
||||
exec_num: 0,
|
||||
op_code: 0,
|
||||
}));
|
||||
let cycled_task = Box::new(FixedCyclesTask {
|
||||
exec_num: shared.clone(),
|
||||
cycles: 1,
|
||||
});
|
||||
let periodic_task_0 = Box::new(PeriodicTask {
|
||||
exec_num: shared.clone(),
|
||||
});
|
||||
let periodic_task_1 = Box::new(PeriodicTask {
|
||||
exec_num: shared.clone(),
|
||||
});
|
||||
assert_eq!(periodic_task_0.task_name(), PERIODIC_TASK_NAME);
|
||||
assert_eq!(periodic_task_1.task_name(), PERIODIC_TASK_NAME);
|
||||
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||
vec![cycled_task, periodic_task_0, periodic_task_1];
|
||||
let jh = exec_sched_multi(
|
||||
task_vec,
|
||||
Some(Duration::from_millis(20)),
|
||||
expected_op_code,
|
||||
Some(terminator.add_rx()),
|
||||
);
|
||||
thread::sleep(Duration::from_millis(60));
|
||||
terminator.broadcast(());
|
||||
let thread_res = jh.join().expect("Periodic Task failed");
|
||||
assert!(thread_res.is_ok());
|
||||
let data = shared.lock().expect("Locking Mutex failed");
|
||||
assert_eq!(thread_res.unwrap(), OpResult::Ok);
|
||||
let range = 7..11;
|
||||
assert!(range.contains(&data.exec_num));
|
||||
assert_eq!(data.op_code, expected_op_code);
|
||||
}
|
||||
}
|
2
satrs-core/src/hal/host/mod.rs
Normal file
2
satrs-core/src/hal/host/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
//! Helper modules intended to be used on hosts with a full [std] runtime
|
||||
pub mod udp_server;
|
212
satrs-core/src/hal/host/udp_server.rs
Normal file
212
satrs-core/src/hal/host/udp_server.rs
Normal file
@ -0,0 +1,212 @@
|
||||
//! UDP server helper components
|
||||
use crate::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
use std::boxed::Box;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
/// This TC server helper can be used to receive raw PUS telecommands thorough a UDP interface.
|
||||
///
|
||||
/// It caches all received telecomands into a vector. The maximum expected telecommand size should
|
||||
/// be declared upfront. This avoids dynamic allocation during run-time. The user can specify a TC
|
||||
/// receiver in form of a special trait object which implements [ReceivesTc]. Please note that the
|
||||
/// receiver should copy out the received data if it the data is required past the
|
||||
/// [ReceivesTcCore::pass_tc] call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
/// use satrs_core::hal::host::udp_server::UdpTcServer;
|
||||
/// use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
/// use spacepackets::SpHeader;
|
||||
/// use spacepackets::tc::PusTc;
|
||||
///
|
||||
/// #[derive (Default)]
|
||||
/// struct PingReceiver {}
|
||||
/// impl ReceivesTcCore for PingReceiver {
|
||||
/// type Error = ();
|
||||
/// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
/// assert_eq!(tc_raw.len(), 13);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut buf = [0; 32];
|
||||
/// let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
||||
/// let ping_receiver = PingReceiver::default();
|
||||
/// let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||
/// .expect("Creating UDP TMTC server failed");
|
||||
/// let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
|
||||
/// let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
|
||||
/// let len = pus_tc
|
||||
/// .write_to_bytes(&mut buf)
|
||||
/// .expect("Error writing PUS TC packet");
|
||||
/// assert_eq!(len, 13);
|
||||
/// let client = UdpSocket::bind("127.0.0.1:7778").expect("Connecting to UDP server failed");
|
||||
/// client
|
||||
/// .send_to(&buf[0..len], dest_addr)
|
||||
/// .expect("Error sending PUS TC via UDP");
|
||||
/// ```
|
||||
///
|
||||
/// The [fsrc-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example)
|
||||
/// server code also includes
|
||||
/// [example code](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-example/src/bin/obsw/tmtc.rs)
|
||||
/// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port
|
||||
/// and then forwards them to a generic CCSDS packet receiver.
|
||||
pub struct UdpTcServer<E> {
|
||||
pub socket: UdpSocket,
|
||||
recv_buf: Vec<u8>,
|
||||
sender_addr: Option<SocketAddr>,
|
||||
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReceiveResult<E> {
|
||||
NothingReceived,
|
||||
IoError(Error),
|
||||
ReceiverError(E),
|
||||
}
|
||||
|
||||
impl<E> From<Error> for ReceiveResult<E> {
|
||||
fn from(e: Error) -> Self {
|
||||
ReceiveResult::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: PartialEq> PartialEq for ReceiveResult<E> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use ReceiveResult::*;
|
||||
match (self, other) {
|
||||
(IoError(ref e), IoError(ref other_e)) => e.kind() == other_e.kind(),
|
||||
(NothingReceived, NothingReceived) => true,
|
||||
(ReceiverError(e), ReceiverError(other_e)) => e == other_e,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Eq + PartialEq> Eq for ReceiveResult<E> {}
|
||||
|
||||
impl<E: 'static> ReceivesTcCore for UdpTcServer<E> {
|
||||
type Error = E;
|
||||
|
||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
self.tc_receiver.pass_tc(tc_raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> UdpTcServer<E> {
|
||||
pub fn new<A: ToSocketAddrs>(
|
||||
addr: A,
|
||||
max_recv_size: usize,
|
||||
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
|
||||
) -> Result<Self, Error> {
|
||||
let server = Self {
|
||||
socket: UdpSocket::bind(addr)?,
|
||||
recv_buf: vec![0; max_recv_size],
|
||||
sender_addr: None,
|
||||
tc_receiver,
|
||||
};
|
||||
server.socket.set_nonblocking(true)?;
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
pub fn try_recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<E>> {
|
||||
let res = match self.socket.recv_from(&mut self.recv_buf) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
return if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut {
|
||||
Err(ReceiveResult::NothingReceived)
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
let (num_bytes, from) = res;
|
||||
self.sender_addr = Some(from);
|
||||
self.tc_receiver
|
||||
.pass_tc(&self.recv_buf[0..num_bytes])
|
||||
.map_err(|e| ReceiveResult::ReceiverError(e))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn last_sender(&self) -> Option<SocketAddr> {
|
||||
self.sender_addr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::hal::host::udp_server::{ReceiveResult, UdpTcServer};
|
||||
use crate::tmtc::ReceivesTcCore;
|
||||
use spacepackets::tc::PusTc;
|
||||
use spacepackets::SpHeader;
|
||||
use std::boxed::Box;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::vec::Vec;
|
||||
|
||||
fn is_send<T: Send>(_: &T) {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PingReceiver {
|
||||
pub sent_cmds: VecDeque<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ReceivesTcCore for PingReceiver {
|
||||
type Error = ();
|
||||
|
||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
let mut sent_data = Vec::new();
|
||||
sent_data.extend_from_slice(tc_raw);
|
||||
self.sent_cmds.push_back(sent_data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_test() {
|
||||
let mut buf = [0; 32];
|
||||
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
|
||||
let ping_receiver = PingReceiver::default();
|
||||
is_send(&ping_receiver);
|
||||
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||
.expect("Creating UDP TMTC server failed");
|
||||
is_send(&udp_tc_server);
|
||||
let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
|
||||
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
|
||||
let len = pus_tc
|
||||
.write_to_bytes(&mut buf)
|
||||
.expect("Error writing PUS TC packet");
|
||||
let client = UdpSocket::bind("127.0.0.1:7778").expect("Connecting to UDP server failed");
|
||||
client
|
||||
.send_to(&buf[0..len], dest_addr)
|
||||
.expect("Error sending PUS TC via UDP");
|
||||
let local_addr = client.local_addr().unwrap();
|
||||
udp_tc_server
|
||||
.try_recv_tc()
|
||||
.expect("Error receiving sent telecommand");
|
||||
assert_eq!(
|
||||
udp_tc_server.last_sender().expect("No sender set"),
|
||||
local_addr
|
||||
);
|
||||
let ping_receiver: &mut PingReceiver = udp_tc_server.tc_receiver.downcast_mut().unwrap();
|
||||
assert_eq!(ping_receiver.sent_cmds.len(), 1);
|
||||
let sent_cmd = ping_receiver.sent_cmds.pop_front().unwrap();
|
||||
assert_eq!(sent_cmd, buf[0..len]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nothing_received() {
|
||||
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7779);
|
||||
let ping_receiver = PingReceiver::default();
|
||||
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
|
||||
.expect("Creating UDP TMTC server failed");
|
||||
let res = udp_tc_server.try_recv_tc();
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert_eq!(err, ReceiveResult::NothingReceived);
|
||||
}
|
||||
}
|
4
satrs-core/src/hal/mod.rs
Normal file
4
satrs-core/src/hal/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
//! # Hardware Abstraction Layer module
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||
pub mod host;
|
38
satrs-core/src/lib.rs
Normal file
38
satrs-core/src/lib.rs
Normal file
@ -0,0 +1,38 @@
|
||||
//! # Core components of the Flight Software Rust Crate (FSRC) collection
|
||||
//!
|
||||
//! This is a collection of Rust crates which can be used to build On-Board Software for remote
|
||||
//! systems like satellites of rovers. It has special support for space tailored protocols
|
||||
//! like the ones provided by CCSDS and ECSS.
|
||||
//!
|
||||
//! The crates can generally be used in a `no_std` environment, but some crates expect that the
|
||||
//! [alloc](https://doc.rust-lang.org/alloc) crate is available to allow boxed trait objects.
|
||||
//! These are used to supply user code to the crates.
|
||||
#![no_std]
|
||||
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate downcast_rs;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
extern crate std;
|
||||
|
||||
pub mod error;
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub mod event_man;
|
||||
pub mod events;
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
||||
pub mod executable;
|
||||
pub mod hal;
|
||||
pub mod objects;
|
||||
pub mod params;
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub mod pool;
|
||||
pub mod pus;
|
||||
pub mod res_code;
|
||||
pub mod seq_count;
|
||||
pub mod tmtc;
|
||||
|
||||
pub use spacepackets;
|
306
satrs-core/src/objects.rs
Normal file
306
satrs-core/src/objects.rs
Normal file
@ -0,0 +1,306 @@
|
||||
//! # Module providing addressable object support and a manager for them
|
||||
//!
|
||||
//! Each addressable object can be identified using an [object ID][ObjectId].
|
||||
//! The [system object][ManagedSystemObject] trait also allows storing these objects into the
|
||||
//! [object manager][ObjectManager]. They can then be retrieved and casted back to a known type
|
||||
//! using the object ID.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::any::Any;
|
||||
//! use std::error::Error;
|
||||
//! use satrs_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||
//!
|
||||
//! struct ExampleSysObj {
|
||||
//! id: ObjectId,
|
||||
//! dummy: u32,
|
||||
//! was_initialized: bool,
|
||||
//! }
|
||||
//!
|
||||
//! impl ExampleSysObj {
|
||||
//! fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
||||
//! ExampleSysObj {
|
||||
//! id,
|
||||
//! dummy,
|
||||
//! was_initialized: false,
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl SystemObject for ExampleSysObj {
|
||||
//! type Error = ();
|
||||
//! fn get_object_id(&self) -> &ObjectId {
|
||||
//! &self.id
|
||||
//! }
|
||||
//!
|
||||
//! fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||
//! self.was_initialized = true;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl ManagedSystemObject for ExampleSysObj {}
|
||||
//!
|
||||
//! let mut obj_manager = ObjectManager::default();
|
||||
//! let obj_id = ObjectId { id: 0, name: "Example 0"};
|
||||
//! let example_obj = ExampleSysObj::new(obj_id, 42);
|
||||
//! obj_manager.insert(Box::new(example_obj));
|
||||
//! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&obj_id);
|
||||
//! let example_obj = obj_back_casted.unwrap();
|
||||
//! assert_eq!(example_obj.id, obj_id);
|
||||
//! assert_eq!(example_obj.dummy, 42);
|
||||
//! ```
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
#[cfg(feature = "alloc")]
|
||||
use downcast_rs::Downcast;
|
||||
#[cfg(feature = "alloc")]
|
||||
use hashbrown::HashMap;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
pub struct ObjectId {
|
||||
pub id: u32,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use super::*;
|
||||
|
||||
/// Each object which is stored inside the [object manager][ObjectManager] needs to implemented
|
||||
/// this trait
|
||||
pub trait SystemObject: Downcast {
|
||||
type Error;
|
||||
fn get_object_id(&self) -> &ObjectId;
|
||||
fn initialize(&mut self) -> Result<(), Self::Error>;
|
||||
}
|
||||
downcast_rs::impl_downcast!(SystemObject assoc Error);
|
||||
|
||||
pub trait ManagedSystemObject: SystemObject + Send {}
|
||||
downcast_rs::impl_downcast!(ManagedSystemObject assoc Error);
|
||||
|
||||
/// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them
|
||||
/// using an [object ID][ObjectId]
|
||||
#[cfg(feature = "alloc")]
|
||||
pub struct ObjectManager<E> {
|
||||
obj_map: HashMap<ObjectId, Box<dyn ManagedSystemObject<Error = E>>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<E: 'static> Default for ObjectManager<E> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<E: 'static> ObjectManager<E> {
|
||||
pub fn new() -> Self {
|
||||
ObjectManager {
|
||||
obj_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, sys_obj: Box<dyn ManagedSystemObject<Error = E>>) -> bool {
|
||||
let obj_id = sys_obj.get_object_id();
|
||||
if self.obj_map.contains_key(obj_id) {
|
||||
return false;
|
||||
}
|
||||
self.obj_map.insert(*obj_id, sys_obj).is_none()
|
||||
}
|
||||
|
||||
/// Initializes all System Objects in the hash map and returns the number of successful
|
||||
/// initializations
|
||||
pub fn initialize(&mut self) -> Result<u32, Box<dyn Error>> {
|
||||
let mut init_success = 0;
|
||||
for val in self.obj_map.values_mut() {
|
||||
if val.initialize().is_ok() {
|
||||
init_success += 1
|
||||
}
|
||||
}
|
||||
Ok(init_success)
|
||||
}
|
||||
|
||||
/// Retrieve a reference to an object stored inside the manager. The type to retrieve needs to
|
||||
/// be explicitly passed as a generic parameter or specified on the left hand side of the
|
||||
/// expression.
|
||||
pub fn get_ref<T: ManagedSystemObject<Error = E>>(&self, key: &ObjectId) -> Option<&T> {
|
||||
self.obj_map.get(key).and_then(|o| o.downcast_ref::<T>())
|
||||
}
|
||||
|
||||
/// Retrieve a mutable reference to an object stored inside the manager. The type to retrieve
|
||||
/// needs to be explicitly passed as a generic parameter or specified on the left hand side
|
||||
/// of the expression.
|
||||
pub fn get_mut<T: ManagedSystemObject<Error = E>>(
|
||||
&mut self,
|
||||
key: &ObjectId,
|
||||
) -> Option<&mut T> {
|
||||
self.obj_map
|
||||
.get_mut(key)
|
||||
.and_then(|o| o.downcast_mut::<T>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
|
||||
use std::boxed::Box;
|
||||
use std::string::String;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
struct ExampleSysObj {
|
||||
id: ObjectId,
|
||||
dummy: u32,
|
||||
was_initialized: bool,
|
||||
}
|
||||
|
||||
impl ExampleSysObj {
|
||||
fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
|
||||
ExampleSysObj {
|
||||
id,
|
||||
dummy,
|
||||
was_initialized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemObject for ExampleSysObj {
|
||||
type Error = ();
|
||||
fn get_object_id(&self) -> &ObjectId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||
self.was_initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedSystemObject for ExampleSysObj {}
|
||||
|
||||
struct OtherExampleObject {
|
||||
id: ObjectId,
|
||||
string: String,
|
||||
was_initialized: bool,
|
||||
}
|
||||
|
||||
impl SystemObject for OtherExampleObject {
|
||||
type Error = ();
|
||||
fn get_object_id(&self) -> &ObjectId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn initialize(&mut self) -> Result<(), Self::Error> {
|
||||
self.was_initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedSystemObject for OtherExampleObject {}
|
||||
|
||||
#[test]
|
||||
fn test_obj_manager_simple() {
|
||||
let mut obj_manager = ObjectManager::default();
|
||||
let expl_obj_id = ObjectId {
|
||||
id: 0,
|
||||
name: "Example 0",
|
||||
};
|
||||
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
||||
assert!(obj_manager.insert(Box::new(example_obj)));
|
||||
let res = obj_manager.initialize();
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), 1);
|
||||
let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&expl_obj_id);
|
||||
assert!(obj_back_casted.is_some());
|
||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||
assert_eq!(expl_obj_back_casted.dummy, 42);
|
||||
assert!(expl_obj_back_casted.was_initialized);
|
||||
|
||||
let second_obj_id = ObjectId {
|
||||
id: 12,
|
||||
name: "Example 1",
|
||||
};
|
||||
let second_example_obj = OtherExampleObject {
|
||||
id: second_obj_id,
|
||||
string: String::from("Hello Test"),
|
||||
was_initialized: false,
|
||||
};
|
||||
|
||||
assert!(obj_manager.insert(Box::new(second_example_obj)));
|
||||
let res = obj_manager.initialize();
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), 2);
|
||||
let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get_ref(&second_obj_id);
|
||||
assert!(obj_back_casted.is_some());
|
||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
||||
assert!(expl_obj_back_casted.was_initialized);
|
||||
|
||||
let existing_obj_id = ObjectId {
|
||||
id: 12,
|
||||
name: "Example 1",
|
||||
};
|
||||
let invalid_obj = OtherExampleObject {
|
||||
id: existing_obj_id,
|
||||
string: String::from("Hello Test"),
|
||||
was_initialized: false,
|
||||
};
|
||||
|
||||
assert!(!obj_manager.insert(Box::new(invalid_obj)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_man_threaded() {
|
||||
let obj_manager = Arc::new(Mutex::new(ObjectManager::new()));
|
||||
let expl_obj_id = ObjectId {
|
||||
id: 0,
|
||||
name: "Example 0",
|
||||
};
|
||||
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
|
||||
let second_obj_id = ObjectId {
|
||||
id: 12,
|
||||
name: "Example 1",
|
||||
};
|
||||
let second_example_obj = OtherExampleObject {
|
||||
id: second_obj_id,
|
||||
string: String::from("Hello Test"),
|
||||
was_initialized: false,
|
||||
};
|
||||
|
||||
let mut obj_man_handle = obj_manager.lock().expect("Mutex lock failed");
|
||||
assert!(obj_man_handle.insert(Box::new(example_obj)));
|
||||
assert!(obj_man_handle.insert(Box::new(second_example_obj)));
|
||||
let res = obj_man_handle.initialize();
|
||||
std::mem::drop(obj_man_handle);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), 2);
|
||||
let obj_man_0 = obj_manager.clone();
|
||||
let jh0 = thread::spawn(move || {
|
||||
let locked_man = obj_man_0.lock().expect("Mutex lock failed");
|
||||
let obj_back_casted: Option<&ExampleSysObj> = locked_man.get_ref(&expl_obj_id);
|
||||
assert!(obj_back_casted.is_some());
|
||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||
assert_eq!(expl_obj_back_casted.dummy, 42);
|
||||
assert!(expl_obj_back_casted.was_initialized);
|
||||
std::mem::drop(locked_man)
|
||||
});
|
||||
|
||||
let jh1 = thread::spawn(move || {
|
||||
let locked_man = obj_manager.lock().expect("Mutex lock failed");
|
||||
let obj_back_casted: Option<&OtherExampleObject> = locked_man.get_ref(&second_obj_id);
|
||||
assert!(obj_back_casted.is_some());
|
||||
let expl_obj_back_casted = obj_back_casted.unwrap();
|
||||
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
|
||||
assert!(expl_obj_back_casted.was_initialized);
|
||||
std::mem::drop(locked_man)
|
||||
});
|
||||
jh0.join().expect("Joining thread 0 failed");
|
||||
jh1.join().expect("Joining thread 1 failed");
|
||||
}
|
||||
}
|
648
satrs-core/src/params.rs
Normal file
648
satrs-core/src/params.rs
Normal file
@ -0,0 +1,648 @@
|
||||
//! Parameter types and enums.
|
||||
//!
|
||||
//! This module contains various helper types.
|
||||
//!
|
||||
//! # Primtive Parameter Wrappers and Enumeration
|
||||
//!
|
||||
//! This module includes wrapper for primitive rust types using the newtype pattern.
|
||||
//! This was also done for pairs and triplets of these primitive types.
|
||||
//! The [WritableToBeBytes] was implemented for all those types as well, which allows to easily
|
||||
//! convert them into a network friendly raw byte format. The [ParamsRaw] enumeration groups
|
||||
//! all newtypes and implements the [WritableToBeBytes] trait itself.
|
||||
//!
|
||||
//! ## Example for primitive type wrapper
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::params::{ParamsRaw, ToBeBytes, U32Pair, WritableToBeBytes};
|
||||
//!
|
||||
//! let u32_pair = U32Pair(0x1010, 25);
|
||||
//! assert_eq!(u32_pair.0, 0x1010);
|
||||
//! assert_eq!(u32_pair.1, 25);
|
||||
//! // Convert to raw stream
|
||||
//! let raw_buf = u32_pair.to_be_bytes();
|
||||
//! assert_eq!(raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
||||
//!
|
||||
//! // Convert to enum variant
|
||||
//! let params_raw: ParamsRaw = u32_pair.into();
|
||||
//! assert_eq!(params_raw, (0x1010_u32, 25_u32).into());
|
||||
//!
|
||||
//! // Convert to stream using the enum variant
|
||||
//! let mut other_raw_buf: [u8; 8] = [0; 8];
|
||||
//! params_raw.write_to_be_bytes(&mut other_raw_buf).expect("Writing parameter to buffer failed");
|
||||
//! assert_eq!(other_raw_buf, [0, 0, 0x10, 0x10, 0, 0, 0, 25]);
|
||||
//!
|
||||
//! // Create a pair from a raw stream
|
||||
//! let u32_pair_from_stream: U32Pair = raw_buf.as_slice().try_into().unwrap();
|
||||
//! assert_eq!(u32_pair_from_stream.0, 0x1010);
|
||||
//! assert_eq!(u32_pair_from_stream.1, 25);
|
||||
//! ```
|
||||
//!
|
||||
//! # Generic Parameter Enumeration
|
||||
//!
|
||||
//! The module also contains generic parameter enumerations.
|
||||
//! This includes the [ParamsHeapless] enumeration for contained values which do not require heap
|
||||
//! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which
|
||||
//! require [alloc] support but allow for more flexbility.
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::pool::StoreAddr;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt::Debug;
|
||||
use core::mem::size_of;
|
||||
use paste::paste;
|
||||
use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU64, EcssEnumU8, EcssEnumeration};
|
||||
use spacepackets::ByteConversionError;
|
||||
use spacepackets::SizeMissmatch;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
pub use spacepackets::ecss::ToBeBytes;
|
||||
|
||||
/// Generic trait which is used for objects which can be converted into a raw network (big) endian
|
||||
/// byte format.
|
||||
pub trait WritableToBeBytes {
|
||||
fn raw_len(&self) -> usize;
|
||||
/// Writes the object to a raw buffer in network endianness (big)
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
|
||||
}
|
||||
|
||||
macro_rules! param_to_be_bytes_impl {
|
||||
($Newtype: ident) => {
|
||||
impl WritableToBeBytes for $Newtype {
|
||||
#[inline]
|
||||
fn raw_len(&self) -> usize {
|
||||
size_of::<<Self as ToBeBytes>::ByteArray>()
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
let raw_len = self.raw_len();
|
||||
if buf.len() < raw_len {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: buf.len(),
|
||||
expected: raw_len,
|
||||
}));
|
||||
}
|
||||
buf[0..raw_len].copy_from_slice(&self.to_be_bytes());
|
||||
Ok(raw_len)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! primitive_newtypes_with_eq {
|
||||
($($ty: ty,)+) => {
|
||||
$(
|
||||
paste! {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct [<$ty:upper>](pub $ty);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
||||
|
||||
param_to_be_bytes_impl!([<$ty:upper>]);
|
||||
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
||||
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
||||
|
||||
impl From<$ty> for [<$ty:upper>] {
|
||||
fn from(v: $ty) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
||||
fn from(v: ($ty, $ty)) -> Self {
|
||||
Self(v.0, v.1)
|
||||
}
|
||||
}
|
||||
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
||||
fn from(v: ($ty, $ty, $ty)) -> Self {
|
||||
Self(v.0, v.1, v.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! primitive_newtypes {
|
||||
($($ty: ty,)+) => {
|
||||
$(
|
||||
paste! {
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct [<$ty:upper>](pub $ty);
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct [<$ty:upper Pair>](pub $ty, pub $ty);
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct [<$ty:upper Triplet>](pub $ty, pub $ty, pub $ty);
|
||||
|
||||
param_to_be_bytes_impl!([<$ty:upper>]);
|
||||
param_to_be_bytes_impl!([<$ty:upper Pair>]);
|
||||
param_to_be_bytes_impl!([<$ty:upper Triplet>]);
|
||||
|
||||
impl From<$ty> for [<$ty:upper>] {
|
||||
fn from(v: $ty) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
impl From<($ty, $ty)> for [<$ty:upper Pair>] {
|
||||
fn from(v: ($ty, $ty)) -> Self {
|
||||
Self(v.0, v.1)
|
||||
}
|
||||
}
|
||||
impl From<($ty, $ty, $ty)> for [<$ty:upper Triplet>] {
|
||||
fn from(v: ($ty, $ty, $ty)) -> Self {
|
||||
Self(v.0, v.1, v.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
primitive_newtypes_with_eq!(u8, u16, u32, u64, i8, i16, i32, i64,);
|
||||
primitive_newtypes!(f32, f64,);
|
||||
|
||||
macro_rules! scalar_byte_conversions_impl {
|
||||
($($ty: ty,)+) => {
|
||||
$(
|
||||
paste! {
|
||||
impl ToBeBytes for [<$ty:upper>] {
|
||||
type ByteArray = [u8; size_of::<$ty>()];
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
self.0.to_be_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for [<$ty:upper>] {
|
||||
type Error = ByteConversionError;
|
||||
|
||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||
if v.len() < size_of::<$ty>() {
|
||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||
expected: size_of::<$ty>(),
|
||||
found: v.len()
|
||||
}));
|
||||
}
|
||||
Ok([<$ty:upper>]($ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! pair_byte_conversions_impl {
|
||||
($($ty: ty,)+) => {
|
||||
$(
|
||||
paste! {
|
||||
impl ToBeBytes for [<$ty:upper Pair>] {
|
||||
type ByteArray = [u8; size_of::<$ty>() * 2];
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; size_of::<$ty>() * 2];
|
||||
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
||||
array[
|
||||
size_of::<$ty>()..2 * size_of::<$ty>()
|
||||
].copy_from_slice(&self.1.to_be_bytes());
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for [<$ty:upper Pair>] {
|
||||
type Error = ByteConversionError;
|
||||
|
||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||
if v.len() < 2 * size_of::<$ty>() {
|
||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||
expected: 2 * size_of::<$ty>(),
|
||||
found: v.len()
|
||||
}));
|
||||
}
|
||||
Ok([<$ty:upper Pair>](
|
||||
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
||||
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap())
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! triplet_to_be_bytes_impl {
|
||||
($($ty: ty,)+) => {
|
||||
$(
|
||||
paste! {
|
||||
impl ToBeBytes for [<$ty:upper Triplet>] {
|
||||
type ByteArray = [u8; size_of::<$ty>() * 3];
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; size_of::<$ty>() * 3];
|
||||
array[0..size_of::<$ty>()].copy_from_slice(&self.0.to_be_bytes());
|
||||
array[
|
||||
size_of::<$ty>()..2 * size_of::<$ty>()
|
||||
].copy_from_slice(&self.1.to_be_bytes());
|
||||
array[
|
||||
2 * size_of::<$ty>()..3 * size_of::<$ty>()
|
||||
].copy_from_slice(&self.2.to_be_bytes());
|
||||
array
|
||||
}
|
||||
}
|
||||
impl TryFrom<&[u8]> for [<$ty:upper Triplet>] {
|
||||
type Error = ByteConversionError;
|
||||
|
||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||
if v.len() < 3 * size_of::<$ty>() {
|
||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||
expected: 3 * size_of::<$ty>(),
|
||||
found: v.len()
|
||||
}));
|
||||
}
|
||||
Ok([<$ty:upper Triplet>](
|
||||
$ty::from_be_bytes(v[0..size_of::<$ty>()].try_into().unwrap()),
|
||||
$ty::from_be_bytes(v[size_of::<$ty>()..2 * size_of::<$ty>()].try_into().unwrap()),
|
||||
$ty::from_be_bytes(v[2 * size_of::<$ty>()..3 * size_of::<$ty>()].try_into().unwrap())
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
scalar_byte_conversions_impl!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64,);
|
||||
|
||||
impl ToBeBytes for U8Pair {
|
||||
type ByteArray = [u8; 2];
|
||||
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; 2];
|
||||
array[0] = self.0;
|
||||
array[1] = self.1;
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBeBytes for I8Pair {
|
||||
type ByteArray = [u8; 2];
|
||||
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; 2];
|
||||
array[0] = self.0 as u8;
|
||||
array[1] = self.1 as u8;
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBeBytes for U8Triplet {
|
||||
type ByteArray = [u8; 3];
|
||||
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; 3];
|
||||
array[0] = self.0;
|
||||
array[1] = self.1;
|
||||
array[2] = self.2;
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBeBytes for I8Triplet {
|
||||
type ByteArray = [u8; 3];
|
||||
|
||||
fn to_be_bytes(&self) -> Self::ByteArray {
|
||||
let mut array = [0; 3];
|
||||
array[0] = self.0 as u8;
|
||||
array[1] = self.1 as u8;
|
||||
array[2] = self.2 as u8;
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
pair_byte_conversions_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
||||
triplet_to_be_bytes_impl!(u16, u32, u64, i16, i32, i64, f32, f64,);
|
||||
|
||||
/// Generic enumeration for additonal parameters only consisting of primitive data types.
|
||||
///
|
||||
/// All contained variants and the enum itself implement the [WritableToBeBytes] trait, which
|
||||
/// allows to easily convert them into a network-friendly format.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum ParamsRaw {
|
||||
U8(U8),
|
||||
U8Pair(U8Pair),
|
||||
U8Triplet(U8Triplet),
|
||||
I8(I8),
|
||||
I8Pair(I8Pair),
|
||||
I8Triplet(I8Triplet),
|
||||
U16(U16),
|
||||
U16Pair(U16Pair),
|
||||
U16Triplet(U16Triplet),
|
||||
I16(I16),
|
||||
I16Pair(I16Pair),
|
||||
I16Triplet(I16Triplet),
|
||||
U32(U32),
|
||||
U32Pair(U32Pair),
|
||||
U32Triplet(U32Triplet),
|
||||
I32(I32),
|
||||
I32Pair(I32Pair),
|
||||
I32Triplet(I32Triplet),
|
||||
F32(F32),
|
||||
F32Pair(F32Pair),
|
||||
F32Triplet(F32Triplet),
|
||||
U64(U64),
|
||||
I64(I64),
|
||||
F64(F64),
|
||||
}
|
||||
|
||||
impl WritableToBeBytes for ParamsRaw {
|
||||
fn raw_len(&self) -> usize {
|
||||
match self {
|
||||
ParamsRaw::U8(v) => v.raw_len(),
|
||||
ParamsRaw::U8Pair(v) => v.raw_len(),
|
||||
ParamsRaw::U8Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::I8(v) => v.raw_len(),
|
||||
ParamsRaw::I8Pair(v) => v.raw_len(),
|
||||
ParamsRaw::I8Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::U16(v) => v.raw_len(),
|
||||
ParamsRaw::U16Pair(v) => v.raw_len(),
|
||||
ParamsRaw::U16Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::I16(v) => v.raw_len(),
|
||||
ParamsRaw::I16Pair(v) => v.raw_len(),
|
||||
ParamsRaw::I16Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::U32(v) => v.raw_len(),
|
||||
ParamsRaw::U32Pair(v) => v.raw_len(),
|
||||
ParamsRaw::U32Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::I32(v) => v.raw_len(),
|
||||
ParamsRaw::I32Pair(v) => v.raw_len(),
|
||||
ParamsRaw::I32Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::F32(v) => v.raw_len(),
|
||||
ParamsRaw::F32Pair(v) => v.raw_len(),
|
||||
ParamsRaw::F32Triplet(v) => v.raw_len(),
|
||||
ParamsRaw::U64(v) => v.raw_len(),
|
||||
ParamsRaw::I64(v) => v.raw_len(),
|
||||
ParamsRaw::F64(v) => v.raw_len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
match self {
|
||||
ParamsRaw::U8(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U8Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U8Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I8(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I8Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I8Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U16(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U16Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U16Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I16(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I16Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I16Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U32(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U32Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U32Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I32(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I32Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I32Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::F32(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::F32Pair(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::F32Triplet(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::U64(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::I64(v) => v.write_to_be_bytes(buf),
|
||||
ParamsRaw::F64(v) => v.write_to_be_bytes(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! params_raw_from_newtype {
|
||||
($($newtype: ident,)+) => {
|
||||
$(
|
||||
impl From<$newtype> for ParamsRaw {
|
||||
fn from(v: $newtype) -> Self {
|
||||
Self::$newtype(v)
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
params_raw_from_newtype!(
|
||||
U8, U8Pair, U8Triplet, U16, U16Pair, U16Triplet, U32, U32Pair, U32Triplet, I8, I8Pair,
|
||||
I8Triplet, I16, I16Pair, I16Triplet, I32, I32Pair, I32Triplet, F32, F32Pair, F32Triplet, U64,
|
||||
I64, F64,
|
||||
);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum EcssEnumParams {
|
||||
U8(EcssEnumU8),
|
||||
U16(EcssEnumU16),
|
||||
U32(EcssEnumU32),
|
||||
U64(EcssEnumU64),
|
||||
}
|
||||
|
||||
macro_rules! writable_as_be_bytes_ecss_enum_impl {
|
||||
($EnumIdent: ident) => {
|
||||
impl WritableToBeBytes for $EnumIdent {
|
||||
fn raw_len(&self) -> usize {
|
||||
self.byte_width()
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
EcssEnumeration::write_to_be_bytes(self, buf).map(|_| self.byte_width())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU8);
|
||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU16);
|
||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU32);
|
||||
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU64);
|
||||
|
||||
impl WritableToBeBytes for EcssEnumParams {
|
||||
fn raw_len(&self) -> usize {
|
||||
match self {
|
||||
EcssEnumParams::U8(e) => e.byte_width(),
|
||||
EcssEnumParams::U16(e) => e.byte_width(),
|
||||
EcssEnumParams::U32(e) => e.byte_width(),
|
||||
EcssEnumParams::U64(e) => e.byte_width(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
match self {
|
||||
EcssEnumParams::U8(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||
EcssEnumParams::U16(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||
EcssEnumParams::U32(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||
EcssEnumParams::U64(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic enumeration for parameters which do not rely on heap allocations.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum ParamsHeapless {
|
||||
Raw(ParamsRaw),
|
||||
EcssEnum(EcssEnumParams),
|
||||
}
|
||||
|
||||
macro_rules! from_conversions_for_raw {
|
||||
($(($raw_ty: ty, $TargetPath: path),)+) => {
|
||||
$(
|
||||
impl From<$raw_ty> for ParamsRaw {
|
||||
fn from(val: $raw_ty) -> Self {
|
||||
$TargetPath(val.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$raw_ty> for ParamsHeapless {
|
||||
fn from(val: $raw_ty) -> Self {
|
||||
ParamsHeapless::Raw(val.into())
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
from_conversions_for_raw!(
|
||||
(u8, Self::U8),
|
||||
((u8, u8), Self::U8Pair),
|
||||
((u8, u8, u8), Self::U8Triplet),
|
||||
(i8, Self::I8),
|
||||
((i8, i8), Self::I8Pair),
|
||||
((i8, i8, i8), Self::I8Triplet),
|
||||
(u16, Self::U16),
|
||||
((u16, u16), Self::U16Pair),
|
||||
((u16, u16, u16), Self::U16Triplet),
|
||||
(i16, Self::I16),
|
||||
((i16, i16), Self::I16Pair),
|
||||
((i16, i16, i16), Self::I16Triplet),
|
||||
(u32, Self::U32),
|
||||
((u32, u32), Self::U32Pair),
|
||||
((u32, u32, u32), Self::U32Triplet),
|
||||
(i32, Self::I32),
|
||||
((i32, i32), Self::I32Pair),
|
||||
((i32, i32, i32), Self::I32Triplet),
|
||||
(f32, Self::F32),
|
||||
((f32, f32), Self::F32Pair),
|
||||
((f32, f32, f32), Self::F32Triplet),
|
||||
(u64, Self::U64),
|
||||
(f64, Self::F64),
|
||||
);
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod alloc_mod {
|
||||
use super::*;
|
||||
/// Generic enumeration for additional parameters, including parameters which rely on heap
|
||||
/// allocations.
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Params {
|
||||
Heapless(ParamsHeapless),
|
||||
Store(StoreAddr),
|
||||
Vec(Vec<u8>),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl From<StoreAddr> for Params {
|
||||
fn from(x: StoreAddr) -> Self {
|
||||
Self::Store(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParamsHeapless> for Params {
|
||||
fn from(x: ParamsHeapless) -> Self {
|
||||
Self::Heapless(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Params {
|
||||
fn from(val: Vec<u8>) -> Self {
|
||||
Self::Vec(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a byte slice into the [Params::Vec] variant
|
||||
impl From<&[u8]> for Params {
|
||||
fn from(val: &[u8]) -> Self {
|
||||
Self::Vec(val.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Params {
|
||||
fn from(val: String) -> Self {
|
||||
Self::String(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a string slice into the [Params::String] variant
|
||||
impl From<&str> for Params {
|
||||
fn from(val: &str) -> Self {
|
||||
Self::String(val.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_basic_u32_pair() {
|
||||
let u32_pair = U32Pair(4, 8);
|
||||
assert_eq!(u32_pair.0, 4);
|
||||
assert_eq!(u32_pair.1, 8);
|
||||
let raw = u32_pair.to_be_bytes();
|
||||
let mut u32_conv_back = u32::from_be_bytes(raw[0..4].try_into().unwrap());
|
||||
assert_eq!(u32_conv_back, 4);
|
||||
u32_conv_back = u32::from_be_bytes(raw[4..8].try_into().unwrap());
|
||||
assert_eq!(u32_conv_back, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_signed_test_pair() {
|
||||
let i8_pair = I8Pair(-3, -16);
|
||||
assert_eq!(i8_pair.0, -3);
|
||||
assert_eq!(i8_pair.1, -16);
|
||||
let raw = i8_pair.to_be_bytes();
|
||||
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
||||
assert_eq!(i8_conv_back, -3);
|
||||
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
||||
assert_eq!(i8_conv_back, -16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_signed_test_triplet() {
|
||||
let i8_triplet = I8Triplet(-3, -16, -126);
|
||||
assert_eq!(i8_triplet.0, -3);
|
||||
assert_eq!(i8_triplet.1, -16);
|
||||
assert_eq!(i8_triplet.2, -126);
|
||||
let raw = i8_triplet.to_be_bytes();
|
||||
let mut i8_conv_back = i8::from_be_bytes(raw[0..1].try_into().unwrap());
|
||||
assert_eq!(i8_conv_back, -3);
|
||||
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
|
||||
assert_eq!(i8_conv_back, -16);
|
||||
i8_conv_back = i8::from_be_bytes(raw[2..3].try_into().unwrap());
|
||||
assert_eq!(i8_conv_back, -126);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_test_string() {
|
||||
let param: Params = "Test String".into();
|
||||
if let Params::String(str) = param {
|
||||
assert_eq!(str, String::from("Test String"));
|
||||
} else {
|
||||
panic!("Params type is not String")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_from_slice() {
|
||||
let test_slice: [u8; 5] = [0; 5];
|
||||
let vec_param: Params = test_slice.as_slice().into();
|
||||
if let Params::Vec(vec) = vec_param {
|
||||
assert_eq!(vec, test_slice.to_vec());
|
||||
} else {
|
||||
panic!("Params type is not a vector")
|
||||
}
|
||||
}
|
||||
}
|
771
satrs-core/src/pool.rs
Normal file
771
satrs-core/src/pool.rs
Normal file
@ -0,0 +1,771 @@
|
||||
//! # Pool implementation providing pre-allocated sub-pools with fixed size memory blocks
|
||||
//!
|
||||
//! This is a simple memory pool implementation which pre-allocates all sub-pools using a given pool
|
||||
//! configuration. After the pre-allocation, no dynamic memory allocation will be performed
|
||||
//! during run-time. This makes the implementation suitable for real-time applications and
|
||||
//! embedded environments. The pool implementation will also track the size of the data stored
|
||||
//! inside it.
|
||||
//!
|
||||
//! Transactions with the [pool][LocalPool] are done using a special [address][StoreAddr] type.
|
||||
//! Adding any data to the pool will yield a store address. Modification and read operations are
|
||||
//! done using a reference to a store address. Deletion will consume the store address.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use satrs_core::pool::{LocalPool, PoolCfg, PoolProvider};
|
||||
//!
|
||||
//! // 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes
|
||||
//! let pool_cfg = PoolCfg::new(vec![(4, 4), (2, 8), (1, 16)]);
|
||||
//! let mut local_pool = LocalPool::new(pool_cfg);
|
||||
//! let mut addr;
|
||||
//! {
|
||||
//! // Add new data to the pool
|
||||
//! let mut example_data = [0; 4];
|
||||
//! example_data[0] = 42;
|
||||
//! let res = local_pool.add(&example_data);
|
||||
//! assert!(res.is_ok());
|
||||
//! addr = res.unwrap();
|
||||
//! }
|
||||
//!
|
||||
//! {
|
||||
//! // Read the store data back
|
||||
//! let res = local_pool.read(&addr);
|
||||
//! assert!(res.is_ok());
|
||||
//! let buf_read_back = res.unwrap();
|
||||
//! assert_eq!(buf_read_back.len(), 4);
|
||||
//! assert_eq!(buf_read_back[0], 42);
|
||||
//! // Modify the stored data
|
||||
//! let res = local_pool.modify(&addr);
|
||||
//! assert!(res.is_ok());
|
||||
//! let buf_read_back = res.unwrap();
|
||||
//! buf_read_back[0] = 12;
|
||||
//! }
|
||||
//!
|
||||
//! {
|
||||
//! // Read the modified data back
|
||||
//! let res = local_pool.read(&addr);
|
||||
//! assert!(res.is_ok());
|
||||
//! let buf_read_back = res.unwrap();
|
||||
//! assert_eq!(buf_read_back.len(), 4);
|
||||
//! assert_eq!(buf_read_back[0], 12);
|
||||
//! }
|
||||
//!
|
||||
//! // Delete the stored data
|
||||
//! local_pool.delete(addr);
|
||||
//!
|
||||
//! // Get a free element in the pool with an appropriate size
|
||||
//! {
|
||||
//! let res = local_pool.free_element(12);
|
||||
//! assert!(res.is_ok());
|
||||
//! let (tmp, mut_buf) = res.unwrap();
|
||||
//! addr = tmp;
|
||||
//! mut_buf[0] = 7;
|
||||
//! }
|
||||
//!
|
||||
//! // Read back the data
|
||||
//! {
|
||||
//! // Read the store data back
|
||||
//! let res = local_pool.read(&addr);
|
||||
//! assert!(res.is_ok());
|
||||
//! let buf_read_back = res.unwrap();
|
||||
//! assert_eq!(buf_read_back.len(), 12);
|
||||
//! assert_eq!(buf_read_back[0], 7);
|
||||
//! }
|
||||
//! ```
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt::{Display, Formatter};
|
||||
use delegate::delegate;
|
||||
#[cfg(feature = "std")]
|
||||
use std::boxed::Box;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "std")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
type NumBlocks = u16;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub type ShareablePoolProvider = Box<dyn PoolProvider + Send + Sync>;
|
||||
#[cfg(feature = "std")]
|
||||
pub type SharedPool = Arc<RwLock<ShareablePoolProvider>>;
|
||||
|
||||
/// Configuration structure of the [local pool][LocalPool]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `cfg`: Vector of tuples which represent a subpool. The first entry in the tuple specifies the
|
||||
/// number of memory blocks in the subpool, the second entry the size of the blocks
|
||||
#[derive(Clone)]
|
||||
pub struct PoolCfg {
|
||||
cfg: Vec<(NumBlocks, usize)>,
|
||||
}
|
||||
|
||||
impl PoolCfg {
|
||||
pub fn new(cfg: Vec<(NumBlocks, usize)>) -> Self {
|
||||
PoolCfg { cfg }
|
||||
}
|
||||
|
||||
pub fn sanitize(&mut self) -> usize {
|
||||
self.cfg
|
||||
.retain(|&(bucket_num, size)| bucket_num > 0 && size < LocalPool::MAX_SIZE);
|
||||
self.cfg
|
||||
.sort_unstable_by(|(_, sz0), (_, sz1)| sz0.partial_cmp(sz1).unwrap());
|
||||
self.cfg.len()
|
||||
}
|
||||
}
|
||||
|
||||
type PoolSize = usize;
|
||||
|
||||
/// Pool implementation providing sub-pools with fixed size memory blocks. More details in
|
||||
/// the [module documentation][super::pool]
|
||||
pub struct LocalPool {
|
||||
pool_cfg: PoolCfg,
|
||||
pool: Vec<Vec<u8>>,
|
||||
sizes_lists: Vec<Vec<PoolSize>>,
|
||||
}
|
||||
|
||||
/// Simple address type used for transactions with the local pool.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct StoreAddr {
|
||||
pool_idx: u16,
|
||||
packet_idx: NumBlocks,
|
||||
}
|
||||
|
||||
impl StoreAddr {
|
||||
pub const INVALID_ADDR: u32 = 0xFFFFFFFF;
|
||||
|
||||
pub fn raw(&self) -> u32 {
|
||||
((self.pool_idx as u32) << 16) | self.packet_idx as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum StoreIdError {
|
||||
InvalidSubpool(u16),
|
||||
InvalidPacketIdx(u16),
|
||||
}
|
||||
|
||||
impl Display for StoreIdError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
StoreIdError::InvalidSubpool(pool) => {
|
||||
write!(f, "invalid subpool, index: {}", pool)
|
||||
}
|
||||
StoreIdError::InvalidPacketIdx(packet_idx) => {
|
||||
write!(f, "invalid packet index: {}", packet_idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for StoreIdError {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum StoreError {
|
||||
/// Requested data block is too large
|
||||
DataTooLarge(usize),
|
||||
/// The store is full. Contains the index of the full subpool
|
||||
StoreFull(u16),
|
||||
/// Store ID is invalid. This also includes partial errors where only the subpool is invalid
|
||||
InvalidStoreId(StoreIdError, Option<StoreAddr>),
|
||||
/// Valid subpool and packet index, but no data is stored at the given address
|
||||
DataDoesNotExist(StoreAddr),
|
||||
/// Internal or configuration errors
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
impl Display for StoreError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
StoreError::DataTooLarge(size) => {
|
||||
write!(f, "data to store with size {} is too large", size)
|
||||
}
|
||||
StoreError::StoreFull(u16) => {
|
||||
write!(f, "store is too full. index for full subpool: {}", u16)
|
||||
}
|
||||
StoreError::InvalidStoreId(id_e, addr) => {
|
||||
write!(f, "invalid store ID: {}, address: {:?}", id_e, addr)
|
||||
}
|
||||
StoreError::DataDoesNotExist(addr) => {
|
||||
write!(f, "no data exists at address {:?}", addr)
|
||||
}
|
||||
StoreError::InternalError(e) => {
|
||||
write!(f, "internal error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for StoreError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
if let StoreError::InvalidStoreId(e, _) = self {
|
||||
return Some(e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PoolProvider {
|
||||
/// Add new data to the pool. The provider should attempt to reserve a memory block with the
|
||||
/// appropriate size and then copy the given data to the block. Yields a [StoreAddr] which can
|
||||
/// be used to access the data stored in the pool
|
||||
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError>;
|
||||
|
||||
/// The provider should attempt to reserve a free memory block with the appropriate size and
|
||||
/// then return a mutable reference to it. Yields a [StoreAddr] which can be used to access
|
||||
/// the data stored in the pool
|
||||
fn free_element(&mut self, len: usize) -> Result<(StoreAddr, &mut [u8]), StoreError>;
|
||||
|
||||
/// Modify data added previously using a given [StoreAddr] by yielding a mutable reference
|
||||
/// to it
|
||||
fn modify(&mut self, addr: &StoreAddr) -> Result<&mut [u8], StoreError>;
|
||||
|
||||
/// This function behaves like [Self::modify], but consumes the provided address and returns a
|
||||
/// RAII conformant guard object.
|
||||
///
|
||||
/// Unless the guard [PoolRwGuard::release] method is called, the data for the
|
||||
/// given address will be deleted automatically when the guard is dropped.
|
||||
/// This can prevent memory leaks. Users can read (and modify) the data and release the guard
|
||||
/// if the data in the store is valid for further processing. If the data is faulty, no
|
||||
/// manual deletion is necessary when returning from a processing function prematurely.
|
||||
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard;
|
||||
|
||||
/// Read data by yielding a read-only reference given a [StoreAddr]
|
||||
fn read(&self, addr: &StoreAddr) -> Result<&[u8], StoreError>;
|
||||
|
||||
/// This function behaves like [Self::read], but consumes the provided address and returns a
|
||||
/// RAII conformant guard object.
|
||||
///
|
||||
/// Unless the guard [PoolRwGuard::release] method is called, the data for the
|
||||
/// given address will be deleted automatically when the guard is dropped.
|
||||
/// This can prevent memory leaks. Users can read the data and release the guard
|
||||
/// if the data in the store is valid for further processing. If the data is faulty, no
|
||||
/// manual deletion is necessary when returning from a processing function prematurely.
|
||||
fn read_with_guard(&mut self, addr: StoreAddr) -> PoolGuard;
|
||||
|
||||
/// Delete data inside the pool given a [StoreAddr]
|
||||
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError>;
|
||||
fn has_element_at(&self, addr: &StoreAddr) -> Result<bool, StoreError>;
|
||||
}
|
||||
|
||||
impl LocalPool {
|
||||
const STORE_FREE: PoolSize = PoolSize::MAX;
|
||||
const MAX_SIZE: PoolSize = Self::STORE_FREE - 1;
|
||||
/// Create a new local pool from the [given configuration][PoolCfg]. This function will sanitize
|
||||
/// the given configuration as well.
|
||||
pub fn new(mut cfg: PoolCfg) -> LocalPool {
|
||||
let subpools_num = cfg.sanitize();
|
||||
let mut local_pool = LocalPool {
|
||||
pool_cfg: cfg,
|
||||
pool: Vec::with_capacity(subpools_num),
|
||||
sizes_lists: Vec::with_capacity(subpools_num),
|
||||
};
|
||||
for &(num_elems, elem_size) in local_pool.pool_cfg.cfg.iter() {
|
||||
let next_pool_len = elem_size * num_elems as usize;
|
||||
local_pool.pool.push(vec![0; next_pool_len]);
|
||||
let next_sizes_list_len = num_elems as usize;
|
||||
local_pool
|
||||
.sizes_lists
|
||||
.push(vec![Self::STORE_FREE; next_sizes_list_len]);
|
||||
}
|
||||
local_pool
|
||||
}
|
||||
|
||||
fn addr_check(&self, addr: &StoreAddr) -> Result<usize, StoreError> {
|
||||
self.validate_addr(addr)?;
|
||||
let pool_idx = addr.pool_idx as usize;
|
||||
let size_list = self.sizes_lists.get(pool_idx).unwrap();
|
||||
let curr_size = size_list[addr.packet_idx as usize];
|
||||
if curr_size == Self::STORE_FREE {
|
||||
return Err(StoreError::DataDoesNotExist(*addr));
|
||||
}
|
||||
Ok(curr_size)
|
||||
}
|
||||
|
||||
fn validate_addr(&self, addr: &StoreAddr) -> Result<(), StoreError> {
|
||||
let pool_idx = addr.pool_idx as usize;
|
||||
if pool_idx >= self.pool_cfg.cfg.len() {
|
||||
return Err(StoreError::InvalidStoreId(
|
||||
StoreIdError::InvalidSubpool(addr.pool_idx),
|
||||
Some(*addr),
|
||||
));
|
||||
}
|
||||
if addr.packet_idx >= self.pool_cfg.cfg[addr.pool_idx as usize].0 {
|
||||
return Err(StoreError::InvalidStoreId(
|
||||
StoreIdError::InvalidPacketIdx(addr.packet_idx),
|
||||
Some(*addr),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reserve(&mut self, data_len: usize) -> Result<StoreAddr, StoreError> {
|
||||
let subpool_idx = self.find_subpool(data_len, 0)?;
|
||||
let (slot, size_slot_ref) = self.find_empty(subpool_idx)?;
|
||||
*size_slot_ref = data_len;
|
||||
Ok(StoreAddr {
|
||||
pool_idx: subpool_idx,
|
||||
packet_idx: slot,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_subpool(&self, req_size: usize, start_at_subpool: u16) -> Result<u16, StoreError> {
|
||||
for (i, &(_, elem_size)) in self.pool_cfg.cfg.iter().enumerate() {
|
||||
if i < start_at_subpool as usize {
|
||||
continue;
|
||||
}
|
||||
if elem_size >= req_size {
|
||||
return Ok(i as u16);
|
||||
}
|
||||
}
|
||||
Err(StoreError::DataTooLarge(req_size))
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: &StoreAddr, data: &[u8]) -> Result<(), StoreError> {
|
||||
let packet_pos = self.raw_pos(addr).ok_or_else(|| {
|
||||
StoreError::InternalError(format!(
|
||||
"write: Error in raw_pos func with address {:?}",
|
||||
addr
|
||||
))
|
||||
})?;
|
||||
let subpool = self.pool.get_mut(addr.pool_idx as usize).ok_or_else(|| {
|
||||
StoreError::InternalError(format!(
|
||||
"write: Error retrieving pool slice with address {:?}",
|
||||
addr
|
||||
))
|
||||
})?;
|
||||
let pool_slice = &mut subpool[packet_pos..packet_pos + data.len()];
|
||||
pool_slice.copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_empty(&mut self, subpool: u16) -> Result<(u16, &mut usize), StoreError> {
|
||||
if let Some(size_list) = self.sizes_lists.get_mut(subpool as usize) {
|
||||
for (i, elem_size) in size_list.iter_mut().enumerate() {
|
||||
if *elem_size == Self::STORE_FREE {
|
||||
return Ok((i as u16, elem_size));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(StoreError::InvalidStoreId(
|
||||
StoreIdError::InvalidSubpool(subpool),
|
||||
None,
|
||||
));
|
||||
}
|
||||
Err(StoreError::StoreFull(subpool))
|
||||
}
|
||||
|
||||
fn raw_pos(&self, addr: &StoreAddr) -> Option<usize> {
|
||||
let (_, size) = self.pool_cfg.cfg.get(addr.pool_idx as usize)?;
|
||||
Some(addr.packet_idx as usize * size)
|
||||
}
|
||||
}
|
||||
|
||||
impl PoolProvider for LocalPool {
|
||||
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError> {
|
||||
let data_len = data.len();
|
||||
if data_len > Self::MAX_SIZE {
|
||||
return Err(StoreError::DataTooLarge(data_len));
|
||||
}
|
||||
let addr = self.reserve(data_len)?;
|
||||
self.write(&addr, data)?;
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
fn free_element(&mut self, len: usize) -> Result<(StoreAddr, &mut [u8]), StoreError> {
|
||||
if len > Self::MAX_SIZE {
|
||||
return Err(StoreError::DataTooLarge(len));
|
||||
}
|
||||
let addr = self.reserve(len)?;
|
||||
let raw_pos = self.raw_pos(&addr).unwrap();
|
||||
let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap()[raw_pos..raw_pos + len];
|
||||
Ok((addr, block))
|
||||
}
|
||||
|
||||
fn modify(&mut self, addr: &StoreAddr) -> Result<&mut [u8], StoreError> {
|
||||
let curr_size = self.addr_check(addr)?;
|
||||
let raw_pos = self.raw_pos(addr).unwrap();
|
||||
let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap()[raw_pos..curr_size];
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard {
|
||||
PoolRwGuard::new(self, addr)
|
||||
}
|
||||
|
||||
fn read(&self, addr: &StoreAddr) -> Result<&[u8], StoreError> {
|
||||
let curr_size = self.addr_check(addr)?;
|
||||
let raw_pos = self.raw_pos(addr).unwrap();
|
||||
let block = &self.pool.get(addr.pool_idx as usize).unwrap()[raw_pos..raw_pos + curr_size];
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn read_with_guard(&mut self, addr: StoreAddr) -> PoolGuard {
|
||||
PoolGuard::new(self, addr)
|
||||
}
|
||||
|
||||
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError> {
|
||||
self.addr_check(&addr)?;
|
||||
let block_size = self.pool_cfg.cfg.get(addr.pool_idx as usize).unwrap().1;
|
||||
let raw_pos = self.raw_pos(&addr).unwrap();
|
||||
let block =
|
||||
&mut self.pool.get_mut(addr.pool_idx as usize).unwrap()[raw_pos..raw_pos + block_size];
|
||||
let size_list = self.sizes_lists.get_mut(addr.pool_idx as usize).unwrap();
|
||||
size_list[addr.packet_idx as usize] = Self::STORE_FREE;
|
||||
block.fill(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_element_at(&self, addr: &StoreAddr) -> Result<bool, StoreError> {
|
||||
self.validate_addr(addr)?;
|
||||
let pool_idx = addr.pool_idx as usize;
|
||||
let size_list = self.sizes_lists.get(pool_idx).unwrap();
|
||||
let curr_size = size_list[addr.packet_idx as usize];
|
||||
if curr_size == Self::STORE_FREE {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PoolGuard<'a> {
|
||||
pool: &'a mut LocalPool,
|
||||
pub addr: StoreAddr,
|
||||
no_deletion: bool,
|
||||
deletion_failed_error: Option<StoreError>,
|
||||
}
|
||||
|
||||
/// This helper object
|
||||
impl<'a> PoolGuard<'a> {
|
||||
pub fn new(pool: &'a mut LocalPool, addr: StoreAddr) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
addr,
|
||||
no_deletion: false,
|
||||
deletion_failed_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Result<&[u8], StoreError> {
|
||||
self.pool.read(&self.addr)
|
||||
}
|
||||
|
||||
/// Releasing the pool guard will disable the automatic deletion of the data when the guard
|
||||
/// is dropped.
|
||||
pub fn release(&mut self) {
|
||||
self.no_deletion = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PoolGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !self.no_deletion {
|
||||
if let Err(e) = self.pool.delete(self.addr) {
|
||||
self.deletion_failed_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PoolRwGuard<'a> {
|
||||
guard: PoolGuard<'a>,
|
||||
}
|
||||
|
||||
impl<'a> PoolRwGuard<'a> {
|
||||
pub fn new(pool: &'a mut LocalPool, addr: StoreAddr) -> Self {
|
||||
Self {
|
||||
guard: PoolGuard::new(pool, addr),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify(&mut self) -> Result<&mut [u8], StoreError> {
|
||||
self.guard.pool.modify(&self.guard.addr)
|
||||
}
|
||||
|
||||
delegate!(
|
||||
to self.guard {
|
||||
pub fn read(&self) -> Result<&[u8], StoreError>;
|
||||
/// Releasing the pool guard will disable the automatic deletion of the data when the guard
|
||||
/// is dropped.
|
||||
pub fn release(&mut self);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::pool::{
|
||||
LocalPool, PoolCfg, PoolGuard, PoolProvider, PoolRwGuard, StoreAddr, StoreError,
|
||||
StoreIdError,
|
||||
};
|
||||
use std::vec;
|
||||
|
||||
fn basic_small_pool() -> LocalPool {
|
||||
// 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes
|
||||
let pool_cfg = PoolCfg::new(vec![(4, 4), (2, 8), (1, 16)]);
|
||||
LocalPool::new(pool_cfg)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cfg() {
|
||||
// Values where number of buckets is 0 or size is too large should be removed
|
||||
let mut pool_cfg = PoolCfg::new(vec![(0, 0), (1, 0), (2, LocalPool::MAX_SIZE)]);
|
||||
pool_cfg.sanitize();
|
||||
assert_eq!(pool_cfg.cfg, vec![(1, 0)]);
|
||||
// Entries should be ordered according to bucket size
|
||||
pool_cfg = PoolCfg::new(vec![(16, 6), (32, 3), (8, 12)]);
|
||||
pool_cfg.sanitize();
|
||||
assert_eq!(pool_cfg.cfg, vec![(32, 3), (16, 6), (8, 12)]);
|
||||
// Unstable sort is used, so order of entries with same block length should not matter
|
||||
pool_cfg = PoolCfg::new(vec![(12, 12), (14, 16), (10, 12)]);
|
||||
pool_cfg.sanitize();
|
||||
assert!(
|
||||
pool_cfg.cfg == vec![(12, 12), (10, 12), (14, 16)]
|
||||
|| pool_cfg.cfg == vec![(10, 12), (12, 12), (14, 16)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_and_read() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let mut test_buf: [u8; 16] = [0; 16];
|
||||
for (i, val) in test_buf.iter_mut().enumerate() {
|
||||
*val = i as u8;
|
||||
}
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
// Read back data and verify correctness
|
||||
let res = local_pool.read(&addr);
|
||||
assert!(res.is_ok());
|
||||
let buf_read_back = res.unwrap();
|
||||
assert_eq!(buf_read_back.len(), 16);
|
||||
for (i, &val) in buf_read_back.iter().enumerate() {
|
||||
assert_eq!(val, i as u8);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_smaller_than_full_slot() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 12] = [0; 12];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let res = local_pool.read(&addr).expect("Read back failed");
|
||||
assert_eq!(res.len(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
// Delete the data
|
||||
let res = local_pool.delete(addr);
|
||||
assert!(res.is_ok());
|
||||
// Verify that the slot is free by trying to get a reference to it
|
||||
let res = local_pool.free_element(12);
|
||||
assert!(res.is_ok());
|
||||
let (addr, buf_ref) = res.unwrap();
|
||||
assert_eq!(
|
||||
addr,
|
||||
StoreAddr {
|
||||
pool_idx: 2,
|
||||
packet_idx: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(buf_ref.len(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let mut test_buf: [u8; 16] = [0; 16];
|
||||
for (i, val) in test_buf.iter_mut().enumerate() {
|
||||
*val = i as u8;
|
||||
}
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
|
||||
{
|
||||
// Verify that the slot is free by trying to get a reference to it
|
||||
let res = local_pool.modify(&addr).expect("Modifying data failed");
|
||||
res[0] = 0;
|
||||
res[1] = 0x42;
|
||||
}
|
||||
|
||||
let res = local_pool.read(&addr).expect("Reading back data failed");
|
||||
assert_eq!(res[0], 0);
|
||||
assert_eq!(res[1], 0x42);
|
||||
assert_eq!(res[2], 2);
|
||||
assert_eq!(res[3], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_consecutive_reservation() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
// Reserve two smaller blocks consecutively and verify that the third reservation fails
|
||||
let res = local_pool.free_element(8);
|
||||
assert!(res.is_ok());
|
||||
let (addr0, _) = res.unwrap();
|
||||
let res = local_pool.free_element(8);
|
||||
assert!(res.is_ok());
|
||||
let (addr1, _) = res.unwrap();
|
||||
let res = local_pool.free_element(8);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert_eq!(err, StoreError::StoreFull(1));
|
||||
|
||||
// Verify that the two deletions are successful
|
||||
assert!(local_pool.delete(addr0).is_ok());
|
||||
assert!(local_pool.delete(addr1).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_does_not_exist() {
|
||||
let local_pool = basic_small_pool();
|
||||
// Try to access data which does not exist
|
||||
let res = local_pool.read(&StoreAddr {
|
||||
packet_idx: 0,
|
||||
pool_idx: 0,
|
||||
});
|
||||
assert!(res.is_err());
|
||||
assert!(matches!(
|
||||
res.unwrap_err(),
|
||||
StoreError::DataDoesNotExist { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_full() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
assert!(local_pool.add(&test_buf).is_ok());
|
||||
// The subpool is now full and the call should fail accordingly
|
||||
let res = local_pool.add(&test_buf);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert!(matches!(err, StoreError::StoreFull { .. }));
|
||||
if let StoreError::StoreFull(subpool) = err {
|
||||
assert_eq!(subpool, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_pool_idx() {
|
||||
let local_pool = basic_small_pool();
|
||||
let addr = StoreAddr {
|
||||
pool_idx: 3,
|
||||
packet_idx: 0,
|
||||
};
|
||||
let res = local_pool.read(&addr);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(3), Some(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_packet_idx() {
|
||||
let local_pool = basic_small_pool();
|
||||
let addr = StoreAddr {
|
||||
pool_idx: 2,
|
||||
packet_idx: 1,
|
||||
};
|
||||
assert_eq!(addr.raw(), 0x00020001);
|
||||
let res = local_pool.read(&addr);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
StoreError::InvalidStoreId(StoreIdError::InvalidPacketIdx(1), Some(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_too_large() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let data_too_large = [0; 20];
|
||||
let res = local_pool.add(&data_too_large);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert_eq!(err, StoreError::DataTooLarge(20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_too_large_1() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let res = local_pool.free_element(LocalPool::MAX_SIZE + 1);
|
||||
assert!(res.is_err());
|
||||
assert_eq!(
|
||||
res.unwrap_err(),
|
||||
StoreError::DataTooLarge(LocalPool::MAX_SIZE + 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_free_element_too_large() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
// Try to request a slot which is too large
|
||||
let res = local_pool.free_element(20);
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err(), StoreError::DataTooLarge(20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_guard_deletion_man_creation() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let read_guard = PoolGuard::new(&mut local_pool, addr);
|
||||
drop(read_guard);
|
||||
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_guard_deletion() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let read_guard = local_pool.read_with_guard(addr);
|
||||
drop(read_guard);
|
||||
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_guard_with_release() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let mut read_guard = PoolGuard::new(&mut local_pool, addr);
|
||||
read_guard.release();
|
||||
drop(read_guard);
|
||||
assert!(local_pool.has_element_at(&addr).expect("Invalid address"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_modify_guard_man_creation() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let mut rw_guard = PoolRwGuard::new(&mut local_pool, addr);
|
||||
let _ = rw_guard.modify().expect("modify failed");
|
||||
drop(rw_guard);
|
||||
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_modify_guard() {
|
||||
let mut local_pool = basic_small_pool();
|
||||
let test_buf: [u8; 16] = [0; 16];
|
||||
let addr = local_pool.add(&test_buf).expect("Adding data failed");
|
||||
let mut rw_guard = local_pool.modify_with_guard(addr);
|
||||
let _ = rw_guard.modify().expect("modify failed");
|
||||
drop(rw_guard);
|
||||
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
|
||||
}
|
||||
}
|
479
satrs-core/src/pus/event.rs
Normal file
479
satrs-core/src/pus/event.rs
Normal file
@ -0,0 +1,479 @@
|
||||
use crate::pus::{source_buffer_large_enough, EcssTmError, EcssTmErrorWithSend};
|
||||
use spacepackets::ecss::EcssEnumeration;
|
||||
use spacepackets::tm::PusTm;
|
||||
use spacepackets::tm::PusTmSecondaryHeader;
|
||||
use spacepackets::{SpHeader, MAX_APID};
|
||||
|
||||
use crate::pus::EcssTmSenderCore;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use allocvec::EventReporter;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum Subservices {
|
||||
TmInfoReport = 1,
|
||||
TmLowSeverityReport = 2,
|
||||
TmMediumSeverityReport = 3,
|
||||
TmHighSeverityReport = 4,
|
||||
TcEnableEventGeneration = 5,
|
||||
TcDisableEventGeneration = 6,
|
||||
TcReportDisabledList = 7,
|
||||
TmDisabledEventsReport = 8,
|
||||
}
|
||||
|
||||
impl From<Subservices> for u8 {
|
||||
fn from(enumeration: Subservices) -> Self {
|
||||
enumeration as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Subservices {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Subservices::TmInfoReport as u8 => Ok(Subservices::TmInfoReport),
|
||||
x if x == Subservices::TmLowSeverityReport as u8 => {
|
||||
Ok(Subservices::TmLowSeverityReport)
|
||||
}
|
||||
x if x == Subservices::TmMediumSeverityReport as u8 => {
|
||||
Ok(Subservices::TmMediumSeverityReport)
|
||||
}
|
||||
x if x == Subservices::TmHighSeverityReport as u8 => {
|
||||
Ok(Subservices::TmHighSeverityReport)
|
||||
}
|
||||
x if x == Subservices::TcEnableEventGeneration as u8 => {
|
||||
Ok(Subservices::TcEnableEventGeneration)
|
||||
}
|
||||
x if x == Subservices::TcDisableEventGeneration as u8 => {
|
||||
Ok(Subservices::TcDisableEventGeneration)
|
||||
}
|
||||
x if x == Subservices::TcReportDisabledList as u8 => {
|
||||
Ok(Subservices::TcReportDisabledList)
|
||||
}
|
||||
x if x == Subservices::TmDisabledEventsReport as u8 => {
|
||||
Ok(Subservices::TmDisabledEventsReport)
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct EventReporterBase {
|
||||
msg_count: u16,
|
||||
apid: u16,
|
||||
pub dest_id: u16,
|
||||
}
|
||||
|
||||
impl EventReporterBase {
|
||||
pub fn new(apid: u16) -> Option<Self> {
|
||||
if apid > MAX_APID {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
msg_count: 0,
|
||||
dest_id: 0,
|
||||
apid,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn event_info<E>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.generate_and_send_generic_tm(
|
||||
buf,
|
||||
Subservices::TmInfoReport,
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_low_severity<E>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.generate_and_send_generic_tm(
|
||||
buf,
|
||||
Subservices::TmLowSeverityReport,
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_medium_severity<E>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.generate_and_send_generic_tm(
|
||||
buf,
|
||||
Subservices::TmMediumSeverityReport,
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_high_severity<E>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.generate_and_send_generic_tm(
|
||||
buf,
|
||||
Subservices::TmHighSeverityReport,
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_and_send_generic_tm<E>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
subservice: Subservices,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
let tm = self.generate_generic_event_tm(buf, subservice, time_stamp, event_id, aux_data)?;
|
||||
sender.send_tm(tm)?;
|
||||
self.msg_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_generic_event_tm<'a>(
|
||||
&'a self,
|
||||
buf: &'a mut [u8],
|
||||
subservice: Subservices,
|
||||
time_stamp: &'a [u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<PusTm, EcssTmError> {
|
||||
let mut src_data_len = event_id.byte_width();
|
||||
if let Some(aux_data) = aux_data {
|
||||
src_data_len += aux_data.len();
|
||||
}
|
||||
source_buffer_large_enough(buf.len(), src_data_len)?;
|
||||
let mut sp_header = SpHeader::tm_unseg(self.apid, 0, 0).unwrap();
|
||||
let sec_header = PusTmSecondaryHeader::new(
|
||||
5,
|
||||
subservice.into(),
|
||||
self.msg_count,
|
||||
self.dest_id,
|
||||
time_stamp,
|
||||
);
|
||||
let mut current_idx = 0;
|
||||
event_id.write_to_be_bytes(&mut buf[0..event_id.byte_width()])?;
|
||||
current_idx += event_id.byte_width();
|
||||
if let Some(aux_data) = aux_data {
|
||||
buf[current_idx..current_idx + aux_data.len()].copy_from_slice(aux_data);
|
||||
current_idx += aux_data.len();
|
||||
}
|
||||
Ok(PusTm::new(
|
||||
&mut sp_header,
|
||||
sec_header,
|
||||
Some(&buf[0..current_idx]),
|
||||
true,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod allocvec {
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub struct EventReporter {
|
||||
source_data_buf: Vec<u8>,
|
||||
pub reporter: EventReporterBase,
|
||||
}
|
||||
|
||||
impl EventReporter {
|
||||
pub fn new(apid: u16, max_event_id_and_aux_data_size: usize) -> Option<Self> {
|
||||
let reporter = EventReporterBase::new(apid)?;
|
||||
Some(Self {
|
||||
source_data_buf: vec![0; max_event_id_and_aux_data_size],
|
||||
reporter,
|
||||
})
|
||||
}
|
||||
pub fn event_info<E>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.reporter.event_info(
|
||||
self.source_data_buf.as_mut_slice(),
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_low_severity<E>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.reporter.event_low_severity(
|
||||
self.source_data_buf.as_mut_slice(),
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_medium_severity<E>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.reporter.event_medium_severity(
|
||||
self.source_data_buf.as_mut_slice(),
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_high_severity<E>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event_id: impl EcssEnumeration,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<(), EcssTmErrorWithSend<E>> {
|
||||
self.reporter.event_high_severity(
|
||||
self.source_data_buf.as_mut_slice(),
|
||||
sender,
|
||||
time_stamp,
|
||||
event_id,
|
||||
aux_data,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::events::{EventU32, Severity};
|
||||
use crate::pus::tests::CommonTmInfo;
|
||||
use spacepackets::ByteConversionError;
|
||||
use std::collections::VecDeque;
|
||||
use std::vec::Vec;
|
||||
|
||||
const EXAMPLE_APID: u16 = 0xee;
|
||||
const EXAMPLE_GROUP_ID: u16 = 2;
|
||||
const EXAMPLE_EVENT_ID_0: u16 = 1;
|
||||
#[allow(dead_code)]
|
||||
const EXAMPLE_EVENT_ID_1: u16 = 2;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
struct TmInfo {
|
||||
pub common: CommonTmInfo,
|
||||
pub event: EventU32,
|
||||
pub aux_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct TestSender {
|
||||
pub service_queue: VecDeque<TmInfo>,
|
||||
}
|
||||
|
||||
impl EcssTmSenderCore for TestSender {
|
||||
type Error = ();
|
||||
|
||||
fn send_tm(&mut self, tm: PusTm) -> Result<(), EcssTmErrorWithSend<()>> {
|
||||
assert!(tm.source_data().is_some());
|
||||
let src_data = tm.source_data().unwrap();
|
||||
assert!(src_data.len() >= 4);
|
||||
let event = EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
||||
let mut aux_data = Vec::new();
|
||||
if src_data.len() > 4 {
|
||||
aux_data.extend_from_slice(&src_data[4..]);
|
||||
}
|
||||
self.service_queue.push_back(TmInfo {
|
||||
common: CommonTmInfo::new_from_tm(&tm),
|
||||
event,
|
||||
aux_data,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn severity_to_subservice(severity: Severity) -> Subservices {
|
||||
match severity {
|
||||
Severity::INFO => Subservices::TmInfoReport,
|
||||
Severity::LOW => Subservices::TmLowSeverityReport,
|
||||
Severity::MEDIUM => Subservices::TmMediumSeverityReport,
|
||||
Severity::HIGH => Subservices::TmHighSeverityReport,
|
||||
}
|
||||
}
|
||||
|
||||
fn report_basic_event(
|
||||
reporter: &mut EventReporter,
|
||||
sender: &mut TestSender,
|
||||
time_stamp: &[u8],
|
||||
event: EventU32,
|
||||
severity: Severity,
|
||||
aux_data: Option<&[u8]>,
|
||||
) {
|
||||
match severity {
|
||||
Severity::INFO => {
|
||||
reporter
|
||||
.event_info(sender, time_stamp, event, aux_data)
|
||||
.expect("Error reporting info event");
|
||||
}
|
||||
Severity::LOW => {
|
||||
reporter
|
||||
.event_low_severity(sender, time_stamp, event, aux_data)
|
||||
.expect("Error reporting low event");
|
||||
}
|
||||
Severity::MEDIUM => {
|
||||
reporter
|
||||
.event_medium_severity(sender, time_stamp, event, aux_data)
|
||||
.expect("Error reporting medium event");
|
||||
}
|
||||
Severity::HIGH => {
|
||||
reporter
|
||||
.event_high_severity(sender, time_stamp, event, aux_data)
|
||||
.expect("Error reporting high event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn basic_event_test(
|
||||
max_event_aux_data_buf: usize,
|
||||
severity: Severity,
|
||||
error_data: Option<&[u8]>,
|
||||
) {
|
||||
let mut sender = TestSender::default();
|
||||
let reporter = EventReporter::new(EXAMPLE_APID, max_event_aux_data_buf);
|
||||
assert!(reporter.is_some());
|
||||
let mut reporter = reporter.unwrap();
|
||||
let time_stamp_empty: [u8; 7] = [0; 7];
|
||||
let mut error_copy = Vec::new();
|
||||
if let Some(err_data) = error_data {
|
||||
error_copy.extend_from_slice(err_data);
|
||||
}
|
||||
let event = EventU32::new(severity, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
|
||||
.expect("Error creating example event");
|
||||
report_basic_event(
|
||||
&mut reporter,
|
||||
&mut sender,
|
||||
&time_stamp_empty,
|
||||
event,
|
||||
severity,
|
||||
error_data,
|
||||
);
|
||||
assert_eq!(sender.service_queue.len(), 1);
|
||||
let tm_info = sender.service_queue.pop_front().unwrap();
|
||||
assert_eq!(
|
||||
tm_info.common.subservice,
|
||||
severity_to_subservice(severity) as u8
|
||||
);
|
||||
assert_eq!(tm_info.common.dest_id, 0);
|
||||
assert_eq!(tm_info.common.time_stamp, time_stamp_empty);
|
||||
assert_eq!(tm_info.common.msg_counter, 0);
|
||||
assert_eq!(tm_info.common.apid, EXAMPLE_APID);
|
||||
assert_eq!(tm_info.event, event);
|
||||
assert_eq!(tm_info.aux_data, error_copy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_info_event_generation() {
|
||||
basic_event_test(4, Severity::INFO, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_low_severity_event() {
|
||||
basic_event_test(4, Severity::LOW, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_medium_severity_event() {
|
||||
basic_event_test(4, Severity::MEDIUM, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_high_severity_event() {
|
||||
basic_event_test(4, Severity::HIGH, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_with_info_string() {
|
||||
let info_string = "Test Information";
|
||||
basic_event_test(32, Severity::INFO, Some(info_string.as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_severity_with_raw_err_data() {
|
||||
let raw_err_param: i32 = -1;
|
||||
let raw_err = raw_err_param.to_be_bytes();
|
||||
basic_event_test(8, Severity::LOW, Some(&raw_err))
|
||||
}
|
||||
|
||||
fn check_buf_too_small(
|
||||
reporter: &mut EventReporter,
|
||||
sender: &mut TestSender,
|
||||
expected_found_len: usize,
|
||||
) {
|
||||
let time_stamp_empty: [u8; 7] = [0; 7];
|
||||
let event = EventU32::new(Severity::INFO, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
|
||||
.expect("Error creating example event");
|
||||
let err = reporter.event_info(sender, &time_stamp_empty, event, None);
|
||||
assert!(err.is_err());
|
||||
let err = err.unwrap_err();
|
||||
if let EcssTmErrorWithSend::EcssTmError(EcssTmError::ByteConversionError(
|
||||
ByteConversionError::ToSliceTooSmall(missmatch),
|
||||
)) = err
|
||||
{
|
||||
assert_eq!(missmatch.expected, 4);
|
||||
assert_eq!(missmatch.found, expected_found_len);
|
||||
} else {
|
||||
panic!("Unexpected error {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insufficient_buffer() {
|
||||
let mut sender = TestSender::default();
|
||||
for i in 0..3 {
|
||||
let reporter = EventReporter::new(EXAMPLE_APID, i);
|
||||
assert!(reporter.is_some());
|
||||
let mut reporter = reporter.unwrap();
|
||||
check_buf_too_small(&mut reporter, &mut sender, i);
|
||||
}
|
||||
}
|
||||
}
|
328
satrs-core/src/pus/event_man.rs
Normal file
328
satrs-core/src/pus/event_man.rs
Normal file
@ -0,0 +1,328 @@
|
||||
use crate::events::{EventU32, GenericEvent, Severity};
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::events::{EventU32TypedSev, HasSeverity};
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::hash::Hash;
|
||||
#[cfg(feature = "alloc")]
|
||||
use hashbrown::HashSet;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use crate::pus::event::EventReporter;
|
||||
use crate::pus::verification::{TcStateStarted, VerificationToken};
|
||||
use crate::pus::EcssTmErrorWithSend;
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::pus::EcssTmSenderCore;
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub use alloc_mod::*;
|
||||
#[cfg(feature = "heapless")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
|
||||
pub use heapless_mod::*;
|
||||
|
||||
/// This trait allows the PUS event manager implementation to stay generic over various types
|
||||
/// of backend containers.
|
||||
///
|
||||
/// These backend containers keep track on whether a particular event is enabled or disabled for
|
||||
/// reporting and also expose a simple API to enable or disable the event reporting.
|
||||
///
|
||||
/// For example, a straight forward implementation for host systems could use a
|
||||
/// [hash set](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html)
|
||||
/// structure to track disabled events. A more primitive and embedded friendly
|
||||
/// solution could track this information in a static or pre-allocated list which contains
|
||||
/// the disabled events.
|
||||
pub trait PusEventMgmtBackendProvider<Provider: GenericEvent> {
|
||||
type Error;
|
||||
|
||||
fn event_enabled(&self, event: &Provider) -> bool;
|
||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>;
|
||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "heapless")]
|
||||
pub mod heapless_mod {
|
||||
use super::*;
|
||||
use crate::events::{GenericEvent, LargestEventRaw};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
|
||||
// TODO: After a new version of heapless is released which uses hash32 version 0.3, try using
|
||||
// regular Event type again.
|
||||
#[derive(Default)]
|
||||
pub struct HeaplessPusMgmtBackendProvider<const N: usize, Provider: GenericEvent> {
|
||||
disabled: heapless::FnvIndexSet<LargestEventRaw, N>,
|
||||
phantom: PhantomData<Provider>,
|
||||
}
|
||||
|
||||
/// Safety: All contained field are [Send] as well
|
||||
unsafe impl<const N: usize, Event: GenericEvent + Send> Send
|
||||
for HeaplessPusMgmtBackendProvider<N, Event>
|
||||
{
|
||||
}
|
||||
|
||||
impl<const N: usize, Provider: GenericEvent> PusEventMgmtBackendProvider<Provider>
|
||||
for HeaplessPusMgmtBackendProvider<N, Provider>
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn event_enabled(&self, event: &Provider) -> bool {
|
||||
self.disabled.contains(&event.raw_as_largest_type())
|
||||
}
|
||||
|
||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
self.disabled
|
||||
.insert(event.raw_as_largest_type())
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
Ok(self.disabled.remove(&event.raw_as_largest_type()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventRequest<Event: GenericEvent = EventU32> {
|
||||
Enable(Event),
|
||||
Disable(Event),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventRequestWithToken<Event: GenericEvent = EventU32> {
|
||||
pub request: EventRequest<Event>,
|
||||
pub token: VerificationToken<TcStateStarted>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventManError<SenderE> {
|
||||
EcssTmError(EcssTmErrorWithSend<SenderE>),
|
||||
SeverityMissmatch(Severity, Severity),
|
||||
}
|
||||
|
||||
impl<SenderE> From<EcssTmErrorWithSend<SenderE>> for EventManError<SenderE> {
|
||||
fn from(v: EcssTmErrorWithSend<SenderE>) -> Self {
|
||||
Self::EcssTmError(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use super::*;
|
||||
|
||||
/// Default backend provider which uses a hash set as the event reporting status container
|
||||
/// like mentioned in the example of the [PusEventMgmtBackendProvider] documentation.
|
||||
///
|
||||
/// This provider is a good option for host systems or larger embedded systems where
|
||||
/// the expected occasional memory allocation performed by the [HashSet] is not an issue.
|
||||
pub struct DefaultPusMgmtBackendProvider<Event: GenericEvent = EventU32> {
|
||||
disabled: HashSet<Event>,
|
||||
}
|
||||
|
||||
/// Safety: All contained field are [Send] as well
|
||||
unsafe impl<Event: GenericEvent + Send> Send for DefaultPusMgmtBackendProvider<Event> {}
|
||||
|
||||
impl<Event: GenericEvent> Default for DefaultPusMgmtBackendProvider<Event> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disabled: HashSet::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider: GenericEvent + PartialEq + Eq + Hash + Copy + Clone>
|
||||
PusEventMgmtBackendProvider<Provider> for DefaultPusMgmtBackendProvider<Provider>
|
||||
{
|
||||
type Error = ();
|
||||
fn event_enabled(&self, event: &Provider) -> bool {
|
||||
!self.disabled.contains(event)
|
||||
}
|
||||
|
||||
fn enable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
Ok(self.disabled.remove(event))
|
||||
}
|
||||
|
||||
fn disable_event_reporting(&mut self, event: &Provider) -> Result<bool, Self::Error> {
|
||||
Ok(self.disabled.insert(*event))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PusEventDispatcher<BackendError, Provider: GenericEvent> {
|
||||
reporter: EventReporter,
|
||||
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>,
|
||||
}
|
||||
|
||||
/// Safety: All contained fields are send as well.
|
||||
unsafe impl<E: Send, Event: GenericEvent + Send> Send for PusEventDispatcher<E, Event> {}
|
||||
|
||||
impl<BackendError, Provider: GenericEvent> PusEventDispatcher<BackendError, Provider> {
|
||||
pub fn new(
|
||||
reporter: EventReporter,
|
||||
backend: Box<dyn PusEventMgmtBackendProvider<Provider, Error = BackendError>>,
|
||||
) -> Self {
|
||||
Self { reporter, backend }
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendError, Event: GenericEvent> PusEventDispatcher<BackendError, Event> {
|
||||
pub fn enable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> {
|
||||
self.backend.enable_event_reporting(event)
|
||||
}
|
||||
|
||||
pub fn disable_tm_for_event(&mut self, event: &Event) -> Result<bool, BackendError> {
|
||||
self.backend.disable_event_reporting(event)
|
||||
}
|
||||
|
||||
pub fn generate_pus_event_tm_generic<E>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event: Event,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<bool, EventManError<E>> {
|
||||
if !self.backend.event_enabled(&event) {
|
||||
return Ok(false);
|
||||
}
|
||||
match event.severity() {
|
||||
Severity::INFO => self
|
||||
.reporter
|
||||
.event_info(sender, time_stamp, event, aux_data)
|
||||
.map(|_| true)
|
||||
.map_err(|e| e.into()),
|
||||
Severity::LOW => self
|
||||
.reporter
|
||||
.event_low_severity(sender, time_stamp, event, aux_data)
|
||||
.map(|_| true)
|
||||
.map_err(|e| e.into()),
|
||||
Severity::MEDIUM => self
|
||||
.reporter
|
||||
.event_medium_severity(sender, time_stamp, event, aux_data)
|
||||
.map(|_| true)
|
||||
.map_err(|e| e.into()),
|
||||
Severity::HIGH => self
|
||||
.reporter
|
||||
.event_high_severity(sender, time_stamp, event, aux_data)
|
||||
.map(|_| true)
|
||||
.map_err(|e| e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendError> PusEventDispatcher<BackendError, EventU32> {
|
||||
pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>(
|
||||
&mut self,
|
||||
event: &EventU32TypedSev<Severity>,
|
||||
) -> Result<bool, BackendError> {
|
||||
self.backend.enable_event_reporting(event.as_ref())
|
||||
}
|
||||
|
||||
pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>(
|
||||
&mut self,
|
||||
event: &EventU32TypedSev<Severity>,
|
||||
) -> Result<bool, BackendError> {
|
||||
self.backend.disable_event_reporting(event.as_ref())
|
||||
}
|
||||
|
||||
pub fn generate_pus_event_tm<E, Severity: HasSeverity>(
|
||||
&mut self,
|
||||
sender: &mut (impl EcssTmSenderCore<Error = E> + ?Sized),
|
||||
time_stamp: &[u8],
|
||||
event: EventU32TypedSev<Severity>,
|
||||
aux_data: Option<&[u8]>,
|
||||
) -> Result<bool, EventManError<E>> {
|
||||
self.generate_pus_event_tm_generic(sender, time_stamp, event.into(), aux_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::events::SeverityInfo;
|
||||
use spacepackets::tm::PusTm;
|
||||
use std::sync::mpsc::{channel, SendError, TryRecvError};
|
||||
use std::vec::Vec;
|
||||
|
||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||
EventU32TypedSev::<SeverityInfo>::const_new(1, 0);
|
||||
const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5);
|
||||
const EMPTY_STAMP: [u8; 7] = [0; 7];
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EventTmSender {
|
||||
sender: std::sync::mpsc::Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl EcssTmSenderCore for EventTmSender {
|
||||
type Error = SendError<Vec<u8>>;
|
||||
fn send_tm(&mut self, tm: PusTm) -> Result<(), EcssTmErrorWithSend<Self::Error>> {
|
||||
let mut vec = Vec::new();
|
||||
tm.append_to_vec(&mut vec)
|
||||
.map_err(|e| EcssTmErrorWithSend::EcssTmError(e.into()))?;
|
||||
self.sender
|
||||
.send(vec)
|
||||
.map_err(EcssTmErrorWithSend::SendError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_basic_man() -> PusEventDispatcher<(), EventU32> {
|
||||
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
|
||||
let backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
|
||||
PusEventDispatcher::new(reporter, Box::new(backend))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = EventTmSender { sender: event_tx };
|
||||
let event_sent = event_man
|
||||
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
|
||||
.expect("Sending info event failed");
|
||||
|
||||
assert!(event_sent);
|
||||
// Will not check packet here, correctness of packet was tested somewhere else
|
||||
event_rx.try_recv().expect("Receiving event TM failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disable_event() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = EventTmSender { sender: event_tx };
|
||||
let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
let mut event_sent = event_man
|
||||
.generate_pus_event_tm_generic(&mut sender, &EMPTY_STAMP, LOW_SEV_EVENT, None)
|
||||
.expect("Sending low severity event failed");
|
||||
assert!(!event_sent);
|
||||
let res = event_rx.try_recv();
|
||||
assert!(res.is_err());
|
||||
assert!(matches!(res.unwrap_err(), TryRecvError::Empty));
|
||||
// Check that only the low severity event was disabled
|
||||
event_sent = event_man
|
||||
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
|
||||
.expect("Sending info event failed");
|
||||
assert!(event_sent);
|
||||
event_rx.try_recv().expect("No info event received");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reenable_event() {
|
||||
let mut event_man = create_basic_man();
|
||||
let (event_tx, event_rx) = channel();
|
||||
let mut sender = EventTmSender { sender: event_tx };
|
||||
let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
res = event_man.enable_tm_for_event_with_sev(&INFO_EVENT);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
let event_sent = event_man
|
||||
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
|
||||
.expect("Sending info event failed");
|
||||
assert!(event_sent);
|
||||
event_rx.try_recv().expect("No info event received");
|
||||
}
|
||||
}
|
8
satrs-core/src/pus/hk.rs
Normal file
8
satrs-core/src/pus/hk.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Subservice {
|
||||
TcEnableGeneration = 5,
|
||||
TcDisableGeneration = 6,
|
||||
TmHkPacket = 25,
|
||||
TcGenerateOneShotHk = 27,
|
||||
TcModifyCollectionInterval = 31,
|
||||
}
|
127
satrs-core/src/pus/mod.rs
Normal file
127
satrs-core/src/pus/mod.rs
Normal file
@ -0,0 +1,127 @@
|
||||
//! All PUS support modules
|
||||
//!
|
||||
//! Currenty includes:
|
||||
//!
|
||||
//! 1. PUS Verification Service 1 module inside [verification]. Requires [alloc] support.
|
||||
#[cfg(feature = "alloc")]
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
#[cfg(feature = "alloc")]
|
||||
use dyn_clone::DynClone;
|
||||
use spacepackets::ecss::PusError;
|
||||
use spacepackets::time::TimestampError;
|
||||
use spacepackets::tm::PusTm;
|
||||
use spacepackets::{ByteConversionError, SizeMissmatch};
|
||||
|
||||
pub mod event;
|
||||
pub mod event_man;
|
||||
pub mod hk;
|
||||
pub mod verification;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EcssTmErrorWithSend<E> {
|
||||
/// Errors related to sending the verification telemetry to a TM recipient
|
||||
SendError(E),
|
||||
EcssTmError(EcssTmError),
|
||||
}
|
||||
|
||||
impl<E> From<EcssTmError> for EcssTmErrorWithSend<E> {
|
||||
fn from(value: EcssTmError) -> Self {
|
||||
Self::EcssTmError(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic error type which is also able to wrap a user send error with the user supplied type E.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EcssTmError {
|
||||
/// Errors related to the time stamp format of the telemetry
|
||||
TimestampError(TimestampError),
|
||||
/// Errors related to byte conversion, for example insufficient buffer size for given data
|
||||
ByteConversionError(ByteConversionError),
|
||||
/// Errors related to PUS packet format
|
||||
PusError(PusError),
|
||||
}
|
||||
|
||||
impl From<PusError> for EcssTmError {
|
||||
fn from(e: PusError) -> Self {
|
||||
EcssTmError::PusError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteConversionError> for EcssTmError {
|
||||
fn from(e: ByteConversionError) -> Self {
|
||||
EcssTmError::ByteConversionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic trait for a user supplied sender object.
|
||||
///
|
||||
/// This sender object is responsible for sending telemetry to a TM sink.
|
||||
pub trait EcssTmSenderCore: Send {
|
||||
type Error;
|
||||
|
||||
fn send_tm(&mut self, tm: PusTm) -> Result<(), EcssTmErrorWithSend<Self::Error>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod alloc_mod {
|
||||
use super::*;
|
||||
|
||||
/// Extension trait for [EcssTmSenderCore].
|
||||
///
|
||||
/// It provides additional functionality, for example by implementing the [Downcast] trait
|
||||
/// and the [DynClone] trait.
|
||||
///
|
||||
/// [Downcast] is implemented to allow passing the sender as a boxed trait object and still
|
||||
/// retrieve the concrete type at a later point.
|
||||
///
|
||||
/// [DynClone] allows cloning the trait object as long as the boxed object implements
|
||||
/// [Clone].
|
||||
pub trait EcssTmSender: EcssTmSenderCore + Downcast + DynClone {}
|
||||
|
||||
/// Blanket implementation for all types which implement [EcssTmSenderCore] and are clonable.
|
||||
impl<T> EcssTmSender for T where T: EcssTmSenderCore + Clone + 'static {}
|
||||
|
||||
dyn_clone::clone_trait_object!(<T> EcssTmSender<Error=T>);
|
||||
impl_downcast!(EcssTmSender assoc Error);
|
||||
}
|
||||
|
||||
pub(crate) fn source_buffer_large_enough(cap: usize, len: usize) -> Result<(), EcssTmError> {
|
||||
if len > cap {
|
||||
return Err(EcssTmError::ByteConversionError(
|
||||
ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: cap,
|
||||
expected: len,
|
||||
}),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use spacepackets::tm::{GenericPusTmSecondaryHeader, PusTm};
|
||||
use spacepackets::CcsdsPacket;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub(crate) struct CommonTmInfo {
|
||||
pub subservice: u8,
|
||||
pub apid: u16,
|
||||
pub msg_counter: u16,
|
||||
pub dest_id: u16,
|
||||
pub time_stamp: [u8; 7],
|
||||
}
|
||||
|
||||
impl CommonTmInfo {
|
||||
pub fn new_from_tm(tm: &PusTm) -> Self {
|
||||
let mut time_stamp = [0; 7];
|
||||
time_stamp.clone_from_slice(&tm.time_stamp()[0..7]);
|
||||
Self {
|
||||
subservice: tm.subservice(),
|
||||
apid: tm.apid(),
|
||||
msg_counter: tm.msg_counter(),
|
||||
dest_id: tm.dest_id(),
|
||||
time_stamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2207
satrs-core/src/pus/verification.rs
Normal file
2207
satrs-core/src/pus/verification.rs
Normal file
File diff suppressed because it is too large
Load Diff
53
satrs-core/src/res_code.rs
Normal file
53
satrs-core/src/res_code.rs
Normal file
@ -0,0 +1,53 @@
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacepackets::ecss::{EcssEnumU16, EcssEnumeration};
|
||||
use spacepackets::{ByteConversionError, SizeMissmatch};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ResultU16 {
|
||||
group_id: u8,
|
||||
unique_id: u8,
|
||||
}
|
||||
|
||||
impl ResultU16 {
|
||||
pub const fn const_new(group_id: u8, unique_id: u8) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
unique_id,
|
||||
}
|
||||
}
|
||||
pub fn raw(&self) -> u16 {
|
||||
((self.group_id as u16) << 8) | self.unique_id as u16
|
||||
}
|
||||
pub fn group_id(&self) -> u8 {
|
||||
self.group_id
|
||||
}
|
||||
pub fn unique_id(&self) -> u8 {
|
||||
self.unique_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResultU16> for EcssEnumU16 {
|
||||
fn from(v: ResultU16) -> Self {
|
||||
EcssEnumU16::new(v.raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl EcssEnumeration for ResultU16 {
|
||||
fn pfc(&self) -> u8 {
|
||||
16
|
||||
}
|
||||
|
||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
|
||||
if buf.len() < 2 {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: buf.len(),
|
||||
expected: 2,
|
||||
}));
|
||||
}
|
||||
buf[0] = self.group_id;
|
||||
buf[1] = self.unique_id;
|
||||
Ok(())
|
||||
}
|
||||
}
|
121
satrs-core/src/seq_count.rs
Normal file
121
satrs-core/src/seq_count.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use core::cell::Cell;
|
||||
use core::sync::atomic::{AtomicU16, Ordering};
|
||||
#[cfg(feature = "alloc")]
|
||||
use dyn_clone::DynClone;
|
||||
#[cfg(feature = "std")]
|
||||
pub use stdmod::*;
|
||||
|
||||
/// Core trait for objects which can provide a sequence count.
|
||||
///
|
||||
/// The core functions are not mutable on purpose to allow easier usage with
|
||||
/// static structs when using the interior mutability pattern. This can be achieved by using
|
||||
/// [Cell], [RefCell] or atomic types.
|
||||
pub trait SequenceCountProviderCore<Raw> {
|
||||
fn get(&self) -> Raw;
|
||||
|
||||
fn increment(&self);
|
||||
|
||||
// TODO: Maybe remove this?
|
||||
fn increment_mut(&mut self) {
|
||||
self.increment();
|
||||
}
|
||||
|
||||
fn get_and_increment(&self) -> Raw {
|
||||
let val = self.get();
|
||||
self.increment();
|
||||
val
|
||||
}
|
||||
|
||||
// TODO: Maybe remove this?
|
||||
fn get_and_increment_mut(&mut self) -> Raw {
|
||||
self.get_and_increment()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait which allows cloning a sequence count provider after it was turned into
|
||||
/// a trait object.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait SequenceCountProvider<Raw>: SequenceCountProviderCore<Raw> + DynClone {}
|
||||
#[cfg(feature = "alloc")]
|
||||
dyn_clone::clone_trait_object!(SequenceCountProvider<u16>);
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T, Raw> SequenceCountProvider<Raw> for T where T: SequenceCountProviderCore<Raw> + Clone {}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SeqCountProviderSimple {
|
||||
seq_count: Cell<u16>,
|
||||
}
|
||||
|
||||
impl SequenceCountProviderCore<u16> for SeqCountProviderSimple {
|
||||
fn get(&self) -> u16 {
|
||||
self.seq_count.get()
|
||||
}
|
||||
|
||||
fn increment(&self) {
|
||||
self.get_and_increment();
|
||||
}
|
||||
|
||||
fn get_and_increment(&self) -> u16 {
|
||||
let curr_count = self.seq_count.get();
|
||||
|
||||
if curr_count == u16::MAX {
|
||||
self.seq_count.set(0);
|
||||
} else {
|
||||
self.seq_count.set(curr_count + 1);
|
||||
}
|
||||
curr_count
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SeqCountProviderAtomicRef {
|
||||
atomic: AtomicU16,
|
||||
ordering: Ordering,
|
||||
}
|
||||
|
||||
impl SeqCountProviderAtomicRef {
|
||||
pub const fn new(ordering: Ordering) -> Self {
|
||||
Self {
|
||||
atomic: AtomicU16::new(0),
|
||||
ordering,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SequenceCountProviderCore<u16> for SeqCountProviderAtomicRef {
|
||||
fn get(&self) -> u16 {
|
||||
self.atomic.load(self.ordering)
|
||||
}
|
||||
|
||||
fn increment(&self) {
|
||||
self.atomic.fetch_add(1, self.ordering);
|
||||
}
|
||||
|
||||
fn get_and_increment(&self) -> u16 {
|
||||
self.atomic.fetch_add(1, self.ordering)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod stdmod {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SeqCountProviderSyncClonable {
|
||||
seq_count: Arc<AtomicU16>,
|
||||
}
|
||||
|
||||
impl SequenceCountProviderCore<u16> for SeqCountProviderSyncClonable {
|
||||
fn get(&self) -> u16 {
|
||||
self.seq_count.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn increment(&self) {
|
||||
self.seq_count.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn get_and_increment(&self) -> u16 {
|
||||
self.seq_count.fetch_add(1, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
}
|
333
satrs-core/src/tmtc/ccsds_distrib.rs
Normal file
333
satrs-core/src/tmtc/ccsds_distrib.rs
Normal file
@ -0,0 +1,333 @@
|
||||
//! CCSDS packet routing components.
|
||||
//!
|
||||
//! The routing components consist of two core components:
|
||||
//! 1. [CcsdsDistributor] component which dispatches received packets to a user-provided handler
|
||||
//! 2. [CcsdsPacketHandler] trait which should be implemented by the user-provided packet handler.
|
||||
//!
|
||||
//! The [CcsdsDistributor] implements the [ReceivesCcsdsTc] and [ReceivesTcCore] trait which allows to
|
||||
//! pass raw or CCSDS packets to it. Upon receiving a packet, it performs the following steps:
|
||||
//!
|
||||
//! 1. It tries to identify the target Application Process Identifier (APID) based on the
|
||||
//! respective CCSDS space packet header field. If that process fails, a [ByteConversionError] is
|
||||
//! returned to the user
|
||||
//! 2. If a valid APID is found and matches one of the APIDs provided by
|
||||
//! [CcsdsPacketHandler::valid_apids], it will pass the packet to the user provided
|
||||
//! [CcsdsPacketHandler::handle_known_apid] function. If no valid APID is found, the packet
|
||||
//! will be passed to the [CcsdsPacketHandler::handle_unknown_apid] function.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use satrs_core::tmtc::ccsds_distrib::{CcsdsPacketHandler, CcsdsDistributor};
|
||||
//! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
//! use spacepackets::{CcsdsPacket, SpHeader};
|
||||
//! use spacepackets::tc::PusTc;
|
||||
//!
|
||||
//! #[derive (Default)]
|
||||
//! struct ConcreteApidHandler {
|
||||
//! known_call_count: u32,
|
||||
//! unknown_call_count: u32
|
||||
//! }
|
||||
//!
|
||||
//! impl ConcreteApidHandler {
|
||||
//! fn mutable_foo(&mut self) {}
|
||||
//! }
|
||||
//!
|
||||
//! impl CcsdsPacketHandler for ConcreteApidHandler {
|
||||
//! type Error = ();
|
||||
//! fn valid_apids(&self) -> &'static [u16] { &[0x002] }
|
||||
//! fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
//! assert_eq!(sp_header.apid(), 0x002);
|
||||
//! assert_eq!(tc_raw.len(), 13);
|
||||
//! self.known_call_count += 1;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! fn handle_unknown_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
//! assert_eq!(sp_header.apid(), 0x003);
|
||||
//! assert_eq!(tc_raw.len(), 13);
|
||||
//! self.unknown_call_count += 1;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let apid_handler = ConcreteApidHandler::default();
|
||||
//! let mut ccsds_distributor = CcsdsDistributor::new(Box::new(apid_handler));
|
||||
//!
|
||||
//! // Create and pass PUS telecommand with a valid APID
|
||||
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
||||
//! let mut pus_tc = PusTc::new_simple(&mut space_packet_header, 17, 1, None, true);
|
||||
//! let mut test_buf: [u8; 32] = [0; 32];
|
||||
//! let mut size = pus_tc
|
||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
||||
//! .expect("Error writing TC to buffer");
|
||||
//! let tc_slice = &test_buf[0..size];
|
||||
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
|
||||
//!
|
||||
//! // Now pass a packet with an unknown APID to the distributor
|
||||
//! pus_tc.set_apid(0x003);
|
||||
//! size = pus_tc
|
||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
||||
//! .expect("Error writing TC to buffer");
|
||||
//! let tc_slice = &test_buf[0..size];
|
||||
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
|
||||
//!
|
||||
//! // User helper function to retrieve concrete class
|
||||
//! let concrete_handler_ref: &ConcreteApidHandler = ccsds_distributor
|
||||
//! .apid_handler_ref()
|
||||
//! .expect("Casting back to concrete type failed");
|
||||
//! assert_eq!(concrete_handler_ref.known_call_count, 1);
|
||||
//! assert_eq!(concrete_handler_ref.unknown_call_count, 1);
|
||||
//!
|
||||
//! // It's also possible to retrieve a mutable reference
|
||||
//! let mutable_ref: &mut ConcreteApidHandler = ccsds_distributor
|
||||
//! .apid_handler_mut()
|
||||
//! .expect("Casting back to concrete type failed");
|
||||
//! mutable_ref.mutable_foo();
|
||||
//! ```
|
||||
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTcCore};
|
||||
use alloc::boxed::Box;
|
||||
use downcast_rs::Downcast;
|
||||
use spacepackets::{ByteConversionError, CcsdsPacket, SizeMissmatch, SpHeader};
|
||||
|
||||
/// Generic trait for a handler or dispatcher object handling CCSDS packets.
|
||||
///
|
||||
/// Users should implement this trait on their custom CCSDS packet handler and then pass a boxed
|
||||
/// instance of this handler to the [CcsdsDistributor]. The distributor will use the trait
|
||||
/// interface to dispatch received packets to the user based on the Application Process Identifier
|
||||
/// (APID) field of the CCSDS packet.
|
||||
///
|
||||
/// This trait automatically implements the [downcast_rs::Downcast] to allow a more convenient API
|
||||
/// to cast trait objects back to their concrete type after the handler was passed to the
|
||||
/// distributor.
|
||||
pub trait CcsdsPacketHandler: Downcast + Send {
|
||||
type Error;
|
||||
|
||||
fn valid_apids(&self) -> &'static [u16];
|
||||
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8])
|
||||
-> Result<(), Self::Error>;
|
||||
fn handle_unknown_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(CcsdsPacketHandler assoc Error);
|
||||
|
||||
/// The CCSDS distributor dispatches received CCSDS packets to a user provided packet handler.
|
||||
pub struct CcsdsDistributor<E> {
|
||||
/// User provided APID handler stored as a generic trait object.
|
||||
/// It can be cast back to the original concrete type using the [Self::apid_handler_ref] or
|
||||
/// the [Self::apid_handler_mut] method.
|
||||
pub apid_handler: Box<dyn CcsdsPacketHandler<Error = E>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum CcsdsError<E> {
|
||||
CustomError(E),
|
||||
PacketError(ByteConversionError),
|
||||
}
|
||||
|
||||
impl<E: 'static> ReceivesCcsdsTc for CcsdsDistributor<E> {
|
||||
type Error = CcsdsError<E>;
|
||||
|
||||
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
self.dispatch_ccsds(header, tc_raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> ReceivesTcCore for CcsdsDistributor<E> {
|
||||
type Error = CcsdsError<E>;
|
||||
|
||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
if tc_raw.len() < 7 {
|
||||
return Err(CcsdsError::PacketError(
|
||||
ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||
found: tc_raw.len(),
|
||||
expected: 7,
|
||||
}),
|
||||
));
|
||||
}
|
||||
let (sp_header, _) =
|
||||
SpHeader::from_be_bytes(tc_raw).map_err(|e| CcsdsError::PacketError(e))?;
|
||||
self.dispatch_ccsds(&sp_header, tc_raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> CcsdsDistributor<E> {
|
||||
pub fn new(apid_handler: Box<dyn CcsdsPacketHandler<Error = E>>) -> Self {
|
||||
CcsdsDistributor { apid_handler }
|
||||
}
|
||||
|
||||
/// This function can be used to retrieve a reference to the concrete instance of the APID
|
||||
/// handler after it was passed to the distributor. See the
|
||||
/// [module documentation][crate::tmtc::ccsds_distrib] for an fsrc-example.
|
||||
pub fn apid_handler_ref<T: CcsdsPacketHandler<Error = E>>(&self) -> Option<&T> {
|
||||
self.apid_handler.downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// This function can be used to retrieve a mutable reference to the concrete instance of the
|
||||
/// APID handler after it was passed to the distributor.
|
||||
pub fn apid_handler_mut<T: CcsdsPacketHandler<Error = E>>(&mut self) -> Option<&mut T> {
|
||||
self.apid_handler.downcast_mut::<T>()
|
||||
}
|
||||
|
||||
fn dispatch_ccsds(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), CcsdsError<E>> {
|
||||
let apid = sp_header.apid();
|
||||
let valid_apids = self.apid_handler.valid_apids();
|
||||
for &valid_apid in valid_apids {
|
||||
if valid_apid == apid {
|
||||
return self
|
||||
.apid_handler
|
||||
.handle_known_apid(sp_header, tc_raw)
|
||||
.map_err(|e| CcsdsError::CustomError(e));
|
||||
}
|
||||
}
|
||||
self.apid_handler
|
||||
.handle_unknown_apid(sp_header, tc_raw)
|
||||
.map_err(|e| CcsdsError::CustomError(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
|
||||
use spacepackets::tc::PusTc;
|
||||
use spacepackets::CcsdsPacket;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::vec::Vec;
|
||||
|
||||
fn is_send<T: Send>(_: &T) {}
|
||||
|
||||
pub fn generate_ping_tc(buf: &mut [u8]) -> &[u8] {
|
||||
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
||||
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
|
||||
let size = pus_tc
|
||||
.write_to_bytes(buf)
|
||||
.expect("Error writing TC to buffer");
|
||||
assert_eq!(size, 13);
|
||||
&buf[0..size]
|
||||
}
|
||||
|
||||
pub struct BasicApidHandlerSharedQueue {
|
||||
pub known_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>,
|
||||
pub unknown_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BasicApidHandlerOwnedQueue {
|
||||
pub known_packet_queue: VecDeque<(u16, Vec<u8>)>,
|
||||
pub unknown_packet_queue: VecDeque<(u16, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl CcsdsPacketHandler for BasicApidHandlerSharedQueue {
|
||||
type Error = ();
|
||||
fn valid_apids(&self) -> &'static [u16] {
|
||||
&[0x000, 0x002]
|
||||
}
|
||||
|
||||
fn handle_known_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(tc_raw);
|
||||
Ok(self
|
||||
.known_packet_queue
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back((sp_header.apid(), vec)))
|
||||
}
|
||||
|
||||
fn handle_unknown_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(tc_raw);
|
||||
Ok(self
|
||||
.unknown_packet_queue
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back((sp_header.apid(), vec)))
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacketHandler for BasicApidHandlerOwnedQueue {
|
||||
type Error = ();
|
||||
|
||||
fn valid_apids(&self) -> &'static [u16] {
|
||||
&[0x000, 0x002]
|
||||
}
|
||||
|
||||
fn handle_known_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(tc_raw);
|
||||
Ok(self.known_packet_queue.push_back((sp_header.apid(), vec)))
|
||||
}
|
||||
|
||||
fn handle_unknown_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(tc_raw);
|
||||
Ok(self.unknown_packet_queue.push_back((sp_header.apid(), vec)))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribs_known_apid() {
|
||||
let known_packet_queue = Arc::new(Mutex::default());
|
||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
||||
let apid_handler = BasicApidHandlerSharedQueue {
|
||||
known_packet_queue: known_packet_queue.clone(),
|
||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
||||
};
|
||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
||||
is_send(&ccsds_distrib);
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
||||
|
||||
ccsds_distrib.pass_tc(tc_slice).expect("Passing TC failed");
|
||||
let recvd = known_packet_queue.lock().unwrap().pop_front();
|
||||
assert!(unknown_packet_queue.lock().unwrap().is_empty());
|
||||
assert!(recvd.is_some());
|
||||
let (apid, packet) = recvd.unwrap();
|
||||
assert_eq!(apid, 0x002);
|
||||
assert_eq!(packet, tc_slice);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribs_unknown_apid() {
|
||||
let known_packet_queue = Arc::new(Mutex::default());
|
||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
||||
let apid_handler = BasicApidHandlerSharedQueue {
|
||||
known_packet_queue: known_packet_queue.clone(),
|
||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
||||
};
|
||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
||||
let mut sph = SpHeader::tc_unseg(0x004, 0x34, 0).unwrap();
|
||||
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
pus_tc
|
||||
.write_to_bytes(test_buf.as_mut_slice())
|
||||
.expect("Error writing TC to buffer");
|
||||
ccsds_distrib.pass_tc(&test_buf).expect("Passing TC failed");
|
||||
let recvd = unknown_packet_queue.lock().unwrap().pop_front();
|
||||
assert!(known_packet_queue.lock().unwrap().is_empty());
|
||||
assert!(recvd.is_some());
|
||||
let (apid, packet) = recvd.unwrap();
|
||||
assert_eq!(apid, 0x004);
|
||||
assert_eq!(packet.as_slice(), test_buf);
|
||||
}
|
||||
}
|
100
satrs-core/src/tmtc/mod.rs
Normal file
100
satrs-core/src/tmtc/mod.rs
Normal file
@ -0,0 +1,100 @@
|
||||
//! Telemetry and Telecommanding (TMTC) module. Contains packet routing components with special
|
||||
//! support for CCSDS and ECSS packets.
|
||||
//!
|
||||
//! The distributor modules provided by this module use trait objects provided by the user to
|
||||
//! directly dispatch received packets to packet listeners based on packet fields like the CCSDS
|
||||
//! Application Process ID (APID) or the ECSS PUS service type. This allows for fast packet
|
||||
//! routing without the overhead and complication of using message queues. However, it also requires
|
||||
#[cfg(feature = "alloc")]
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacepackets::tc::PusTc;
|
||||
use spacepackets::{ByteConversionError, SizeMissmatch, SpHeader};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod ccsds_distrib;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod pus_distrib;
|
||||
pub mod tm_helper;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler};
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use pus_distrib::{PusDistributor, PusServiceProvider};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct AddressableId {
|
||||
pub target_id: u32,
|
||||
pub unique_id: u32,
|
||||
}
|
||||
|
||||
impl AddressableId {
|
||||
pub fn from_raw_be(buf: &[u8]) -> Result<Self, ByteConversionError> {
|
||||
if buf.len() < 8 {
|
||||
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||
found: buf.len(),
|
||||
expected: 8,
|
||||
}));
|
||||
}
|
||||
Ok(Self {
|
||||
target_id: u32::from_be_bytes(buf[0..4].try_into().unwrap()),
|
||||
unique_id: u32::from_be_bytes(buf[4..8].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
|
||||
if buf.len() < 8 {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: buf.len(),
|
||||
expected: 8,
|
||||
}));
|
||||
}
|
||||
buf[0..4].copy_from_slice(&self.target_id.to_be_bytes());
|
||||
buf[4..8].copy_from_slice(&self.unique_id.to_be_bytes());
|
||||
Ok(8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic trait for object which can receive any telecommands in form of a raw bytestream, with
|
||||
/// no assumptions about the received protocol.
|
||||
///
|
||||
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
|
||||
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows to pass the respective packets in
|
||||
/// raw byte format into them.
|
||||
pub trait ReceivesTcCore: Send {
|
||||
type Error;
|
||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Extension trait of [ReceivesTcCore] which allows downcasting by implementing [Downcast]
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait ReceivesTc: ReceivesTcCore + Downcast {}
|
||||
|
||||
/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature
|
||||
/// is enabled.
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T> ReceivesTc for T where T: ReceivesTcCore + 'static {}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl_downcast!(ReceivesTc assoc Error);
|
||||
|
||||
/// Generic trait for object which can receive CCSDS space packets, for example ECSS PUS packets
|
||||
/// for CCSDS File Delivery Protocol (CFDP) packets.
|
||||
///
|
||||
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
|
||||
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows
|
||||
/// to pass the respective packets in raw byte format or in CCSDS format into them.
|
||||
pub trait ReceivesCcsdsTc {
|
||||
type Error;
|
||||
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Generic trait for objects which can receive ECSS PUS telecommands. This trait is
|
||||
/// implemented by the [crate::tmtc::pus_distrib::PusDistributor] objects to allow passing PUS TC
|
||||
/// packets into it.
|
||||
pub trait ReceivesEcssPusTc {
|
||||
type Error;
|
||||
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTc) -> Result<(), Self::Error>;
|
||||
}
|
333
satrs-core/src/tmtc/pus_distrib.rs
Normal file
333
satrs-core/src/tmtc/pus_distrib.rs
Normal file
@ -0,0 +1,333 @@
|
||||
//! ECSS PUS packet routing components.
|
||||
//!
|
||||
//! The routing components consist of two core components:
|
||||
//! 1. [PusDistributor] component which dispatches received packets to a user-provided handler.
|
||||
//! 2. [PusServiceProvider] trait which should be implemented by the user-provided PUS packet
|
||||
//! handler.
|
||||
//!
|
||||
//! The [PusDistributor] implements the [ReceivesEcssPusTc], [ReceivesCcsdsTc] and the
|
||||
//! [ReceivesTcCore] trait which allows to pass raw packets, CCSDS packets and PUS TC packets into
|
||||
//! it. Upon receiving a packet, it performs the following steps:
|
||||
//!
|
||||
//! 1. It tries to extract the [SpHeader] and [PusTc] objects from the raw bytestream. If this
|
||||
//! process fails, a [PusDistribError::PusError] is returned to the user.
|
||||
//! 2. If it was possible to extract both components, the packet will be passed to the
|
||||
//! [PusServiceProvider::handle_pus_tc_packet] method provided by the user.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use satrs_core::tmtc::pus_distrib::{PusDistributor, PusServiceProvider};
|
||||
//! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore};
|
||||
//! use spacepackets::SpHeader;
|
||||
//! use spacepackets::tc::PusTc;
|
||||
//! struct ConcretePusHandler {
|
||||
//! handler_call_count: u32
|
||||
//! }
|
||||
//!
|
||||
//! // This is a very simple possible service provider. It increments an internal call count field,
|
||||
//! // which is used to verify the handler was called
|
||||
//! impl PusServiceProvider for ConcretePusHandler {
|
||||
//! type Error = ();
|
||||
//! fn handle_pus_tc_packet(&mut self, service: u8, header: &SpHeader, pus_tc: &PusTc) -> Result<(), Self::Error> {
|
||||
//! assert_eq!(service, 17);
|
||||
//! assert_eq!(pus_tc.len_packed(), 13);
|
||||
//! self.handler_call_count += 1;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let service_handler = ConcretePusHandler {
|
||||
//! handler_call_count: 0
|
||||
//! };
|
||||
//! let mut pus_distributor = PusDistributor::new(Box::new(service_handler));
|
||||
//!
|
||||
//! // Create and pass PUS ping telecommand with a valid APID
|
||||
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
|
||||
//! let mut pus_tc = PusTc::new_simple(&mut space_packet_header, 17, 1, None, true);
|
||||
//! let mut test_buf: [u8; 32] = [0; 32];
|
||||
//! let mut size = pus_tc
|
||||
//! .write_to_bytes(test_buf.as_mut_slice())
|
||||
//! .expect("Error writing TC to buffer");
|
||||
//! let tc_slice = &test_buf[0..size];
|
||||
//!
|
||||
//! pus_distributor.pass_tc(tc_slice).expect("Passing PUS telecommand failed");
|
||||
//!
|
||||
//! // User helper function to retrieve concrete class. We check the call count here to verify
|
||||
//! // that the PUS ping telecommand was routed successfully.
|
||||
//! let concrete_handler_ref: &ConcretePusHandler = pus_distributor
|
||||
//! .service_provider_ref()
|
||||
//! .expect("Casting back to concrete type failed");
|
||||
//! assert_eq!(concrete_handler_ref.handler_call_count, 1);
|
||||
//! ```
|
||||
use crate::tmtc::{ReceivesCcsdsTc, ReceivesEcssPusTc, ReceivesTcCore};
|
||||
use alloc::boxed::Box;
|
||||
use downcast_rs::Downcast;
|
||||
use spacepackets::ecss::{PusError, PusPacket};
|
||||
use spacepackets::tc::PusTc;
|
||||
use spacepackets::SpHeader;
|
||||
|
||||
pub trait PusServiceProvider: Downcast + Send {
|
||||
type Error;
|
||||
fn handle_pus_tc_packet(
|
||||
&mut self,
|
||||
service: u8,
|
||||
header: &SpHeader,
|
||||
pus_tc: &PusTc,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
downcast_rs::impl_downcast!(PusServiceProvider assoc Error);
|
||||
|
||||
pub struct PusDistributor<E> {
|
||||
pub service_provider: Box<dyn PusServiceProvider<Error = E>>,
|
||||
}
|
||||
|
||||
impl<E> PusDistributor<E> {
|
||||
pub fn new(service_provider: Box<dyn PusServiceProvider<Error = E>>) -> Self {
|
||||
PusDistributor { service_provider }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PusDistribError<E> {
|
||||
CustomError(E),
|
||||
PusError(PusError),
|
||||
}
|
||||
|
||||
impl<E: 'static> ReceivesTcCore for PusDistributor<E> {
|
||||
type Error = PusDistribError<E>;
|
||||
fn pass_tc(&mut self, tm_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
// Convert to ccsds and call pass_ccsds
|
||||
let (sp_header, _) = SpHeader::from_be_bytes(tm_raw)
|
||||
.map_err(|e| PusDistribError::PusError(PusError::ByteConversionError(e)))?;
|
||||
self.pass_ccsds(&sp_header, tm_raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> ReceivesCcsdsTc for PusDistributor<E> {
|
||||
type Error = PusDistribError<E>;
|
||||
fn pass_ccsds(&mut self, header: &SpHeader, tm_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
let (tc, _) = PusTc::from_bytes(tm_raw).map_err(|e| PusDistribError::PusError(e))?;
|
||||
self.pass_pus_tc(header, &tc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> ReceivesEcssPusTc for PusDistributor<E> {
|
||||
type Error = PusDistribError<E>;
|
||||
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTc) -> Result<(), Self::Error> {
|
||||
self.service_provider
|
||||
.handle_pus_tc_packet(pus_tc.service(), header, pus_tc)
|
||||
.map_err(|e| PusDistribError::CustomError(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: 'static> PusDistributor<E> {
|
||||
pub fn service_provider_ref<T: PusServiceProvider<Error = E>>(&self) -> Option<&T> {
|
||||
self.service_provider.downcast_ref::<T>()
|
||||
}
|
||||
|
||||
pub fn service_provider_mut<T: PusServiceProvider<Error = E>>(&mut self) -> Option<&mut T> {
|
||||
self.service_provider.downcast_mut::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tmtc::ccsds_distrib::tests::{
|
||||
generate_ping_tc, BasicApidHandlerOwnedQueue, BasicApidHandlerSharedQueue,
|
||||
};
|
||||
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
|
||||
use alloc::vec::Vec;
|
||||
use spacepackets::ecss::PusError;
|
||||
use spacepackets::tc::PusTc;
|
||||
use spacepackets::CcsdsPacket;
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::VecDeque;
|
||||
#[cfg(feature = "std")]
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
fn is_send<T: Send>(_: &T) {}
|
||||
|
||||
struct PusHandlerSharedQueue {
|
||||
pub pus_queue: Arc<Mutex<VecDeque<(u8, u16, Vec<u8>)>>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PusHandlerOwnedQueue {
|
||||
pub pus_queue: VecDeque<(u8, u16, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl PusServiceProvider for PusHandlerSharedQueue {
|
||||
type Error = PusError;
|
||||
fn handle_pus_tc_packet(
|
||||
&mut self,
|
||||
service: u8,
|
||||
sp_header: &SpHeader,
|
||||
pus_tc: &PusTc,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec: Vec<u8> = Vec::new();
|
||||
pus_tc.append_to_vec(&mut vec)?;
|
||||
Ok(self
|
||||
.pus_queue
|
||||
.lock()
|
||||
.expect("Mutex lock failed")
|
||||
.push_back((service, sp_header.apid(), vec)))
|
||||
}
|
||||
}
|
||||
|
||||
impl PusServiceProvider for PusHandlerOwnedQueue {
|
||||
type Error = PusError;
|
||||
fn handle_pus_tc_packet(
|
||||
&mut self,
|
||||
service: u8,
|
||||
sp_header: &SpHeader,
|
||||
pus_tc: &PusTc,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut vec: Vec<u8> = Vec::new();
|
||||
pus_tc.append_to_vec(&mut vec)?;
|
||||
Ok(self.pus_queue.push_back((service, sp_header.apid(), vec)))
|
||||
}
|
||||
}
|
||||
|
||||
struct ApidHandlerShared {
|
||||
pub pus_distrib: PusDistributor<PusError>,
|
||||
pub handler_base: BasicApidHandlerSharedQueue,
|
||||
}
|
||||
|
||||
struct ApidHandlerOwned {
|
||||
pub pus_distrib: PusDistributor<PusError>,
|
||||
handler_base: BasicApidHandlerOwnedQueue,
|
||||
}
|
||||
|
||||
macro_rules! apid_handler_impl {
|
||||
() => {
|
||||
type Error = PusError;
|
||||
|
||||
fn valid_apids(&self) -> &'static [u16] {
|
||||
&[0x000, 0x002]
|
||||
}
|
||||
|
||||
fn handle_known_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.handler_base
|
||||
.handle_known_apid(&sp_header, tc_raw)
|
||||
.ok()
|
||||
.expect("Unexpected error");
|
||||
match self.pus_distrib.pass_ccsds(&sp_header, tc_raw) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => match e {
|
||||
PusDistribError::CustomError(_) => Ok(()),
|
||||
PusDistribError::PusError(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_unknown_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.handler_base
|
||||
.handle_unknown_apid(&sp_header, tc_raw)
|
||||
.ok()
|
||||
.expect("Unexpected error");
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl CcsdsPacketHandler for ApidHandlerOwned {
|
||||
apid_handler_impl!();
|
||||
}
|
||||
|
||||
impl CcsdsPacketHandler for ApidHandlerShared {
|
||||
apid_handler_impl!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
fn test_pus_distribution() {
|
||||
let known_packet_queue = Arc::new(Mutex::default());
|
||||
let unknown_packet_queue = Arc::new(Mutex::default());
|
||||
let pus_queue = Arc::new(Mutex::default());
|
||||
let pus_handler = PusHandlerSharedQueue {
|
||||
pus_queue: pus_queue.clone(),
|
||||
};
|
||||
let handler_base = BasicApidHandlerSharedQueue {
|
||||
known_packet_queue: known_packet_queue.clone(),
|
||||
unknown_packet_queue: unknown_packet_queue.clone(),
|
||||
};
|
||||
|
||||
let pus_distrib = PusDistributor {
|
||||
service_provider: Box::new(pus_handler),
|
||||
};
|
||||
is_send(&pus_distrib);
|
||||
let apid_handler = ApidHandlerShared {
|
||||
pus_distrib,
|
||||
handler_base,
|
||||
};
|
||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
||||
|
||||
// Pass packet to distributor
|
||||
ccsds_distrib
|
||||
.pass_tc(tc_slice)
|
||||
.expect("Passing TC slice failed");
|
||||
let recvd_ccsds = known_packet_queue.lock().unwrap().pop_front();
|
||||
assert!(unknown_packet_queue.lock().unwrap().is_empty());
|
||||
assert!(recvd_ccsds.is_some());
|
||||
let (apid, packet) = recvd_ccsds.unwrap();
|
||||
assert_eq!(apid, 0x002);
|
||||
assert_eq!(packet.as_slice(), tc_slice);
|
||||
let recvd_pus = pus_queue.lock().unwrap().pop_front();
|
||||
assert!(recvd_pus.is_some());
|
||||
let (service, apid, tc_raw) = recvd_pus.unwrap();
|
||||
assert_eq!(service, 17);
|
||||
assert_eq!(apid, 0x002);
|
||||
assert_eq!(tc_raw, tc_slice);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_any_cast() {
|
||||
let pus_handler = PusHandlerOwnedQueue::default();
|
||||
let handler_base = BasicApidHandlerOwnedQueue::default();
|
||||
let pus_distrib = PusDistributor {
|
||||
service_provider: Box::new(pus_handler),
|
||||
};
|
||||
|
||||
let apid_handler = ApidHandlerOwned {
|
||||
pus_distrib,
|
||||
handler_base,
|
||||
};
|
||||
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
|
||||
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
|
||||
|
||||
ccsds_distrib
|
||||
.pass_tc(tc_slice)
|
||||
.expect("Passing TC slice failed");
|
||||
|
||||
let apid_handler_casted_back: &mut ApidHandlerOwned = ccsds_distrib
|
||||
.apid_handler_mut()
|
||||
.expect("Cast to concrete type ApidHandler failed");
|
||||
assert!(!apid_handler_casted_back
|
||||
.handler_base
|
||||
.known_packet_queue
|
||||
.is_empty());
|
||||
let handler_casted_back: &mut PusHandlerOwnedQueue = apid_handler_casted_back
|
||||
.pus_distrib
|
||||
.service_provider_mut()
|
||||
.expect("Cast to concrete type PusHandlerOwnedQueue failed");
|
||||
assert!(!handler_casted_back.pus_queue.is_empty());
|
||||
let (service, apid, packet_raw) = handler_casted_back.pus_queue.pop_front().unwrap();
|
||||
assert_eq!(service, 17);
|
||||
assert_eq!(apid, 0x002);
|
||||
assert_eq!(packet_raw.as_slice(), tc_slice);
|
||||
}
|
||||
}
|
52
satrs-core/src/tmtc/tm_helper.rs
Normal file
52
satrs-core/src/tmtc/tm_helper.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use spacepackets::time::cds::TimeProvider;
|
||||
use spacepackets::time::TimeWriter;
|
||||
use spacepackets::tm::{PusTm, PusTmSecondaryHeader};
|
||||
use spacepackets::SpHeader;
|
||||
|
||||
pub struct PusTmWithCdsShortHelper {
|
||||
apid: u16,
|
||||
cds_short_buf: [u8; 7],
|
||||
}
|
||||
|
||||
impl PusTmWithCdsShortHelper {
|
||||
pub fn new(apid: u16) -> Self {
|
||||
Self {
|
||||
apid,
|
||||
cds_short_buf: [0; 7],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn create_pus_tm_timestamp_now<'a>(
|
||||
&'a mut self,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
source_data: Option<&'a [u8]>,
|
||||
) -> PusTm {
|
||||
let time_stamp = TimeProvider::from_now_with_u16_days().unwrap();
|
||||
time_stamp.write_to_bytes(&mut self.cds_short_buf).unwrap();
|
||||
self.create_pus_tm_common(service, subservice, source_data)
|
||||
}
|
||||
|
||||
pub fn create_pus_tm_with_stamp<'a>(
|
||||
&'a mut self,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
source_data: Option<&'a [u8]>,
|
||||
stamper: &TimeProvider,
|
||||
) -> PusTm {
|
||||
stamper.write_to_bytes(&mut self.cds_short_buf).unwrap();
|
||||
self.create_pus_tm_common(service, subservice, source_data)
|
||||
}
|
||||
|
||||
fn create_pus_tm_common<'a>(
|
||||
&'a self,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
source_data: Option<&'a [u8]>,
|
||||
) -> PusTm {
|
||||
let mut reply_header = SpHeader::tm_unseg(self.apid, 0, 0).unwrap();
|
||||
let tc_header = PusTmSecondaryHeader::new_simple(service, subservice, &self.cds_short_buf);
|
||||
PusTm::new(&mut reply_header, tc_header, source_data, true)
|
||||
}
|
||||
}
|
164
satrs-core/tests/hk_helpers.rs
Normal file
164
satrs-core/tests/hk_helpers.rs
Normal file
@ -0,0 +1,164 @@
|
||||
#![allow(dead_code)]
|
||||
use core::mem::size_of;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacepackets::ecss::{Ptc, RealPfc, UnsignedPfc};
|
||||
use spacepackets::time::cds::TimeProvider;
|
||||
use spacepackets::time::{CcsdsTimeProvider, TimeWriter};
|
||||
|
||||
enum NumOfParamsInfo {
|
||||
/// The parameter entry is a scalar field
|
||||
Scalar = 0b00,
|
||||
/// The parameter entry is a vector, and its length field is one byte wide (max. 255 entries)
|
||||
VecLenFieldOneByte = 0b01,
|
||||
/// The parameter entry is a vecotr, and its length field is two bytes wide (max. 65565 entries)
|
||||
VecLenFieldTwoBytes = 0b10,
|
||||
/// The parameter entry is a matrix, and its length field contains a one byte row number
|
||||
/// and a one byte column number.
|
||||
MatrixRowsAndColumns = 0b11,
|
||||
}
|
||||
|
||||
const HAS_VALIDITY_MASK: u8 = 1 << 7;
|
||||
|
||||
struct ParamWithValidity<T> {
|
||||
valid: bool,
|
||||
val: T,
|
||||
}
|
||||
|
||||
struct TestMgmHk {
|
||||
temp: f32,
|
||||
mgm_vals: [u16; 3],
|
||||
}
|
||||
|
||||
struct TestMgmHkWithIndividualValidity {
|
||||
temp: ParamWithValidity<f32>,
|
||||
mgm_vals: ParamWithValidity<[u16; 3]>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TestMgmHkWithGroupValidity {
|
||||
last_valid_stamp: TimeProvider,
|
||||
valid: bool,
|
||||
temp: f32,
|
||||
mgm_vals: [u16; 3],
|
||||
}
|
||||
|
||||
impl TestMgmHk {
|
||||
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ()> {
|
||||
let mut curr_idx = 0;
|
||||
buf[curr_idx..curr_idx + size_of::<f32>()].copy_from_slice(&self.temp.to_be_bytes());
|
||||
curr_idx += size_of::<f32>();
|
||||
for val in self.mgm_vals {
|
||||
buf[curr_idx..curr_idx + size_of::<u16>()].copy_from_slice(&val.to_be_bytes());
|
||||
curr_idx += size_of::<u16>();
|
||||
}
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
/// This could in principle be auto-generated.
|
||||
impl TestMgmHkWithIndividualValidity {
|
||||
pub fn write_to_be_bytes_self_describing(&self, buf: &mut [u8]) -> Result<usize, ()> {
|
||||
let mut curr_idx = 0;
|
||||
buf[curr_idx] = 0;
|
||||
buf[curr_idx] |= HAS_VALIDITY_MASK | (self.temp.valid as u8) << 6;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = Ptc::Real as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = RealPfc::Float as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx..curr_idx + size_of::<f32>()].copy_from_slice(&self.temp.val.to_be_bytes());
|
||||
curr_idx += size_of::<f32>();
|
||||
buf[curr_idx] = 0;
|
||||
buf[curr_idx] |= HAS_VALIDITY_MASK
|
||||
| (self.mgm_vals.valid as u8) << 6
|
||||
| (NumOfParamsInfo::VecLenFieldOneByte as u8) << 4;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = Ptc::UnsignedInt as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = UnsignedPfc::TwoBytes as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = 3;
|
||||
curr_idx += 1;
|
||||
for val in self.mgm_vals.val {
|
||||
buf[curr_idx..curr_idx + size_of::<u16>()].copy_from_slice(&val.to_be_bytes());
|
||||
curr_idx += size_of::<u16>();
|
||||
}
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestMgmHkWithGroupValidity {
|
||||
pub fn write_to_be_bytes_self_describing(&self, buf: &mut [u8]) -> Result<usize, ()> {
|
||||
let mut curr_idx = 0;
|
||||
buf[curr_idx] = self.valid as u8;
|
||||
curr_idx += 1;
|
||||
self.last_valid_stamp
|
||||
.write_to_bytes(&mut buf[curr_idx..curr_idx + self.last_valid_stamp.len_as_bytes()])
|
||||
.unwrap();
|
||||
curr_idx += self.last_valid_stamp.len_as_bytes();
|
||||
buf[curr_idx] = 0;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = Ptc::Real as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = RealPfc::Float as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx..curr_idx + size_of::<f32>()].copy_from_slice(&self.temp.to_be_bytes());
|
||||
curr_idx += size_of::<f32>();
|
||||
buf[curr_idx] = 0;
|
||||
buf[curr_idx] |= (NumOfParamsInfo::VecLenFieldOneByte as u8) << 4;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = Ptc::UnsignedInt as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = UnsignedPfc::TwoBytes as u8;
|
||||
curr_idx += 1;
|
||||
buf[curr_idx] = 3;
|
||||
for val in self.mgm_vals {
|
||||
buf[curr_idx..curr_idx + size_of::<u16>()].copy_from_slice(&val.to_be_bytes());
|
||||
curr_idx += size_of::<u16>();
|
||||
}
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn main() {
|
||||
let mut raw_buf: [u8; 32] = [0; 32];
|
||||
let mgm_hk = TestMgmHk {
|
||||
temp: 20.0,
|
||||
mgm_vals: [0x1f1f, 0x2f2f, 0x3f3f],
|
||||
};
|
||||
// 4 byte float + 3 * 2 bytes MGM values
|
||||
let written = mgm_hk.write_to_be_bytes(&mut raw_buf).unwrap();
|
||||
assert_eq!(written, 10);
|
||||
|
||||
let mgm_hk_individual_validity = TestMgmHkWithIndividualValidity {
|
||||
temp: ParamWithValidity {
|
||||
valid: true,
|
||||
val: 20.0,
|
||||
},
|
||||
mgm_vals: ParamWithValidity {
|
||||
valid: true,
|
||||
val: [0x1f1f, 0x2f2f, 0x3f3f],
|
||||
},
|
||||
};
|
||||
let written = mgm_hk_individual_validity
|
||||
.write_to_be_bytes_self_describing(&mut raw_buf)
|
||||
.unwrap();
|
||||
// 3 byte float description, 4 byte float, 4 byte MGM val description, 3 * 2 bytes MGM values
|
||||
assert_eq!(written, 17);
|
||||
|
||||
// The easiest and probably best approach, trading off big advantages for TM downlink capacity:
|
||||
// Use a JSON format
|
||||
let mgm_hk_group_validity = TestMgmHkWithGroupValidity {
|
||||
last_valid_stamp: TimeProvider::from_now_with_u16_days().unwrap(),
|
||||
valid: false,
|
||||
temp: 20.0,
|
||||
mgm_vals: [0x1f1f, 0x2f2f, 0x3f3f],
|
||||
};
|
||||
let mgm_as_json_str = serde_json::to_string(&mgm_hk_group_validity).unwrap();
|
||||
println!(
|
||||
"JSON string with length {}: {}",
|
||||
mgm_as_json_str.len(),
|
||||
mgm_as_json_str
|
||||
);
|
||||
}
|
35
satrs-core/tests/pools.rs
Normal file
35
satrs-core/tests/pools.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use satrs_core::pool::{LocalPool, PoolCfg, PoolGuard, PoolProvider, StoreAddr};
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
const DUMMY_DATA: [u8; 4] = [0, 1, 2, 3];
|
||||
|
||||
#[test]
|
||||
fn threaded_usage() {
|
||||
let pool_cfg = PoolCfg::new(vec![(16, 6), (32, 3), (8, 12)]);
|
||||
let shared_pool = Arc::new(RwLock::new(LocalPool::new(pool_cfg)));
|
||||
let shared_clone = shared_pool.clone();
|
||||
let (tx, rx): (Sender<StoreAddr>, Receiver<StoreAddr>) = mpsc::channel();
|
||||
let jh0 = thread::spawn(move || {
|
||||
let mut dummy = shared_pool.write().unwrap();
|
||||
let addr = dummy.add(&DUMMY_DATA).expect("Writing data failed");
|
||||
tx.send(addr).expect("Sending store address failed");
|
||||
});
|
||||
|
||||
let jh1 = thread::spawn(move || {
|
||||
let mut pool_access = shared_clone.write().unwrap();
|
||||
let addr;
|
||||
{
|
||||
addr = rx.recv().expect("Receiving store address failed");
|
||||
let pg = PoolGuard::new(pool_access.deref_mut(), addr);
|
||||
let read_res = pg.read().expect("Reading failed");
|
||||
assert_eq!(read_res, DUMMY_DATA);
|
||||
}
|
||||
assert!(!pool_access.has_element_at(&addr).expect("Invalid address"));
|
||||
});
|
||||
jh0.join().unwrap();
|
||||
jh1.join().unwrap();
|
||||
}
|
94
satrs-core/tests/pus_autogen_events.rs
Normal file
94
satrs-core/tests/pus_autogen_events.rs
Normal file
@ -0,0 +1,94 @@
|
||||
#![allow(dead_code, unused_imports)]
|
||||
|
||||
use satrs_core::events::{
|
||||
EventU32, EventU32TypedSev, GenericEvent, HasSeverity, LargestEventRaw, LargestGroupIdRaw,
|
||||
Severity, SeverityInfo, SeverityLow, SeverityMedium,
|
||||
};
|
||||
use std::convert::AsRef;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GroupIdIntrospection {
|
||||
name: &'static str,
|
||||
id: LargestGroupIdRaw,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EventIntrospection {
|
||||
name: &'static str,
|
||||
group_id: GroupIdIntrospection,
|
||||
event: &'static EventU32,
|
||||
info: &'static str,
|
||||
}
|
||||
|
||||
//#[event(descr="This is some info event")]
|
||||
const INFO_EVENT_0: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(0, 0);
|
||||
const INFO_EVENT_0_ERASED: EventU32 = EventU32::const_from_info(INFO_EVENT_0);
|
||||
|
||||
// This is ideally auto-generated
|
||||
const INFO_EVENT_0_INTROSPECTION: EventIntrospection = EventIntrospection {
|
||||
name: "INFO_EVENT_0",
|
||||
group_id: GroupIdIntrospection {
|
||||
id: 0,
|
||||
name: "Group ID 0 without name",
|
||||
},
|
||||
event: &INFO_EVENT_0_ERASED,
|
||||
info: "This is some info event",
|
||||
};
|
||||
|
||||
//#[event(descr="This is some low severity event")]
|
||||
const SOME_LOW_SEV_EVENT: EventU32TypedSev<SeverityLow> = EventU32TypedSev::const_new(0, 12);
|
||||
|
||||
//const EVENT_LIST: [&'static Event; 2] = [&INFO_EVENT_0, &SOME_LOW_SEV_EVENT];
|
||||
|
||||
//#[event_group]
|
||||
const TEST_GROUP_NAME: u16 = 1;
|
||||
// Auto-generated?
|
||||
const TEST_GROUP_NAME_NAME: &str = "TEST_GROUP_NAME";
|
||||
|
||||
//#[event(desc="Some medium severity event")]
|
||||
const MEDIUM_SEV_EVENT_IN_OTHER_GROUP: EventU32TypedSev<SeverityMedium> =
|
||||
EventU32TypedSev::const_new(TEST_GROUP_NAME, 0);
|
||||
const MEDIUM_SEV_EVENT_IN_OTHER_GROUP_REDUCED: EventU32 =
|
||||
EventU32::const_from_medium(MEDIUM_SEV_EVENT_IN_OTHER_GROUP);
|
||||
|
||||
// Also auto-generated
|
||||
const MEDIUM_SEV_EVENT_IN_OTHER_GROUP_INTROSPECTION: EventIntrospection = EventIntrospection {
|
||||
name: "MEDIUM_SEV_EVENT_IN_OTHER_GROUP",
|
||||
group_id: GroupIdIntrospection {
|
||||
name: TEST_GROUP_NAME_NAME,
|
||||
id: TEST_GROUP_NAME,
|
||||
},
|
||||
event: &MEDIUM_SEV_EVENT_IN_OTHER_GROUP_REDUCED,
|
||||
info: "Some medium severity event",
|
||||
};
|
||||
|
||||
const CONST_SLICE: &'static [u8] = &[0, 1, 2, 3];
|
||||
const INTROSPECTION_FOR_TEST_GROUP_0: [&EventIntrospection; 2] =
|
||||
[&INFO_EVENT_0_INTROSPECTION, &INFO_EVENT_0_INTROSPECTION];
|
||||
|
||||
//const INTROSPECTION_FOR_TABLE: &'static [&EventIntrospection] = &INTROSPECTION_FOR_TEST_GROUP_0;
|
||||
|
||||
const INTROSPECTION_FOR_TEST_GROUP_NAME: [&EventIntrospection; 1] =
|
||||
[&MEDIUM_SEV_EVENT_IN_OTHER_GROUP_INTROSPECTION];
|
||||
//const BLAH: &'static [&EventIntrospection] = &INTROSPECTION_FOR_TEST_GROUP_NAME;
|
||||
|
||||
const ALL_EVENTS: [&[&EventIntrospection]; 2] = [
|
||||
&INTROSPECTION_FOR_TEST_GROUP_0,
|
||||
&INTROSPECTION_FOR_TEST_GROUP_NAME,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
//let test = stringify!(INFO_EVENT);
|
||||
//println!("{:?}", test);
|
||||
//for event in EVENT_LIST {
|
||||
// println!("{:?}", event);
|
||||
//}
|
||||
//for events in ALL_EVENTS.into_iter().flatten() {
|
||||
// dbg!("{:?}", events);
|
||||
//}
|
||||
//for introspection_info in INTROSPECTION_FOR_TEST_GROUP {
|
||||
// dbg!("{:?}", introspection_info);
|
||||
//}
|
||||
//let test_struct =
|
||||
}
|
174
satrs-core/tests/pus_events.rs
Normal file
174
satrs-core/tests/pus_events.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use satrs_core::event_man::{
|
||||
EventManagerWithMpscQueue, MpscEventU32Receiver, MpscEventU32SendProvider, SendEventProvider,
|
||||
};
|
||||
use satrs_core::events::{EventU32, EventU32TypedSev, Severity, SeverityInfo};
|
||||
use satrs_core::params::U32Pair;
|
||||
use satrs_core::params::{Params, ParamsHeapless, WritableToBeBytes};
|
||||
use satrs_core::pus::event_man::{
|
||||
DefaultPusMgmtBackendProvider, EventReporter, PusEventDispatcher,
|
||||
};
|
||||
use satrs_core::pus::{EcssTmErrorWithSend, EcssTmSenderCore};
|
||||
use spacepackets::ecss::PusPacket;
|
||||
use spacepackets::tm::PusTm;
|
||||
use std::sync::mpsc::{channel, SendError, TryRecvError};
|
||||
use std::thread;
|
||||
|
||||
const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||
EventU32TypedSev::<SeverityInfo>::const_new(1, 0);
|
||||
const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5);
|
||||
const EMPTY_STAMP: [u8; 7] = [0; 7];
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EventTmSender {
|
||||
sender: std::sync::mpsc::Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl EcssTmSenderCore for EventTmSender {
|
||||
type Error = SendError<Vec<u8>>;
|
||||
fn send_tm(&mut self, tm: PusTm) -> Result<(), EcssTmErrorWithSend<Self::Error>> {
|
||||
let mut vec = Vec::new();
|
||||
tm.append_to_vec(&mut vec)
|
||||
.map_err(|e| EcssTmErrorWithSend::EcssTmError(e.into()))?;
|
||||
self.sender
|
||||
.send(vec)
|
||||
.map_err(EcssTmErrorWithSend::SendError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_threaded_usage() {
|
||||
let (event_sender, event_man_receiver) = channel();
|
||||
let event_receiver = MpscEventU32Receiver::new(event_man_receiver);
|
||||
let mut event_man = EventManagerWithMpscQueue::new(Box::new(event_receiver));
|
||||
|
||||
let (pus_event_man_tx, pus_event_man_rx) = channel();
|
||||
let pus_event_man_send_provider = MpscEventU32SendProvider::new(1, pus_event_man_tx);
|
||||
event_man.subscribe_all(pus_event_man_send_provider.id());
|
||||
event_man.add_sender(pus_event_man_send_provider);
|
||||
let (event_tx, event_rx) = channel();
|
||||
let reporter = EventReporter::new(0x02, 128).expect("Creating event reporter failed");
|
||||
let backend = DefaultPusMgmtBackendProvider::<EventU32>::default();
|
||||
let mut pus_event_man = PusEventDispatcher::new(reporter, Box::new(backend));
|
||||
// PUS + Generic event manager thread
|
||||
let jh0 = thread::spawn(move || {
|
||||
let mut sender = EventTmSender { sender: event_tx };
|
||||
let mut event_cnt = 0;
|
||||
let mut params_array: [u8; 128] = [0; 128];
|
||||
loop {
|
||||
let res = event_man.try_event_handling();
|
||||
assert!(res.is_ok());
|
||||
match pus_event_man_rx.try_recv() {
|
||||
Ok((event, aux_data)) => {
|
||||
let mut gen_event = |aux_data| {
|
||||
pus_event_man.generate_pus_event_tm_generic(
|
||||
&mut sender,
|
||||
&EMPTY_STAMP,
|
||||
event,
|
||||
aux_data,
|
||||
)
|
||||
};
|
||||
let res = if let Some(aux_data) = aux_data {
|
||||
match aux_data {
|
||||
Params::Heapless(heapless) => match heapless {
|
||||
ParamsHeapless::Raw(raw) => {
|
||||
raw.write_to_be_bytes(&mut params_array)
|
||||
.expect("Writing raw parameter failed");
|
||||
gen_event(Some(¶ms_array[0..raw.raw_len()]))
|
||||
}
|
||||
ParamsHeapless::EcssEnum(e) => {
|
||||
e.write_to_be_bytes(&mut params_array)
|
||||
.expect("Writing ECSS enum failed");
|
||||
gen_event(Some(¶ms_array[0..e.raw_len()]))
|
||||
}
|
||||
},
|
||||
Params::Vec(vec) => gen_event(Some(vec.as_slice())),
|
||||
Params::String(str) => gen_event(Some(str.as_bytes())),
|
||||
Params::Store(_) => gen_event(None),
|
||||
}
|
||||
} else {
|
||||
gen_event(None)
|
||||
};
|
||||
event_cnt += 1;
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
if event_cnt == 2 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if let TryRecvError::Disconnected = e {
|
||||
panic!("Event receiver disconnected!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Event sender and TM checker thread
|
||||
let jh1 = thread::spawn(move || {
|
||||
event_sender
|
||||
.send((INFO_EVENT.into(), None))
|
||||
.expect("Sending info event failed");
|
||||
loop {
|
||||
match event_rx.try_recv() {
|
||||
// Event TM received successfully
|
||||
Ok(event_tm) => {
|
||||
let tm =
|
||||
PusTm::from_bytes(event_tm.as_slice(), 7).expect("Deserializing TM failed");
|
||||
assert_eq!(tm.0.service(), 5);
|
||||
assert_eq!(tm.0.subservice(), 1);
|
||||
let src_data = tm.0.source_data();
|
||||
assert!(src_data.is_some());
|
||||
let src_data = src_data.unwrap();
|
||||
assert_eq!(src_data.len(), 4);
|
||||
let event =
|
||||
EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
||||
assert_eq!(event, INFO_EVENT);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
if let TryRecvError::Disconnected = e {
|
||||
panic!("Event sender disconnected!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
event_sender
|
||||
.send((
|
||||
LOW_SEV_EVENT.into(),
|
||||
Some(Params::Heapless((2_u32, 3_u32).into())),
|
||||
))
|
||||
.expect("Sending low severity event failed");
|
||||
loop {
|
||||
match event_rx.try_recv() {
|
||||
// Event TM received successfully
|
||||
Ok(event_tm) => {
|
||||
let tm =
|
||||
PusTm::from_bytes(event_tm.as_slice(), 7).expect("Deserializing TM failed");
|
||||
assert_eq!(tm.0.service(), 5);
|
||||
assert_eq!(tm.0.subservice(), 2);
|
||||
let src_data = tm.0.source_data();
|
||||
assert!(src_data.is_some());
|
||||
let src_data = src_data.unwrap();
|
||||
assert_eq!(src_data.len(), 12);
|
||||
let event =
|
||||
EventU32::from(u32::from_be_bytes(src_data[0..4].try_into().unwrap()));
|
||||
assert_eq!(event, LOW_SEV_EVENT);
|
||||
let u32_pair: U32Pair =
|
||||
src_data[4..].try_into().expect("Creating U32Pair failed");
|
||||
assert_eq!(u32_pair.0, 2);
|
||||
assert_eq!(u32_pair.1, 3);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
if let TryRecvError::Disconnected = e {
|
||||
panic!("Event sender disconnected!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
jh0.join().expect("Joining manager thread failed");
|
||||
jh1.join().expect("Joining creator thread failed");
|
||||
}
|
199
satrs-core/tests/pus_verification.rs
Normal file
199
satrs-core/tests/pus_verification.rs
Normal file
@ -0,0 +1,199 @@
|
||||
// TODO: Refactor this to also test the STD impl using mpsc
|
||||
#[cfg(feature = "crossbeam")]
|
||||
pub mod crossbeam_test {
|
||||
use hashbrown::HashMap;
|
||||
use satrs_core::pool::{LocalPool, PoolCfg, PoolProvider, SharedPool};
|
||||
use satrs_core::pus::verification::{
|
||||
CrossbeamVerifSender, FailParams, RequestId, VerificationReporterCfg,
|
||||
VerificationReporterWithSender,
|
||||
};
|
||||
use satrs_core::seq_count::SeqCountProviderSyncClonable;
|
||||
use spacepackets::ecss::{EcssEnumU16, EcssEnumU8, PusPacket};
|
||||
use spacepackets::tc::{PusTc, PusTcSecondaryHeader};
|
||||
use spacepackets::tm::PusTm;
|
||||
use spacepackets::SpHeader;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
const TEST_APID: u16 = 0x03;
|
||||
const FIXED_STAMP: [u8; 7] = [0; 7];
|
||||
const PACKETS_SENT: u8 = 8;
|
||||
|
||||
/// This test also shows how the verification report could be used in a multi-threaded context,
|
||||
/// wrapping it into an [Arc] and [Mutex] and then passing it to two threads.
|
||||
///
|
||||
/// - The first thread generates a acceptance, a start, two steps and one completion report
|
||||
/// - The second generates an acceptance and start success report and a completion failure
|
||||
/// - The third thread is the verification receiver. In the test case, it verifies the other two
|
||||
/// threads have sent the correct expected verification reports
|
||||
#[test]
|
||||
fn test_shared_reporter() {
|
||||
// We use a synced sequence count provider here because both verification reporters have the
|
||||
// the same APID. If they had distinct APIDs, the more correct approach would be to have
|
||||
// each reporter have an own sequence count provider.
|
||||
let cfg = VerificationReporterCfg::new(
|
||||
TEST_APID,
|
||||
Box::new(SeqCountProviderSyncClonable::default()),
|
||||
1,
|
||||
2,
|
||||
8,
|
||||
)
|
||||
.unwrap();
|
||||
// Shared pool object to store the verification PUS telemetry
|
||||
let pool_cfg = PoolCfg::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)]);
|
||||
let shared_tm_pool: SharedPool =
|
||||
Arc::new(RwLock::new(Box::new(LocalPool::new(pool_cfg.clone()))));
|
||||
let shared_tc_pool_0 = Arc::new(RwLock::new(LocalPool::new(pool_cfg)));
|
||||
let shared_tc_pool_1 = shared_tc_pool_0.clone();
|
||||
let (tx, rx) = crossbeam_channel::bounded(5);
|
||||
let sender = CrossbeamVerifSender::new(shared_tm_pool.clone(), tx.clone());
|
||||
let mut reporter_with_sender_0 =
|
||||
VerificationReporterWithSender::new(&cfg, Box::new(sender));
|
||||
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
|
||||
// tread.
|
||||
let req_id_0;
|
||||
let req_id_1;
|
||||
|
||||
let (tx_tc_0, rx_tc_0) = crossbeam_channel::bounded(3);
|
||||
let (tx_tc_1, rx_tc_1) = crossbeam_channel::bounded(3);
|
||||
{
|
||||
let mut tc_guard = shared_tc_pool_0.write().unwrap();
|
||||
let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap();
|
||||
let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
|
||||
let pus_tc_0 = PusTc::new(&mut sph, tc_header, None, true);
|
||||
req_id_0 = RequestId::new(&pus_tc_0);
|
||||
let (addr, mut buf) = tc_guard.free_element(pus_tc_0.len_packed()).unwrap();
|
||||
pus_tc_0.write_to_bytes(&mut buf).unwrap();
|
||||
tx_tc_0.send(addr).unwrap();
|
||||
let mut sph = SpHeader::tc_unseg(TEST_APID, 1, 0).unwrap();
|
||||
let tc_header = PusTcSecondaryHeader::new_simple(5, 1);
|
||||
let pus_tc_1 = PusTc::new(&mut sph, tc_header, None, true);
|
||||
req_id_1 = RequestId::new(&pus_tc_1);
|
||||
let (addr, mut buf) = tc_guard.free_element(pus_tc_0.len_packed()).unwrap();
|
||||
pus_tc_1.write_to_bytes(&mut buf).unwrap();
|
||||
tx_tc_1.send(addr).unwrap();
|
||||
}
|
||||
let verif_sender_0 = thread::spawn(move || {
|
||||
let mut tc_buf: [u8; 1024] = [0; 1024];
|
||||
let tc_addr = rx_tc_0
|
||||
.recv_timeout(Duration::from_millis(20))
|
||||
.expect("Receive timeout");
|
||||
let tc_len;
|
||||
{
|
||||
let mut tc_guard = shared_tc_pool_0.write().unwrap();
|
||||
let pg = tc_guard.read_with_guard(tc_addr);
|
||||
let buf = pg.read().unwrap();
|
||||
tc_len = buf.len();
|
||||
tc_buf[0..tc_len].copy_from_slice(buf);
|
||||
}
|
||||
let (_tc, _) = PusTc::from_bytes(&tc_buf[0..tc_len]).unwrap();
|
||||
let accepted_token;
|
||||
|
||||
let token = reporter_with_sender_0.add_tc_with_req_id(req_id_0);
|
||||
accepted_token = reporter_with_sender_0
|
||||
.acceptance_success(token, &FIXED_STAMP)
|
||||
.expect("Acceptance success failed");
|
||||
|
||||
// Do some start handling here
|
||||
let started_token;
|
||||
started_token = reporter_with_sender_0
|
||||
.start_success(accepted_token, &FIXED_STAMP)
|
||||
.expect("Start success failed");
|
||||
// Do some step handling here
|
||||
reporter_with_sender_0
|
||||
.step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(0))
|
||||
.expect("Start success failed");
|
||||
|
||||
// Finish up
|
||||
reporter_with_sender_0
|
||||
.step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(1))
|
||||
.expect("Start success failed");
|
||||
reporter_with_sender_0
|
||||
.completion_success(started_token, &FIXED_STAMP)
|
||||
.expect("Completion success failed");
|
||||
});
|
||||
|
||||
let verif_sender_1 = thread::spawn(move || {
|
||||
let mut tc_buf: [u8; 1024] = [0; 1024];
|
||||
let tc_addr = rx_tc_1
|
||||
.recv_timeout(Duration::from_millis(20))
|
||||
.expect("Receive timeout");
|
||||
let tc_len;
|
||||
{
|
||||
let mut tc_guard = shared_tc_pool_1.write().unwrap();
|
||||
let pg = tc_guard.read_with_guard(tc_addr);
|
||||
let buf = pg.read().unwrap();
|
||||
tc_len = buf.len();
|
||||
tc_buf[0..tc_len].copy_from_slice(buf);
|
||||
}
|
||||
let (tc, _) = PusTc::from_bytes(&tc_buf[0..tc_len]).unwrap();
|
||||
let token = reporter_with_sender_1.add_tc(&tc);
|
||||
let accepted_token = reporter_with_sender_1
|
||||
.acceptance_success(token, &FIXED_STAMP)
|
||||
.expect("Acceptance success failed");
|
||||
let started_token = reporter_with_sender_1
|
||||
.start_success(accepted_token, &FIXED_STAMP)
|
||||
.expect("Start success failed");
|
||||
let fail_code = EcssEnumU16::new(2);
|
||||
let params = FailParams::new(&FIXED_STAMP, &fail_code, None);
|
||||
reporter_with_sender_1
|
||||
.completion_failure(started_token, params)
|
||||
.expect("Completion success failed");
|
||||
});
|
||||
|
||||
let verif_receiver = thread::spawn(move || {
|
||||
let mut packet_counter = 0;
|
||||
let mut tm_buf: [u8; 1024] = [0; 1024];
|
||||
let mut verif_map = HashMap::new();
|
||||
while packet_counter < PACKETS_SENT {
|
||||
let verif_addr = rx
|
||||
.recv_timeout(Duration::from_millis(50))
|
||||
.expect("Packet reception timeout");
|
||||
let tm_len;
|
||||
{
|
||||
let mut rg = shared_tm_pool.write().expect("Error locking shared pool");
|
||||
let store_guard = rg.read_with_guard(verif_addr);
|
||||
let slice = store_guard.read().expect("Error reading TM slice");
|
||||
tm_len = slice.len();
|
||||
tm_buf[0..tm_len].copy_from_slice(slice);
|
||||
}
|
||||
let (pus_tm, _) = PusTm::from_bytes(&tm_buf[0..tm_len], 7)
|
||||
.expect("Error reading verification TM");
|
||||
let req_id = RequestId::from_bytes(
|
||||
&pus_tm.source_data().expect("Invalid TM source data")
|
||||
[0..RequestId::SIZE_AS_BYTES],
|
||||
)
|
||||
.unwrap();
|
||||
if !verif_map.contains_key(&req_id) {
|
||||
let mut content = Vec::new();
|
||||
content.push(pus_tm.subservice());
|
||||
verif_map.insert(req_id, content);
|
||||
} else {
|
||||
let content = verif_map.get_mut(&req_id).unwrap();
|
||||
content.push(pus_tm.subservice())
|
||||
}
|
||||
packet_counter += 1;
|
||||
}
|
||||
for (req_id, content) in verif_map {
|
||||
if req_id == req_id_1 {
|
||||
assert_eq!(content[0], 1);
|
||||
assert_eq!(content[1], 3);
|
||||
assert_eq!(content[2], 8);
|
||||
} else if req_id == req_id_0 {
|
||||
assert_eq!(content[0], 1);
|
||||
assert_eq!(content[1], 3);
|
||||
assert_eq!(content[2], 5);
|
||||
assert_eq!(content[3], 5);
|
||||
assert_eq!(content[4], 7);
|
||||
} else {
|
||||
panic!("Unexpected request ID {:?}", req_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
verif_sender_0.join().expect("Joining thread 0 failed");
|
||||
verif_sender_1.join().expect("Joining thread 1 failed");
|
||||
verif_receiver.join().expect("Joining thread 2 failed");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user