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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Quick check (what CI runs):
docker compose build -f docker/compose.yml
docker compose up -f docker/compose.yml
# or the one-shot version from the README:
docker run --rm murilomarinho/sas:jazzy /bin/bash -c "ros2 launch sas_robot_driver sas_robot_driver_ros_composer_example.py"
docker run --rm murilomarinho/sas:jazzy /bin/bash -c "ros2 launch sas_robot_driver composer_example_launch.py"
```

Building outside docker (requires a ROS 2 jazzy workspace with the sibling
Expand All @@ -58,7 +58,7 @@ source install/setup.bash
Examples:

```bash
ros2 launch sas_robot_driver sas_robot_driver_ros_composer_example.py
ros2 launch sas_robot_driver composer_example_launch.py
ros2 run sas_robot_driver sas_robot_driver_interface_example.py
```

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ install(TARGETS
# According to https://github.com/SmartArmStack/sas_robot_driver/blob/ros2/CMakeLists.txt
install(DIRECTORY
launch
config
DESTINATION share/${PROJECT_NAME}/
)
# ^^^^^^^^^^^^^^^^^^ #
Expand Down
76 changes: 68 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
## Quick check

```bash
docker run --rm murilomarinho/sas:jazzy /bin/bash -c "ros2 launch sas_robot_driver sas_robot_driver_ros_composer_example.py"
docker run --rm murilomarinho/sas:jazzy /bin/bash -c "ros2 launch sas_robot_driver composer_example_launch.py"
```

## Contents
Expand All @@ -16,6 +16,7 @@ docker run --rm murilomarinho/sas:jazzy /bin/bash -c "ros2 launch sas_robot_driv
- `src/` — library and node implementations.
- `scripts/` — Python example scripts.
- `launch/` — example launch files.
- `config/` — example parameter configuration file.

## Client–Server pair

Expand All @@ -41,18 +42,77 @@ from sas_robot_driver import (
)
```

## Nodes
## ROS 2 Nodes & Parameters

- `sas_robot_driver_ros_composer_node` — Composes multiple RobotDriver clients.
- `sas_robot_watchdog_commander_node` — Sends periodic watchdog triggers to clients.
- `sas_robot_driver_ros_example` — Minimal example robot driver node.
Each node loads its parameters from a YAML configuration file. The default is
`config/config.yaml` in this package; pass a different file with the
`config_file:=` launch argument of the corresponding launch file.

## Launch file
### Node: `sas_robot_driver_ros_composer_node`

An example to compose multiple `RobotDriver` clients serially.
| Property | Value |
|---|---|
| **Executable** | `sas_robot_driver_ros_composer_node` |
| **ROS node name** | `robot_composed` (set by the `name` launch argument of `composer_launch.py`) |
| **Description** | Composes multiple `RobotDriver` clients serially into a single robot driver, exposing one `RobotDriverROS` control loop for all of them. |

#### Parameters

| Parameter | Type | Mandatory / Optional | Default | Purpose |
|---|---|---|---|---|
| `robot_driver_client_names` | array of strings | **Mandatory** | none — must be provided | Topic prefixes of the robot-driver clients to compose |
| `override_joint_limits_with_robot_parameter_file` | bool | **Mandatory** | none — must be provided | Whether to take the joint limits from a robot parameter file instead of concatenating the clients' limits |
| `robot_parameter_file_path` | string | **Mandatory** if `override_joint_limits_with_robot_parameter_file` is `true` | none — must be provided | Path of the robot parameter file with the joint limits |
| `thread_sampling_time_sec` | double | **Mandatory** | none — must be provided | Sampling period of the robot control-loop thread |

### Node: `sas_robot_watchdog_commander_node`

| Property | Value |
|---|---|
| **Executable** | `sas_robot_watchdog_commander_node` |
| **ROS node name** | `sas_robot_watchdog_commander_node` |
| **Description** | Sends periodic watchdog triggers to a robot-driver client (joint control is blacklisted). |

#### Parameters

| Parameter | Type | Mandatory / Optional | Default | Purpose |
|---|---|---|---|---|
| `thread_sampling_time_sec` | double | **Mandatory** | none — must be provided | Sampling period of the watchdog loop |
| `watchdog_period` | double | **Mandatory** | none — must be provided | Watchdog trigger period |
| `watchdog_maximum_acceptable_delay` | double | **Mandatory** | none — must be provided | Maximum acceptable delay before the watchdog is triggered |
| `robot_name` | string | **Mandatory** | none — must be provided | Topic prefix of the robot-driver client |

### Node: `sas_robot_driver_ros_example`

| Property | Value |
|---|---|
| **Executable** | `sas_robot_driver_ros_example` |
| **ROS node name** | `sas_robot_driver_ros_example` (the launch example runs two instances named `robot_1` and `robot_2`) |
| **Description** | Minimal example robot driver node. |

#### Parameters

