Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion doc/manual/chapters/create/convert.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,89 @@
**TODO**
== Generate Files from CSV

The helper scripts in `tools/helpers` can generate a contact plan and a
Docker Compose file from the same contact CSV. The CSV contains one link
per row with the columns `source`, `destination`, `start`, `end`,
`bandwidth`, `delay`, and an optional label. The scripts skip the header
row.

=== Generate a Contact Plan

Use `csv_to_ccp.py` to create a `.ccp` file:

[source,bash]
----
csv_to_ccp.py contacts.csv --output contacts.ccp
----

Options:

* `--output`, `-o`: output file; `-` (the default) writes to standard output.
* `--strip-prefix PREFIX`: remove `PREFIX` from source and destination names.
* `--speedup FACTOR`: divide contact timestamps by `FACTOR`, useful for a
shorter test run. The default is `1`.
* `--bw-scale FACTOR` (also `--bandwidth-scale`): multiply bandwidths by
`FACTOR`. The default is `1`.

Rows with a start time of `0` and an end time of `-1` become fixed links.
Matching reverse-direction rows are combined into a symmetric link using
`=`. When a node pair has multiple labels, the label is used to generate a
dedicated destination interface such as `dev:n1_n2_payload`.

=== Generate a Compose File

Use `csv_to_compose.py` with the same CSV:

[source,bash]
----
csv_to_compose.py contacts.csv --output compose.yml
----

Options:

* `--output`, `-o`: output file; `-` writes to standard output.
* `--strip-prefix PREFIX`: remove `PREFIX` from node names.
* `--nodes FILE` (also `--mapping`): JSON file containing node labels, names,
and IDs.
* `--name NAME`, `-n`: Compose project name.
* `--image IMAGE`, `-i`: container image; the default is `alpine`.
* `--entrypoint COMMAND`, `-e`: container entrypoint. The default installs
`iproute2` and `bash`, then keeps the container running.
* `--build PATH`, `-b`: use a build directory instead of an image.
* `--base-subnet PREFIX`: first two subnet octets; the default is `172.33`.
* `--export-graphml FILE`, `-g`: additionally export the topology as GraphML.
* `--node-volumes DIRECTORY`: create per-node data directories and mount them
into the containers.
* `--mount-compose`: mount the generated Compose file read-only in each
container.

The generated Compose topology uses one Docker bridge network for each link.
Every such network connects exactly two nodes; this keeps each link isolated
and gives both endpoints a predictable interface. Network names are based on
the sorted node names, and, when several differently labelled links connect
the same pair, also include the link label. Direction suffixes `_ul` and
`_dl` are removed and `high`/`low` are shortened to `hi`/`lo`.

Linux limits network interface names to 15 characters including the
terminating null byte. Therefore names with 14 or more characters are
replaced by the first 12 characters of their MD5 hash, with a warning printed
by the script. Use `--strip-prefix` when node labels share a long common
prefix, for example:

[source,bash]
----
csv_to_compose.py --strip-prefix eo --nodes nodes.json \
--output compose.yml actual_contacts.csv
----

Removing the prefix can keep the generated network and interface names short
and human-readable instead of forcing the hash fallback.

Both scripts write the generation date and complete command to the generated
file. A typical scenario therefore uses the same CSV to generate both files:

[source,bash]
----
csv_to_ccp.py --strip-prefix eo --output contacts.ccp actual_contacts.csv
csv_to_compose.py --strip-prefix eo --nodes nodes.json \
--name earth-orbit --output compose.yml actual_contacts.csv
----
Binary file modified doc/manual/manual.pdf
Binary file not shown.
76 changes: 34 additions & 42 deletions scenarios/eo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,47 @@ In this scenario the satellite passes are frequent and data can be downlinked at

## Topology

