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
137 changes: 137 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# AGENTS.md

Guidance for AI agents and contributors working in this repository.

## What this is

`sas_robot_driver` is a ROS 2 (jazzy) `ament_cmake` package from the
[SmartArmStack](https://smartarmstack.github.io/) project. It provides a
ROS-topic-based interface to robot drivers: a `RobotDriverServer` (a real robot
driver exposes state and consumes commands) and a `RobotDriverClient` (a
controller sends commands and reads state), both bound to a shared
`rclcpp::Node` and a `topic_prefix`. It is exposed to C++ and to Python
(pybind11).

## Repository layout

- `include/sas_robot_driver/` — public C++ headers
- `sas_robot_driver_client.hpp` — `sas::RobotDriverClient`
- `sas_robot_driver_server.hpp` — `sas::RobotDriverServer`
- `sas_robot_driver_ros.hpp` — `sas::RobotDriverROS` (control loop + watchdog)
- `src/` — library (`sas_robot_driver_server.cpp`, `sas_robot_driver_client.cpp`,
`sas_robot_driver_ros.cpp`), the pybind11 module (`sas_robot_driver_py.cpp`),
the composer (`sas_robot_driver_ros_composer.{hpp,cpp}`) and node entry points
- `src/examples/` — minimal example node (`sas_robot_driver_ros_example.cpp`)
- `sas_robot_driver/` — pure-Python package (`__init__.py` re-exports the
compiled `_sas_robot_driver` pybind11 module)
- `scripts/` — executable Python example scripts (installed with execute perms)
- `launch/` — example launch files
- `pybind11/` — git submodule (pybind11, branch `v3.0`)
- `docker/` — Dockerfile + compose used by CI for the build/test run

## Build, run, verify

There is no local test suite; verification is "it builds and the example
script runs". All builds happen inside the prebuilt SmartArmStack image
`murilomarinho/sas:jazzy`, which provides `ros2`, `colcon`, `Eigen3`,
`-ldqrobotics`, and the sibling SAS packages.

Quick check (what CI runs):

```bash
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"
```

Building outside docker (requires a ROS 2 jazzy workspace with the sibling
packages `sas_common`, `sas_core`, `sas_conversions`, `sas_msgs`, plus
`Eigen3` and `libdqrobotics` on the system):

```bash
# from the parent of this folder (e.g. /root/sas_robot_driver_devel/src/):
colcon build
source install/setup.bash
```

Examples:

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

CI (`.github/workflows/build.yml`) does `docker compose build` then
`docker compose up`, which builds with `colcon` and runs the Python
interface example. Make sure changes keep that flow green.

## Architecture notes

- Client/server pairing: `RobotDriverClient` and `RobotDriverServer` take
`(shared_ptr<rclcpp::Node>, topic_prefix)`. With `topic_prefix ==
"GET_FROM_NODE"` (the default) the node name is used. Topics are
namespaced under the prefix, so a client and server only connect when
their prefixes match.
- Client enablement: the client is only enabled after it receives joint
states and joint limits from its server (`is_enabled()`); servers that
wait on a client should send `send_joint_states()` / `send_joint_limits()`
while spinning.
- Mode blacklisting: `RobotDriverClient` accepts `blacklisted_modes`
(`MODE_BLACKLIST_FLAG`, e.g. `JOINT_CONTROL`) to disable functionality
from the client side — the watchdog commander node uses this.
- `RobotDriverROS` wraps any `sas::RobotDriver` with the ROS control loop
(`control_loop()`) and an optional watchdog
(`RobotDriverROSConfiguration::watchdog_period_in_seconds`; <= 0 disables).
- `RobotDriverROSComposer` composes multiple clients into one driver
(concatenating joint limits, or overriding them from a robot parameter
file). CoppeliaSim support was moved out to `sas_robot_driver_coppeliasim`
in 2025 — do not re-add it here.
- Python API (compiled via `src/sas_robot_driver_py.cpp`, module
`_sas_robot_driver`): `RobotDriverServer`, `RobotDriverClient`,
`Functionality`, `MODE_BLACKLIST_FLAG`, `RobotDriverROS`,
`RobotDriverROSConfiguration`. Python uses `sas_common` helpers
(`rclcpp_init`, `rclcpp_Node`, `rclcpp_spin_some`, `rclcpp_shutdown`).

## ROS parameters

Nodes read configuration via `sas::get_ros_parameter(node, name, value)`
(typically in launch files or `ros2 run` parameters):

- `sas_robot_driver_ros_example`: `robot_name`, `initial_joint_positions`,
`joint_limits_min`, `joint_limits_max`, `thread_sampling_time_sec`
- `sas_robot_watchdog_commander_node`: `robot_name`, `thread_sampling_time_sec`,
`watchdog_period`, `watchdog_maximum_acceptable_delay`
- `sas_robot_driver_ros_composer_node`: `robot_driver_client_names`,
`override_joint_limits_with_robot_parameter_file`,
`robot_parameter_file_path` (only if override is true),
`thread_sampling_time_sec`

## Conventions

- License: LGPLv3. Every source/header/script file starts with the standard
copyright/license/comment block used throughout this repo — keep it when
creating or touching files, and keep the "Contributors:" section current
for non-trivial additions.
- C++ compiles with `-Wall -Wextra -Wpedantic`; keep new code warning-clean.
- Be aware the client/server .cpp files are compiled twice (once for the
shared library, once into the pybind11 module) — keep Python-specific
code in `src/sas_robot_driver_py.cpp`.
- External dependency: the code links against `dqrobotics`
(`-ldqrobotics`, plus `dqrobotics-interface-json11` for the composer
node) — a system library shipped with the SAS docker image, not a ROS
package.
- When changing `CMakeLists.txt`, note the pybind11 import block marked
`pybind11 import block [BEGIN]/[END]` is copied across the SAS repos —
keep its delimiters intact.
- Python is linted with `ament_flake8` / `ament_pep257` (declared in
`package.xml` as test deps); no `.flake8` override file is present.
- No unit tests exist in this package; don't add test frameworks unless
asked.

## Git

- Default/development branch is `jazzy` (ROS 2 distribution branch naming).
- Shallow clones may lack full history; `git fetch --unshallow` if needed.
- `pybind11` is a submodule — initialize with
`git submodule update --init --recursive` after cloning.
1 change: 0 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ target_include_directories(_${PROJECT_NAME}
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

target_compile_definitions(_${PROJECT_NAME} PRIVATE IS_SAS_PYTHON_BUILD)
# https://github.com/pybind/pybind11/issues/387
target_link_libraries(_${PROJECT_NAME} PRIVATE ${PROJECT_NAME} -ldqrobotics)

Expand Down
5 changes: 0 additions & 5 deletions include/sas_robot_driver/sas_robot_driver_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ class RobotDriverServer: private sas::Object
RobotDriverServer() = delete;
RobotDriverServer(const RobotDriverServer&) = delete;

//see the discussion in sas_common to understand why this is commented out
//#ifdef IS_SAS_PYTHON_BUILD
// RobotDriverServer(const std::string& node_prefix);
//#endif

/**
* @brief Construct a new RobotDriverServer
*
Expand Down
1 change: 1 addition & 0 deletions sas_robot_driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
RobotDriverServer,
RobotDriverClient,
Functionality,
MODE_BLACKLIST_FLAG,
RobotDriverROS,
RobotDriverROSConfiguration
)
6 changes: 6 additions & 0 deletions src/sas_robot_driver_py.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ PYBIND11_MODULE(_sas_robot_driver, m) {
.value("ClearPositions",sas::RobotDriver::Functionality::ClearPositions)
.export_values();

py::enum_<RDC::MODE_BLACKLIST_FLAG>(m, "MODE_BLACKLIST_FLAG")
.value("JOINT_CONTROL",RDC::MODE_BLACKLIST_FLAG::JOINT_CONTROL)
.value("JOINT_MONITORING",RDC::MODE_BLACKLIST_FLAG::JOINT_MONITORING)
.value("WATCHDOG_CONTROL",RDC::MODE_BLACKLIST_FLAG::WATCHDOG_CONTROL)
.export_values();

py::class_<RDC>(m, "RobotDriverClient")
.def(py::init<const std::shared_ptr<rclcpp::Node>&, const std::string&,const std::vector<RDC::MODE_BLACKLIST_FLAG>&>(),
py::arg("node"),
Expand Down
Loading