| Parameter | Type | Mandatory / Optional | Default | Purpose |
|---|---|---|---|---|
| `robot_name` | string | **Mandatory** | none — must be provided | Name of the robot |
| `initial_joint_positions` | array of doubles (radians) | **Mandatory** | none — must be provided | Initial joint positions |
| `joint_limits_min` | array of doubles (radians) | **Mandatory** | none — must be provided | Minimum joint limits |
| `joint_limits_max` | array of doubles (radians) | **Mandatory** | none — must be provided | Maximum joint limits |
| `thread_sampling_time_sec` | double | **Mandatory** | none — must be provided | Sampling period of the robot control-loop thread |

**How mandatory/optional is determined in code:**
- **Mandatory** params are read with `sas::get_ros_parameter(...)` — if missing, the node throws and fails to start.
- **Optional** params are read with `sas::get_ros_optional_parameter(..., <default>)` — they carry in-code defaults.

#### Sample launches

```bash
ros2 launch sas_robot_driver sas_robot_driver_ros_composer_example.py
# Compose two example robots plus a composer node
ros2 launch sas_robot_driver composer_example_launch.py
# Single composer node
ros2 launch sas_robot_driver composer_launch.py
# Watchdog commander
ros2 launch sas_robot_driver watchdog_launch.py
```

## Script
Expand Down
39 changes: 39 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Parameters for the sas_robot_driver 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. The two
# `sas_robot_driver_ros_example` instances are started with the node names
# `robot_1` and `robot_2` (see composer_example_launch.py).

# sas_robot_driver_ros_example instance 1
robot_1:
ros__parameters:
robot_name: "robot_1"
initial_joint_positions: [0., 0., 0., 0.]
joint_limits_min: [-1., -2., -3., -4.]
joint_limits_max: [1., 2., 3., 4.]
thread_sampling_time_sec: 0.001 # Robot thread is at 1000 Hz

# sas_robot_driver_ros_example instance 2
robot_2:
ros__parameters:
robot_name: "robot_2"
initial_joint_positions: [0., 0., 0., 0., 0., 0.]
joint_limits_min: [-5., -6., -7., -8., -9., -10.]
joint_limits_max: [5., 6., 7., 8., 9., 10.]
thread_sampling_time_sec: 0.001 # Robot thread is at 1000 Hz

# sas_robot_driver_ros_composer_node
robot_composed:
ros__parameters:
robot_driver_client_names: ["robot_1", "robot_2"]
override_joint_limits_with_robot_parameter_file: false
thread_sampling_time_sec: 0.01

# sas_robot_watchdog_commander_node
sas_robot_watchdog_commander_node:
ros__parameters:
robot_name: "robot_composed"
thread_sampling_time_sec: 0.001
watchdog_period: 0.01
watchdog_maximum_acceptable_delay: 0.05
41 changes: 41 additions & 0 deletions launch/composer_example_launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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():
"""Example composing two example robots and a RobotDriverROSComposer node.

All parameters are loaded from a YAML configuration file. Pass a
different file with ``config_file:=/path/to/config.yaml``.
"""
config_file = LaunchConfiguration('config_file')

return LaunchDescription([
DeclareLaunchArgument(
'config_file',
default_value=os.path.join(get_package_share_directory('sas_robot_driver'), 'config', 'config.yaml')
),
Node(
package='sas_robot_driver',
executable='sas_robot_driver_ros_example',
name='robot_1',
parameters=[config_file]
),
Node(
package='sas_robot_driver',
executable='sas_robot_driver_ros_example',
name='robot_2',
parameters=[config_file]
),
Node(
package='sas_robot_driver',
executable='sas_robot_driver_ros_composer_node',
name='robot_composed',
parameters=[config_file]
)
])
34 changes: 34 additions & 0 deletions launch/composer_launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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 RobotDriverROSComposer 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='robot_composed'
),
DeclareLaunchArgument(
'config_file',
default_value=os.path.join(get_package_share_directory('sas_robot_driver'), 'config', 'config.yaml')
),
Node(
package='sas_robot_driver',
executable='sas_robot_driver_ros_composer_node',
name=name,
parameters=[config_file]
)
])
43 changes: 0 additions & 43 deletions launch/sas_robot_driver_ros_composer_example.py

This file was deleted.

28 changes: 28 additions & 0 deletions launch/watchdog_launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 watchdog commander node.

Parameters are loaded from a YAML configuration file. Pass a different
file with ``config_file:=/path/to/config.yaml``.
"""
config_file = LaunchConfiguration('config_file')

return LaunchDescription([
DeclareLaunchArgument(
'config_file',
default_value=os.path.join(get_package_share_directory('sas_robot_driver'), 'config', 'config.yaml')
),
Node(
package='sas_robot_driver',
executable='sas_robot_watchdog_commander_node',
parameters=[config_file]
)
])
Loading