```mermaid
flowchart LR
style Earth rx:20,ry:20

MCC(fa:fa-computer Mission control centre<br>ipn:1.0)
PCC(fa:fa-computer Payload control centre<br>ipn:100.0)
GS1(fa:fa-satellite-dish GS1<br>ipn:20.0)
GS2(fa:fa-satellite-dish GS2<br>ipn:30.0)
EOSAT(fa:fa-satellite eosat<br>ipn:50.0)

subgraph Earth
MCC --- GS1
MCC --- GS2
PCC --- GS1
PCC --- GS2

end
GS1 -.- EOSAT
GS2 -.- EOSAT
```
![leo topo](./extras/leo.png)


## Datarates
| from\to | Mission Control Centre | Payload Control Centre | Ground Station | Satellite |
| - | - | - | - | - |
| __Mission Control Centre__ | / | 0 | 100 | 0 |
| __Payload Control Centre__ | 0 | / | 100 | 0 |
| __Ground Station__ | 100 | 100 | / | 64Kbps |
| __Satellite__ | 0 | 0 | 8Mbps/10Gbps | / |

- TC Uplink: 64 kBits
| from\to | Mission Control Centre | Payload Control Centre | Ground Station 1 | Ground Station 2 | Satellite |
| - | - | - | - | | - |
| __Mission Control Centre__ | / | 0 | 100 Mbps | 100 Mbps | 0 |
| __Payload Control Centre__ | 0 | / | 100 Mbps | 1000 Mbps | 0 |
| __Ground Station 1__ | 100 Mbps | 100 Mbps | / | 0 | 64 kbps |
| __Ground Station 2__ | 100 Mbps | 100 Mbps | 0 | / | 0 |
| __Satellite__ | 0 | 0 | 8Mbps | 10 Gbps | / |

- TC Uplink: 64 kbps
- HK TM Downlink: 8 Mbps
- Payload TM Downlink: 10 Gbits
- Payload TM Downlink: 1 Gbps


## Contact Plan and Compose File

## Datarates scaled for simulation
| from\to | Mission Control Centre | Payload Control Centre | Ground Station | Satellite |
| - | - | - | - | - |
| __Mission Control Centre__ | / | 0 | 10 | 0 |
| __Payload Control Centre__ | 0 | / | 10 | 0 |
| __Ground Station__ | 10 | 10 | / | 0.1 |
| __Satellite__ | 0 | 0 | 1 (HK), 1000 (Payload) | / |
The contact plan [contacts.ccp](contacts.ccp) is generated from
`actual_contacts.csv` via `csv_to_ccp.py`. The compose file
[compose.yml](compose.yml) is generated from the same CSV via
`csv_to_compose.py`.

## Contacts
The CSV-to-Compose conversion uses `nodes.json` for node metadata and strips
the `eo` prefix from node names. The generated contact plan also strips the
`eo` prefix. Both generated files contain the date and command used to create
them.

For this scenario, two contact plans are provided:
- [eo.testing.contacts.ccp](eo.testing.contacts.ccp): this simple plan is just alternating between both ground stations with 30s of no contact in between.
- [eo.realtime.contacts.ccp](eo.realtime.contacts.ccp): this plan has realistic contacts and runs in realtime, thus, long delays between contacts.
For short test runs, [contacts_testing.ccp](contacts_testing.ccp) is derived
from `contacts.ccp` with `random-contacts.py`:

```bash
random-contacts.py contacts.ccp contacts_testing.ccp \
--length 120 --min-contact 30 --max-contact 30 --seed 0
```

By default the `start_net.sh` script runs the testing contacts but you can provide another contact plan as first parameter.
This preserves fixed links and dynamic link properties while replacing the
original contact windows with one randomized 30-second window per unique
dynamic direction. The `start_net.sh` script uses `contacts.ccp` by default,
but accepts another contact plan as its first argument.

## Actions

Expand All @@ -67,6 +59,6 @@ An [example action file](eo.actions) is provided. It just starts a few processes
2. if you want fluctuating connectivity and bandwidth limitations: `./start_net.sh`
3. start the automatic actions on the nodes: `./start_actions.sh`
4. *OPTIONALLY: start the network visualization: `./start_viz.sh`*
5. *OPTIONALLY: start the docker test bed manager: `nse2_mgr eo.compose.yml eo.testing.contacts.ccp`*
5. *OPTIONALLY: start the docker test bed manager: `nse2_mgr compose.yml contacts.ccp`*

