rosotacom-dev 2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. rosotacom/__init__.py +32 -0
  2. rosotacom/__main__.py +8 -0
  3. rosotacom/anonymize.py +360 -0
  4. rosotacom/bag_ground_truth.py +395 -0
  5. rosotacom/benched_set.py +492 -0
  6. rosotacom/benchmark.py +1842 -0
  7. rosotacom/bundle_check.py +517 -0
  8. rosotacom/cli.py +8637 -0
  9. rosotacom/cli_benchmark.py +8211 -0
  10. rosotacom/deployment.py +196 -0
  11. rosotacom/ffmpeg_packet.py +123 -0
  12. rosotacom/forensics.py +1172 -0
  13. rosotacom/geomap.py +848 -0
  14. rosotacom/network_profiles.py +563 -0
  15. rosotacom/network_shaper.py +127 -0
  16. rosotacom/plots.py +609 -0
  17. rosotacom/py.typed +0 -0
  18. rosotacom/resources/benched-set.yaml +317 -0
  19. rosotacom/resources/examples/.gitignore +1 -0
  20. rosotacom/resources/examples/README.md +163 -0
  21. rosotacom/resources/examples/__init__.py +1 -0
  22. rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +17 -0
  23. rosotacom/resources/examples/deployment.example.yaml +14 -0
  24. rosotacom/resources/examples/profiles.yaml +85 -0
  25. rosotacom/resources/examples/ros2docker.json +33 -0
  26. rosotacom/resources/examples/rosotacom.yaml +12 -0
  27. rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +10 -0
  28. rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +9 -0
  29. rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +9 -0
  30. rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +21 -0
  31. rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +9 -0
  32. rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +15 -0
  33. rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +10 -0
  34. rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +19 -0
  35. rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +9 -0
  36. rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +15 -0
  37. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +12 -0
  38. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +9 -0
  39. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +89 -0
  40. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +9 -0
  41. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +15 -0
  42. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  43. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +44 -0
  44. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +13 -0
  45. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +9 -0
  46. rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +15 -0
  47. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +12 -0
  48. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +9 -0
  49. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +89 -0
  50. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +9 -0
  51. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +15 -0
  52. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  53. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +44 -0
  54. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +13 -0
  55. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +9 -0
  56. rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +15 -0
  57. rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +20 -0
  58. rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +22 -0
  59. rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +9 -0
  60. rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +15 -0
  61. rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +20 -0
  62. rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +22 -0
  63. rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +9 -0
  64. rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +15 -0
  65. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +18 -0
  66. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +22 -0
  67. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +9 -0
  68. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +15 -0
  69. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +18 -0
  70. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +22 -0
  71. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +9 -0
  72. rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +15 -0
  73. rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +39 -0
  74. rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +42 -0
  75. rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +33 -0
  76. rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +42 -0
  77. rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +211 -0
  78. rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +59 -0
  79. rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +61 -0
  80. rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +57 -0
  81. rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +26 -0
  82. rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +37 -0
  83. rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +34 -0
  84. rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +43 -0
  85. rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +43 -0
  86. rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +39 -0
  87. rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +50 -0
  88. rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +49 -0
  89. rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +37 -0
  90. rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +37 -0
  91. rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +42 -0
  92. rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +51 -0
  93. rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +41 -0
  94. rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +47 -0
  95. rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +45 -0
  96. rosotacom/resources/ros2docker.json.example +33 -0
  97. rosotacom/resources/ws/.vscode/c_cpp_properties.json +20 -0
  98. rosotacom/resources/ws/.vscode/settings.json +8 -0
  99. rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +32 -0
  100. rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +21 -0
  101. rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +17 -0
  102. rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +48 -0
  103. rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +95 -0
  104. rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +64 -0
  105. rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +56 -0
  106. rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +9 -0
  107. rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +33 -0
  108. rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +71 -0
  109. rosotacom/resources/ws/ota_configs/get_ota_xml.py +214 -0
  110. rosotacom/resources/ws/ota_configs/utils.py +74 -0
  111. rosotacom/resources/ws/ota_configs/zenoh.json5.template +32 -0
  112. rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  113. rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +67 -0
  114. rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  115. rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +25 -0
  116. rosotacom/resources/ws/ros2src/com_cpp/package.xml +37 -0
  117. rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +192 -0
  118. rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +283 -0
  119. rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +306 -0
  120. rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +22 -0
  121. rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +25 -0
  122. rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +3 -0
  123. rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +10 -0
  124. rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +4 -0
  125. rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +4 -0
  126. rosotacom/resources/ws/ros2src/com_msgs/package.xml +25 -0
  127. rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +25 -0
  128. rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  129. rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +106 -0
  130. rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +76 -0
  131. rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +70 -0
  132. rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +221 -0
  133. rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +178 -0
  134. rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +218 -0
  135. rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +370 -0
  136. rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +80 -0
  137. rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +205 -0
  138. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +73 -0
  139. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +93 -0
  140. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +107 -0
  141. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +156 -0
  142. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +91 -0
  143. rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +213 -0
  144. rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +177 -0
  145. rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +186 -0
  146. rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +465 -0
  147. rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +258 -0
  148. rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +277 -0
  149. rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +87 -0
  150. rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +145 -0
  151. rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +254 -0
  152. rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +78 -0
  153. rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +75 -0
  154. rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +191 -0
  155. rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +244 -0
  156. rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +64 -0
  157. rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +51 -0
  158. rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +476 -0
  159. rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +989 -0
  160. rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +99 -0
  161. rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +327 -0
  162. rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +171 -0
  163. rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +183 -0
  164. rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +653 -0
  165. rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +372 -0
  166. rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +191 -0
  167. rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +154 -0
  168. rosotacom/resources/ws/ros2src/com_py/package.xml +34 -0
  169. rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  170. rosotacom/resources/ws/ros2src/com_py/setup.cfg +6 -0
  171. rosotacom/resources/ws/ros2src/com_py/setup.py +55 -0
  172. rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +25 -0
  173. rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +25 -0
  174. rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +23 -0
  175. rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +633 -0
  176. rosotacom/resources/ws/session/content/network/up_connectivity.py +364 -0
  177. rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +215 -0
  178. rosotacom/resources/ws/session/creation/anonymize_bag.py +156 -0
  179. rosotacom/resources/ws/session/creation/catmux_log_setup.sh +65 -0
  180. rosotacom/resources/ws/session/creation/create_session_yaml.py +145 -0
  181. rosotacom/resources/ws/session/creation/generate_session_files.py +3217 -0
  182. rosotacom/resources/ws/session/creation/run_session.py +447 -0
  183. rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +54 -0
  184. rosotacom/resources/ws/session/creation/strip_ansi.py +42 -0
  185. rosotacom/status_eval.py +475 -0
  186. rosotacom/stream_stats.py +650 -0
  187. rosotacom/trace_profiles.py +561 -0
  188. rosotacom/transit.py +249 -0
  189. rosotacom/video_quality.py +906 -0
  190. rosotacom_dev-2.2.dist-info/METADATA +874 -0
  191. rosotacom_dev-2.2.dist-info/RECORD +195 -0
  192. rosotacom_dev-2.2.dist-info/WHEEL +5 -0
  193. rosotacom_dev-2.2.dist-info/entry_points.txt +4 -0
  194. rosotacom_dev-2.2.dist-info/licenses/LICENSE.txt +25 -0
  195. rosotacom_dev-2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,447 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # -- BEGIN LICENSE BLOCK ----------------------------------------------
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ #
10
+ # * Redistributions in binary form must reproduce the above copyright
11
+ # notice, this list of conditions and the following disclaimer in the
12
+ # documentation and/or other materials provided with the distribution.
13
+ #
14
+ # * Neither the name of the {copyright_holder} nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+ # -- END LICENSE BLOCK ------------------------------------------------
30
+ #
31
+ # ---------------------------------------------------------------------
32
+ # !\file
33
+ #
34
+ # \author Martin Gontscharow <gontscharow@fzi.de>
35
+ # \date 2024-11-13
36
+ #
37
+ #
38
+ # ---------------------------------------------------------------------
39
+
40
+ import argparse
41
+ import copy
42
+ import os
43
+ import re
44
+ import subprocess
45
+ import sys
46
+ from typing import Dict, Optional
47
+
48
+ import yaml
49
+
50
+ ws_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
51
+ sys.path.append(ws_path)
52
+
53
+ from session.creation.create_session_yaml import main as create_session_yaml
54
+ from session.creation import generate_session_files as session_gen
55
+
56
+ _CATMUX_NO_SERVER_RE = re.compile(
57
+ r"^error connecting to /tmp/tmux-\d+/catmux \(No such file or directory\)$"
58
+ )
59
+ # catmux's tmux wrapper swallows failed tmux calls: it prints this marker to
60
+ # stdout and keeps going (exit 0) instead of raising. Treat it as a launch
61
+ # failure so a half-built session surfaces loudly instead of silently.
62
+ _CATMUX_SWALLOWED_ERROR = "Error while calling"
63
+
64
+
65
+ def _report_catmux_failure(full_command, result, *, swallowed: bool, failure_log_path) -> None:
66
+ reason = (
67
+ "catmux swallowed a tmux error and left the session incomplete"
68
+ if swallowed and result.returncode == 0
69
+ else f"catmux_create_session exited with status {result.returncode}"
70
+ )
71
+ # Print to stdout: the stderr traceback can be captured/dropped by the layers
72
+ # above (the smoke harness), so keep the cause next to the failure on stdout.
73
+ detail = (result.stderr or "").strip() or (result.stdout or "").strip()
74
+ print("=" * 72)
75
+ print(f"catmux session launch FAILED: {reason}.")
76
+ if detail:
77
+ print("--- catmux output ---")
78
+ print(detail)
79
+ print("--- end catmux output ---")
80
+ if failure_log_path:
81
+ try:
82
+ os.makedirs(os.path.dirname(failure_log_path) or ".", exist_ok=True)
83
+ with open(failure_log_path, "w") as handle:
84
+ handle.write("command: " + " ".join(str(part) for part in full_command) + "\n")
85
+ handle.write(f"returncode: {result.returncode}\n\n")
86
+ handle.write("===== stdout =====\n")
87
+ handle.write(result.stdout or "")
88
+ handle.write("\n===== stderr =====\n")
89
+ handle.write(result.stderr or "")
90
+ print(f"Full catmux launch log: {failure_log_path}")
91
+ except OSError as exc:
92
+ print(f"(could not write catmux launch log to {failure_log_path}: {exc})")
93
+ print("=" * 72)
94
+
95
+
96
+ def _run_catmux(full_command, attach: bool, failure_log_path=None) -> None:
97
+ if attach:
98
+ subprocess.run(full_command, check=True)
99
+ return
100
+
101
+ result = subprocess.run(full_command, text=True, capture_output=True)
102
+ if result.stdout:
103
+ print(result.stdout, end="")
104
+
105
+ stderr_lines = [
106
+ line
107
+ for line in (result.stderr or "").splitlines()
108
+ if not _CATMUX_NO_SERVER_RE.match(line.strip())
109
+ ]
110
+ if stderr_lines:
111
+ print("\n".join(stderr_lines), file=sys.stderr)
112
+
113
+ swallowed = _CATMUX_SWALLOWED_ERROR in (result.stdout or "")
114
+ if result.returncode != 0 or swallowed:
115
+ _report_catmux_failure(
116
+ full_command, result, swallowed=swallowed, failure_log_path=failure_log_path
117
+ )
118
+ raise subprocess.CalledProcessError(
119
+ result.returncode or 1, full_command, output=result.stdout, stderr=result.stderr
120
+ )
121
+
122
+
123
+ def _parse_peer_address_overrides(overrides) -> Dict[str, str]:
124
+ result: Dict[str, str] = {}
125
+ for override in overrides or []:
126
+ peer_key, sep, address_value = (override or "").partition("=")
127
+ peer_key = peer_key.strip()
128
+ address_value = address_value.strip()
129
+ if sep != "=" or not peer_key or not address_value:
130
+ raise RuntimeError(
131
+ "--peer-address must use '<peer_key>=<address>', "
132
+ "for example 'a=192.168.1.10'."
133
+ )
134
+ if peer_key in result:
135
+ raise RuntimeError(f"Duplicate --peer-address override for peer '{peer_key}'.")
136
+ result[peer_key] = address_value
137
+ return result
138
+
139
+
140
+ def _load_session_config_input(session_config_yaml: str) -> Dict:
141
+ param_dir = os.path.dirname(session_config_yaml)
142
+ param = session_gen._load_yaml(session_config_yaml)
143
+ if not isinstance(param, dict):
144
+ raise RuntimeError(
145
+ f"Session config input YAML must be a mapping, got {type(param)} for '{session_config_yaml}'"
146
+ )
147
+
148
+ if "load_template" in param:
149
+ session_template_fs, provided_params = session_gen._parse_session_config_template_spec(param, param_dir)
150
+ cfg_raw = session_gen._load_yaml(session_template_fs)
151
+ vars_map = session_gen._build_vars_map_from_template(cfg_raw, provided_params)
152
+ cfg = session_gen._substitute(cfg_raw, vars_map) or {}
153
+ else:
154
+ cfg = dict(param)
155
+
156
+ session_gen._validate_session_template_cfg(cfg)
157
+ return cfg
158
+
159
+
160
+ def _apply_link_trace_overrides(
161
+ cfg: Dict,
162
+ *,
163
+ link_trace: Optional[bool] = None,
164
+ link_trace_interval_s: Optional[float] = None,
165
+ link_trace_modem_command: Optional[str] = None,
166
+ ) -> Dict:
167
+ if link_trace is None and link_trace_interval_s is None and link_trace_modem_command is None:
168
+ return cfg
169
+ updated = copy.deepcopy(cfg)
170
+ shared = updated.setdefault("shared", {})
171
+ if not isinstance(shared, dict):
172
+ raise RuntimeError("shared must be a mapping before applying link trace overrides.")
173
+ trace_cfg = shared.setdefault("link_trace", {})
174
+ if not isinstance(trace_cfg, dict):
175
+ raise RuntimeError("shared.link_trace must be a mapping before applying link trace overrides.")
176
+ shared["use_status_overview"] = True
177
+ trace_cfg["enabled"] = True if link_trace is None else bool(link_trace)
178
+ if link_trace_interval_s is not None:
179
+ trace_cfg["interval_s"] = float(link_trace_interval_s)
180
+ if link_trace_modem_command is not None:
181
+ trace_cfg["modem_metrics_command"] = link_trace_modem_command
182
+ session_gen._validate_session_template_cfg(updated)
183
+ return updated
184
+
185
+
186
+ def _resolve_peer_dir(
187
+ session_dir: str,
188
+ output_dir: Optional[str],
189
+ identity: Optional[str],
190
+ force: bool,
191
+ rewrite_formatting: bool,
192
+ peer_address=None,
193
+ link_trace: Optional[bool] = None,
194
+ link_trace_interval_s: Optional[float] = None,
195
+ link_trace_modem_command: Optional[str] = None,
196
+ ) -> str:
197
+ """
198
+ Resolve a runnable session directory (the directory containing session_specification.yaml).
199
+
200
+ Supported input:
201
+ - session directory that contains one of:
202
+ - session-definition.yaml (self-contained)
203
+ - session-parametrization.yaml (template + parameters)
204
+ plus: --identity <peer_key>
205
+ """
206
+ if not os.path.isabs(session_dir):
207
+ for base in (os.environ.get("SESSION_DEFINITIONS_DIR"), os.environ.get("SESSION_CONFIGS_DIR")):
208
+ if not base:
209
+ continue
210
+ candidate = os.path.join(base, session_dir)
211
+ if os.path.isdir(candidate):
212
+ session_dir = candidate
213
+ break
214
+
215
+ p = os.path.abspath(session_dir)
216
+ if not os.path.isdir(p):
217
+ raise RuntimeError(f"--session-dir must be a directory, got: {p}")
218
+ out = os.path.abspath(output_dir) if output_dir else p
219
+ os.makedirs(out, exist_ok=True)
220
+
221
+ if not identity:
222
+ raise RuntimeError(
223
+ "Missing --identity. Provide --identity <name of the peer>."
224
+ )
225
+
226
+ # Resolve which session config input file to use.
227
+ #
228
+ # Precedence:
229
+ # - session-parametrization.yaml (if present): treated as the "primary" input even if a generated
230
+ # session-definition.yaml also exists.
231
+ # - session-definition.yaml
232
+ candidates = [
233
+ "session-parametrization.yaml",
234
+ "session-definition.yaml",
235
+ ]
236
+ param_yaml = None
237
+ for name in candidates:
238
+ fp = os.path.join(p, name)
239
+ if os.path.exists(fp):
240
+ param_yaml = fp
241
+ break
242
+ if not param_yaml:
243
+ raise RuntimeError(
244
+ "Missing session config input file in session dir. Expected one of: "
245
+ f"{candidates}. Got dir: {p}"
246
+ )
247
+
248
+ cfg_for_generation = _apply_link_trace_overrides(
249
+ _load_session_config_input(param_yaml),
250
+ link_trace=link_trace,
251
+ link_trace_interval_s=link_trace_interval_s,
252
+ link_trace_modem_command=link_trace_modem_command,
253
+ )
254
+ peer_addresses = _parse_peer_address_overrides(peer_address)
255
+ known_peers = set((cfg_for_generation.get("peers") or {}).keys())
256
+ unknown = sorted(set(peer_addresses) - known_peers)
257
+ if unknown:
258
+ raise RuntimeError(
259
+ f"--peer-address references unknown peer(s) {unknown}. Known peers: {sorted(known_peers)}"
260
+ )
261
+ session_gen.func(
262
+ session_config_yaml=param_yaml,
263
+ force=force,
264
+ rewrite_formatting=rewrite_formatting,
265
+ session_config_obj=cfg_for_generation,
266
+ output_dir=out,
267
+ write_resolved_definition=True,
268
+ peer_addresses=peer_addresses,
269
+ )
270
+ peer_dir = os.path.join(out, identity)
271
+ spec_file = os.path.join(peer_dir, "session_specification.yaml")
272
+ if not os.path.exists(spec_file):
273
+ # Try to help with a quick "what identities exist" hint.
274
+ try:
275
+ subdirs = sorted(d for d in os.listdir(out) if os.path.isdir(os.path.join(out, d)) and not d.startswith("."))
276
+ except Exception:
277
+ subdirs = []
278
+ hint = f" Available subdirs: {subdirs}" if subdirs else ""
279
+ raise RuntimeError(f"Identity '{identity}' did not resolve to a runnable peer dir: missing {spec_file}.{hint}")
280
+ return peer_dir
281
+
282
+
283
+ def main(
284
+ session_dir: str,
285
+ identity: Optional[str] = None,
286
+ output_dir: Optional[str] = None,
287
+ instance_dir: Optional[str] = None,
288
+ catmux_log_dir: Optional[str] = None,
289
+ rosbag_dir: Optional[str] = None,
290
+ force: bool = False,
291
+ rewrite_formatting: bool = False,
292
+ peer_address=None,
293
+ link_trace: Optional[bool] = None,
294
+ link_trace_interval_s: Optional[float] = None,
295
+ link_trace_modem_command: Optional[str] = None,
296
+ attach: Optional[bool] = None,
297
+ ):
298
+
299
+ peer_dir = _resolve_peer_dir(
300
+ session_dir,
301
+ output_dir,
302
+ identity,
303
+ force,
304
+ rewrite_formatting,
305
+ peer_address=peer_address,
306
+ link_trace=link_trace,
307
+ link_trace_interval_s=link_trace_interval_s,
308
+ link_trace_modem_command=link_trace_modem_command,
309
+ )
310
+
311
+ # Ensure merged .session_readonly.yaml exists for catmux
312
+ config_dir = output_dir or os.path.dirname(peer_dir)
313
+ create_session_yaml(
314
+ peer_dir,
315
+ instance_dir=instance_dir or os.environ.get("ROSOTACOM_INSTANCE_DIR"),
316
+ config_dir=config_dir,
317
+ catmux_log_dir=catmux_log_dir or os.environ.get("ROSOTACOM_CATMUX_LOG_DIR"),
318
+ rosbag_dir=rosbag_dir or os.environ.get("ROSOTACOM_ROSBAG_DIR"),
319
+ )
320
+
321
+ # Define the command and arguments
322
+ command = "catmux_create_session"
323
+ yaml_file_path = f"{peer_dir}/.session_readonly.yaml"
324
+ session_name_arg = "--session_name"
325
+
326
+ # get name
327
+ spec_file = os.path.join(peer_dir, "session_specification.yaml")
328
+ with open(spec_file, "r") as f:
329
+ spec = yaml.safe_load(f)
330
+ session_name = (spec or {}).get("name", "ros_communication")
331
+
332
+ # Combine them into a single command
333
+ full_command = [
334
+ command,
335
+ yaml_file_path,
336
+ session_name_arg,
337
+ session_name,
338
+ "--overwrite",
339
+ f"dir_path={peer_dir}",
340
+ ]
341
+ if attach is None:
342
+ attach = sys.stdin.isatty() and sys.stdout.isatty()
343
+ if not attach:
344
+ full_command.append("--detach")
345
+
346
+ resolved_catmux_log_dir = catmux_log_dir or os.environ.get("ROSOTACOM_CATMUX_LOG_DIR")
347
+ failure_log_path = os.path.join(
348
+ resolved_catmux_log_dir or peer_dir, "catmux-launch-failure.log"
349
+ )
350
+
351
+ # Execute the command
352
+ try:
353
+ _run_catmux(full_command, attach, failure_log_path=failure_log_path)
354
+ print("Command executed successfully.")
355
+ except subprocess.CalledProcessError as e:
356
+ print(
357
+ f"catmux_create_session failed (exit {e.returncode}); see the catmux "
358
+ f"output above and {failure_log_path}.",
359
+ file=sys.stderr,
360
+ )
361
+ raise SystemExit(e.returncode)
362
+
363
+ if __name__ == "__main__":
364
+ parser = argparse.ArgumentParser(
365
+ description="Generate peer session files from a session definition/parametrization and launch catmux for one identity."
366
+ )
367
+ parser.add_argument(
368
+ "-s",
369
+ "--session-dir",
370
+ required=True,
371
+ help="Directory containing session-definition.yaml or session-parametrization.yaml.",
372
+ )
373
+ parser.add_argument(
374
+ "--output-dir",
375
+ help="Directory where generated runtime session files are written.",
376
+ )
377
+ parser.add_argument(
378
+ "--instance-dir",
379
+ help="Runtime session instance directory.",
380
+ )
381
+ parser.add_argument(
382
+ "--catmux-log-dir",
383
+ help="Directory where catmux pane logs for this peer are written.",
384
+ )
385
+ parser.add_argument(
386
+ "--rosbag-dir",
387
+ help="Directory where rosbags for this peer should be written.",
388
+ )
389
+ parser.add_argument(
390
+ "--identity",
391
+ required=True,
392
+ type=str,
393
+ help="Which peer to launch.",
394
+ )
395
+ parser.add_argument(
396
+ "-f",
397
+ "--force",
398
+ action="store_true",
399
+ help="If generating session files: overwrite existing files even if they differ semantically.",
400
+ )
401
+ parser.add_argument(
402
+ "--rewrite-formatting",
403
+ action="store_true",
404
+ help="If generating session files: rewrite files even when semantically equal (format-only differences).",
405
+ )
406
+ parser.add_argument(
407
+ "--peer-address",
408
+ action="append",
409
+ default=[],
410
+ metavar="PEER=ADDRESS",
411
+ help="Resolved peer address. Pass once for every logical peer.",
412
+ )
413
+ parser.add_argument(
414
+ "--link-trace",
415
+ action="store_true",
416
+ default=None,
417
+ help="Enable link_trace.jsonl recording for this generated session instance.",
418
+ )
419
+ parser.add_argument(
420
+ "--link-trace-interval",
421
+ dest="link_trace_interval_s",
422
+ type=float,
423
+ help="Link trace sample interval in seconds; enables link tracing.",
424
+ )
425
+ parser.add_argument(
426
+ "--link-trace-modem-command",
427
+ dest="link_trace_modem_command",
428
+ help="Shell command returning a JSON object to merge under each trace row's modem block.",
429
+ )
430
+ attach_group = parser.add_mutually_exclusive_group()
431
+ attach_group.add_argument(
432
+ "--attach",
433
+ dest="attach",
434
+ action="store_true",
435
+ help="Attach to the catmux/tmux session after creating it.",
436
+ )
437
+ attach_group.add_argument(
438
+ "--detach",
439
+ dest="attach",
440
+ action="store_false",
441
+ help="Create the catmux/tmux session and return without attaching.",
442
+ )
443
+ parser.set_defaults(attach=None)
444
+ args = parser.parse_args()
445
+
446
+ # Use **vars(args) to convert argparse.Namespace to a dict, filtering out None values
447
+ main(**{k: v for k, v in vars(args).items() if v is not None})
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # -- BEGIN LICENSE BLOCK ----------------------------------------------
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ #
10
+ # * Redistributions in binary form must reproduce the above copyright
11
+ # notice, this list of conditions and the following disclaimer in the
12
+ # documentation and/or other materials provided with the distribution.
13
+ #
14
+ # * Neither the name of the {copyright_holder} nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+ # -- END LICENSE BLOCK ------------------------------------------------
30
+ #
31
+ # ---------------------------------------------------------------------
32
+ # !\file
33
+ #
34
+ # \author Martin Gontscharow <gontscharow@fzi.de>
35
+ # \date 2024-11-13
36
+ #
37
+ #
38
+ # ---------------------------------------------------------------------
39
+
40
+ import argparse
41
+ import subprocess
42
+
43
+ def main(session_dir):
44
+ print("External Terminal requested. Restarting script inside new gnome-terminal...")
45
+ gnome_terminal_command = f'gnome-terminal -- bash -c "/ws/session_creation/run_session.py --session-dir {session_dir}"'
46
+ print(f"Executing in gnome-terminal with command: {gnome_terminal_command}")
47
+ subprocess.run(gnome_terminal_command, shell=True, check=True)
48
+ print("Script execution in external gnome-terminal initiated.")
49
+
50
+ if __name__ == "__main__":
51
+ parser = argparse.ArgumentParser(description=main.__doc__)
52
+ parser.add_argument("-s", "--session-dir")
53
+ args = parser.parse_args()
54
+ main(**{k: v for k, v in vars(args).items() if v is not None})
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+ """Strip ANSI/VT100 control sequences from stdin to stdout."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ import sys
8
+
9
+
10
+ _ANSI_PATTERN = re.compile(
11
+ rb"\x1b\[[0-9;?]*[ -/]*[@-~]"
12
+ rb"|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)"
13
+ rb"|\x1b[@-Z\\-_]"
14
+ )
15
+
16
+
17
+ def _strip(chunk: bytes) -> bytes:
18
+ return _ANSI_PATTERN.sub(b"", chunk)
19
+
20
+
21
+ def main() -> None:
22
+ src = sys.stdin.buffer
23
+ dst = sys.stdout.buffer
24
+ pending = b""
25
+ while True:
26
+ chunk = src.read1(4096)
27
+ if not chunk:
28
+ break
29
+ pending += chunk
30
+ cut = pending.rfind(b"\n")
31
+ if cut < 0:
32
+ continue
33
+ dst.write(_strip(pending[: cut + 1]))
34
+ dst.flush()
35
+ pending = pending[cut + 1:]
36
+ if pending:
37
+ dst.write(_strip(pending))
38
+ dst.flush()
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()