diff --git a/Dockerfile b/Dockerfile index 6d0ceb2..5db1546 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ros-${ROS_DISTRO}-geometry-msgs \ ros-${ROS_DISTRO}-std-msgs \ ros-${ROS_DISTRO}-sensor-msgs \ + ros-${ROS_DISTRO}-launch-ros \ python3-smbus \ && rm -rf /var/lib/apt/lists/* @@ -42,4 +43,4 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["ros2", "run", "my_robot", "motor_controller"] +CMD ["ros2", "launch", "my_robot", "robot.launch.py"] diff --git a/README.md b/README.md index f27dd58..eade836 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Replace `matt` with the username configured in [ansible/inventory.ini](ansible/i ## Launching -The container needs access to the I²C bus that the motor controller is wired to. Pass only that device rather than running the container in privileged mode: +The default `CMD` starts both nodes together via the launch file. The container needs access to the I²C bus — pass only that device rather than running privileged: ```bash docker run --rm \ @@ -175,11 +175,11 @@ docker run --rm \ my_robot:latest ``` -If your board exposes the motor controller on a different bus (check with `ls /dev/i2c-*` on the host), substitute the correct device node (e.g. `--device /dev/i2c-0`). +If your board exposes the controller on a different bus (check with `ls /dev/i2c-*` on the host), substitute the correct device node (e.g. `--device /dev/i2c-0`). -### Camera orientation node +### Overriding parameters at launch -Override the default `CMD` to run the camera orientation node instead: +Launch arguments can be appended after the image name: ```bash docker run --rm \ @@ -187,63 +187,22 @@ docker run --rm \ --device /dev/i2c-1 \ --env ROS_DOMAIN_ID=0 \ my_robot:latest \ - ros2 run my_robot camera_orientation + ros2 launch my_robot robot.launch.py \ + wheel_base:=0.25 max_speed:=0.8 tilt_center_deg:=45.0 ``` -#### Overriding parameters at launch +Available launch arguments: -```bash -docker run --rm \ - --network=host \ - --device /dev/i2c-1 \ - --env ROS_DOMAIN_ID=0 \ - my_robot:latest \ - ros2 run my_robot camera_orientation \ - --ros-args -p pan_center_deg:=90.0 -p tilt_center_deg:=45.0 -``` - -#### Commanding the camera from the host - -Pan to centre (90°) and tilt to 30°: - -```bash -ros2 topic pub --once /joint_command sensor_msgs/msg/JointState \ - "{name: ['pan', 'tilt'], position: [1.5708, 0.5236]}" -``` - -You can command a single axis by omitting the other joint name: - -```bash -# Pan only -ros2 topic pub --once /joint_command sensor_msgs/msg/JointState \ - "{name: ['pan'], position: [0.0]}" -``` - -#### Verifying orientation state - -```bash -ros2 topic echo /joint_states -``` - ---- - -### Overriding parameters at launch (motor controller) - -ROS 2 parameters can be passed through `--ros-args`: - -```bash -docker run --rm \ - --network=host \ - --device /dev/i2c-1 \ - --env ROS_DOMAIN_ID=0 \ - my_robot:latest \ - ros2 run my_robot motor_controller \ - --ros-args -p wheel_base:=0.25 -p max_speed:=0.8 -``` +| Argument | Default | Description | +|---|---|---| +| `wheel_base` | `0.3` | Distance between left and right wheels (m) | +| `max_speed` | `1.0` | Maximum motor speed in library units | +| `pan_center_deg` | `90.0` | Pan angle at startup and shutdown (degrees) | +| `tilt_center_deg` | `60.0` | Tilt angle at startup and shutdown (degrees) | ### Sending velocity commands from the host -With the container running, publish a `cmd_vel` message from another terminal (requires ROS 2 installed on the host or a second container on the same network): +With the container running, publish from another terminal (requires ROS 2 on the host or a second container on the same network): ```bash # Drive forward at 0.2 m/s @@ -259,10 +218,31 @@ ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \ "{linear: {x: 0.0}, angular: {z: 0.0}}" ``` +### Commanding the camera from the host + +Pan to centre (90°) and tilt to 30°: + +```bash +ros2 topic pub --once /joint_command sensor_msgs/msg/JointState \ + "{name: ['pan', 'tilt'], position: [1.5708, 0.5236]}" +``` + +A single axis can be commanded by omitting the other joint name: + +```bash +# Pan only +ros2 topic pub --once /joint_command sensor_msgs/msg/JointState \ + "{name: ['pan'], position: [0.0]}" +``` + ### Verifying telemetry ```bash +# Wheel speeds ros2 topic echo /current_wheel_speeds + +# Camera orientation +ros2 topic echo /joint_states ``` --- @@ -277,6 +257,8 @@ ros2 topic echo /current_wheel_speeds │ └── my_robot/ │ ├── package.xml # ROS package manifest │ ├── setup.py # ament_python build definition +│ ├── launch/ +│ │ └── robot.launch.py # Starts both nodes together │ └── my_robot/ │ ├── __init__.py │ ├── motor_controller_node.py # Differential-drive motor control diff --git a/src/my_robot/launch/robot.launch.py b/src/my_robot/launch/robot.launch.py new file mode 100644 index 0000000..b137090 --- /dev/null +++ b/src/my_robot/launch/robot.launch.py @@ -0,0 +1,45 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + + +def generate_launch_description(): + return LaunchDescription([ + + # ── Motor controller arguments ──────────────────────────────────── + DeclareLaunchArgument('wheel_base', default_value='0.3', + description='Distance between left and right wheels (m)'), + DeclareLaunchArgument('max_speed', default_value='1.0', + description='Maximum motor speed in library units'), + + # ── Camera orientation arguments ────────────────────────────────── + DeclareLaunchArgument('pan_center_deg', default_value='90.0', + description='Pan angle at startup and shutdown (degrees)'), + DeclareLaunchArgument('tilt_center_deg', default_value='60.0', + description='Tilt angle at startup and shutdown (degrees)'), + + # ── Nodes ───────────────────────────────────────────────────────── + Node( + package='my_robot', + executable='motor_controller', + name='motor_controller', + parameters=[{ + 'wheel_base': LaunchConfiguration('wheel_base'), + 'max_speed': LaunchConfiguration('max_speed'), + }], + output='screen', + ), + + Node( + package='my_robot', + executable='camera_orientation', + name='camera_orientation', + parameters=[{ + 'pan_center_deg': LaunchConfiguration('pan_center_deg'), + 'tilt_center_deg': LaunchConfiguration('tilt_center_deg'), + }], + output='screen', + ), + + ]) diff --git a/src/my_robot/package.xml b/src/my_robot/package.xml index 28d89fb..0d238ed 100644 --- a/src/my_robot/package.xml +++ b/src/my_robot/package.xml @@ -10,6 +10,8 @@ geometry_msgs std_msgs sensor_msgs + launch + launch_ros ament_python diff --git a/src/my_robot/setup.py b/src/my_robot/setup.py index 32c1aa4..096ae89 100644 --- a/src/my_robot/setup.py +++ b/src/my_robot/setup.py @@ -9,6 +9,7 @@ setup( data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), + ('share/' + package_name + '/launch', ['launch/robot.launch.py']), ], install_requires=['setuptools'], zip_safe=True,