camera_tests #13
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
||||
|
||||
# Ignore logs folder generared by application.
|
||||
/logs
|
||||
/exp278.toml
|
||||
|
90
Cargo.lock
generated
90
Cargo.lock
generated
@ -138,9 +138,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.94"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
|
||||
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -251,7 +251,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -590,7 +590,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -617,8 +617,10 @@ dependencies = [
|
||||
"satrs-mib",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socket2",
|
||||
"strum",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -658,7 +660,7 @@ version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
"toml_edit 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -776,7 +778,7 @@ checksum = "474b583ffa521fbc362ae71da11c9fe29a6b60af47744e067550b6eef4f60d43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -817,7 +819,7 @@ checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -832,12 +834,21 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
<<<<<<< HEAD
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
=======
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
>>>>>>> main
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -867,9 +878,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacepackets"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08e05169d37db5d0f8527f4abcacb72f6d178654879892c0e37ab5f04df85e3e"
|
||||
checksum = "fa9f4d7df5fa3bc25ecfc95f1f612fc3d16c566df538d3d3c82db0e523096216"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crc",
|
||||
@ -900,7 +911,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -916,9 +927,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.59"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -927,22 +938,34 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -950,6 +973,9 @@ name = "toml_datetime"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@ -959,7 +985,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1007,7 +1046,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -1029,7 +1068,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -1238,6 +1277,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
<<<<<<< HEAD
|
||||
name = "wmi"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
@ -1249,6 +1289,14 @@ dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"windows",
|
||||
=======
|
||||
name = "winnow"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
>>>>>>> main
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1269,5 +1317,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fern = "0.6"
|
||||
toml = "0.8"
|
||||
chrono = "0.4"
|
||||
log = "0.4"
|
||||
lazy_static = "1"
|
||||
@ -19,6 +20,7 @@ serde = "1"
|
||||
serde_json = "1"
|
||||
mio = "0.8"
|
||||
homedir = "0.2"
|
||||
socket2 = "0.5"
|
||||
|
||||
[dependencies.satrs]
|
||||
version = "0.2.0-rc.3"
|
||||
|
68
README.md
68
README.md
@ -2,6 +2,8 @@ ESA OPS-SAT Rust experiment
|
||||
========
|
||||
|
||||
This is the primary repository for the ESA OPS-SAT experiment.
|
||||
The primary repository to generate packages for ESOC can be found [here](https://egit.irs.uni-stuttgart.de/rust/ops-sat-experiment).
|
||||
You can also find some more general documentation about OPS-SAT there.
|
||||
|
||||
## Pre-Requisites
|
||||
|
||||
@ -9,7 +11,7 @@ This is the primary repository for the ESA OPS-SAT experiment.
|
||||
[podman](https://podman.io/) installed
|
||||
- [`cross`](https://github.com/cross-rs/cross) package installed
|
||||
|
||||
## Build
|
||||
## Build for Target Hardware
|
||||
|
||||
You might need to set the [`CROSS_CONTAINER_ENGINE`](https://github.com/cross-rs/cross/wiki/FAQ#explicitly-choose-the-container-engine)
|
||||
and [`CROSS_ROOTLESS_CONTAINER_ENGINE`](https://github.com/cross-rs/cross/blob/main/docs/environment_variables.md#configuring-cross-with-environment-variables)
|
||||
@ -27,16 +29,58 @@ cross build
|
||||
cross build --release
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## Build for Host
|
||||
|
||||
The [wiki](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki)
|
||||
appears to be a useful source for documentation.
|
||||
The software was designed to be runnable and testable on a host computer.
|
||||
You can use the regular cargo workflow for this.
|
||||
|
||||
- [OBSW documents](https://opssat1.esoc.esa.int/projects/experimenter-information/dmsf?folder_id=7)
|
||||
- [Software Integration Process](https://opssat1.esoc.esa.int/dmsf/files/34/view)
|
||||
- [SPP/TCP bridge](https://opssat1.esoc.esa.int/dmsf/files/65/view)
|
||||
- [Cross-compiling SEPP](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Cross-compiling_SEPP_application)
|
||||
- [TMTC infrastructure](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Live_TM_TC_data)
|
||||
- [Submitting an Experiment](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Building_and_submitting_your_application_to_ESOC)
|
||||
- [Building with Yocto and Docker](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Building_an_application_locally_using_Yocto_Toolchain_in_a_Docker)
|
||||
- [SPP over CAN](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/SPP_over_CAN_communication)
|
||||
### Running
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```sh
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Commanding Infrastructure
|
||||
|
||||
Commanding of the `ops-sat-rs` application is possible by different means.
|
||||
|
||||
<img src="docs/networking-structure.png" alt="Networking and Commanding Structure" width="500"/>
|
||||
|
||||
### Using the `pyclient` and `pyserver` applications
|
||||
|
||||
You can find both commanding application inside the `pytmtc` folder.
|
||||
You can also find a `requirements.txt` file there to install all required Python dependencies.
|
||||
|
||||
If you want to command the satellite using the OPS-SAT infrastrucute, start the `pyserver.py`
|
||||
as a background application first, for example by simply running `pyserver.py` inside a
|
||||
new terminal window.
|
||||
|
||||
After that, you can run `pyclient.py -p /test/ping -l` to send a ping telecommand and then
|
||||
go into listener mode using the following `tmtc_conf.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"com_if": "tcp",
|
||||
"tcpip_tcp_ip_addr": "127.0.0.1",
|
||||
"tcpip_tcp_port": 4097
|
||||
}
|
||||
```
|
||||
|
||||
You can command the TCP server in the OPS-SAT software directly by running the commands with
|
||||
the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"com_if": "tcp",
|
||||
"tcpip_tcp_ip_addr": "127.0.0.1",
|
||||
"tcpip_tcp_port": 7031
|
||||
}
|
||||
```
|
||||
|
||||
You can run `pyclient.py -T` or `pyclient.py -h` for more information on the client application.
|
||||
|
193
docs/networking-structure.graphml
Normal file
193
docs/networking-structure.graphml
Normal file
@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<!--Created by yEd 3.23.2-->
|
||||
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<data key="d0"/>
|
||||
<node id="n0">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="100.0" width="304.0" x="551.5" y="196.5"/>
|
||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="133.7265625" x="20.866945876288696" xml:space="preserve" y="9.230939716312065">ops-sat-rs on satellite<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4313587306701031" nodeRatioY="-0.4076906028368794" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n1">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="37.0" width="72.0" x="572.5" y="233.0"/>
|
||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="52.849609375" x="9.5751953125" xml:space="preserve" y="2.53125">TCP SPP
|
||||
Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n2">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="182.0" width="197.5" x="551.5" y="328.5"/>
|
||||
<y:Fill color="#FFFF99" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.705078125" x="12.1474609375" xml:space="preserve" y="13.53125">pyserver
|
||||
daemon<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4384938686708861" nodeRatioY="-0.4256524725274725" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n3">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="53.0" width="77.5" x="640.75" y="346.0"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.5703125" x="7.46484375" xml:space="preserve" y="3.546875">OPS-SAT
|
||||
Server
|
||||
Port 4096<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n4">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="53.0" width="77.5" x="640.75" y="440.5"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.5703125" x="7.46484375" xml:space="preserve" y="3.546875">TMTC
|
||||
Server
|
||||
Port 4097<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n5">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="30.0" width="99.0" x="756.5" y="536.5"/>
|
||||
<y:Fill color="#FFCC00" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.677734375" x="23.6611328125" xml:space="preserve" y="6.015625">pyclient<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n6">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="53.0" width="72.0" x="661.0" y="225.0"/>
|
||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.5703125" x="4.71484375" xml:space="preserve" y="3.546875">TCP
|
||||
Server
|
||||
Port 7301<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="n7">
|
||||
<data key="d5"/>
|
||||
<data key="d6">
|
||||
<y:ShapeNode>
|
||||
<y:Geometry height="53.0" width="72.0" x="756.0" y="225.0"/>
|
||||
<y:Fill color="#CCFFFF" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="62.5703125" x="4.71484375" xml:space="preserve" y="3.546875">UDP
|
||||
Server
|
||||
Port 7301<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
|
||||
<y:Shape type="rectangle"/>
|
||||
</y:ShapeNode>
|
||||
</data>
|
||||
</node>
|
||||
<edge id="e0" source="n4" target="n3">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="standard" target="standard"/>
|
||||
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="43.421875" x="8.2890625" xml:space="preserve" y="-37.21875">TMTC
|
||||
Queue<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.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>
|
||||
<edge id="e1" source="n1" target="n3">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="4.333333333333334" sy="0.0" tx="-19.5" ty="0.0">
|
||||
<y:Point x="612.8333333333334" y="312.5"/>
|
||||
<y:Point x="660.0" y="312.5"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="standard" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e2" source="n5" target="n4">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-29.5" sy="-8.875" tx="6.75" ty="10.0">
|
||||
<y:Point x="776.5" y="516.5"/>
|
||||
<y:Point x="686.25" y="516.5"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="standard" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e3" source="n5" target="n7">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-14.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="standard" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="e4" source="n5" target="n6">
|
||||
<data key="d9"/>
|
||||
<data key="d10">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="-14.0" sy="0.0" tx="15.75" ty="0.0">
|
||||
<y:Point x="792.0" y="311.5"/>
|
||||
<y:Point x="712.75" y="311.5"/>
|
||||
</y:Path>
|
||||
<y:LineStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:Arrows source="none" target="standard"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
<data key="d7">
|
||||
<y:Resources/>
|
||||
</data>
|
||||
</graphml>
|
BIN
docs/networking-structure.png
Normal file
BIN
docs/networking-structure.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
176
pytmtc/archive/tcp_server.py
Normal file
176
pytmtc/archive/tcp_server.py
Normal file
@ -0,0 +1,176 @@
|
||||
from typing import Any, Optional
|
||||
import select
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
from threading import Thread, Event, Lock
|
||||
from collections import deque
|
||||
|
||||
from tmtccmd.com import ComInterface
|
||||
from tmtccmd.tmtc import TelemetryListT
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TcpServer(ComInterface):
|
||||
def __init__(self, port: int):
|
||||
self.port = port
|
||||
self._max_num_packets_in_tc_queue = 500
|
||||
self._max_num_packets_in_tm_queue = 500
|
||||
self._default_timeout_secs = 0.5
|
||||
self._server_addr = ("localhost", self.port)
|
||||
self._tc_packet_queue = deque()
|
||||
self._tm_packet_queue = deque()
|
||||
self._tc_lock = Lock()
|
||||
self._tm_lock = Lock()
|
||||
self._kill_signal = Event()
|
||||
self._server_socket: Optional[socket.socket] = None
|
||||
self._server_thread = Thread(target=self._server_task, daemon=True)
|
||||
self._connected = False
|
||||
# self._conn_start = None
|
||||
# self._writing_done = False
|
||||
# self._reading_done = False
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return "tcp_server"
|
||||
|
||||
def initialize(self, args: Any = 0) -> Any:
|
||||
"""Perform initializations step which can not be done in constructor or which require
|
||||
returnvalues.
|
||||
"""
|
||||
pass
|
||||
|
||||
def open(self, args: Any = 0):
|
||||
"""Opens the communication interface to allow communication.
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self.connected:
|
||||
return
|
||||
self._connected = True
|
||||
self._server_thread.start()
|
||||
|
||||
def _server_task(self):
|
||||
self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# We need to check the kill signal periodically to allow closing the server.
|
||||
self._server_socket.settimeout(self._default_timeout_secs)
|
||||
self._server_socket.bind(self._server_addr)
|
||||
self._server_socket.listen()
|
||||
while True and not self._kill_signal.is_set():
|
||||
try:
|
||||
(conn_socket, conn_addr) = self._server_socket.accept()
|
||||
self._handle_connection(conn_socket, conn_addr)
|
||||
# conn_socket.close()
|
||||
"""
|
||||
if (
|
||||
self._reading_done and self._writing_done
|
||||
) or time.time() - self.conn_start > 0.5:
|
||||
print("reading and writing done")
|
||||
break
|
||||
|
||||
"""
|
||||
except TimeoutError:
|
||||
continue
|
||||
|
||||
def _handle_connection(self, conn_socket: socket.socket, conn_addr: Any):
|
||||
_LOGGER.info(f"TCP client {conn_addr} connected")
|
||||
queue_len = 0
|
||||
|
||||
while True:
|
||||
with self._tc_lock:
|
||||
queue_len = len(self._tc_packet_queue)
|
||||
outputs = []
|
||||
if queue_len > 0:
|
||||
outputs.append(conn_socket)
|
||||
(readable, writable, _) = select.select(
|
||||
[conn_socket],
|
||||
outputs,
|
||||
[],
|
||||
0.2,
|
||||
)
|
||||
|
||||
if writable and writable[0]:
|
||||
print("writeable")
|
||||
while queue_len > 0:
|
||||
next_packet = bytes()
|
||||
with self._tc_lock:
|
||||
next_packet = self._tc_packet_queue.popleft()
|
||||
if len(next_packet) > 0:
|
||||
conn_socket.sendall(next_packet)
|
||||
queue_len -= 1
|
||||
if readable and readable[0]:
|
||||
print("readable")
|
||||
while True:
|
||||
bytes_recvd = conn_socket.recv(4096)
|
||||
if len(bytes_recvd) > 0:
|
||||
print(f"Received bytes from TCP client: {bytes_recvd.decode()}")
|
||||
with self._tm_lock:
|
||||
self._tm_packet_queue.append(bytes_recvd)
|
||||
elif len(bytes_recvd) == 0:
|
||||
break
|
||||
else:
|
||||
print("error receiving data from TCP client")
|
||||
|
||||
def is_open(self) -> bool:
|
||||
"""Can be used to check whether the communication interface is open. This is useful if
|
||||
opening a COM interface takes a longer time and is non-blocking
|
||||
"""
|
||||
return self.connected
|
||||
|
||||
def close(self, args: Any = 0):
|
||||
"""Closes the ComIF and releases any held resources (for example a Communication Port).
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._kill_signal.set()
|
||||
self._server_thread.join()
|
||||
self._connected = False
|
||||
|
||||
def send(self, data: bytes):
|
||||
"""Send raw data.
|
||||
|
||||
:raises SendError: Sending failed for some reason.
|
||||
"""
|
||||
with self._tc_lock:
|
||||
if len(self._tc_packet_queue) >= self._max_num_packets_in_tc_queue:
|
||||
# Remove oldest packet
|
||||
self._tc_packet_queue.popleft()
|
||||
self._tc_packet_queue.append(data)
|
||||
|
||||
def receive(self, parameters: Any = 0) -> TelemetryListT:
|
||||
"""Returns a list of received packets. The child class can use a separate thread to poll for
|
||||
the packets or use some other mechanism and container like a deque to store packets
|
||||
to be returned here.
|
||||
|
||||
:param parameters:
|
||||
:raises ReceptionDecodeError: If the underlying COM interface uses encoding and
|
||||
decoding and the decoding fails, this exception will be returned.
|
||||
:return:
|
||||
"""
|
||||
|
||||
with self._tm_lock:
|
||||
packet_list = []
|
||||
while self._tm_packet_queue:
|
||||
packet_list.append(self._tm_packet_queue.popleft())
|
||||
return packet_list
|
||||
|
||||
def data_available(self, timeout: float, parameters: Any = 0) -> int:
|
||||
"""Check whether TM packets are available.
|
||||
|
||||
:param timeout: Can be used to block on available data if supported by the specific
|
||||
communication interface.
|
||||
:param parameters: Can be an arbitrary parameter.
|
||||
:raises ReceptionDecodeError: If the underlying COM interface uses encoding and
|
||||
decoding when determining the number of available packets, this exception can be
|
||||
thrown on decoding errors.
|
||||
:return: 0 if no data is available, number of packets otherwise.
|
||||
"""
|
||||
with self._tm_lock:
|
||||
return len(self._tm_packet_queue)
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Example client for the sat-rs example application"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import sys
|
||||
import time
|
185
pytmtc/pyserver.py
Executable file
185
pytmtc/pyserver.py
Executable file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import abc
|
||||
import time
|
||||
import select
|
||||
import logging
|
||||
from typing import Any
|
||||
from threading import Event, Thread
|
||||
from collections import deque
|
||||
from multiprocessing import Queue
|
||||
from spacepackets.ccsds.spacepacket import parse_space_packets, PacketId
|
||||
from spacepackets.ecss.tc import PacketType
|
||||
|
||||
EXP_ID = 278
|
||||
EXP_APID = 1024 + EXP_ID
|
||||
EXP_PACKET_ID_TM = PacketId(PacketType.TM, True, EXP_APID)
|
||||
EXP_PACKET_ID_TC = PacketId(PacketType.TC, True, EXP_APID)
|
||||
OPSSAT_SERVER_PORT = 4096
|
||||
TMTC_SERVER_PORT = 4097
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
|
||||
|
||||
TC_QUEUE = Queue()
|
||||
TM_QUEUE = Queue()
|
||||
KILL_SIGNAL = Event()
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(
|
||||
format="[%(asctime)s] [%(levelname)-5s] %(message)s",
|
||||
level=LOG_LEVEL,
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
print("Starting OPS-SAT ground TMTC server")
|
||||
KILL_SIGNAL.clear()
|
||||
ops_sat_thread = OpsSatServer()
|
||||
ops_sat_thread.start()
|
||||
tmtc_thread = TmtcServer()
|
||||
tmtc_thread.start()
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
_LOGGER.info("Shutting down server gracefully")
|
||||
KILL_SIGNAL.set()
|
||||
ops_sat_thread.join()
|
||||
tmtc_thread.join()
|
||||
|
||||
|
||||
class BaseServer(Thread):
|
||||
def __init__(self, log_prefix: str, port: int):
|
||||
self.log_prefix = log_prefix
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_addr = ("0.0.0.0", port)
|
||||
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.server_socket.setblocking(False)
|
||||
self.server_socket.settimeout(0.4)
|
||||
self.server_socket.bind(server_addr)
|
||||
super().__init__()
|
||||
|
||||
def run(self) -> None:
|
||||
self.run_sync_version()
|
||||
|
||||
def run_sync_version(self) -> None:
|
||||
self.server_socket.listen()
|
||||
while True and not KILL_SIGNAL.is_set():
|
||||
try:
|
||||
(conn_socket, conn_addr) = self.server_socket.accept()
|
||||
self.handle_connection(conn_socket, conn_addr)
|
||||
except TimeoutError:
|
||||
continue
|
||||
|
||||
def handle_connection(self, conn_socket: socket.socket, conn_addr: Any):
|
||||
conn_socket.setblocking(False)
|
||||
print(f"{self.log_prefix} TCP client {conn_addr} connected")
|
||||
analysis_deque = deque()
|
||||
while True and not KILL_SIGNAL.is_set():
|
||||
conn_socket.settimeout(0.2)
|
||||
try:
|
||||
bytes_recvd = conn_socket.recv(4096)
|
||||
if len(bytes_recvd) > 0:
|
||||
_LOGGER.debug(f"{self.log_prefix} RX RAW: {bytes_recvd}")
|
||||
analysis_deque.append(bytes_recvd)
|
||||
elif len(bytes_recvd) == 0:
|
||||
self.handle_read_bytestream(analysis_deque)
|
||||
break
|
||||
else:
|
||||
print("error receiving data from TCP client")
|
||||
except BlockingIOError:
|
||||
self.handle_timeout(conn_socket, analysis_deque)
|
||||
time.sleep(0.2)
|
||||
except TimeoutError:
|
||||
self.handle_timeout(conn_socket, analysis_deque)
|
||||
|
||||
def handle_timeout(self, conn_socket: socket.socket, analysis_deque: deque):
|
||||
if len(analysis_deque) > 0:
|
||||
self.handle_read_bytestream(analysis_deque)
|
||||
self.send_data_to_client(conn_socket)
|
||||
|
||||
def run_select_version(self) -> None:
|
||||
while True:
|
||||
self.server_socket.listen()
|
||||
(conn_socket, conn_addr) = self.server_socket.accept()
|
||||
print(f"{self.log_prefix} TCP client {conn_addr} connected")
|
||||
analysis_deque = deque()
|
||||
while True:
|
||||
outputs = []
|
||||
if self.send_data_available():
|
||||
outputs.append(conn_socket)
|
||||
(readable, writable, _) = select.select([conn_socket], outputs, [], 0.2)
|
||||
if readable and readable[0]:
|
||||
bytes_recvd = conn_socket.recv(4096)
|
||||
if len(bytes_recvd) > 0:
|
||||
_LOGGER.debug("received data from TCP client: {}", bytes_recvd)
|
||||
analysis_deque.append(bytes_recvd)
|
||||
elif len(bytes_recvd) == 0:
|
||||
self.handle_read_bytestream(analysis_deque)
|
||||
break
|
||||
else:
|
||||
print("error receiving data from TCP client")
|
||||
if writable and writable[0]:
|
||||
self.send_data_to_client(conn_socket)
|
||||
if not writable and not readable:
|
||||
if len(analysis_deque) > 0:
|
||||
self.handle_read_bytestream(analysis_deque)
|
||||
self.send_data_to_client(conn_socket)
|
||||
|
||||
@abc.abstractmethod
|
||||
def handle_read_bytestream(self, analysis_deque: deque):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def send_data_to_client(self, conn_socket: socket.socket):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def send_data_available(self) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
class OpsSatServer(BaseServer):
|
||||
def __init__(self):
|
||||
super().__init__("[OPS-SAT]", OPSSAT_SERVER_PORT)
|
||||
|
||||
def handle_read_bytestream(self, analysis_deque: deque):
|
||||
parsed_packets = parse_space_packets(analysis_deque, [EXP_PACKET_ID_TM])
|
||||
for packet in parsed_packets:
|
||||
_LOGGER.info(f"{self.log_prefix} RX TM: [{packet.hex(sep=',')}]")
|
||||
TM_QUEUE.put(packet)
|
||||
|
||||
def send_data_to_client(self, conn_socket: socket.socket):
|
||||
while not TC_QUEUE.empty():
|
||||
next_packet = TC_QUEUE.get()
|
||||
_LOGGER.info(f"{self.log_prefix} TX TC [{next_packet.hex(sep=',')}]")
|
||||
conn_socket.sendall(next_packet)
|
||||
|
||||
def send_data_available(self) -> bool:
|
||||
return not TC_QUEUE.empty()
|
||||
|
||||
|
||||
class TmtcServer(BaseServer):
|
||||
def __init__(self):
|
||||
super().__init__("[TMTC]", TMTC_SERVER_PORT)
|
||||
|
||||
def handle_read_bytestream(self, analysis_deque: deque):
|
||||
parsed_packets = parse_space_packets(analysis_deque, [EXP_PACKET_ID_TC])
|
||||
for packet in parsed_packets:
|
||||
_LOGGER.info(f"{self.log_prefix} RX TM: [{packet.hex(sep=',')}]")
|
||||
TC_QUEUE.put(packet)
|
||||
|
||||
def send_data_to_client(self, conn_socket: socket.socket):
|
||||
while not TM_QUEUE.empty():
|
||||
next_packet = TM_QUEUE.get()
|
||||
_LOGGER.info(f"{self.log_prefix} TX TM [{next_packet.hex(sep=',')}]")
|
||||
conn_socket.sendall(next_packet)
|
||||
|
||||
def send_data_available(self) -> bool:
|
||||
return not TM_QUEUE.empty()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -4,7 +4,6 @@ on a remote machine, e.g. a Raspberry Pi"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import time
|
||||
from typing import Final
|
||||
|
||||
@ -16,10 +15,10 @@ BUILDER = "cross"
|
||||
# remote configurations by tweaking / hardcoding these parameter, which generally are constant
|
||||
# for a given board
|
||||
DEFAULT_USER_NAME: Final = "root"
|
||||
DEFAULT_ADDRESS: Final = "192.254.108.30"
|
||||
DEFAULT_ADDRESS: Final = "small_flatsat"
|
||||
DEFAULT_TOOLCHAIN: Final = "armv7-unknown-linux-gnueabihf"
|
||||
DEFAULT_APP_NAME: Final = "ops-sat-rs"
|
||||
DEFAULT_TARGET_FOLDER: Final = "/tmp"
|
||||
DEFAULT_TARGET_FOLDER: Final = "/home/exp278/"
|
||||
DEFAULT_DEBUG_PORT: Final = "1234"
|
||||
DEFAULT_GDB_APP = "gdb-multiarch"
|
||||
|
||||
@ -140,10 +139,11 @@ def bld_deploy_run(args):
|
||||
sshpass_args = f"-f {args.sshfile}"
|
||||
elif args.sshenv:
|
||||
sshpass_args = "-e"
|
||||
ssh_target_ident = f"{args.user}@{args.address}"
|
||||
# ssh_target_ident = f"{args.user}@{args.address}"
|
||||
ssh_target_ident = "small_flatsat"
|
||||
sshpass_cmd = ""
|
||||
if platform.system() != "Windows":
|
||||
sshpass_cmd = f"sshpass {sshpass_args}"
|
||||
# if platform.system() != "Windows":
|
||||
# sshpass_cmd = f"sshpass {sshpass_args}"
|
||||
dest_path = f"{args.dest}/{args.app}"
|
||||
if not args.source:
|
||||
source_path = f"{os.getcwd()}/target/{args.tc}/{build_folder}/{args.app}"
|
||||
|
@ -8,7 +8,8 @@ use std::net::Ipv4Addr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const STOP_FILE_NAME: &str = "stop-experiment";
|
||||
pub const HOME_FOLDER_EXPERIMENT: &str = "/home/exp278";
|
||||
pub const CONFIG_FILE_NAME: &str = "exp278.toml";
|
||||
pub const HOME_FOLER_EXPERIMENT: &str = "/home/exp278";
|
||||
pub const LOG_FOLDER: &str = "logs";
|
||||
|
||||
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
||||
@ -21,7 +22,8 @@ pub const VALID_PACKET_ID_LIST: &[PacketId] = &[PacketId::new_for_tc(true, EXPER
|
||||
|
||||
// TODO: Would be nice if this can be commanded as well..
|
||||
/// Can be enabled to print all SPP packets received from the SPP server on port 4096.
|
||||
pub const SPP_CLIENT_WIRETAPPING_RX: bool = false;
|
||||
pub const SPP_CLIENT_WIRETAPPING_RX: bool = true;
|
||||
pub const SPP_CLIENT_WIRETAPPING_TX: bool = true;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||
#[repr(u8)]
|
||||
@ -52,6 +54,80 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
pub mod cfg_file {
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use super::{CONFIG_FILE_NAME, HOME_PATH, TCP_SPP_SERVER_PORT};
|
||||
|
||||
pub const SPP_CLIENT_PORT_CFG_KEY: &str = "tcp_spp_server_port";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppCfg {
|
||||
pub tcp_spp_server_port: u16,
|
||||
}
|
||||
|
||||
impl Default for AppCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tcp_spp_server_port: TCP_SPP_SERVER_PORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_app_config() -> AppCfg {
|
||||
let mut cfg_path = HOME_PATH.clone();
|
||||
cfg_path.push(CONFIG_FILE_NAME);
|
||||
let cfg_path_home = cfg_path.as_path();
|
||||
let relevant_path = if Path::new(CONFIG_FILE_NAME).exists() {
|
||||
Some(PathBuf::from(Path::new(CONFIG_FILE_NAME)))
|
||||
} else if cfg_path_home.exists() {
|
||||
Some(PathBuf::from(cfg_path_home))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut app_cfg = AppCfg::default();
|
||||
if relevant_path.is_none() {
|
||||
log::warn!("No config file found, using default values");
|
||||
return app_cfg;
|
||||
}
|
||||
let relevant_path = relevant_path.unwrap();
|
||||
match File::open(relevant_path.as_path()) {
|
||||
Ok(mut file) => {
|
||||
let mut toml_str = String::new();
|
||||
match file.read_to_string(&mut toml_str) {
|
||||
Ok(_size) => match toml_str.parse::<toml::Table>() {
|
||||
Ok(table) => {
|
||||
handle_config_file_table(table, &mut app_cfg);
|
||||
}
|
||||
Err(e) => log::error!("error parsing TOML config file: {e}"),
|
||||
},
|
||||
Err(e) => log::error!("error reading TOML config file: {e}"),
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("error opening TOML config file: {e}"),
|
||||
}
|
||||
app_cfg
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_match)]
|
||||
pub fn handle_config_file_table(table: toml::Table, app_cfg: &mut AppCfg) {
|
||||
if let Some(value) = table.get(SPP_CLIENT_PORT_CFG_KEY) {
|
||||
if let toml::Value::Integer(port) = value {
|
||||
if *port < 0 {
|
||||
log::warn!("invalid port value, is negative");
|
||||
} else {
|
||||
app_cfg.tcp_spp_server_port = *port as u16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod tmtc_err {
|
||||
use super::*;
|
||||
use satrs::res_code::ResultU16;
|
||||
@ -144,11 +220,14 @@ pub mod components {
|
||||
}
|
||||
|
||||
pub mod tasks {
|
||||
use std::time::Duration;
|
||||
|
||||
pub const FREQ_MS_UDP_TMTC: u64 = 200;
|
||||
pub const FREQ_MS_EVENT_HANDLING: u64 = 400;
|
||||
pub const FREQ_MS_AOCS: u64 = 500;
|
||||
pub const FREQ_MS_PUS_STACK: u64 = 200;
|
||||
pub const FREQ_MS_CTRL: u64 = 400;
|
||||
|
||||
pub const STOP_CHECK_FREQUENCY: u64 = 400;
|
||||
pub const STOP_CHECK_FREQUENCY_MS: u64 = 400;
|
||||
pub const STOP_CHECK_FREQUENCY: Duration = Duration::from_millis(STOP_CHECK_FREQUENCY_MS);
|
||||
}
|
||||
|
@ -177,10 +177,20 @@ impl IMS100BatchHandler {
|
||||
self.handle_hk_request(&msg.requestor_info, hk_request);
|
||||
}
|
||||
CompositeRequest::Action(action_request) => {
|
||||
self.handle_action_request(&msg.requestor_info, action_request).unwrap();
|
||||
if let Err(e) =
|
||||
self.handle_action_request(&msg.requestor_info, action_request)
|
||||
{
|
||||
log::warn!("camera action request IO error: {e}");
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => match e {
|
||||
mpsc::TryRecvError::Empty => break,
|
||||
mpsc::TryRecvError::Disconnected => {
|
||||
log::warn!("composite request receiver disconnected");
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
@ -113,9 +112,7 @@ impl TcpTask {
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) {
|
||||
let result = self
|
||||
.0
|
||||
.handle_all_connections(Some(Duration::from_millis(STOP_CHECK_FREQUENCY)));
|
||||
let result = self.0.handle_all_connections(Some(STOP_CHECK_FREQUENCY));
|
||||
match result {
|
||||
Ok(_conn_result) => (),
|
||||
Err(e) => {
|
||||
|
@ -1,12 +1,13 @@
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{self, Read};
|
||||
use std::net::TcpStream as StdTcpStream;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use mio::net::TcpStream;
|
||||
use mio::net::TcpStream as MioTcpStream;
|
||||
use mio::{Events, Interest, Poll, Token};
|
||||
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
||||
use ops_sat_rs::config::{SPP_CLIENT_WIRETAPPING_RX, TCP_SPP_SERVER_PORT};
|
||||
use ops_sat_rs::config::{SPP_CLIENT_WIRETAPPING_RX, SPP_CLIENT_WIRETAPPING_TX};
|
||||
use satrs::encoding::ccsds::parse_buffer_for_ccsds_space_packets;
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::spacepackets::PacketId;
|
||||
@ -17,19 +18,22 @@ use thiserror::Error;
|
||||
use super::{SimpleSpValidator, TcpComponent};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PacketForwardingError {
|
||||
pub enum ClientError {
|
||||
#[error("send error: {0}")]
|
||||
Send(#[from] GenericSendError),
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub struct TcpSppClient {
|
||||
#[derive(Debug)]
|
||||
pub enum ClientResult {
|
||||
Ok,
|
||||
ConnectionLost,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TcpSppClientCommon {
|
||||
id: ComponentId,
|
||||
poll: Poll,
|
||||
events: Events,
|
||||
// Optional to allow periodic reconnection attempts on the TCP server.
|
||||
client: Option<TcpStream>,
|
||||
read_buf: [u8; 4096],
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
server_addr: SocketAddr,
|
||||
@ -37,143 +41,13 @@ pub struct TcpSppClient {
|
||||
validator: SimpleSpValidator,
|
||||
}
|
||||
|
||||
impl TcpSppClient {
|
||||
pub fn new(
|
||||
id: ComponentId,
|
||||
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
valid_ids: &'static [PacketId],
|
||||
) -> io::Result<Self> {
|
||||
let mut poll = Poll::new()?;
|
||||
let events = Events::with_capacity(128);
|
||||
let server_addr =
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 1)), TCP_SPP_SERVER_PORT);
|
||||
let client = Self::attempt_connection(&mut poll, &server_addr);
|
||||
if client.is_err() {
|
||||
log::warn!(
|
||||
"connection to TCP server {} failed: {}",
|
||||
server_addr,
|
||||
client.unwrap_err()
|
||||
);
|
||||
return Ok(Self {
|
||||
id,
|
||||
poll,
|
||||
events,
|
||||
client: None,
|
||||
read_buf: [0; 4096],
|
||||
server_addr,
|
||||
tm_tcp_client_rx,
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
});
|
||||
}
|
||||
Ok(Self {
|
||||
id,
|
||||
poll,
|
||||
events,
|
||||
client: Some(client.unwrap()),
|
||||
read_buf: [0; 4096],
|
||||
server_addr,
|
||||
tm_tcp_client_rx,
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attempt_connection(poll: &mut Poll, server_addr: &SocketAddr) -> io::Result<TcpStream> {
|
||||
let mut client = TcpStream::connect(*server_addr)?;
|
||||
poll.registry().register(
|
||||
&mut client,
|
||||
Token(0),
|
||||
Interest::READABLE | Interest::WRITABLE,
|
||||
)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) -> Result<(), PacketForwardingError> {
|
||||
if self.client.is_some() {
|
||||
return self.perform_regular_operation();
|
||||
} else {
|
||||
let client_result = Self::attempt_connection(&mut self.poll, &self.server_addr);
|
||||
match client_result {
|
||||
Ok(client) => {
|
||||
self.client = Some(client);
|
||||
self.perform_regular_operation()?;
|
||||
}
|
||||
Err(ref e) => {
|
||||
log::warn!(
|
||||
"connection to TCP server {} failed: {}",
|
||||
self.server_addr,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn perform_regular_operation(&mut self) -> Result<(), PacketForwardingError> {
|
||||
self.poll.poll(
|
||||
&mut self.events,
|
||||
Some(Duration::from_millis(STOP_CHECK_FREQUENCY)),
|
||||
)?;
|
||||
let events: Vec<mio::event::Event> = self.events.iter().cloned().collect();
|
||||
for event in events {
|
||||
if event.token() == Token(0) {
|
||||
if event.is_readable() {
|
||||
self.read_from_server()?;
|
||||
}
|
||||
if event.is_writable() {
|
||||
self.write_to_server()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_from_server(&mut self) -> Result<(), PacketForwardingError> {
|
||||
let client = self
|
||||
.client
|
||||
.as_mut()
|
||||
.expect("TCP stream invalid when it should not be");
|
||||
match client.read(&mut self.read_buf) {
|
||||
Ok(0) => return Err(io::Error::from(io::ErrorKind::BrokenPipe).into()),
|
||||
Ok(read_bytes) => self.handle_read_bytstream(read_bytes)?,
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to_server(&mut self) -> io::Result<()> {
|
||||
let client = self
|
||||
.client
|
||||
.as_mut()
|
||||
.expect("TCP stream invalid when it should not be");
|
||||
loop {
|
||||
match self.tm_tcp_client_rx.try_recv() {
|
||||
Ok(tm) => {
|
||||
client.write_all(&tm.packet)?;
|
||||
}
|
||||
Err(e) => match e {
|
||||
mpsc::TryRecvError::Empty => break,
|
||||
mpsc::TryRecvError::Disconnected => {
|
||||
log::error!("TM sender to TCP client has disconnected");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_read_bytstream(
|
||||
&mut self,
|
||||
read_bytes: usize,
|
||||
) -> Result<(), PacketForwardingError> {
|
||||
#[allow(dead_code)]
|
||||
impl TcpSppClientCommon {
|
||||
pub fn handle_read_bytstream(&mut self, read_bytes: usize) -> Result<(), ClientError> {
|
||||
let mut dummy = 0;
|
||||
if SPP_CLIENT_WIRETAPPING_RX {
|
||||
log::debug!(
|
||||
"received {} bytes on TCP client: {:x?}",
|
||||
"SPP TCP RX {} bytes: {:x?}",
|
||||
read_bytes,
|
||||
&self.read_buf[..read_bytes]
|
||||
);
|
||||
@ -188,4 +62,604 @@ impl TcpSppClient {
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to_server(&mut self, client: &mut impl io::Write) -> io::Result<()> {
|
||||
loop {
|
||||
match self.tm_tcp_client_rx.try_recv() {
|
||||
Ok(tm) => {
|
||||
if SPP_CLIENT_WIRETAPPING_TX {
|
||||
log::debug!(
|
||||
"SPP TCP TX {}: {:x?}",
|
||||
tm.packet.len(),
|
||||
tm.packet.as_slice()
|
||||
);
|
||||
}
|
||||
client.write_all(&tm.packet)?;
|
||||
}
|
||||
Err(e) => match e {
|
||||
mpsc::TryRecvError::Empty => break,
|
||||
mpsc::TryRecvError::Disconnected => {
|
||||
println!("god fuckikng damn it");
|
||||
log::error!("TM sender to TCP client has disconnected");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TcpSppClientStd {
|
||||
common: TcpSppClientCommon,
|
||||
read_and_idle_delay: Duration,
|
||||
// Optional to allow periodic reconnection attempts on the TCP server.
|
||||
stream: Option<StdTcpStream>,
|
||||
}
|
||||
|
||||
impl TcpSppClientStd {
|
||||
pub fn new(
|
||||
id: ComponentId,
|
||||
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
valid_ids: &'static [PacketId],
|
||||
read_timeout: Duration,
|
||||
port: u16,
|
||||
) -> io::Result<Self> {
|
||||
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
|
||||
let mut client = Self {
|
||||
common: TcpSppClientCommon {
|
||||
id,
|
||||
read_buf: [0; 4096],
|
||||
tm_tcp_client_rx,
|
||||
server_addr,
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
},
|
||||
read_and_idle_delay: read_timeout,
|
||||
stream: None,
|
||||
};
|
||||
client.attempt_connect(true)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn attempt_connect(&mut self, log_error: bool) -> io::Result<bool> {
|
||||
Ok(match StdTcpStream::connect(self.common.server_addr) {
|
||||
Ok(stream) => {
|
||||
stream.set_read_timeout(Some(self.read_and_idle_delay))?;
|
||||
self.stream = Some(stream);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
if log_error {
|
||||
log::warn!("error connecting to server: {}", e);
|
||||
}
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn connected(&self) -> bool {
|
||||
self.stream.is_some()
|
||||
}
|
||||
|
||||
pub fn operation(&mut self) -> Result<ClientResult, ClientError> {
|
||||
if let Some(client) = &mut self.stream {
|
||||
// Write TM first before blocking on the read call.
|
||||
self.common.write_to_server(client)?;
|
||||
match client.read(&mut self.common.read_buf) {
|
||||
// Not sure if this can happen or this is actually an error condition..
|
||||
Ok(0) => {
|
||||
log::info!("server closed connection");
|
||||
self.stream = None;
|
||||
return Ok(ClientResult::ConnectionLost);
|
||||
}
|
||||
Ok(read_bytes) => self.common.handle_read_bytstream(read_bytes)?,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut
|
||||
{
|
||||
self.common.write_to_server(client)?;
|
||||
return Ok(ClientResult::ConnectionLost);
|
||||
}
|
||||
log::warn!("server error: {e:?}");
|
||||
if e.kind() == io::ErrorKind::ConnectionReset {
|
||||
self.stream = None;
|
||||
return Ok(ClientResult::ConnectionLost);
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.attempt_connect(false)? {
|
||||
log::info!("reconnected to server succesfully");
|
||||
return self.operation();
|
||||
}
|
||||
std::thread::sleep(self.read_and_idle_delay);
|
||||
}
|
||||
|
||||
Ok(ClientResult::Ok)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ConnectionStatus {
|
||||
Unknown,
|
||||
Connected,
|
||||
LostConnection,
|
||||
TryingReconnect,
|
||||
}
|
||||
|
||||
/// Currently not used, not behaving as expected..
|
||||
#[allow(dead_code)]
|
||||
pub struct TcpSppClientMio {
|
||||
common: TcpSppClientCommon,
|
||||
poll: Poll,
|
||||
events: Events,
|
||||
// Optional to allow periodic reconnection attempts on the TCP server.
|
||||
client: Option<MioTcpStream>,
|
||||
connection: ConnectionStatus,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TcpSppClientMio {
|
||||
pub fn new(
|
||||
id: ComponentId,
|
||||
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
valid_ids: &'static [PacketId],
|
||||
port: u16,
|
||||
) -> io::Result<Self> {
|
||||
let poll = Poll::new()?;
|
||||
let events = Events::with_capacity(128);
|
||||
let mut client = Self {
|
||||
common: TcpSppClientCommon {
|
||||
id,
|
||||
read_buf: [0; 4096],
|
||||
tm_tcp_client_rx,
|
||||
server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
},
|
||||
poll,
|
||||
events,
|
||||
client: None,
|
||||
connection: ConnectionStatus::Unknown,
|
||||
};
|
||||
client.connect()?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn connect(&mut self) -> io::Result<()> {
|
||||
let mut client = MioTcpStream::connect(self.common.server_addr)?;
|
||||
|
||||
self.poll.registry().register(
|
||||
&mut client,
|
||||
Token(0),
|
||||
Interest::READABLE | Interest::WRITABLE,
|
||||
)?;
|
||||
self.client = Some(client);
|
||||
self.connection = ConnectionStatus::TryingReconnect;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn operation(&mut self) -> Result<(), ClientError> {
|
||||
match self.connection {
|
||||
ConnectionStatus::TryingReconnect | ConnectionStatus::Unknown => {
|
||||
self.check_conn_status()?
|
||||
}
|
||||
ConnectionStatus::Connected => {
|
||||
self.check_conn_status()?;
|
||||
self.poll
|
||||
.poll(&mut self.events, Some(STOP_CHECK_FREQUENCY))?;
|
||||
let events: Vec<mio::event::Event> = self.events.iter().cloned().collect();
|
||||
for event in events {
|
||||
if event.token() == Token(0) {
|
||||
if event.is_readable() {
|
||||
self.read_from_server()?;
|
||||
}
|
||||
// For some reason, we only get this once..
|
||||
if event.is_writable() {
|
||||
self.common.write_to_server(self.client.as_mut().unwrap())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
ConnectionStatus::LostConnection => self.connect()?,
|
||||
};
|
||||
std::thread::sleep(STOP_CHECK_FREQUENCY);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_from_server(&mut self) -> Result<(), ClientError> {
|
||||
match self
|
||||
.client
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read(&mut self.common.read_buf)
|
||||
{
|
||||
Ok(0) => (),
|
||||
Ok(read_bytes) => self.common.handle_read_bytstream(read_bytes)?,
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_conn_status(&mut self) -> io::Result<()> {
|
||||
match self.client.as_mut().unwrap().peer_addr() {
|
||||
Ok(_) => {
|
||||
if self.connection == ConnectionStatus::Unknown
|
||||
|| self.connection == ConnectionStatus::TryingReconnect
|
||||
{
|
||||
self.connection = ConnectionStatus::Connected;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::NotConnected {
|
||||
log::warn!("lost connection, or do not have one");
|
||||
self.connection = ConnectionStatus::LostConnection;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ops_sat_rs::config::EXPERIMENT_APID;
|
||||
use satrs::spacepackets::{PacketSequenceCtrl, PacketType, SequenceFlags, SpHeader};
|
||||
use std::{
|
||||
io::Write,
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
const VALID_IDS: &[PacketId] = &[PacketId::new_for_tc(true, EXPERIMENT_APID)];
|
||||
|
||||
const TEST_TC: SpHeader = SpHeader::new(
|
||||
PacketId::new(PacketType::Tc, true, EXPERIMENT_APID),
|
||||
PacketSequenceCtrl::new(SequenceFlags::Unsegmented, 0),
|
||||
1,
|
||||
);
|
||||
const TEST_TM: SpHeader = SpHeader::new(
|
||||
PacketId::new(PacketType::Tm, true, EXPERIMENT_APID),
|
||||
PacketSequenceCtrl::new(SequenceFlags::Unsegmented, 0),
|
||||
1,
|
||||
);
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
}
|
||||
|
||||
struct TcpServerTestbench {
|
||||
tcp_server: TcpListener,
|
||||
}
|
||||
|
||||
impl TcpServerTestbench {
|
||||
fn new(port: u16) -> Self {
|
||||
let tcp_server =
|
||||
TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)).unwrap();
|
||||
tcp_server
|
||||
.set_nonblocking(true)
|
||||
.expect("setting TCP server non-blocking failed");
|
||||
Self { tcp_server }
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> SocketAddr {
|
||||
self.tcp_server.local_addr().unwrap()
|
||||
}
|
||||
|
||||
fn check_for_connections(&mut self, limit: u32) -> Result<TcpStream, ()> {
|
||||
for _ in 0..limit {
|
||||
match self.tcp_server.accept() {
|
||||
Ok((stream, _)) => {
|
||||
return Ok(stream);
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
panic!("TCP server accept error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn try_reading_one_packet(
|
||||
&mut self,
|
||||
stream: &mut TcpStream,
|
||||
limit: u32,
|
||||
read_buf: &mut [u8],
|
||||
) -> usize {
|
||||
let mut read_data = 0;
|
||||
|
||||
for _ in 0..limit {
|
||||
match stream.read(read_buf) {
|
||||
Ok(0) => {}
|
||||
Ok(len) => {
|
||||
// assert_eq!(&tm_buf, &read_buf[0..len]);
|
||||
// read_bufd_expected_data = true;
|
||||
read_data = len;
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
continue;
|
||||
}
|
||||
panic!("TCP server read error: {:?}", e);
|
||||
}
|
||||
}
|
||||
if read_data > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
read_data
|
||||
}
|
||||
}
|
||||
|
||||
// This test just simplifies that the client properly connects to a server.
|
||||
#[test]
|
||||
fn basic_client_test() {
|
||||
let (tc_source_tx, _tc_source_rx) = mpsc::channel();
|
||||
let (_tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
let mut tcp_server = TcpServerTestbench::new(0);
|
||||
let local_addr = tcp_server.local_addr();
|
||||
let jh0 = thread::spawn(move || {
|
||||
tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
});
|
||||
let mut spp_client = TcpSppClientStd::new(
|
||||
1,
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_IDS,
|
||||
Duration::from_millis(30),
|
||||
local_addr.port(),
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
spp_client.operation().unwrap();
|
||||
jh0.join().unwrap();
|
||||
}
|
||||
|
||||
// This test verifies that TM is sent to the server properly.
|
||||
#[test]
|
||||
fn basic_client_tm_test() {
|
||||
let (tc_source_tx, _tc_source_rx) = mpsc::channel();
|
||||
let (tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
let mut tcp_server = TcpServerTestbench::new(0);
|
||||
let local_addr = tcp_server.local_addr();
|
||||
let mut buf: [u8; 7] = [0; 7];
|
||||
TEST_TM
|
||||
.write_to_be_bytes(&mut buf)
|
||||
.expect("writing TM failed");
|
||||
let jh0 = thread::spawn(move || {
|
||||
let mut read_buf: [u8; 64] = [0; 64];
|
||||
let mut stream = tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_millis(10)))
|
||||
.expect("setting read timeout failed");
|
||||
|
||||
let read_bytes = tcp_server.try_reading_one_packet(&mut stream, 5, &mut read_buf);
|
||||
if read_bytes == 0 {
|
||||
panic!("did not receive expected data");
|
||||
} else {
|
||||
assert_eq!(&buf, &read_buf[0..read_bytes]);
|
||||
}
|
||||
});
|
||||
tm_tcp_client_tx
|
||||
.send(PacketAsVec::new(0, buf.to_vec()))
|
||||
.unwrap();
|
||||
let mut spp_client = TcpSppClientStd::new(
|
||||
1,
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_IDS,
|
||||
Duration::from_millis(30),
|
||||
local_addr.port(),
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
spp_client.operation().unwrap();
|
||||
|
||||
jh0.join().unwrap();
|
||||
}
|
||||
|
||||
// Test that the client can read telecommands from the server.
|
||||
#[test]
|
||||
fn basic_client_tc_test() {
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||
let (_tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
let mut tcp_server = TcpServerTestbench::new(0);
|
||||
let local_addr = tcp_server.local_addr();
|
||||
let mut buf: [u8; 8] = [0; 8];
|
||||
TEST_TC
|
||||
.write_to_be_bytes(&mut buf)
|
||||
.expect("writing TM failed");
|
||||
let jh0 = thread::spawn(move || {
|
||||
let mut stream = tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_millis(10)))
|
||||
.expect("setting read timeout failed");
|
||||
stream.write_all(&buf).expect("writing TC failed");
|
||||
});
|
||||
|
||||
let mut spp_client = TcpSppClientStd::new(
|
||||
1,
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_IDS,
|
||||
Duration::from_millis(30),
|
||||
local_addr.port(),
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
assert!(spp_client.connected());
|
||||
let mut received_packet = false;
|
||||
(0..3).for_each(|_| {
|
||||
spp_client.operation().unwrap();
|
||||
if let Ok(packet) = tc_source_rx.try_recv() {
|
||||
assert_eq!(packet.packet, buf.to_vec());
|
||||
received_packet = true;
|
||||
}
|
||||
});
|
||||
if !received_packet {
|
||||
panic!("did not receive expected data");
|
||||
}
|
||||
jh0.join().unwrap();
|
||||
}
|
||||
|
||||
// Test that the client can both read telecommands from the server and send back
|
||||
// telemetry to the server.
|
||||
#[test]
|
||||
fn basic_client_tmtc_test() {
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||
let (tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
let mut tcp_server = TcpServerTestbench::new(0);
|
||||
let local_addr = tcp_server.local_addr();
|
||||
let mut tc_buf: [u8; 8] = [0; 8];
|
||||
let mut tm_buf: [u8; 8] = [0; 8];
|
||||
TEST_TC
|
||||
.write_to_be_bytes(&mut tc_buf)
|
||||
.expect("writing TM failed");
|
||||
TEST_TM
|
||||
.write_to_be_bytes(&mut tm_buf)
|
||||
.expect("writing TM failed");
|
||||
let jh0 = thread::spawn(move || {
|
||||
let mut read_buf: [u8; 64] = [0; 64];
|
||||
let mut stream = tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_millis(10)))
|
||||
.expect("setting read timeout failed");
|
||||
stream.write_all(&tc_buf).expect("writing TC failed");
|
||||
let read_bytes = tcp_server.try_reading_one_packet(&mut stream, 5, &mut read_buf);
|
||||
if read_bytes == 0 {
|
||||
panic!("did not receive expected data");
|
||||
} else {
|
||||
assert_eq!(&tm_buf, &read_buf[0..read_bytes]);
|
||||
}
|
||||
});
|
||||
tm_tcp_client_tx
|
||||
.send(PacketAsVec::new(0, tm_buf.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let mut spp_client = TcpSppClientStd::new(
|
||||
1,
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_IDS,
|
||||
Duration::from_millis(30),
|
||||
local_addr.port(),
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
assert!(spp_client.connected());
|
||||
let mut received_packet = false;
|
||||
(0..3).for_each(|_| {
|
||||
spp_client.operation().unwrap();
|
||||
if let Ok(packet) = tc_source_rx.try_recv() {
|
||||
assert_eq!(packet.packet, tc_buf.to_vec());
|
||||
received_packet = true;
|
||||
}
|
||||
});
|
||||
if !received_packet {
|
||||
panic!("did not receive expected data");
|
||||
}
|
||||
jh0.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_broken_connection() {
|
||||
init();
|
||||
let (tc_source_tx, _tc_source_rx) = mpsc::channel();
|
||||
let (tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
let mut tcp_server = TcpServerTestbench::new(0);
|
||||
let local_port = tcp_server.local_addr().port();
|
||||
let drop_signal = Arc::new(AtomicBool::new(false));
|
||||
let drop_signal_0 = drop_signal.clone();
|
||||
let mut tc_buf: [u8; 8] = [0; 8];
|
||||
let mut tm_buf: [u8; 8] = [0; 8];
|
||||
TEST_TC
|
||||
.write_to_be_bytes(&mut tc_buf)
|
||||
.expect("writing TM failed");
|
||||
TEST_TM
|
||||
.write_to_be_bytes(&mut tm_buf)
|
||||
.expect("writing TM failed");
|
||||
|
||||
let mut jh0 = thread::spawn(move || {
|
||||
tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
drop_signal_0.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
let mut spp_client = TcpSppClientStd::new(
|
||||
1,
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_IDS,
|
||||
Duration::from_millis(30),
|
||||
local_port,
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
while !drop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
tm_tcp_client_tx
|
||||
.send(PacketAsVec::new(0, tm_buf.to_vec()))
|
||||
.unwrap();
|
||||
match spp_client.operation() {
|
||||
Ok(ClientResult::ConnectionLost) => (),
|
||||
Ok(ClientResult::Ok) => {
|
||||
panic!("expected operation error");
|
||||
}
|
||||
Err(ClientError::Io(e)) => {
|
||||
println!("io error: {:?}", e);
|
||||
if e.kind() != io::ErrorKind::ConnectionReset
|
||||
&& e.kind() != io::ErrorKind::ConnectionAborted
|
||||
{
|
||||
panic!("expected some disconnet error");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected error")
|
||||
}
|
||||
};
|
||||
assert!(!spp_client.connected());
|
||||
jh0.join().unwrap();
|
||||
// spp_client.operation();
|
||||
tcp_server = TcpServerTestbench::new(local_port);
|
||||
tm_tcp_client_tx
|
||||
.send(PacketAsVec::new(0, tm_buf.to_vec()))
|
||||
.unwrap();
|
||||
jh0 = thread::spawn(move || {
|
||||
let mut stream = tcp_server
|
||||
.check_for_connections(3)
|
||||
.expect("no client connection detected");
|
||||
let mut read_buf: [u8; 64] = [0; 64];
|
||||
let read_bytes = tcp_server.try_reading_one_packet(&mut stream, 5, &mut read_buf);
|
||||
if read_bytes == 0 {
|
||||
panic!("did not receive expected data");
|
||||
} else {
|
||||
assert_eq!(&tm_buf, &read_buf[0..read_bytes]);
|
||||
}
|
||||
});
|
||||
let result = spp_client.operation();
|
||||
println!("{:?}", result);
|
||||
assert!(!spp_client.connected());
|
||||
assert!(result.is_ok());
|
||||
jh0.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
22
src/main.rs
22
src/main.rs
@ -8,8 +8,9 @@ use std::{
|
||||
use log::info;
|
||||
use ops_sat_rs::config::components::CAMERA_HANDLER;
|
||||
use ops_sat_rs::config::{
|
||||
cfg_file::create_app_config,
|
||||
components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER},
|
||||
tasks::{FREQ_MS_CTRL, FREQ_MS_PUS_STACK},
|
||||
tasks::{FREQ_MS_CTRL, FREQ_MS_PUS_STACK, STOP_CHECK_FREQUENCY},
|
||||
VALID_PACKET_ID_LIST,
|
||||
};
|
||||
use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT};
|
||||
@ -17,7 +18,7 @@ use satrs::hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer};
|
||||
use ops_sat_rs::TimeStampHelper;
|
||||
|
||||
use crate::handlers::camera::IMS100BatchHandler;
|
||||
use crate::tmtc::tc_source::TcSourceTaskDynamic;
|
||||
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
|
||||
use crate::tmtc::tm_sink::TmFunnelDynamic;
|
||||
use crate::{controller::ExperimentController, pus::test::create_test_service};
|
||||
use crate::{
|
||||
@ -25,10 +26,7 @@ use crate::{
|
||||
interface::udp_server::{DynamicUdpTmHandler, UdpTmtcServer},
|
||||
logger::setup_logger,
|
||||
};
|
||||
use crate::{
|
||||
interface::tcp_spp_client::TcpSppClient,
|
||||
pus::{PusTcDistributor, PusTcMpscRouter},
|
||||
};
|
||||
use crate::{interface::tcp_spp_client::TcpSppClientStd, tmtc::tc_source::TcSourceTaskDynamic};
|
||||
use crate::{
|
||||
pus::{action::create_action_service, stack::PusStack},
|
||||
requests::GenericRequestRouter,
|
||||
@ -46,6 +44,9 @@ fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
println!("OPS-SAT Rust Experiment OBSW");
|
||||
|
||||
let app_cfg = create_app_config();
|
||||
println!("App Configuration: {:?}", app_cfg);
|
||||
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||
@ -172,11 +173,13 @@ fn main() {
|
||||
stop_signal.clone(),
|
||||
);
|
||||
|
||||
let mut tcp_spp_client = TcpSppClient::new(
|
||||
let mut tcp_spp_client = TcpSppClientStd::new(
|
||||
TCP_SPP_CLIENT.id(),
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_PACKET_ID_LIST,
|
||||
STOP_CHECK_FREQUENCY,
|
||||
app_cfg.tcp_spp_server_port,
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
|
||||
@ -245,7 +248,10 @@ fn main() {
|
||||
.spawn(move || {
|
||||
info!("Running TCP SPP client");
|
||||
loop {
|
||||
let _result = tcp_spp_client.periodic_operation();
|
||||
let result = tcp_spp_client.operation();
|
||||
if let Err(e) = result {
|
||||
log::error!("TCP SPP client error: {}", e);
|
||||
}
|
||||
if tcp_client_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
@ -432,7 +432,8 @@ mod tests {
|
||||
.send(EcssTcAndToken::new(
|
||||
TcInMemory::Vec(PacketAsVec::new(
|
||||
self.service.service_helper.id(),
|
||||
tc.to_vec().unwrap().into(),
|
||||
//tc.to_vec().unwrap().into(),
|
||||
tc.to_vec().unwrap(),
|
||||
)),
|
||||
accepted_token,
|
||||
))
|
||||
|
@ -42,7 +42,6 @@ pub struct TestCustomServiceWrapper {
|
||||
EcssTcInVecConverter,
|
||||
VerificationReporter,
|
||||
>,
|
||||
// pub test_srv_event_sender: mpsc::Sender<EventMessageU32>,
|
||||
}
|
||||
|
||||
impl TestCustomServiceWrapper {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::mpsc, time::Duration};
|
||||
use std::{collections::HashMap, sync::mpsc};
|
||||
|
||||
use log::info;
|
||||
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
||||
@ -101,10 +101,7 @@ impl TmFunnelDynamic {
|
||||
|
||||
pub fn operation(&mut self) {
|
||||
loop {
|
||||
match self
|
||||
.tm_funnel_rx
|
||||
.recv_timeout(Duration::from_millis(STOP_CHECK_FREQUENCY))
|
||||
{
|
||||
match self.tm_funnel_rx.recv_timeout(STOP_CHECK_FREQUENCY) {
|
||||
Ok(mut tm) => {
|
||||
// Read the TM, set sequence counter and message counter, and finally update
|
||||
// the CRC.
|
||||
|
5
templates/exp278.toml
Normal file
5
templates/exp278.toml
Normal file
@ -0,0 +1,5 @@
|
||||
# This configuration file should either be inside the (experiment) home folder or in the current
|
||||
# folder the application is run from.
|
||||
|
||||
# On the small flatsat, change this to 9999.
|
||||
tcp_spp_server_port = 4096
|
Loading…
x
Reference in New Issue
Block a user