You can get an interactive shell on any of the nodes through docker: `docker exec -it <node> bash` or `nse2_sh <node>`
149 changes: 149 additions & 0 deletions scenarios/eo/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Generated: 2026-09-02
# Command: csv_to_compose.py --strip-prefix eo --name earth-orbit --nodes nodes.json --output compose.yml actual_contacts.csv

name: earth-orbit
services:
gs1:
container_name: gs1
hostname: gs1
cap_add:
- NET_ADMIN
privileged: true
environment:
- NODE_NAME=Ground Station 1
- NODE_ID=10
- TYPE=Host
networks:
gs1_mcc:
ipv4_address: 172.33.0.2
gs1_pcc:
ipv4_address: 172.33.1.2
gs1_sat:
ipv4_address: 172.33.2.2
image: alpine
entrypoint: sh -c "apk add iproute2 bash && tail -f /dev/null"
mcc:
container_name: mcc
hostname: mcc
cap_add:
- NET_ADMIN
privileged: true
environment:
- NODE_NAME=Mission Control Centre
- NODE_ID=30
- TYPE=Host
networks:
gs1_mcc:
ipv4_address: 172.33.0.3
gs2_mcc:
ipv4_address: 172.33.3.3
mcc_pcc:
ipv4_address: 172.33.4.2
image: alpine
entrypoint: sh -c "apk add iproute2 bash && tail -f /dev/null"
pcc:
container_name: pcc
hostname: pcc
cap_add:
- NET_ADMIN
privileged: true
environment:
- NODE_NAME=Payload Control Centre
- NODE_ID=40
- TYPE=Host
networks:
gs1_pcc:
ipv4_address: 172.33.1.3
mcc_pcc:
ipv4_address: 172.33.4.3
gs2_pcc:
ipv4_address: 172.33.5.3
image: alpine
entrypoint: sh -c "apk add iproute2 bash && tail -f /dev/null"
gs2:
container_name: gs2
hostname: gs2
cap_add:
- NET_ADMIN
privileged: true
environment:
- NODE_NAME=Ground Station 2
- NODE_ID=20
- TYPE=Host
networks:
gs2_mcc:
ipv4_address: 172.33.3.2
gs2_pcc:
ipv4_address: 172.33.5.2
gs2_sat:
ipv4_address: 172.33.6.2
image: alpine
entrypoint: sh -c "apk add iproute2 bash && tail -f /dev/null"
sat:
container_name: sat
hostname: sat
cap_add:
- NET_ADMIN
privileged: true
environment:
- NODE_NAME=Earth Observation Satellite
- NODE_ID=50
- TYPE=Host
networks:
gs1_sat:
ipv4_address: 172.33.2.3
gs2_sat:
ipv4_address: 172.33.6.3
image: alpine
entrypoint: sh -c "apk add iproute2 bash && tail -f /dev/null"
networks:
gs1_mcc:
driver: bridge
ipam:
config:
- subnet: 172.33.0.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs1_mcc_
gs1_pcc:
driver: bridge
ipam:
config:
- subnet: 172.33.1.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs1_pcc_
gs1_sat:
driver: bridge
ipam:
config:
- subnet: 172.33.2.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs1_sat_
gs2_mcc:
driver: bridge
ipam:
config:
- subnet: 172.33.3.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs2_mcc_
mcc_pcc:
driver: bridge
ipam:
config:
- subnet: 172.33.4.0/24
driver_opts:
com.docker.network.container_iface_prefix: mcc_pcc_
gs2_pcc:
driver: bridge
ipam:
config:
- subnet: 172.33.5.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs2_pcc_
gs2_sat:
driver: bridge
ipam:
config:
- subnet: 172.33.6.0/24
driver_opts:
com.docker.network.container_iface_prefix: gs2_sat_

Loading