From 13747604168c3fec5af8769e34e90f08a1230ef7 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton <59611394+civerachb-cpr@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:20:42 -0400 Subject: [PATCH 01/17] Ensure the right horizontal axis is used for yaw control, left horizontal axis for linear-y (#178) --- clearpath_control/config/do100/teleop_logitech.yaml | 3 ++- clearpath_control/config/do100/teleop_ps4.yaml | 2 +- clearpath_control/config/do100/teleop_ps5.yaml | 2 +- clearpath_control/config/do100/teleop_xbox.yaml | 2 +- clearpath_control/config/do150/teleop_logitech.yaml | 3 ++- clearpath_control/config/do150/teleop_ps4.yaml | 2 +- clearpath_control/config/do150/teleop_ps5.yaml | 2 +- clearpath_control/config/do150/teleop_xbox.yaml | 2 +- clearpath_control/config/r100/teleop_logitech.yaml | 3 ++- clearpath_control/config/r100/teleop_ps4.yaml | 2 +- clearpath_control/config/r100/teleop_ps5.yaml | 2 +- clearpath_control/config/r100/teleop_xbox.yaml | 2 +- 12 files changed, 15 insertions(+), 12 deletions(-) diff --git a/clearpath_control/config/do100/teleop_logitech.yaml b/clearpath_control/config/do100/teleop_logitech.yaml index eba9a079..adf88409 100644 --- a/clearpath_control/config/do100/teleop_logitech.yaml +++ b/clearpath_control/config/do100/teleop_logitech.yaml @@ -46,9 +46,10 @@ teleop_twist_joy_node: publish_stamped_twist: True use_sim_time: False axis_linear.x: 1 + axis_linear.y: 0 scale_linear.x: 0.4 scale_linear_turbo.x: 1.0 - axis_angular.yaw: 0 + axis_angular.yaw: 3 scale_angular.yaw: 0.6 scale_angular_turbo.yaw: 1.0 enable_button: 4 diff --git a/clearpath_control/config/do100/teleop_ps4.yaml b/clearpath_control/config/do100/teleop_ps4.yaml index 2b359ab3..b0127718 100644 --- a/clearpath_control/config/do100/teleop_ps4.yaml +++ b/clearpath_control/config/do100/teleop_ps4.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.4 enable_button: 4 diff --git a/clearpath_control/config/do100/teleop_ps5.yaml b/clearpath_control/config/do100/teleop_ps5.yaml index 7b14dccc..43496dbf 100644 --- a/clearpath_control/config/do100/teleop_ps5.yaml +++ b/clearpath_control/config/do100/teleop_ps5.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.4 enable_button: 4 diff --git a/clearpath_control/config/do100/teleop_xbox.yaml b/clearpath_control/config/do100/teleop_xbox.yaml index 85b1ab60..382c2766 100644 --- a/clearpath_control/config/do100/teleop_xbox.yaml +++ b/clearpath_control/config/do100/teleop_xbox.yaml @@ -53,7 +53,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 3 + axis_angular.yaw: 2 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.0 enable_button: 6 diff --git a/clearpath_control/config/do150/teleop_logitech.yaml b/clearpath_control/config/do150/teleop_logitech.yaml index eba9a079..adf88409 100644 --- a/clearpath_control/config/do150/teleop_logitech.yaml +++ b/clearpath_control/config/do150/teleop_logitech.yaml @@ -46,9 +46,10 @@ teleop_twist_joy_node: publish_stamped_twist: True use_sim_time: False axis_linear.x: 1 + axis_linear.y: 0 scale_linear.x: 0.4 scale_linear_turbo.x: 1.0 - axis_angular.yaw: 0 + axis_angular.yaw: 3 scale_angular.yaw: 0.6 scale_angular_turbo.yaw: 1.0 enable_button: 4 diff --git a/clearpath_control/config/do150/teleop_ps4.yaml b/clearpath_control/config/do150/teleop_ps4.yaml index 11935758..08925ff6 100644 --- a/clearpath_control/config/do150/teleop_ps4.yaml +++ b/clearpath_control/config/do150/teleop_ps4.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.0 enable_button: 4 diff --git a/clearpath_control/config/do150/teleop_ps5.yaml b/clearpath_control/config/do150/teleop_ps5.yaml index 148a366a..eb9913ed 100644 --- a/clearpath_control/config/do150/teleop_ps5.yaml +++ b/clearpath_control/config/do150/teleop_ps5.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.0 enable_button: 4 diff --git a/clearpath_control/config/do150/teleop_xbox.yaml b/clearpath_control/config/do150/teleop_xbox.yaml index 85b1ab60..382c2766 100644 --- a/clearpath_control/config/do150/teleop_xbox.yaml +++ b/clearpath_control/config/do150/teleop_xbox.yaml @@ -53,7 +53,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 1.0 scale_linear_turbo.y: 1.0 - axis_angular.yaw: 3 + axis_angular.yaw: 2 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 1.0 enable_button: 6 diff --git a/clearpath_control/config/r100/teleop_logitech.yaml b/clearpath_control/config/r100/teleop_logitech.yaml index eba9a079..adf88409 100644 --- a/clearpath_control/config/r100/teleop_logitech.yaml +++ b/clearpath_control/config/r100/teleop_logitech.yaml @@ -46,9 +46,10 @@ teleop_twist_joy_node: publish_stamped_twist: True use_sim_time: False axis_linear.x: 1 + axis_linear.y: 0 scale_linear.x: 0.4 scale_linear_turbo.x: 1.0 - axis_angular.yaw: 0 + axis_angular.yaw: 3 scale_angular.yaw: 0.6 scale_angular_turbo.yaw: 1.0 enable_button: 4 diff --git a/clearpath_control/config/r100/teleop_ps4.yaml b/clearpath_control/config/r100/teleop_ps4.yaml index 50203de5..4f06fe58 100644 --- a/clearpath_control/config/r100/teleop_ps4.yaml +++ b/clearpath_control/config/r100/teleop_ps4.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 0.4 scale_linear_turbo.y: 0.4 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 0.5 enable_button: 4 diff --git a/clearpath_control/config/r100/teleop_ps5.yaml b/clearpath_control/config/r100/teleop_ps5.yaml index 77dd18bd..fb363134 100644 --- a/clearpath_control/config/r100/teleop_ps5.yaml +++ b/clearpath_control/config/r100/teleop_ps5.yaml @@ -64,7 +64,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 0.4 scale_linear_turbo.y: 0.4 - axis_angular.yaw: 4 + axis_angular.yaw: 3 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 0.5 enable_button: 4 diff --git a/clearpath_control/config/r100/teleop_xbox.yaml b/clearpath_control/config/r100/teleop_xbox.yaml index 8e481b35..76d0bd4c 100644 --- a/clearpath_control/config/r100/teleop_xbox.yaml +++ b/clearpath_control/config/r100/teleop_xbox.yaml @@ -53,7 +53,7 @@ teleop_twist_joy_node: scale_linear.y: 0.4 scale_linear_turbo.x: 0.4 scale_linear_turbo.y: 0.4 - axis_angular.yaw: 3 + axis_angular.yaw: 2 scale_angular.yaw: 0.5 scale_angular_turbo.yaw: 0.5 enable_button: 6 From 079f018dd1564346949b1e06372eaee26160688c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Mastrangelo?= Date: Thu, 13 Mar 2025 11:31:10 -0400 Subject: [PATCH 02/17] initial changes for adding the linited velocity output --- clearpath_control/config/a200/control.yaml | 3 +-- clearpath_control/config/a300/control.yaml | 3 +-- clearpath_control/config/j100/control.yaml | 3 +-- clearpath_control/config/w200/control.yaml | 3 +-- clearpath_control/launch/control.launch.py | 1 + 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/clearpath_control/config/a200/control.yaml b/clearpath_control/config/a200/control.yaml index 453e18ad..917fa47b 100644 --- a/clearpath_control/config/a200/control.yaml +++ b/clearpath_control/config/a200/control.yaml @@ -32,7 +32,6 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.5 - #publish_limited_velocity: true use_stamped_vel: true #velocity_rolling_window_size: 10 @@ -40,7 +39,7 @@ platform_velocity_controller: preserve_turning_radius: true # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true diff --git a/clearpath_control/config/a300/control.yaml b/clearpath_control/config/a300/control.yaml index 8fbc6997..af7da19a 100644 --- a/clearpath_control/config/a300/control.yaml +++ b/clearpath_control/config/a300/control.yaml @@ -32,14 +32,13 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.5 - #publish_limited_velocity: true #velocity_rolling_window_size: 10 # Preserve turning radius when limiting speed/acceleration/jerk preserve_turning_radius: true # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true diff --git a/clearpath_control/config/j100/control.yaml b/clearpath_control/config/j100/control.yaml index cde9669b..4868aac3 100644 --- a/clearpath_control/config/j100/control.yaml +++ b/clearpath_control/config/j100/control.yaml @@ -32,7 +32,6 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.5 - #publish_limited_velocity: true use_stamped_vel: true #velocity_rolling_window_size: 10 @@ -40,7 +39,7 @@ platform_velocity_controller: preserve_turning_radius: true # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true diff --git a/clearpath_control/config/w200/control.yaml b/clearpath_control/config/w200/control.yaml index a2039dd7..c1de5590 100644 --- a/clearpath_control/config/w200/control.yaml +++ b/clearpath_control/config/w200/control.yaml @@ -32,7 +32,6 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.25 - #publish_limited_velocity: true use_stamped_vel: true #velocity_rolling_window_size: 10 @@ -43,7 +42,7 @@ platform_velocity_controller: # position_feedback: false # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true diff --git a/clearpath_control/launch/control.launch.py b/clearpath_control/launch/control.launch.py index 90155fa1..f37e2604 100644 --- a/clearpath_control/launch/control.launch.py +++ b/clearpath_control/launch/control.launch.py @@ -44,6 +44,7 @@ ('dynamic_joint_states', 'platform/dynamic_joint_states'), ('platform_velocity_controller/odom', 'platform/odom'), ('platform_velocity_controller/cmd_vel', 'platform/cmd_vel'), + ('platform_velocity_controller/cmd_vel_out', 'platform/cmd_vel_out'), ('platform_velocity_controller/transition_event', 'platform/transition_event'), ('/diagnostics', 'diagnostics'), ('/tf', 'tf'), From f4ed3ef08ea54f6427d9c0d7ff1fa7d8f8bf9c46 Mon Sep 17 00:00:00 2001 From: Roni Kreinin <59886299+roni-kreinin@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:12:41 -0400 Subject: [PATCH 03/17] Jazzy dingo and ridgeback fixes (#179) * Updated mecanum controller type * Changed puma control frequency to 20hz * Added mecanum package dep --- clearpath_control/config/dd100/control.yaml | 4 ++-- clearpath_control/config/dd150/control.yaml | 4 ++-- clearpath_control/config/do100/control.yaml | 6 +++--- clearpath_control/config/do150/control.yaml | 6 +++--- clearpath_control/config/r100/control.yaml | 6 +++--- clearpath_control/launch/control.launch.py | 1 + clearpath_control/package.xml | 2 +- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/clearpath_control/config/dd100/control.yaml b/clearpath_control/config/dd100/control.yaml index 4396f416..8daccbc6 100644 --- a/clearpath_control/config/dd100/control.yaml +++ b/clearpath_control/config/dd100/control.yaml @@ -1,6 +1,6 @@ controller_manager: ros__parameters: - update_rate: 50 # Hz + update_rate: 20 # Hz use_sim_time: False joint_state_broadcaster.type: joint_state_broadcaster/JointStateBroadcaster @@ -21,7 +21,7 @@ platform_velocity_controller: left_wheel_radius_multiplier: 1.0 right_wheel_radius_multiplier: 1.0 - publish_rate: 50.0 + publish_rate: 20.0 odom_frame_id: odom base_frame_id: base_link pose_covariance_diagonal : [0.001, 0.001, 0.001, 0.001, 0.001, 0.01] diff --git a/clearpath_control/config/dd150/control.yaml b/clearpath_control/config/dd150/control.yaml index 4396f416..8daccbc6 100644 --- a/clearpath_control/config/dd150/control.yaml +++ b/clearpath_control/config/dd150/control.yaml @@ -1,6 +1,6 @@ controller_manager: ros__parameters: - update_rate: 50 # Hz + update_rate: 20 # Hz use_sim_time: False joint_state_broadcaster.type: joint_state_broadcaster/JointStateBroadcaster @@ -21,7 +21,7 @@ platform_velocity_controller: left_wheel_radius_multiplier: 1.0 right_wheel_radius_multiplier: 1.0 - publish_rate: 50.0 + publish_rate: 20.0 odom_frame_id: odom base_frame_id: base_link pose_covariance_diagonal : [0.001, 0.001, 0.001, 0.001, 0.001, 0.01] diff --git a/clearpath_control/config/do100/control.yaml b/clearpath_control/config/do100/control.yaml index 34d6a016..ad803d0d 100644 --- a/clearpath_control/config/do100/control.yaml +++ b/clearpath_control/config/do100/control.yaml @@ -1,11 +1,11 @@ controller_manager: ros__parameters: - update_rate: 50 # Hz + update_rate: 20 # Hz use_sim_time: False joint_state_broadcaster.type: joint_state_broadcaster/JointStateBroadcaster - platform_velocity_controller.type: "clearpath_mecanum_drive_controller/MecanumDriveController" + platform_velocity_controller.type: "mecanum_drive_controller/MecanumDriveController" platform_velocity_controller: ros__parameters: @@ -25,7 +25,7 @@ platform_velocity_controller: wheel_separation_multiplier: 1.0 wheel_radius_multiplier: 1.0 - publish_rate: 50.0 + publish_rate: 20.0 reference_timeout: 0.1 use_stamped_vel: False diff --git a/clearpath_control/config/do150/control.yaml b/clearpath_control/config/do150/control.yaml index 34d6a016..ad803d0d 100644 --- a/clearpath_control/config/do150/control.yaml +++ b/clearpath_control/config/do150/control.yaml @@ -1,11 +1,11 @@ controller_manager: ros__parameters: - update_rate: 50 # Hz + update_rate: 20 # Hz use_sim_time: False joint_state_broadcaster.type: joint_state_broadcaster/JointStateBroadcaster - platform_velocity_controller.type: "clearpath_mecanum_drive_controller/MecanumDriveController" + platform_velocity_controller.type: "mecanum_drive_controller/MecanumDriveController" platform_velocity_controller: ros__parameters: @@ -25,7 +25,7 @@ platform_velocity_controller: wheel_separation_multiplier: 1.0 wheel_radius_multiplier: 1.0 - publish_rate: 50.0 + publish_rate: 20.0 reference_timeout: 0.1 use_stamped_vel: False diff --git a/clearpath_control/config/r100/control.yaml b/clearpath_control/config/r100/control.yaml index d0eb9b34..67dcd282 100644 --- a/clearpath_control/config/r100/control.yaml +++ b/clearpath_control/config/r100/control.yaml @@ -1,11 +1,11 @@ controller_manager: ros__parameters: - update_rate: 50 # Hz + update_rate: 20 # Hz use_sim_time: False joint_state_broadcaster.type: joint_state_broadcaster/JointStateBroadcaster - platform_velocity_controller.type: "clearpath_mecanum_drive_controller/MecanumDriveController" + platform_velocity_controller.type: "mecanum_drive_controller/MecanumDriveController" platform_velocity_controller: ros__parameters: @@ -25,7 +25,7 @@ platform_velocity_controller: wheel_separation_multiplier: 1.0 wheel_radius_multiplier: 1.0 - publish_rate: 50.0 + publish_rate: 20.0 reference_timeout: 0.1 use_stamped_vel: False diff --git a/clearpath_control/launch/control.launch.py b/clearpath_control/launch/control.launch.py index f37e2604..d5ed2c29 100644 --- a/clearpath_control/launch/control.launch.py +++ b/clearpath_control/launch/control.launch.py @@ -45,6 +45,7 @@ ('platform_velocity_controller/odom', 'platform/odom'), ('platform_velocity_controller/cmd_vel', 'platform/cmd_vel'), ('platform_velocity_controller/cmd_vel_out', 'platform/cmd_vel_out'), + ('platform_velocity_controller/reference', 'platform/cmd_vel'), ('platform_velocity_controller/transition_event', 'platform/transition_event'), ('/diagnostics', 'diagnostics'), ('/tf', 'tf'), diff --git a/clearpath_control/package.xml b/clearpath_control/package.xml index 29b60d87..86c72d18 100644 --- a/clearpath_control/package.xml +++ b/clearpath_control/package.xml @@ -19,7 +19,7 @@ ament_cmake clearpath_bt_joy - + mecanum_drive_controller controller_manager diff_drive_controller imu_filter_madgwick From 1b986a043193dac3aa47c224f3c39022c7f73b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Mastrangelo?= Date: Thu, 13 Mar 2025 12:55:27 -0400 Subject: [PATCH 04/17] remove duplicate remap --- clearpath_control/launch/control.launch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/clearpath_control/launch/control.launch.py b/clearpath_control/launch/control.launch.py index d5ed2c29..f37e2604 100644 --- a/clearpath_control/launch/control.launch.py +++ b/clearpath_control/launch/control.launch.py @@ -45,7 +45,6 @@ ('platform_velocity_controller/odom', 'platform/odom'), ('platform_velocity_controller/cmd_vel', 'platform/cmd_vel'), ('platform_velocity_controller/cmd_vel_out', 'platform/cmd_vel_out'), - ('platform_velocity_controller/reference', 'platform/cmd_vel'), ('platform_velocity_controller/transition_event', 'platform/transition_event'), ('/diagnostics', 'diagnostics'), ('/tf', 'tf'), From 30a7628da56ce3a18f7e4e1e9b691ee24a13eb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Mastrangelo?= Date: Mon, 17 Mar 2025 10:21:11 -0400 Subject: [PATCH 05/17] added cmd_vel_out to dingo Ds --- clearpath_control/config/dd100/control.yaml | 3 +-- clearpath_control/config/dd150/control.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/clearpath_control/config/dd100/control.yaml b/clearpath_control/config/dd100/control.yaml index 8daccbc6..c9076693 100644 --- a/clearpath_control/config/dd100/control.yaml +++ b/clearpath_control/config/dd100/control.yaml @@ -32,7 +32,6 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.5 - #publish_limited_velocity: true use_stamped_vel: true #velocity_rolling_window_size: 10 @@ -40,7 +39,7 @@ platform_velocity_controller: preserve_turning_radius: true # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true diff --git a/clearpath_control/config/dd150/control.yaml b/clearpath_control/config/dd150/control.yaml index 8daccbc6..c9076693 100644 --- a/clearpath_control/config/dd150/control.yaml +++ b/clearpath_control/config/dd150/control.yaml @@ -32,7 +32,6 @@ platform_velocity_controller: tf_frame_prefix_enable: false cmd_vel_timeout: 0.5 - #publish_limited_velocity: true use_stamped_vel: true #velocity_rolling_window_size: 10 @@ -40,7 +39,7 @@ platform_velocity_controller: preserve_turning_radius: true # Publish limited velocity - publish_cmd: true + publish_limited_velocity: true # Publish wheel data publish_wheel_data: true From 3793551374c28b74607b9d6418e45c00d550d194 Mon Sep 17 00:00:00 2001 From: Hilary Luo <103377417+hilary-luo@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:06:09 -0400 Subject: [PATCH 06/17] Add MCU diagnostic category if not A200 (#183) --- .../clearpath_generator_common/param/platform.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clearpath_generator_common/clearpath_generator_common/param/platform.py b/clearpath_generator_common/clearpath_generator_common/param/platform.py index ce4810d1..a11880d0 100644 --- a/clearpath_generator_common/clearpath_generator_common/param/platform.py +++ b/clearpath_generator_common/clearpath_generator_common/param/platform.py @@ -221,6 +221,21 @@ def __init__(self, def generate_parameters(self, use_sim_time: bool = False) -> None: super().generate_parameters(use_sim_time) + # Add MCU diagnostic category for all platforms except A200 + if self.clearpath_config.get_platform_model() != Platform.A200: + self.param_file.update( + {self.DIAGNOSTIC_AGGREGATOR_NODE: { + 'platform': { + 'analyzers': { + 'mcu': { + 'type': 'diagnostic_aggregator/GenericAnalyzer', + 'path': 'MCU', + 'expected': [ + 'clearpath_diagnostic_updater: MCU Firmware Version', + 'clearpath_diagnostic_updater: MCU Status' + ], + 'contains': ['MCU']}}}}}) + sensor_analyzers = {} # List all topics to be monitored from each launched sensor From 0d589467f3e518e3504efb0ae7bb66648329b342 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton <59611394+civerachb-cpr@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:17:23 -0400 Subject: [PATCH 07/17] Add support for INS sensors + Fixposition XVN (#184) * Rename swift_antenna -> gnss_spherical, add gnss_helical, start filling in the fixposition URDF. * Add patch antenna STL, move gnss_antenna macro to its own file, fix parameters in the fixposition macro * Add INS description, remove unnecessary parameters from the URDF * Use the new gnss_antenna for the Swiftnav Duro URDF * Add the 3.5cm front offset between the XVN physical mounting point & the sensor origin --- .../description/sensors.py | 59 ++++++++++- .../meshes/fixposition.stl | Bin 0 -> 11884 bytes .../meshes/gnss_helical.stl | Bin 0 -> 53884 bytes .../meshes/gnss_patch.stl | Bin 0 -> 140184 bytes .../{swift_antenna.stl => gnss_spherical.stl} | Bin .../urdf/fixposition.urdf.xacro | 96 ++++++++++++++++++ .../urdf/gnss_antenna.urdf.xacro | 91 +++++++++++++++++ .../urdf/swiftnav_duro.urdf.xacro | 22 +--- 8 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 clearpath_sensors_description/meshes/fixposition.stl create mode 100644 clearpath_sensors_description/meshes/gnss_helical.stl create mode 100644 clearpath_sensors_description/meshes/gnss_patch.stl rename clearpath_sensors_description/meshes/{swift_antenna.stl => gnss_spherical.stl} (100%) create mode 100644 clearpath_sensors_description/urdf/fixposition.urdf.xacro create mode 100644 clearpath_sensors_description/urdf/gnss_antenna.urdf.xacro diff --git a/clearpath_generator_common/clearpath_generator_common/description/sensors.py b/clearpath_generator_common/clearpath_generator_common/description/sensors.py index 91c8413a..82615f0a 100644 --- a/clearpath_generator_common/clearpath_generator_common/description/sensors.py +++ b/clearpath_generator_common/clearpath_generator_common/description/sensors.py @@ -45,6 +45,10 @@ Microstrain, RedshiftUM7 ) +from clearpath_config.sensors.types.ins import ( + BaseINS, + Fixposition, +) from clearpath_config.sensors.types.lidars_2d import BaseLidar2D, HokuyoUST, SickLMS1XX from clearpath_config.sensors.types.lidars_3d import ( BaseLidar3D, @@ -136,6 +140,56 @@ def __init__(self, sensor: BaseLidar3D) -> None: self.UPDATE_RATE: 20 # TODO: link to clearpath_config property }) + class InsDescription(BaseDescription): + NUM_ANTENNAS = 'num_antennas' + + GPS_0_TYPE = 'gps_0_type' + GPS_0_XYZ_X = 'gps_0_xyz_x' + GPS_0_XYZ_Y = 'gps_0_xyz_y' + GPS_0_XYZ_Z = 'gps_0_xyz_z' + GPS_0_RPY_R = 'gps_0_rpy_r' + GPS_0_RPY_P = 'gps_0_rpy_p' + GPS_0_RPY_Y = 'gps_0_rpy_y' + GPS_0_PARENT = 'gps_0_parent' + + GPS_1_TYPE = 'gps_1_type' + GPS_1_XYZ_X = 'gps_1_xyz_x' + GPS_1_XYZ_Y = 'gps_1_xyz_y' + GPS_1_XYZ_Z = 'gps_1_xyz_z' + GPS_1_RPY_R = 'gps_1_rpy_r' + GPS_1_RPY_P = 'gps_1_rpy_p' + GPS_1_RPY_Y = 'gps_1_rpy_y' + GPS_1_PARENT = 'gps_1_parent' + + def __init__(self, sensor: BaseINS) -> None: + super().__init__(sensor) + + self.parameters.update({ + self.NUM_ANTENNAS: len(sensor.antennas), + + self.GPS_0_TYPE: sensor.antennas[0].antenna_type, + self.GPS_0_XYZ_X: sensor.antennas[0].xyz[0], + self.GPS_0_XYZ_Y: sensor.antennas[0].xyz[1], + self.GPS_0_XYZ_Z: sensor.antennas[0].xyz[2], + self.GPS_0_RPY_R: sensor.antennas[0].rpy[0], + self.GPS_0_RPY_P: sensor.antennas[0].rpy[1], + self.GPS_0_RPY_Y: sensor.antennas[0].rpy[2], + self.GPS_0_PARENT: sensor.antennas[0].parent, + + # we only have 1 or 2 antennas, so use -1: + # if there's only one antenna this is the same as 0 + # but the duplication is safely ignored because + # we set NUM_ANTENNAS above + self.GPS_1_TYPE: sensor.antennas[-1].antenna_type, + self.GPS_1_XYZ_X: sensor.antennas[-1].xyz[0], + self.GPS_1_XYZ_Y: sensor.antennas[-1].xyz[1], + self.GPS_1_XYZ_Z: sensor.antennas[-1].xyz[2], + self.GPS_1_RPY_R: sensor.antennas[-1].rpy[0], + self.GPS_1_RPY_P: sensor.antennas[-1].rpy[1], + self.GPS_1_RPY_Y: sensor.antennas[-1].rpy[2], + self.GPS_1_PARENT: sensor.antennas[-1].parent, + }) + class OusterOS1Description(Lidar3dDescription): SAMPLES_HORIZONTAL = 'samples_h' SAMPLES_VERTICAL = 'samples_v' @@ -194,7 +248,7 @@ def __init__(self, sensor: IntelRealsense) -> None: self.MODEL: sensor.device_type, }) - class LuxonisOAKDDescroption(CameraDescription): + class LuxonisOAKDDescription(CameraDescription): MODEL = 'model' def __init__(self, sensor: LuxonisOAKD) -> None: @@ -227,7 +281,8 @@ def __init__(self, sensor: StereolabsZed) -> None: CHRoboticsUM6.SENSOR_MODEL: ImuDescription, RedshiftUM7.SENSOR_MODEL: ImuDescription, StereolabsZed.SENSOR_MODEL: StereolabsZedDescription, - LuxonisOAKD.SENSOR_MODEL: LuxonisOAKDDescroption, + LuxonisOAKD.SENSOR_MODEL: LuxonisOAKDDescription, + Fixposition.SENSOR_MODEL: InsDescription, } def __new__(cls, sensor: BaseSensor) -> BaseDescription: diff --git a/clearpath_sensors_description/meshes/fixposition.stl b/clearpath_sensors_description/meshes/fixposition.stl new file mode 100644 index 0000000000000000000000000000000000000000..70f849131474bb111905f85fb6129a6412f7b770 GIT binary patch literal 11884 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 WAB<3+c3D)MUDZe=Jg)eYdHf$AuWUO2 literal 0 HcmV?d00001 diff --git a/clearpath_sensors_description/meshes/gnss_helical.stl b/clearpath_sensors_description/meshes/gnss_helical.stl new file mode 100644 index 0000000000000000000000000000000000000000..0b08cebf9089603a1f61b86decbaa51f574bfd04 GIT binary patch literal 53884 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 BU!(v4 literal 0 HcmV?d00001 diff --git a/clearpath_sensors_description/meshes/gnss_patch.stl b/clearpath_sensors_description/meshes/gnss_patch.stl new file mode 100644 index 0000000000000000000000000000000000000000..efc0290758070aed2768bcd59c7070a28c65f8ea GIT binary patch literal 140184 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�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 B*a-jt literal 0 HcmV?d00001 diff --git a/clearpath_sensors_description/meshes/swift_antenna.stl b/clearpath_sensors_description/meshes/gnss_spherical.stl similarity index 100% rename from clearpath_sensors_description/meshes/swift_antenna.stl rename to clearpath_sensors_description/meshes/gnss_spherical.stl diff --git a/clearpath_sensors_description/urdf/fixposition.urdf.xacro b/clearpath_sensors_description/urdf/fixposition.urdf.xacro new file mode 100644 index 00000000..044a0f08 --- /dev/null +++ b/clearpath_sensors_description/urdf/fixposition.urdf.xacro @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clearpath_sensors_description/urdf/gnss_antenna.urdf.xacro b/clearpath_sensors_description/urdf/gnss_antenna.urdf.xacro new file mode 100644 index 00000000..4312078e --- /dev/null +++ b/clearpath_sensors_description/urdf/gnss_antenna.urdf.xacro @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clearpath_sensors_description/urdf/swiftnav_duro.urdf.xacro b/clearpath_sensors_description/urdf/swiftnav_duro.urdf.xacro index d5941206..45acde14 100644 --- a/clearpath_sensors_description/urdf/swiftnav_duro.urdf.xacro +++ b/clearpath_sensors_description/urdf/swiftnav_duro.urdf.xacro @@ -1,26 +1,10 @@ - - - - - - - - - - - - - - - - - - + + - + From 1946f9eff8ee0626f11098bbc6f61c374b275616 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton <59611394+civerachb-cpr@users.noreply.github.com> Date: Fri, 28 Mar 2025 14:25:59 -0400 Subject: [PATCH 08/17] Move extras launch into a new service (#185) * Create a new platform-extras.service, move platform.extras.launch there instead of including it as part of the sensors launch * Add empty-by-default platform extras launch file --- .../launch/platform_extras.launch.py | 56 +++++++++++++++++++ .../clearpath_generator_common/common.py | 3 + .../launch/generator.py | 19 +++++++ 3 files changed, 78 insertions(+) create mode 100644 clearpath_common/launch/platform_extras.launch.py diff --git a/clearpath_common/launch/platform_extras.launch.py b/clearpath_common/launch/platform_extras.launch.py new file mode 100644 index 00000000..047bdcda --- /dev/null +++ b/clearpath_common/launch/platform_extras.launch.py @@ -0,0 +1,56 @@ +# Software License Agreement (BSD) +# +# @author Tony Baltovski +# @author Roni Kreinin +# @copyright (c) 2023, Clearpath Robotics, Inc., All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Clearpath Robotics nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +from launch import LaunchDescription +from launch.actions import ( + DeclareLaunchArgument, +) + +ARGUMENTS = [ + DeclareLaunchArgument( + 'setup_path', + default_value='/etc/clearpath/' + ), + + DeclareLaunchArgument( + 'use_sim_time', + choices=['true', 'false'], + default_value='false', + description='Use simulation time' + ), + + DeclareLaunchArgument( + 'namespace', + default_value='', + description='Robot namespace' + ), +] + +def generate_launch_description(): + ld = LaunchDescription(ARGUMENTS) + return ld diff --git a/clearpath_generator_common/clearpath_generator_common/common.py b/clearpath_generator_common/clearpath_generator_common/common.py index 1371f313..ed83e3f4 100644 --- a/clearpath_generator_common/clearpath_generator_common/common.py +++ b/clearpath_generator_common/clearpath_generator_common/common.py @@ -276,6 +276,7 @@ def full_path(self): class BaseGenerator(): SENSORS_PATH = 'sensors/' PLATFORM_PATH = 'platform/' + PLATFORM_EXTRAS_PATH = 'platform-extras/' MANIPULATORS_PATH = 'manipulators/' LAUNCH_PATH = 'launch/' PARAM_PATH = 'config/' @@ -295,6 +296,8 @@ def __init__(self, self.setup_path, self.PLATFORM_PATH, self.PARAM_PATH) self.platform_launch_path = os.path.join( self.setup_path, self.PLATFORM_PATH, self.LAUNCH_PATH) + self.platform_extras_launch_path = os.path.join( + self.setup_path, self.PLATFORM_EXTRAS_PATH, self.LAUNCH_PATH) self.manipulators_params_path = os.path.join( self.setup_path, self.MANIPULATORS_PATH, self.PARAM_PATH) self.manipulators_launch_path = os.path.join( diff --git a/clearpath_generator_common/clearpath_generator_common/launch/generator.py b/clearpath_generator_common/clearpath_generator_common/launch/generator.py index 99a92b12..0f3813fd 100644 --- a/clearpath_generator_common/clearpath_generator_common/launch/generator.py +++ b/clearpath_generator_common/clearpath_generator_common/launch/generator.py @@ -54,6 +54,11 @@ def __init__(self, except FileNotFoundError: pass + try: + shutil.rmtree(self.platform_extras_launch_path) + except FileNotFoundError: + pass + try: shutil.rmtree(self.manipulators_launch_path) except FileNotFoundError: @@ -62,6 +67,7 @@ def __init__(self, # Make new directories os.makedirs(os.path.dirname(self.sensors_launch_path), exist_ok=True) os.makedirs(os.path.dirname(self.platform_launch_path), exist_ok=True) + os.makedirs(os.path.dirname(self.platform_extras_launch_path), exist_ok=True) os.makedirs(os.path.dirname(self.manipulators_launch_path), exist_ok=True) self.platform_launch_file = LaunchFile( @@ -74,6 +80,15 @@ def __init__(self, ('enable_ekf', str(self.clearpath_config.platform.enable_ekf).lower()), ]) + self.platform_extras_launch_file = LaunchFile( + name='platform_extras', + package=self.pkg_clearpath_common, + args=[ + ('setup_path', self.setup_path), + ('use_sim_time', 'false'), + ('namespace', self.namespace), + ]) + self.manipulators_launch_file = LaunchFile( name='manipulators', package=self.pkg_clearpath_manipulators, @@ -92,6 +107,10 @@ def __init__(self, name='platform-service', path=self.platform_launch_path) + self.platform_extras_service_launch_file = LaunchFile( + name='platform-extras-service', + path=self.platform_extras_launch_path) + self.manipulators_service_launch_file = LaunchFile( name='manipulators-service', path=self.manipulators_launch_path From 91969b2417fb8befd411761fcdd0dce7b2445a58 Mon Sep 17 00:00:00 2001 From: Chris Iverach-Brereton <59611394+civerachb-cpr@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:41:04 -0400 Subject: [PATCH 09/17] Add exception handling to the generators to print a nicer error message in the case of self-generated Unsupported* exceptions (#192) --- .../bash/generate_bash | 18 +++++-- .../description/generate_description | 18 +++++-- .../generate_discovery_server | 18 +++++-- .../generate_semantic_description | 50 ++++++++++++------- .../vcan/generate_vcan | 18 +++++-- .../zenoh_router/generate_zenoh_router | 18 +++++-- 6 files changed, 106 insertions(+), 34 deletions(-) diff --git a/clearpath_generator_common/clearpath_generator_common/bash/generate_bash b/clearpath_generator_common/clearpath_generator_common/bash/generate_bash index 2e0896d1..0dca67b6 100755 --- a/clearpath_generator_common/clearpath_generator_common/bash/generate_bash +++ b/clearpath_generator_common/clearpath_generator_common/bash/generate_bash @@ -32,14 +32,26 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.bash.generator import BashGenerator def main(): - setup_path = BaseGenerator.get_args() - bg = BashGenerator(setup_path) - bg.generate() + try: + setup_path = BaseGenerator.get_args() + bg = BashGenerator(setup_path) + bg.generate() + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate bash: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate bash: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate bash: {err}') if __name__ == '__main__': diff --git a/clearpath_generator_common/clearpath_generator_common/description/generate_description b/clearpath_generator_common/clearpath_generator_common/description/generate_description index caa67bb0..53fc35a7 100755 --- a/clearpath_generator_common/clearpath_generator_common/description/generate_description +++ b/clearpath_generator_common/clearpath_generator_common/description/generate_description @@ -32,14 +32,26 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.description.generator import DescriptionGenerator def main(): - setup_path = BaseGenerator.get_args() - dg = DescriptionGenerator(setup_path) - dg.generate() + try: + setup_path = BaseGenerator.get_args() + dg = DescriptionGenerator(setup_path) + dg.generate() + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate description: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate description: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate description: {err}') if __name__ == '__main__': diff --git a/clearpath_generator_common/clearpath_generator_common/discovery_server/generate_discovery_server b/clearpath_generator_common/clearpath_generator_common/discovery_server/generate_discovery_server index 9fc5a100..674b6d21 100755 --- a/clearpath_generator_common/clearpath_generator_common/discovery_server/generate_discovery_server +++ b/clearpath_generator_common/clearpath_generator_common/discovery_server/generate_discovery_server @@ -32,14 +32,26 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.discovery_server.generator import DiscoveryServerGenerator def main(): - setup_path = BaseGenerator.get_args() - dsg = DiscoveryServerGenerator(setup_path) - dsg.generate() + try: + setup_path = BaseGenerator.get_args() + dsg = DiscoveryServerGenerator(setup_path) + dsg.generate() + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate discovery server: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate discovery server: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate discovery server: {err}') if __name__ == '__main__': diff --git a/clearpath_generator_common/clearpath_generator_common/semantic_description/generate_semantic_description b/clearpath_generator_common/clearpath_generator_common/semantic_description/generate_semantic_description index 09b46886..c4e727bc 100755 --- a/clearpath_generator_common/clearpath_generator_common/semantic_description/generate_semantic_description +++ b/clearpath_generator_common/clearpath_generator_common/semantic_description/generate_semantic_description @@ -33,6 +33,11 @@ # of Clearpath Robotics. import os +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.semantic_description.generator import SemanticDescriptionGenerator from ros2run.api import get_executable_path, run_executable @@ -41,25 +46,32 @@ PACKAGE = 'clearpath_generator_common\n' def main(): - setup_path = BaseGenerator.get_args() - sdg = SemanticDescriptionGenerator(setup_path) - sdg.generate() - # Create pseudo package - with open(os.path.join(setup_path, 'package.xml'), 'w+') as f: - f.write(PACKAGE) - # Update collision matrix - path = get_executable_path( - executable_name='moveit_collision_updater', - package_name='clearpath_generator_common' - ) - argv = [ - '--urdf', os.path.join(setup_path, 'robot.urdf.xacro'), - '--srdf', os.path.join(setup_path, 'robot.srdf.xacro'), - '--output', os.path.join(setup_path, 'robot.srdf') - ] - run_executable(path=path, argv=argv) - # Delete pseudo package - os.remove(os.path.join(setup_path, 'package.xml')) + try: + setup_path = BaseGenerator.get_args() + sdg = SemanticDescriptionGenerator(setup_path) + sdg.generate() + # Create pseudo package + with open(os.path.join(setup_path, 'package.xml'), 'w+') as f: + f.write(PACKAGE) + # Update collision matrix + path = get_executable_path( + executable_name='moveit_collision_updater', + package_name='clearpath_generator_common' + ) + argv = [ + '--urdf', os.path.join(setup_path, 'robot.urdf.xacro'), + '--srdf', os.path.join(setup_path, 'robot.srdf.xacro'), + '--output', os.path.join(setup_path, 'robot.srdf') + ] + run_executable(path=path, argv=argv) + # Delete pseudo package + os.remove(os.path.join(setup_path, 'package.xml')) + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate semantic description: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate semantic description: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate semantic description: {err}') if __name__ == '__main__': diff --git a/clearpath_generator_common/clearpath_generator_common/vcan/generate_vcan b/clearpath_generator_common/clearpath_generator_common/vcan/generate_vcan index e12be70d..423a68b6 100755 --- a/clearpath_generator_common/clearpath_generator_common/vcan/generate_vcan +++ b/clearpath_generator_common/clearpath_generator_common/vcan/generate_vcan @@ -32,14 +32,26 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.vcan.generator import VirtualCANGenerator def main(): - setup_path = BaseGenerator.get_args() - dsg = VirtualCANGenerator(setup_path) - dsg.generate() + try: + setup_path = BaseGenerator.get_args() + dsg = VirtualCANGenerator(setup_path) + dsg.generate() + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate vcan: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate vcan: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate vcan: {err}') if __name__ == '__main__': diff --git a/clearpath_generator_common/clearpath_generator_common/zenoh_router/generate_zenoh_router b/clearpath_generator_common/clearpath_generator_common/zenoh_router/generate_zenoh_router index c2c24db4..96c00edc 100644 --- a/clearpath_generator_common/clearpath_generator_common/zenoh_router/generate_zenoh_router +++ b/clearpath_generator_common/clearpath_generator_common/zenoh_router/generate_zenoh_router @@ -32,14 +32,26 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. +from clearpath_config.common.types.exception import ( + UnsupportedAccessoryException, + UnsupportedMiddlewareException, + UnsupportedPlatformException, +) from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.zenoh_router.generator import ZenohRouterGenerator def main(): - setup_path = BaseGenerator.get_args() - dsg = ZenohRouterGenerator(setup_path) - dsg.generate() + try: + setup_path = BaseGenerator.get_args() + dsg = ZenohRouterGenerator(setup_path) + dsg.generate() + except UnsupportedAccessoryException as err: + print(f'[ERROR] Unable to generate zenoh router: {err}') + except UnsupportedMiddlewareException as err: + print(f'[ERROR] Unable to generate zenoh router: {err}') + except UnsupportedPlatformException as err: + print(f'[ERROR] Unable to generate zenoh router: {err}') if __name__ == '__main__': From f63660b89eff890d01a20bb2bd2dde9da432e082 Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:37:01 -0400 Subject: [PATCH 10/17] Feature: Manipulator Samples and Poses (#163) (#188) * Add group_state macros to arm SRDF * Add group_state macros to grippers SRDF * Generate pose macros in URDF --- .../semantic_description/generator.py | 17 +++++++ .../semantic_description/manipulators.py | 26 +++++++++- .../srdf/arm/kinova_gen3_6dof.srdf.xacro | 41 ++++++++++------ .../srdf/arm/kinova_gen3_7dof.srdf.xacro | 44 ++++++++++------- .../srdf/arm/kinova_gen3_lite.srdf.xacro | 26 ++++++++++ .../srdf/arm/universal_robots.srdf.xacro | 49 ++++++++++--------- .../srdf/gripper/kinova_2f_lite.srdf.xacro | 25 +++++++--- .../srdf/gripper/robotiq_2f_140.srdf.xacro | 25 +++++++--- .../srdf/gripper/robotiq_2f_85.srdf.xacro | 26 +++++++--- 9 files changed, 201 insertions(+), 78 deletions(-) diff --git a/clearpath_generator_common/clearpath_generator_common/semantic_description/generator.py b/clearpath_generator_common/clearpath_generator_common/semantic_description/generator.py index 653a809b..8de0832b 100644 --- a/clearpath_generator_common/clearpath_generator_common/semantic_description/generator.py +++ b/clearpath_generator_common/clearpath_generator_common/semantic_description/generator.py @@ -32,6 +32,7 @@ from clearpath_generator_common.common import BaseGenerator from clearpath_generator_common.description.writer import XacroWriter from clearpath_generator_common.semantic_description.manipulators import ( + ManipulatorPoseMacro, ManipulatorSemanticDescription ) @@ -75,6 +76,14 @@ def generate_arms(self) -> None: path=arm_semantic_description.path, ) + for pose in arm.poses: + pose_macro = ManipulatorPoseMacro(arm, pose) + self.xacro_writer.write_macro( + macro=pose_macro.macro(), + parameters=pose_macro.parameters(), + blocks=pose_macro.blocks(), + ) + self.xacro_writer.write_macro( macro='{0}'.format(arm_semantic_description.model), parameters=arm_semantic_description.parameters, @@ -101,6 +110,14 @@ def generate_grippers(self) -> None: path=gripper_semantic_description.path, ) + for pose in arm.gripper.poses: + pose_macro = ManipulatorPoseMacro(arm.gripper, pose) + self.xacro_writer.write_macro( + macro=pose_macro.macro(), + parameters=pose_macro.parameters(), + blocks=pose_macro.blocks(), + ) + self.xacro_writer.write_macro( macro='{0}'.format(gripper_semantic_description.model), parameters=gripper_semantic_description.parameters, diff --git a/clearpath_generator_common/clearpath_generator_common/semantic_description/manipulators.py b/clearpath_generator_common/clearpath_generator_common/semantic_description/manipulators.py index 8bac0ba3..1571b9d7 100644 --- a/clearpath_generator_common/clearpath_generator_common/semantic_description/manipulators.py +++ b/clearpath_generator_common/clearpath_generator_common/semantic_description/manipulators.py @@ -29,7 +29,31 @@ # Redistribution and use in source and binary forms, with or without # modification, is not permitted without the express permission # of Clearpath Robotics. -from clearpath_config.manipulators.types.manipulator import BaseManipulator +from clearpath_config.manipulators.types.manipulator import ( + BaseManipulator, + ManipulatorPose +) + + +class ManipulatorPoseMacro(): + + def __init__(self, manipulator: BaseManipulator, pose: ManipulatorPose) -> None: + self.manipulator = manipulator + self.pose = pose + + def macro(self) -> str: + return f'{self.manipulator.MANIPULATOR_MODEL}_group_state' + + def parameters(self) -> dict: + str_joints = [f'{joint:.4f}' for joint in self.pose.joints] + return { + 'name': self.manipulator.name, + 'group_state': self.pose.name, + 'joint_positions': f'${{[{", ".join(str_joints)}]}}' + } + + def blocks(self) -> str: + return None class ManipulatorSemanticDescription(): diff --git a/clearpath_manipulators_description/srdf/arm/kinova_gen3_6dof.srdf.xacro b/clearpath_manipulators_description/srdf/arm/kinova_gen3_6dof.srdf.xacro index efc0be6c..535198be 100644 --- a/clearpath_manipulators_description/srdf/arm/kinova_gen3_6dof.srdf.xacro +++ b/clearpath_manipulators_description/srdf/arm/kinova_gen3_6dof.srdf.xacro @@ -1,5 +1,17 @@ + + + + + + + + + + + + @@ -9,21 +21,18 @@ - - - - - - - - - - - - - - - - + + + + + diff --git a/clearpath_manipulators_description/srdf/arm/kinova_gen3_7dof.srdf.xacro b/clearpath_manipulators_description/srdf/arm/kinova_gen3_7dof.srdf.xacro index c52ee0a3..6285c2b3 100644 --- a/clearpath_manipulators_description/srdf/arm/kinova_gen3_7dof.srdf.xacro +++ b/clearpath_manipulators_description/srdf/arm/kinova_gen3_7dof.srdf.xacro @@ -1,5 +1,18 @@ + + + + + + + + + + + + + @@ -10,23 +23,18 @@ - - - - - - - - - - - - - - - - - - + + + + + diff --git a/clearpath_manipulators_description/srdf/arm/kinova_gen3_lite.srdf.xacro b/clearpath_manipulators_description/srdf/arm/kinova_gen3_lite.srdf.xacro index 80498a03..38f512de 100644 --- a/clearpath_manipulators_description/srdf/arm/kinova_gen3_lite.srdf.xacro +++ b/clearpath_manipulators_description/srdf/arm/kinova_gen3_lite.srdf.xacro @@ -1,5 +1,17 @@ + + + + + + + + + + + + @@ -33,5 +45,19 @@ + + + + + + diff --git a/clearpath_manipulators_description/srdf/arm/universal_robots.srdf.xacro b/clearpath_manipulators_description/srdf/arm/universal_robots.srdf.xacro index 65202af1..5c942a8a 100644 --- a/clearpath_manipulators_description/srdf/arm/universal_robots.srdf.xacro +++ b/clearpath_manipulators_description/srdf/arm/universal_robots.srdf.xacro @@ -1,5 +1,17 @@ + + + + + + + + + + + + @@ -9,29 +21,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/clearpath_manipulators_description/srdf/gripper/kinova_2f_lite.srdf.xacro b/clearpath_manipulators_description/srdf/gripper/kinova_2f_lite.srdf.xacro index 6493a9cd..ae49f66f 100644 --- a/clearpath_manipulators_description/srdf/gripper/kinova_2f_lite.srdf.xacro +++ b/clearpath_manipulators_description/srdf/gripper/kinova_2f_lite.srdf.xacro @@ -1,5 +1,12 @@ + + + + + + + @@ -7,12 +14,18 @@ - - - - - - + + + + diff --git a/clearpath_manipulators_description/srdf/gripper/robotiq_2f_140.srdf.xacro b/clearpath_manipulators_description/srdf/gripper/robotiq_2f_140.srdf.xacro index 1eb0ab9e..167d747b 100644 --- a/clearpath_manipulators_description/srdf/gripper/robotiq_2f_140.srdf.xacro +++ b/clearpath_manipulators_description/srdf/gripper/robotiq_2f_140.srdf.xacro @@ -1,5 +1,12 @@ + + + + + + + @@ -9,12 +16,18 @@ - - - - - - + + + + diff --git a/clearpath_manipulators_description/srdf/gripper/robotiq_2f_85.srdf.xacro b/clearpath_manipulators_description/srdf/gripper/robotiq_2f_85.srdf.xacro index 353d15c4..9b6d0983 100644 --- a/clearpath_manipulators_description/srdf/gripper/robotiq_2f_85.srdf.xacro +++ b/clearpath_manipulators_description/srdf/gripper/robotiq_2f_85.srdf.xacro @@ -1,5 +1,12 @@ + + + + + + + @@ -12,16 +19,21 @@ - - - - - - + + + + - From 37ed411d8db4ff9ba02ef498760506a9fa4a96fa Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:37:36 -0400 Subject: [PATCH 11/17] Feature: MoveIt Parameters and Enable (#166) (#189) --- .../clearpath_generator_common/launch/generator.py | 3 ++- .../param/manipulators.py | 8 ++++---- .../launch/manipulators.launch.py | 13 ++++++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/clearpath_generator_common/clearpath_generator_common/launch/generator.py b/clearpath_generator_common/clearpath_generator_common/launch/generator.py index 0f3813fd..d80c0539 100644 --- a/clearpath_generator_common/clearpath_generator_common/launch/generator.py +++ b/clearpath_generator_common/clearpath_generator_common/launch/generator.py @@ -95,7 +95,8 @@ def __init__(self, args=[ ('setup_path', self.setup_path), ('use_sim_time', 'false'), - ('namespace', self.namespace) + ('namespace', self.namespace), + ('launch_moveit', str(self.clearpath_config.manipulators.moveit.enable).lower()), ] ) diff --git a/clearpath_generator_common/clearpath_generator_common/param/manipulators.py b/clearpath_generator_common/clearpath_generator_common/param/manipulators.py index d9932978..68005ec9 100644 --- a/clearpath_generator_common/clearpath_generator_common/param/manipulators.py +++ b/clearpath_generator_common/clearpath_generator_common/param/manipulators.py @@ -182,11 +182,11 @@ def generate_parameters(self, use_sim_time: bool = False) -> None: self.param_file.add_node_header() - # Get extra ros parameters from config - extras = self.clearpath_config.platform.extras.ros_parameters - for node in extras: + # Get MoveIt ros parameters from config + moveit_params = self.clearpath_config.manipulators.moveit.ros_parameters + for node in moveit_params: if node in self.param_file.parameters: - self.param_file.update({node: extras.get(node)}) + self.param_file.update({node: moveit_params.get(node)}) if use_sim_time: for node in self.param_file.parameters: diff --git a/clearpath_manipulators/launch/manipulators.launch.py b/clearpath_manipulators/launch/manipulators.launch.py index b07d51a6..ecffd3e2 100644 --- a/clearpath_manipulators/launch/manipulators.launch.py +++ b/clearpath_manipulators/launch/manipulators.launch.py @@ -38,6 +38,7 @@ IncludeLaunchDescription, TimerAction ) +from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import ( LaunchConfiguration, @@ -72,10 +73,18 @@ def generate_launch_description(): description='Robot namespace' ) + arg_launch_moveit = DeclareLaunchArgument( + 'launch_moveit', + choices=['true', 'false'], + default_value='false', + description='Launch MoveIt' + ) + # Launch Configurations setup_path = LaunchConfiguration('setup_path') use_sim_time = LaunchConfiguration('use_sim_time') namespace = LaunchConfiguration('namespace') + launch_moveit = LaunchConfiguration('launch_moveit') # Launch files launch_file_manipulators_description = PathJoinSubstitution([ @@ -123,7 +132,8 @@ def generate_launch_description(): launch_arguments=[ ('setup_path', setup_path), ('use_sim_time', use_sim_time) - ] + ], + condition=IfCondition(launch_moveit) ) moveit_delayed = TimerAction( @@ -135,6 +145,7 @@ def generate_launch_description(): ld.add_action(arg_setup_path) ld.add_action(arg_use_sim_time) ld.add_action(arg_namespace) + ld.add_action(arg_launch_moveit) ld.add_action(group_manipulators_action) ld.add_action(moveit_delayed) return ld From fabad7dc1416efca12095ca5d52fdfca01b8ec5f Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:14:55 -0400 Subject: [PATCH 12/17] Feature: Manipulator URDF Parameters (#181) (#190) --- .../clearpath_generator_common/description/manipulators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_generator_common/clearpath_generator_common/description/manipulators.py b/clearpath_generator_common/clearpath_generator_common/description/manipulators.py index 8471f77f..f34611f6 100644 --- a/clearpath_generator_common/clearpath_generator_common/description/manipulators.py +++ b/clearpath_generator_common/clearpath_generator_common/description/manipulators.py @@ -69,6 +69,7 @@ def __init__(self, manipulator: BaseManipulator) -> None: self.NAME: manipulator.name, self.PARENT: manipulator.parent, } + self.parameters.update(manipulator.get_urdf_parameters()) @property def name(self) -> str: @@ -121,7 +122,6 @@ class UniversalRobotsDescription(ArmDescription): def __init__(self, arm: BaseArm) -> None: super().__init__(arm) self.parameters.pop(self.PORT) - self.parameters.update(arm.get_urdf_parameters()) class LiftDescription(BaseDescription): From 7695e5adba5fdd65e8d5ec6004b0200f2c1fa265 Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:07:01 -0400 Subject: [PATCH 13/17] Fast Forward Feature: Add delay to manipulator controller (#191) --- .../launch/generator.py | 1 + .../launch/manipulators.launch.py | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/clearpath_generator_common/clearpath_generator_common/launch/generator.py b/clearpath_generator_common/clearpath_generator_common/launch/generator.py index d80c0539..a34a348e 100644 --- a/clearpath_generator_common/clearpath_generator_common/launch/generator.py +++ b/clearpath_generator_common/clearpath_generator_common/launch/generator.py @@ -97,6 +97,7 @@ def __init__(self, ('use_sim_time', 'false'), ('namespace', self.namespace), ('launch_moveit', str(self.clearpath_config.manipulators.moveit.enable).lower()), + ('delay_moveit', str(self.clearpath_config.manipulators.moveit.delay)) ] ) diff --git a/clearpath_manipulators/launch/manipulators.launch.py b/clearpath_manipulators/launch/manipulators.launch.py index ecffd3e2..0d4fa94b 100644 --- a/clearpath_manipulators/launch/manipulators.launch.py +++ b/clearpath_manipulators/launch/manipulators.launch.py @@ -80,11 +80,25 @@ def generate_launch_description(): description='Launch MoveIt' ) + arg_control_delay = DeclareLaunchArgument( + 'control_delay', + default_value='0.0', + description='Control launch delay in seconds.' + ) + + arg_moveit_delay = DeclareLaunchArgument( + 'moveit_delay', + default_value='1.0', + description='MoveIt launch delay in seconds.' + ) + # Launch Configurations setup_path = LaunchConfiguration('setup_path') use_sim_time = LaunchConfiguration('use_sim_time') namespace = LaunchConfiguration('namespace') launch_moveit = LaunchConfiguration('launch_moveit') + control_delay = LaunchConfiguration('control_delay') + moveit_delay = LaunchConfiguration('moveit_delay') # Launch files launch_file_manipulators_description = PathJoinSubstitution([ @@ -126,6 +140,11 @@ def generate_launch_description(): ] ) + control_delayed = TimerAction( + period=control_delay, + actions=[group_manipulators_action] + ) + # Launch MoveIt moveit_node_action = IncludeLaunchDescription( PythonLaunchDescriptionSource(launch_file_moveit), @@ -137,7 +156,7 @@ def generate_launch_description(): ) moveit_delayed = TimerAction( - period=10.0, + period=moveit_delay, actions=[moveit_node_action] ) @@ -146,6 +165,8 @@ def generate_launch_description(): ld.add_action(arg_use_sim_time) ld.add_action(arg_namespace) ld.add_action(arg_launch_moveit) - ld.add_action(group_manipulators_action) + ld.add_action(arg_control_delay) + ld.add_action(arg_moveit_delay) + ld.add_action(control_delayed) ld.add_action(moveit_delayed) return ld From 4c95284b983fa42e40a17b90bb956404ed3103a1 Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:34:19 -0400 Subject: [PATCH 14/17] Feature: Add CAN adapters (#196) * Update generator to use new script * Add missing space * Use config to generate vcan * Fix import order --- .../vcan/generator.py | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/clearpath_generator_common/clearpath_generator_common/vcan/generator.py b/clearpath_generator_common/clearpath_generator_common/vcan/generator.py index e291c0cb..b04b0399 100644 --- a/clearpath_generator_common/clearpath_generator_common/vcan/generator.py +++ b/clearpath_generator_common/clearpath_generator_common/vcan/generator.py @@ -32,6 +32,11 @@ # modification, is not permitted without the express permission # of Clearpath Robotics. from clearpath_config.common.types.platform import Platform +from clearpath_config.platform.can import ( + PhysicalCANAdapter, + SerialCANAdapter, + VirtualCANAdapter, +) from clearpath_generator_common.bash.writer import BashWriter from clearpath_generator_common.common import BaseGenerator, BashFile @@ -58,33 +63,32 @@ def generate_vcan_start(self) -> None: vcan_start = BashFile(filename='vcan-start', path=self.setup_path) bash_writer = BashWriter(vcan_start) - # Check platform - if self.clearpath_config.get_platform_model() in PLATFORMS: - port = 11412 - serial = '/dev/ttycan0' - can = 'vcan0' - baud = 's8' - bash_writer.write( - f'/bin/sh -e /usr/sbin/clearpath-vcan-bridge ' - f'-p {port} ' - f'-d {serial} ' - f'-v {can} ' - f'-b {baud}' - ) - # Add second vcan for A300 - if self.clearpath_config.get_platform_model() == Platform.A300: - port = 11413 - serial = '/dev/ttycan1' - can = 'vcan1' - baud = 's5' + for adapter in self.clearpath_config.platform.can_adapters.get_all(): + if adapter.TYPE == VirtualCANAdapter.TYPE: + bash_writer.write( + f'/bin/bash -e /usr/sbin/clearpath-vcan-bridge ' + f'-t {adapter.TYPE} ' + f'-p {adapter.port} ' + f'-s {adapter.serial_dev} ' + f'-c {adapter.can_dev} ' + f'-b {adapter.baud}' + ) + if adapter.TYPE == SerialCANAdapter.TYPE: + bash_writer.write( + f'/bin/bash -e /usr/sbin/clearpath-vcan-bridge ' + f'-t {adapter.TYPE} ' + f'-s {adapter.serial_dev} ' + f'-c {adapter.can_dev} ' + f'-b {adapter.baud}' + ) + if adapter.TYPE == PhysicalCANAdapter.TYPE: bash_writer.write( - f'/bin/sh -e /usr/sbin/clearpath-vcan-bridge ' - f'-p {port} ' - f'-d {serial} ' - f'-v {can} ' - f'-b {baud}' + f'/bin/bash -e /usr/sbin/clearpath-vcan-bridge ' + f'-t {adapter.TYPE} ' + f'-c {adapter.can_dev} ' + f'-b {adapter.baud}' ) - else: + if not self.clearpath_config.platform.can_adapters.get_all(): bash_writer.add_echo( 'No vcan bridge required.' + 'If this was launched as a service then the service will now end.' From bdd329d97ebcfc58a4ddb77f75dba37b4b01f5f6 Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:48:09 -0400 Subject: [PATCH 15/17] Feature: Wiferion Charger (#197) * Wiferion sample URDF * Resize Wiferion --- .../urdf/wiferion.urdf.xacro | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 clearpath_sensors_description/urdf/wiferion.urdf.xacro diff --git a/clearpath_sensors_description/urdf/wiferion.urdf.xacro b/clearpath_sensors_description/urdf/wiferion.urdf.xacro new file mode 100644 index 00000000..352bc842 --- /dev/null +++ b/clearpath_sensors_description/urdf/wiferion.urdf.xacro @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ff20b2b9f126bc094094234dc8fb66f958e113af Mon Sep 17 00:00:00 2001 From: luis-camero <88782189+luis-camero@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:09:53 -0400 Subject: [PATCH 16/17] Re-add reference remapping (#198) --- clearpath_control/launch/control.launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clearpath_control/launch/control.launch.py b/clearpath_control/launch/control.launch.py index f37e2604..d5ed2c29 100644 --- a/clearpath_control/launch/control.launch.py +++ b/clearpath_control/launch/control.launch.py @@ -45,6 +45,7 @@ ('platform_velocity_controller/odom', 'platform/odom'), ('platform_velocity_controller/cmd_vel', 'platform/cmd_vel'), ('platform_velocity_controller/cmd_vel_out', 'platform/cmd_vel_out'), + ('platform_velocity_controller/reference', 'platform/cmd_vel'), ('platform_velocity_controller/transition_event', 'platform/transition_event'), ('/diagnostics', 'diagnostics'), ('/tf', 'tf'), From ff7afef3001f77093cb2cbe7263c7cff80105e03 Mon Sep 17 00:00:00 2001 From: Tony Baltovski Date: Thu, 10 Apr 2025 12:20:42 -0400 Subject: [PATCH 17/17] [clearpath_control] Added remapping for upstream mecanum controller. (#199) --- clearpath_control/launch/control.launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clearpath_control/launch/control.launch.py b/clearpath_control/launch/control.launch.py index d5ed2c29..fc1e70b8 100644 --- a/clearpath_control/launch/control.launch.py +++ b/clearpath_control/launch/control.launch.py @@ -43,6 +43,7 @@ ('joint_states', 'platform/joint_states'), ('dynamic_joint_states', 'platform/dynamic_joint_states'), ('platform_velocity_controller/odom', 'platform/odom'), + ('platform_velocity_controller/odometry', 'platform/odom'), ('platform_velocity_controller/cmd_vel', 'platform/cmd_vel'), ('platform_velocity_controller/cmd_vel_out', 'platform/cmd_vel_out'), ('platform_velocity_controller/reference', 'platform/cmd_vel'),