Skip to content
Merged
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
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
Expand Down
87 changes: 67 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
28 changes: 28 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions config/gui_test.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions docker/compose_gui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: .
Expand All @@ -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"
&& 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"
2 changes: 1 addition & 1 deletion docker/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
24 changes: 23 additions & 1 deletion launch/sas_datalogger_client_cpp_example_launch.py
Original file line number Diff line number Diff line change
@@ -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]
),
])

28 changes: 20 additions & 8 deletions launch/sas_datalogger_client_python_example_launch.py
Original file line number Diff line number Diff line change
@@ -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]
),
])

28 changes: 20 additions & 8 deletions launch/sas_datalogger_gui_launch.py
Original file line number Diff line number Diff line change
@@ -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]
)
])

25 changes: 24 additions & 1 deletion launch/sas_datalogger_launch.py
Original file line number Diff line number Diff line change
@@ -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]
)
])