diff --git a/CMakeLists.txt b/CMakeLists.txt index a975a08..497b21b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,17 @@ install(DIRECTORY # Launch Block [END] # ###################### +######################### +# Config Block [BEGIN] # +# vvvvvvvvvvvvvvvvvvvvv # +install(DIRECTORY + config + DESTINATION share/${PROJECT_NAME}/ +) +# ^^^^^^^^^^^^^^^^^^^ # +# Config Block [END] # +####################### + ######################### # Scripts Block [BEGIN] # # vvvvvvvvvvvvvvvvvvvvv # diff --git a/README.md b/README.md index 327af7c..ba21a82 100644 --- a/README.md +++ b/README.md @@ -23,50 +23,97 @@ docker compose up The saved `.mat` file will be located at `~/sas_datalogger/docker/sas_datalogger_example/logs`. -## ROS 2 Nodes and Launch Files +## ROS 2 Nodes & Parameters -This package provides a datalogger server and suitable client APIs in C++ and Python. +This package provides a datalogger server and suitable client APIs in C++ and +Python. Each node is launched through its own launch file, which loads +parameters from `config/config.yaml` (pass a different file with +`config_file:=/path/to/config.yaml`). -### sas_datalogger_node +### Node: `sas_datalogger` -The main datalogger node subscribes to `/sas_datalogger/log` and stores received values in memory. -When the node is shut down it saves the collected data to a MATLAB-compatible `.mat` file (via `scipy.io.savemat`). +| Property | Value | +|---|---| +| **Executable** | `sas_datalogger_node.py` | +| **ROS node name** | `sas_datalogger` (set by the `name` launch argument of `sas_datalogger_launch.py`) | +| **Description** | Main datalogger node. Subscribes to `/sas_datalogger/log` and stores received values in memory. When the node is shut down it saves the collected data to a MATLAB-compatible `.mat` file (via `scipy.io.savemat`). | -Recommended use is through the launch file. The server must be launched separately. +#### Parameters + +The server declares **no ROS parameters** — it subscribes to the fixed topic `/sas_datalogger/log`. + +#### Sample launch ```bash ros2 launch sas_datalogger sas_datalogger_launch.py ``` -### sas_datalogger_gui_node +### Node: `sas_datalogger_gui_node` + +| Property | Value | +|---|---| +| **Executable** | `sas_datalogger_gui_node.py` | +| **ROS node name** | `sas_datalogger_gui_node` (set by the `name` launch argument of `sas_datalogger_gui_launch.py`) | +| **Description** | A Qt-based GUI that reads the datalogger's internal dictionary and creates execution-time plots for numeric values. | -A Qt-based GUI that reads the datalogger's internal dictionary and creates - execution-time plots for numeric values. +#### Parameters -Recommended use is through the launch file. The server must be launched separately. +| Parameter | Type | Mandatory / Optional | Default | Purpose | +|---|---|---|---|---| +| `whitelist` | string array | Optional | `[' ']` | List of values to plot. The single-space entry `[' ']` is the package convention for "plot all values" (the node maps it to `None`) | + +#### Sample launch ```bash ros2 launch sas_datalogger sas_datalogger_gui_launch.py ``` -### Example client scripts +To plot only specific values: + +```bash +ros2 launch sas_datalogger sas_datalogger_gui_launch.py config_file:=/path/to/config.yaml +``` + +### Node: `sas_datalogger_client_example` (C++ example client) + +| Property | Value | +|---|---| +| **Executable** | `sas_datalogger_client_example` | +| **ROS node name** | `sas_datalogger_client_example` (set by the `name` launch argument of `sas_datalogger_client_cpp_example_launch.py`) | +| **Description** | C++ example client. Publishes matrices, vectors, scalars and strings to the datalogger topic. | -The package includes simple example clients that publish matrices, vectors, -scalars and strings to the datalogger topic. +#### Parameters -- `scripts/sas_datalogger_client_example_py.py` — Python example client. -- `src/examples/sas_datalogger_client_example.cpp` (binary: `sas_datalogger_client_example`) — C++ example client. -- `scripts/sas_datalogger_client_example_result_check.py` — opens and inspects the generated `.mat` file using `scipy.io.loadmat`. +The C++ example client declares **no ROS parameters**. -Recommended use is through the launch file. The server must be launched separately. +#### Sample launch ```bash -ros2 launch sas_datalogger sas_datalogger_client_python_example_launch.py +ros2 launch sas_datalogger sas_datalogger_client_cpp_example_launch.py ``` -Recommended use is through the launch file. The server must be launched separately. +### Node: `sas_datalogger_client_example_py_rclpy` (Python example client) + +| Property | Value | +|---|---| +| **Executable** | `sas_datalogger_client_example_py.py` | +| **ROS node name** | `sas_datalogger_client_example_py_rclpy` (fixed in the script; the script creates two nodes, so no `name` launch argument is set) | +| **Description** | Python example client. Publishes matrices, vectors, scalars and strings to the datalogger topic. | + +#### Parameters + +| Parameter | Type | Mandatory / Optional | Default | Purpose | +|---|---|---|---|---| +| `execution_times` | integer | Optional | `5` | How many times the example logs the sample values | + +#### Sample launch ```bash -ros2 launch sas_datalogger sas_datalogger_client_cpp_example_launch.py +ros2 launch sas_datalogger sas_datalogger_client_python_example_launch.py ``` +> [!NOTE] +> The Python example client executable creates **two** nodes (an rclcpp node and +> an rclpy node). The `execution_times` parameter is declared on the rclpy node, +> whose fixed code name is `sas_datalogger_client_example_py_rclpy`; the +> corresponding block in `config/config.yaml` is keyed by that name. diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..7b2d131 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,28 @@ +# Parameters for the sas_datalogger nodes. +# +# Each top-level block is keyed by the ROS node name and is loaded by the +# corresponding launch file via its `config_file` argument. + +# Main datalogger server (scripts/sas_datalogger_node.py) +sas_datalogger: + ros__parameters: {} + # The datalogger server declares no ROS parameters. + +# Qt GUI (scripts/sas_datalogger_gui_node.py) +sas_datalogger_gui_node: + ros__parameters: + # A list of values to plot. The single-space entry `[' ']` is the + # package convention for "plot all values" (the node maps it to None). + whitelist: [' '] + +# C++ example client (src/examples/sas_datalogger_client_example.cpp) +sas_datalogger_client_example: + ros__parameters: {} + # The C++ example client declares no ROS parameters. + +# Python example client (scripts/sas_datalogger_client_example_py.py) +# The script creates two nodes; only the rclpy node below declares parameters. +# (The rclcpp node `sas_datalogger_client_example_py_rclcpp` has none.) +sas_datalogger_client_example_py_rclpy: + ros__parameters: + execution_times: 5 diff --git a/config/gui_test.yaml b/config/gui_test.yaml new file mode 100644 index 0000000..345fd7a --- /dev/null +++ b/config/gui_test.yaml @@ -0,0 +1,21 @@ +# Shared configuration for the GUI docker scenario (docker/compose_gui.yml). +# +# This file backs both services in that compose file: +# * `sas_datalogger_gui` -> the GUI node (plots the whitelisted values). +# * `sas_datalogger_client_py` -> the Python example client (logs values). +# +# Each block is keyed by the ROS node name, so each node picks up only its own +# block. The values that were previously passed as launch arguments +# (`whitelist:=...`, `execution_times:=...`) are now expressed here. + +# GUI node (scripts/sas_datalogger_gui_node.py) +sas_datalogger_gui_node: + ros__parameters: + whitelist: ['value_double', 'another_value_double', 'v'] + +# Python example client (scripts/sas_datalogger_client_example_py.py) +# The `execution_times` parameter is declared on the rclpy node, whose fixed +# code name is `sas_datalogger_client_example_py_rclpy`. +sas_datalogger_client_example_py_rclpy: + ros__parameters: + execution_times: 50000 diff --git a/docker/compose_gui.yml b/docker/compose_gui.yml index 328cd39..6606922 100644 --- a/docker/compose_gui.yml +++ b/docker/compose_gui.yml @@ -14,7 +14,7 @@ services: cd /root/sas_datalogger_devel && colcon build && source install/setup.bash - && timeout -s INT $$TIMEOUT ros2 launch sas_datalogger sas_datalogger_gui_launch.py whitelist:=['value_double','another_value_double','v']" + && timeout -s INT $$TIMEOUT ros2 launch sas_datalogger sas_datalogger_gui_launch.py config_file:=/root/sas_datalogger_devel/install/sas_datalogger/share/sas_datalogger/config/gui_test.yaml" sas_datalogger_client_py: build: . @@ -28,4 +28,4 @@ services: cd /root/sas_datalogger_devel && colcon build && source install/setup.bash - && timeout -s INT $$TIMEOUT ros2 launch sas_datalogger sas_datalogger_client_python_example_launch.py execution_times:=50000" \ No newline at end of file + && timeout -s INT $$TIMEOUT ros2 launch sas_datalogger sas_datalogger_client_python_example_launch.py config_file:=/root/sas_datalogger_devel/install/sas_datalogger/share/sas_datalogger/config/gui_test.yaml" \ No newline at end of file diff --git a/docker/test.sh b/docker/test.sh index a9da26b..33445b6 100755 --- a/docker/test.sh +++ b/docker/test.sh @@ -12,5 +12,5 @@ ros2 run sas_datalogger sas_datalogger_client_example_result_check.py sudo apt-get install -y xvfb sudo /usr/bin/Xvfb :99 -screen 0 1280x1024x24 & -timeout --foreground -s INT $TIMEOUT ros2 launch sas_datalogger sas_datalogger_client_cpp_example_launch.py execution_times:=50000 & +timeout --foreground -s INT $TIMEOUT ros2 launch sas_datalogger sas_datalogger_client_cpp_example_launch.py & timeout -s INT $TIMEOUT ros2 launch sas_datalogger sas_datalogger_gui_launch.py \ No newline at end of file diff --git a/launch/sas_datalogger_client_cpp_example_launch.py b/launch/sas_datalogger_client_cpp_example_launch.py index 325e438..0103ea3 100755 --- a/launch/sas_datalogger_client_cpp_example_launch.py +++ b/launch/sas_datalogger_client_cpp_example_launch.py @@ -1,15 +1,37 @@ +import os.path + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node # Run the example "src/examples/sas_datalogger_client_example.cpp" def generate_launch_description(): + """Launch the C++ example datalogger client. + + Parameters are loaded from a YAML configuration file. Pass a different + file with ``config_file:=/path/to/config.yaml``. + """ + name = LaunchConfiguration('name') + config_file = LaunchConfiguration('config_file') + return LaunchDescription([ + DeclareLaunchArgument( + 'name', + default_value='sas_datalogger_client_example' + ), + DeclareLaunchArgument( + 'config_file', + default_value=os.path.join(get_package_share_directory('sas_datalogger'), 'config', 'config.yaml') + ), Node( output='screen', emulate_tty=True, package='sas_datalogger', executable='sas_datalogger_client_example', - name='sas_datalogger_client_example' + name=name, + parameters=[config_file] ), ]) diff --git a/launch/sas_datalogger_client_python_example_launch.py b/launch/sas_datalogger_client_python_example_launch.py index 1ba1a99..f3b7f72 100755 --- a/launch/sas_datalogger_client_python_example_launch.py +++ b/launch/sas_datalogger_client_python_example_launch.py @@ -1,25 +1,37 @@ +import os.path + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch_ros.actions import Node from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + def generate_launch_description(): - execution_times = LaunchConfiguration('execution_times') + """Launch the Python example datalogger client. + + This executable creates two nodes (an rclcpp node and an rclpy node), each + with a fixed name set in the code. The `execution_times` parameter is + declared on the rclpy node, whose code name is + ``sas_datalogger_client_example_py_rclpy``. A launch ``name`` is therefore + not set here (a `__node` remap would be applied ambiguously to one of the + two nodes); the parameter is loaded from the matching block in the YAML + configuration file instead. Pass a different file with + ``config_file:=/path/to/config.yaml``. + """ + config_file = LaunchConfiguration('config_file') return LaunchDescription([ DeclareLaunchArgument( - 'execution_times', - default_value='5' + 'config_file', + default_value=os.path.join(get_package_share_directory('sas_datalogger'), 'config', 'config.yaml') ), Node( output='screen', emulate_tty=True, package='sas_datalogger', executable='sas_datalogger_client_example_py.py', - name='sas_datalogger_client_example', - parameters=[{ - "execution_times": execution_times - }] + parameters=[config_file] ), ]) diff --git a/launch/sas_datalogger_gui_launch.py b/launch/sas_datalogger_gui_launch.py index 84414e6..65c4c87 100755 --- a/launch/sas_datalogger_gui_launch.py +++ b/launch/sas_datalogger_gui_launch.py @@ -1,25 +1,37 @@ +import os.path + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch_ros.actions import Node from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + def generate_launch_description(): - whitelist = LaunchConfiguration('whitelist') + """Launch the datalogger Qt GUI. + + Parameters are loaded from a YAML configuration file. Pass a different + file with ``config_file:=/path/to/config.yaml``. + """ + name = LaunchConfiguration('name') + config_file = LaunchConfiguration('config_file') return LaunchDescription([ DeclareLaunchArgument( - 'whitelist', - default_value="[' ',]" + 'name', + default_value='sas_datalogger_gui_node' + ), + DeclareLaunchArgument( + 'config_file', + default_value=os.path.join(get_package_share_directory('sas_datalogger'), 'config', 'config.yaml') ), Node( output='screen', emulate_tty=True, package='sas_datalogger', executable='sas_datalogger_gui_node.py', - name='sas_datalogger', - parameters=[{ - 'whitelist': whitelist - }] + name=name, + parameters=[config_file] ) ]) diff --git a/launch/sas_datalogger_launch.py b/launch/sas_datalogger_launch.py index 42cad4f..e89e6c0 100755 --- a/launch/sas_datalogger_launch.py +++ b/launch/sas_datalogger_launch.py @@ -1,14 +1,37 @@ +import os.path + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node + def generate_launch_description(): + """Launch the datalogger server node. + + Parameters are loaded from a YAML configuration file. Pass a different + file with ``config_file:=/path/to/config.yaml``. + """ + name = LaunchConfiguration('name') + config_file = LaunchConfiguration('config_file') + return LaunchDescription([ + DeclareLaunchArgument( + 'name', + default_value='sas_datalogger' + ), + DeclareLaunchArgument( + 'config_file', + default_value=os.path.join(get_package_share_directory('sas_datalogger'), 'config', 'config.yaml') + ), Node( output='screen', emulate_tty=True, package='sas_datalogger', executable='sas_datalogger_node.py', - name='sas_datalogger' + name=name, + parameters=[config_file] ) ])