diff --git a/.clang-tidy b/.clang-tidy index c4d64102cea300627de8e8b13d42dbea3d4587f7..7fac802e36b9425ad1c0e1a5fb616322f4c83f70 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -Checks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-alpha*,google-*,misc-*,readability-*,modernize-*,performance-*,-clang-diagnostic-deprecated-declarations,-modernize-pass-by-value,-clang-diagnostic-reinterpret-base-class,-clang-diagnostic-return-type,-clang-diagnostic-switch' +Checks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-optin.cplusplus.VirtualCall,-clang-analyzer-alpha*,google-*,misc-*,readability-*,modernize-*,performance-*,-clang-diagnostic-deprecated-declarations,-modernize-pass-by-value,-clang-diagnostic-reinterpret-base-class,-clang-diagnostic-return-type,-clang-diagnostic-switch' HeaderFilterRegex: '^franka_.*' CheckOptions: # Classes, structs, ... diff --git a/CHANGELOG.md b/CHANGELOG.md index 5527d9b141f0e7abbc9be5d908215d140f549435..e610347a86fd394e379879effaa652830b2a73aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,30 @@ ## 0.7.0 - UNRELEASED -Requires `libfranka` >= 0.5.0 +Requires `libfranka` >= 0.7.0 - * `franka_control`: - - Publish whole `libfranka` `franka::RobotState` in `franka_state_controller`. - * `franka_description` + * **BREAKING** moved services and action from `franka_control` to `franka_msgs`. + * **BREAKING** moved Service container from `franka_control` to `franka_hw`. + * `franka_example_controllers` : Added example for dual-arm control based on franka_combinable_hw. + * `franka_description` : + - Added an example urdf with two panda arms. - **BREAKING** Updated collision volumes. - Removed invalid `axis` for `joint8`. + * `franka_hw`: + - Added hardware classes to support torque-controlling multiple robots from one controller. + - Refactored FrankaHW class to serve as base class (e.g. for FrankaCombinableHW). + - Added joint limits checking to FrankaHW which means parameterized warning prints when approaching limits. + - Made initial collision behavior a parameter. + - Added default constructor and init method to FrankaHW. + - **BREAKING** moved parsing of parameters from control node to FrankaHW::init. + - **BREAKING** made libfranka robot a member of FrankaHW. + - Added missing return value to `franka::ControllerMode` stream operator function. + * `franka_control`: + - Added control node that can runs a `FrankaCombinedHW` to control mulitple Pandas. + - Publish whole `libfranka` `franka::RobotState` in `franka_state_controller`. * `franka_example_controllers`: - Cartesian impedance example controller: Interpolate desired orientations with slerp and change orientation error to quaternion. - * `franka_hw`: - - Added missing return value to `franka::ControllerMode` stream operator function. * **BREAKING** Moved `panda_moveit_config` to [`ros-planning`](https://github.com/ros-planning/panda_moveit_config). * Added support for ROS Melodic Morenia. * Raised minimum CMake version to 3.4 to match `libfranka`. @@ -122,4 +134,3 @@ Requires `libfranka` >= 0.2.0 ## 0.1.0 - 2017-09-15 * Initial release - diff --git a/cmake/ClangTools.cmake b/cmake/ClangTools.cmake index 4a356d0296392723adf33d118d41c7111bb1c263..fbc80d6a37b1b59370fe56ea65c70d52b38438d5 100644 --- a/cmake/ClangTools.cmake +++ b/cmake/ClangTools.cmake @@ -31,7 +31,7 @@ function(add_format_target _target) add_dependencies(format format-${_target}) add_custom_target(check-format-${_target} - COMMAND scripts/format-check.sh ${CLANG_FORMAT_PROG} -output-replacements-xml ${ARG_FILES} + COMMAND ${CLANG_FORMAT_PROG} -output-replacements-xml ${ARG_FILES} | grep "<replacement " > /dev/null && exit 1 || exit 0 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/.. COMMENT "Checking ${_target} code formatting with clang-format" VERBATIM @@ -55,7 +55,7 @@ function(add_tidy_target _target) add_dependencies(tidy tidy-${_target}) add_custom_target(check-tidy-${_target} - COMMAND scripts/fail-on-output.sh ${CLANG_TIDY_PROG} -p=${CMAKE_BINARY_DIR} ${ARG_FILES} + COMMAND ${CLANG_TIDY_PROG} -p=${CMAKE_BINARY_DIR} ${ARG_FILES} | grep . && exit 1 || exit 0 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/.. DEPENDS ${ARG_DEPENDS} COMMENT "Running clang-tidy for ${_target}" diff --git a/franka_control/CMakeLists.txt b/franka_control/CMakeLists.txt index 219f2d08cde4b7638be4f0c614493a6162dfb5be..2abf7e50c5d508a5bc941e01eb2038d94fec3b63 100644 --- a/franka_control/CMakeLists.txt +++ b/franka_control/CMakeLists.txt @@ -5,8 +5,6 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(catkin REQUIRED COMPONENTS - actionlib - actionlib_msgs controller_interface controller_manager franka_hw @@ -21,34 +19,16 @@ find_package(catkin REQUIRED COMPONENTS tf2_msgs ) -find_package(Franka 0.5.0 REQUIRED) - -add_service_files(FILES - SetCartesianImpedance.srv - SetEEFrame.srv - SetForceTorqueCollisionBehavior.srv - SetFullCollisionBehavior.srv - SetJointImpedance.srv - SetKFrame.srv - SetLoad.srv -) - -add_action_files(FILES - ErrorRecovery.action -) - -generate_messages(DEPENDENCIES actionlib_msgs) +find_package(Franka 0.7.0 REQUIRED) catkin_package( INCLUDE_DIRS include - LIBRARIES franka_state_controller franka_control_services + LIBRARIES franka_state_controller CATKIN_DEPENDS - actionlib controller_interface franka_hw franka_msgs geometry_msgs - message_runtime pluginlib realtime_tools roscpp @@ -80,54 +60,47 @@ target_include_directories(franka_state_controller PUBLIC include ) -## franka_control_services -add_library(franka_control_services - src/services.cpp +## franka_control_node +add_executable(franka_control_node + src/franka_control_node.cpp ) -add_dependencies(franka_control_services +add_dependencies(franka_control_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} ) -target_link_libraries(franka_control_services +target_link_libraries(franka_control_node ${Franka_LIBRARIES} ${catkin_LIBRARIES} ) -target_include_directories(franka_control_services SYSTEM PUBLIC +target_include_directories(franka_control_node SYSTEM PUBLIC ${Franka_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS} ) -target_include_directories(franka_control_services PUBLIC - include -) -## franka_control_node -add_executable(franka_control_node - src/franka_control_node.cpp +add_executable(franka_combined_control_node + src/franka_combined_control_node.cpp ) -add_dependencies(franka_control_node - ${${PROJECT_NAME}_EXPORTED_TARGETS} - ${catkin_EXPORTED_TARGETS} +add_dependencies(franka_combined_control_node + ${catkin_EXPORTED_TARGETS} ) -target_link_libraries(franka_control_node - ${Franka_LIBRARIES} - franka_control_services +target_link_libraries(franka_combined_control_node ${catkin_LIBRARIES} ) -target_include_directories(franka_control_node SYSTEM PUBLIC - ${Franka_INCLUDE_DIRS} +target_include_directories(franka_combined_control_node SYSTEM PUBLIC ${catkin_INCLUDE_DIRS} ) + ## Installation install(TARGETS franka_state_controller - franka_control_services franka_control_node + franka_combined_control_node ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} @@ -158,6 +131,6 @@ if(CLANG_TOOLS) add_format_target(franka_control FILES ${SOURCES} ${HEADERS}) add_tidy_target(franka_control FILES ${SOURCES} - DEPENDS franka_control_node franka_state_controller + DEPENDS franka_control_node franka_combined_control_node franka_state_controller ) endif() diff --git a/franka_control/config/default_combined_controllers.yaml b/franka_control/config/default_combined_controllers.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e7ef4923f4f38e5d4ab443d1e0d1462b1f29f661 --- /dev/null +++ b/franka_control/config/default_combined_controllers.yaml @@ -0,0 +1,88 @@ +effort_joint_trajectory_controller: + type: effort_controllers/JointTrajectoryController + joints: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + constraints: + goal_time: 0.5 + panda_2_joint1: + goal: 0.05 + panda_2_joint2: + goal: 0.05 + panda_2_joint3: + goal: 0.05 + panda_2_joint4: + goal: 0.05 + panda_2_joint5: + goal: 0.05 + panda_2_joint6: + goal: 0.05 + panda_2_joint7: + goal: 0.05 + panda_1_joint1: + goal: 0.05 + panda_1_joint2: + goal: 0.05 + panda_1_joint3: + goal: 0.05 + panda_1_joint4: + goal: 0.05 + panda_1_joint5: + goal: 0.05 + panda_1_joint6: + goal: 0.05 + panda_1_joint7: + goal: 0.05 + gains: + panda_1_joint1: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_1_joint2: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_1_joint3: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_1_joint4: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_1_joint5: {p: 250, d: 10, i: 0, i_clamp: 1} + panda_1_joint6: {p: 150, d: 10, i: 0, i_clamp: 1} + panda_1_joint7: {p: 50, d: 5, i: 0, i_clamp: 1} + panda_2_joint1: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_2_joint2: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_2_joint3: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_2_joint4: {p: 600, d: 30, i: 0, i_clamp: 1} + panda_2_joint5: {p: 250, d: 10, i: 0, i_clamp: 1} + panda_2_joint6: {p: 150, d: 10, i: 0, i_clamp: 1} + panda_2_joint7: {p: 50, d: 5, i: 0, i_clamp: 1} + +panda_1_state_controller: + type: franka_control/FrankaStateController + arm_id: panda_1 + joint_names: + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + publish_rate: 30 # [Hz] + +panda_2_state_controller: + type: franka_control/FrankaStateController + arm_id: panda_2 + joint_names: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 + publish_rate: 30 # [Hz] diff --git a/franka_control/config/franka_combined_control_node.yaml b/franka_control/config/franka_combined_control_node.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fa103fabc97615ed6f0e37f686e53ba7cdc3d338 --- /dev/null +++ b/franka_control/config/franka_combined_control_node.yaml @@ -0,0 +1,67 @@ +robot_hardware: + - panda_1 + - panda_2 + +panda_1: + type: franka_hw/FrankaCombinableHW + arm_id: panda_1 + joint_names: + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + # Configure the threshold angle for printing joint limit warnings. + joint_limit_warning_threshold: 0.1 # [rad] + # Activate rate limiter? [true|false] + rate_limiting: true + # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate. + cutoff_frequency: 1000 + # Internal controller for motion generators [joint_impedance|cartesian_impedance] + internal_controller: joint_impedance + # Used to decide whether to enforce realtime mode [enforce|ignore] + realtime_config: enforce + # Configure the initial defaults for the collision behavior reflexes. + collision_config: + lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + +panda_2: + type: franka_hw/FrankaCombinableHW + arm_id: panda_2 + joint_names: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 + # Configure the threshold angle for printing joint limit warnings. + joint_limit_warning_threshold: 0.1 # [rad] + # Activate rate limiter? [true|false] + rate_limiting: true + # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate. + cutoff_frequency: 1000 + # Internal controller for motion generators [joint_impedance|cartesian_impedance] + internal_controller: joint_impedance + # Used to decide whether to enforce realtime mode [enforce|ignore] + realtime_config: enforce + # Configure the initial defaults for the collision behavior reflexes. + collision_config: + lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] diff --git a/franka_control/config/franka_control_node.yaml b/franka_control/config/franka_control_node.yaml index 333c390abb729f53bc337ebe99e4aa3d36a9078e..b27d5706d75d7b869de8c1a3d91622e88c0a56f2 100644 --- a/franka_control/config/franka_control_node.yaml +++ b/franka_control/config/franka_control_node.yaml @@ -7,6 +7,8 @@ joint_names: - panda_joint6 - panda_joint7 arm_id: panda +# Configure the threshold angle for printing joint limit warnings. +joint_limit_warning_threshold: 0.1 # [rad] # Activate rate limiter? [true|false] rate_limiting: true # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate. @@ -15,3 +17,13 @@ cutoff_frequency: 100 internal_controller: joint_impedance # Used to decide whether to enforce realtime mode [enforce|ignore] realtime_config: enforce +# Configure the initial defaults for the collision behavior reflexes. +collision_config: + lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] diff --git a/franka_control/include/franka_control/services.h b/franka_control/include/franka_control/services.h deleted file mode 100644 index 26814b9652d640425f8745d9008980df9b058cea..0000000000000000000000000000000000000000 --- a/franka_control/include/franka_control/services.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2017 Franka Emika GmbH -// Use of this source code is governed by the Apache-2.0 license, see LICENSE -#pragma once - -#include <franka/exception.h> -#include <franka/robot.h> -#include <ros/console.h> -#include <ros/node_handle.h> -#include <ros/service_server.h> - -#include <franka_control/SetCartesianImpedance.h> -#include <franka_control/SetEEFrame.h> -#include <franka_control/SetForceTorqueCollisionBehavior.h> -#include <franka_control/SetFullCollisionBehavior.h> -#include <franka_control/SetJointImpedance.h> -#include <franka_control/SetKFrame.h> -#include <franka_control/SetLoad.h> - -namespace franka_control { - -template <typename T> -ros::ServiceServer advertiseService( - ros::NodeHandle& node_handle, - const std::string& name, - std::function<void(typename T::Request&, typename T::Response&)> handler) { - return node_handle.advertiseService<typename T::Request, typename T::Response>( - name, [name, handler](typename T::Request& request, typename T::Response& response) { - try { - handler(request, response); - response.success = true; - ROS_DEBUG_STREAM(name << " succeeded."); - } catch (const franka::Exception& ex) { - ROS_ERROR_STREAM(name << " failed: " << ex.what()); - response.success = false; - response.error = ex.what(); - } - return true; - }); -} - -void setCartesianImpedance(franka::Robot& robot, - const SetCartesianImpedance::Request& req, - SetCartesianImpedance::Response& res); -void setJointImpedance(franka::Robot& robot, - const SetJointImpedance::Request& req, - SetJointImpedance::Response& res); -void setEEFrame(franka::Robot& robot, const SetEEFrame::Request& req, SetEEFrame::Response& res); -void setKFrame(franka::Robot& robot, const SetKFrame::Request& req, SetKFrame::Response& res); -void setForceTorqueCollisionBehavior(franka::Robot& robot, - const SetForceTorqueCollisionBehavior::Request& req, - SetForceTorqueCollisionBehavior::Response& res); -void setFullCollisionBehavior(franka::Robot& robot, - const SetFullCollisionBehavior::Request& req, - SetFullCollisionBehavior::Response& res); -void setLoad(franka::Robot& robot, const SetLoad::Request& req, SetLoad::Response& res); - -} // namespace franka_control diff --git a/franka_control/launch/franka_combined_control.launch b/franka_control/launch/franka_combined_control.launch new file mode 100644 index 0000000000000000000000000000000000000000..bfdf67336a144b64800d5c199443eb3282dba7a0 --- /dev/null +++ b/franka_control/launch/franka_combined_control.launch @@ -0,0 +1,53 @@ +<?xml version="1.0" ?> +<launch> + + <!-- The path to a robot description (xacro) file to control --> + <arg name="robot" default="$(find franka_description)/robots/dual_panda_example.urdf.xacro" /> + + <!-- Additional XACRO args. Be sure to escape colons and equal signs + with backslashes, because XACRO use same syntax as roslaunch: + roslaunch <pkg> <launchfile> robot:=path/to/urdf.xacro args:="foo\:\=bar other\:\=true" --> + <arg name="args" default="" /> + + <!-- The name of the combined robot, used as control node name --> + <arg name="robot_id" default="combined_panda" /> + + <!-- The config file containing all parameters for the combined hardware node like the IPs of the robots etc. --> + <arg name="hw_config_file" default="$(find franka_control)/config/franka_combined_control_node.yaml"/> + + <!-- Optional arg to manually configure the ips of all robots, overwriting ips that are configured in hw_config_file --> + <!-- The ips must be set as dictionary like {<arm_id_1>/robot_ip: <my_ip_1>, <arm_id_2>/robot_ip: <my_ip_2>} --> + <arg name="robot_ips" /> + + <!-- The config file containing the parameterization for all controllers to start with this launch file --> + <arg name="controllers_file" default="$(find franka_control)/config/default_combined_controllers.yaml" /> + + <!-- The space separated list of controllers to start in this launch files. The controllers should be known from the controllers_file --> + <arg name="controllers_to_start" default="panda_1_state_controller panda_2_state_controller"/> + + <!-- The list of the joint_states topics of all combined robots to fuse to a complete topic --> + <arg name="joint_states_source_list" default="[panda_1_state_controller/joint_states, panda_2_state_controller/joint_states, panda_1/franka_gripper/joint_states, panda_2/franka_gripper/joint_states]"/> + + <node name="$(arg robot_id)" pkg="franka_control" type="franka_combined_control_node" output="screen" required="true" > + <rosparam command="load" file="$(arg hw_config_file)" /> + <!-- Add or overwrite manually configured ips --> + <rosparam subst_value="True">$(arg robot_ips)</rosparam> + <param name="robot_description" command="xacro --inorder $(arg robot) $(arg args)" /> + </node> + + <group ns="$(arg robot_id)"> + + <rosparam command="load" file="$(arg controllers_file)" /> + <node name="hw_controller_spawner" pkg="controller_manager" type="spawner" respawn="false" output="screen" + args="$(arg controllers_to_start)"/> + + <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" output="screen"/> + <node name="joint_state_publisher" type="joint_state_publisher" pkg="joint_state_publisher" output="screen"> + <param name="robot_description" command="xacro --inorder $(arg robot) $(arg args)" /> + <rosparam param="source_list" subst_value="true">$(arg joint_states_source_list)</rosparam> + <param name="rate" value="30"/> + </node> + + </group> + +</launch> diff --git a/franka_control/package.xml b/franka_control/package.xml index c01d2bd7726f86affcde02c44a9a56c0beb6a6bf..af060e9eecabd971fb5c79570f57f47e30f4e4d0 100644 --- a/franka_control/package.xml +++ b/franka_control/package.xml @@ -13,11 +13,7 @@ <buildtool_depend>catkin</buildtool_depend> - <build_depend>message_generation</build_depend> - <depend>libfranka</depend> - <depend>actionlib</depend> - <depend>actionlib_msgs</depend> <depend>controller_interface</depend> <depend>controller_manager</depend> <depend>franka_hw</depend> @@ -33,11 +29,8 @@ <exec_depend>franka_description</exec_depend> <exec_depend>franka_gripper</exec_depend> <exec_depend>joint_state_publisher</exec_depend> - <exec_depend>message_runtime</exec_depend> <exec_depend>robot_state_publisher</exec_depend> - <build_export_depend>message_runtime</build_export_depend> - <export> <controller_interface plugin="${prefix}/franka_controller_plugins.xml"/> </export> diff --git a/franka_control/src/franka_combined_control_node.cpp b/franka_control/src/franka_combined_control_node.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dfcb9ba4f6839b8fb0e37dc27b9bcf9693c28e98 --- /dev/null +++ b/franka_control/src/franka_combined_control_node.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE + +#include <controller_manager/controller_manager.h> +#include <franka_hw/franka_combined_hw.h> +#include <ros/ros.h> + +#include <franka/control_tools.h> +#include <sched.h> +#include <string> + +int main(int argc, char** argv) { + ros::init(argc, argv, "franka_combined_control_node"); + + ros::AsyncSpinner spinner(4); + spinner.start(); + + ros::NodeHandle private_node_handle("~"); + franka_hw::FrankaCombinedHW franka_control; + if (!franka_control.init(private_node_handle, private_node_handle)) { + ROS_ERROR("franka_combined_control_node:: Initialization of FrankaCombinedHW failed!"); + return 1; + } + + // set current thread to real-time priority + std::string error_message; + if (!franka::setCurrentThreadToHighestSchedulerPriority(&error_message)) { + ROS_ERROR("franka_combined_control_node: Failed to set thread priority to real-time. Error: %s", + error_message.c_str()); + return 1; + } + + controller_manager::ControllerManager cm(&franka_control, private_node_handle); + ros::Duration period(0.001); + ros::Rate rate(period); + + while (ros::ok()) { + rate.sleep(); + ros::Time now = ros::Time::now(); + franka_control.read(now, period); + cm.update(now, period, franka_control.controllerNeedsReset()); + franka_control.write(now, period); + } + + return 0; +} diff --git a/franka_control/src/franka_control_node.cpp b/franka_control/src/franka_control_node.cpp index f6fbd71140625f2559571b451ba9e86494c8f062..9562a1a657d5d74731085ce575588a4bd1c4a362 100644 --- a/franka_control/src/franka_control_node.cpp +++ b/franka_control/src/franka_control_node.cpp @@ -1,180 +1,52 @@ // Copyright (c) 2017 Franka Emika GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE #include <algorithm> -#include <array> #include <atomic> -#include <string> -#include <utility> #include <actionlib/server/simple_action_server.h> #include <controller_manager/controller_manager.h> #include <franka/exception.h> #include <franka/robot.h> #include <franka_hw/franka_hw.h> +#include <franka_hw/services.h> +#include <franka_msgs/ErrorRecoveryAction.h> #include <ros/ros.h> -#include <franka_control/ErrorRecoveryAction.h> -#include <franka_control/services.h> - -class ServiceContainer { - public: - template <typename T, typename... TArgs> - ServiceContainer& advertiseService(TArgs&&... args) { - ros::ServiceServer server = franka_control::advertiseService<T>(std::forward<TArgs>(args)...); - services_.push_back(server); - return *this; - } - - private: - std::vector<ros::ServiceServer> services_; -}; +using franka_hw::ServiceContainer; int main(int argc, char** argv) { ros::init(argc, argv, "franka_control_node"); + ros::NodeHandle public_node_handle; ros::NodeHandle node_handle("~"); - std::vector<std::string> joint_names_vector; - if (!node_handle.getParam("joint_names", joint_names_vector) || joint_names_vector.size() != 7) { - ROS_ERROR("Invalid or no joint_names parameters provided"); - return 1; - } - - std::array<std::string, 7> joint_names; - std::copy(joint_names_vector.cbegin(), joint_names_vector.cend(), joint_names.begin()); - - bool rate_limiting; - if (!node_handle.getParamCached("rate_limiting", rate_limiting)) { - ROS_ERROR("Invalid or no rate_limiting parameter provided"); - return 1; - } - - double cutoff_frequency; - if (!node_handle.getParamCached("cutoff_frequency", cutoff_frequency)) { - ROS_ERROR("Invalid or no cutoff_frequency parameter provided"); - return 1; - } - - std::string internal_controller; - if (!node_handle.getParam("internal_controller", internal_controller)) { - ROS_ERROR("No internal_controller parameter provided"); - return 1; - } - - urdf::Model urdf_model; - if (!urdf_model.initParamWithNodeHandle("robot_description", public_node_handle)) { - ROS_ERROR("Could not initialize URDF model from robot_description"); - return 1; - } - - std::string robot_ip; - if (!node_handle.getParam("robot_ip", robot_ip)) { - ROS_ERROR("Invalid or no robot_ip parameter provided"); + franka_hw::FrankaHW franka_control; + if (!franka_control.init(public_node_handle, node_handle)) { + ROS_ERROR("franka_control_node: Failed to initialize FrankaHW class. Shutting down!"); return 1; } - std::string arm_id; - if (!node_handle.getParam("arm_id", arm_id)) { - ROS_ERROR("Invalid or no arm_id parameter provided"); - return 1; - } - - std::string realtime_config_param = node_handle.param("realtime_config", std::string("enforce")); - franka::RealtimeConfig realtime_config; - if (realtime_config_param == "enforce") { - realtime_config = franka::RealtimeConfig::kEnforce; - } else if (realtime_config_param == "ignore") { - realtime_config = franka::RealtimeConfig::kIgnore; - } else { - ROS_ERROR("Invalid realtime_config parameter provided. Valid values are 'enforce', 'ignore'."); - return 1; - } - franka::Robot robot(robot_ip, realtime_config); - - // Set default collision behavior - robot.setCollisionBehavior( - {{20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}}, {{20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}}, - {{20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}}, {{20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}}, - {{20.0, 20.0, 20.0, 25.0, 25.0, 25.0}}, {{20.0, 20.0, 20.0, 25.0, 25.0, 25.0}}, - {{20.0, 20.0, 20.0, 25.0, 25.0, 25.0}}, {{20.0, 20.0, 20.0, 25.0, 25.0, 25.0}}); + franka::Robot& robot = franka_control.robot(); std::atomic_bool has_error(false); ServiceContainer services; - services - .advertiseService<franka_control::SetJointImpedance>( - node_handle, "set_joint_impedance", - [&robot](auto&& req, auto&& res) { - return franka_control::setJointImpedance(robot, req, res); - }) - .advertiseService<franka_control::SetCartesianImpedance>( - node_handle, "set_cartesian_impedance", - [&robot](auto&& req, auto&& res) { - return franka_control::setCartesianImpedance(robot, req, res); - }) - .advertiseService<franka_control::SetEEFrame>( - node_handle, "set_EE_frame", - [&robot](auto&& req, auto&& res) { return franka_control::setEEFrame(robot, req, res); }) - .advertiseService<franka_control::SetKFrame>( - node_handle, "set_K_frame", - [&robot](auto&& req, auto&& res) { return franka_control::setKFrame(robot, req, res); }) - .advertiseService<franka_control::SetForceTorqueCollisionBehavior>( - node_handle, "set_force_torque_collision_behavior", - [&robot](auto&& req, auto&& res) { - return franka_control::setForceTorqueCollisionBehavior(robot, req, res); - }) - .advertiseService<franka_control::SetFullCollisionBehavior>( - node_handle, "set_full_collision_behavior", - [&robot](auto&& req, auto&& res) { - return franka_control::setFullCollisionBehavior(robot, req, res); - }) - .advertiseService<franka_control::SetLoad>( - node_handle, "set_load", - [&robot](auto&& req, auto&& res) { return franka_control::setLoad(robot, req, res); }); - - actionlib::SimpleActionServer<franka_control::ErrorRecoveryAction> recovery_action_server( + franka_hw::setupServices(robot, node_handle, services); + + actionlib::SimpleActionServer<franka_msgs::ErrorRecoveryAction> recovery_action_server( node_handle, "error_recovery", - [&](const franka_control::ErrorRecoveryGoalConstPtr&) { + [&](const franka_msgs::ErrorRecoveryGoalConstPtr&) { try { robot.automaticErrorRecovery(); has_error = false; recovery_action_server.setSucceeded(); ROS_INFO("Recovered from error"); } catch (const franka::Exception& ex) { - recovery_action_server.setAborted(franka_control::ErrorRecoveryResult(), ex.what()); + recovery_action_server.setAborted(franka_msgs::ErrorRecoveryResult(), ex.what()); } }, false); - franka::Model model = robot.loadModel(); - auto get_rate_limiting = [&]() { - node_handle.getParamCached("rate_limiting", rate_limiting); - return rate_limiting; - }; - auto get_internal_controller = [&]() { - node_handle.getParamCached("internal_controller", internal_controller); - - franka::ControllerMode controller_mode; - if (internal_controller == "joint_impedance") { - controller_mode = franka::ControllerMode::kJointImpedance; - } else if (internal_controller == "cartesian_impedance") { - controller_mode = franka::ControllerMode::kCartesianImpedance; - } else { - ROS_WARN( - "Invalid internal_controller parameter provided, falling back to joint impedance. Valid " - "values are: 'joint_impedance', 'cartesian_impedance'."); - controller_mode = franka::ControllerMode::kJointImpedance; - } - - return controller_mode; - }; - auto get_cutoff_frequency = [&]() { - node_handle.getParamCached("cutoff_frequency", cutoff_frequency); - return cutoff_frequency; - }; - franka_hw::FrankaHW franka_control(joint_names, arm_id, urdf_model, model, get_rate_limiting, - get_cutoff_frequency, get_internal_controller); - // Initialize robot state before loading any controller franka_control.update(robot.readOnce()); @@ -195,6 +67,7 @@ int main(int argc, char** argv) { ros::Time now = ros::Time::now(); control_manager.update(now, now - last_time); + franka_control.checkJointLimits(); last_time = now; if (!ros::ok()) { @@ -204,13 +77,15 @@ int main(int argc, char** argv) { try { // Run control loop. Will exit if the controller is switched. - franka_control.control(robot, [&](const ros::Time& now, const ros::Duration& period) { + franka_control.control([&](const ros::Time& now, const ros::Duration& period) { if (period.toSec() == 0.0) { // Reset controllers before starting a motion control_manager.update(now, period, true); + franka_control.checkJointLimits(); franka_control.reset(); } else { control_manager.update(now, period); + franka_control.checkJointLimits(); franka_control.enforceLimits(period); } return ros::ok(); diff --git a/franka_description/robots/dual_panda_example.urdf.xacro b/franka_description/robots/dual_panda_example.urdf.xacro new file mode 100644 index 0000000000000000000000000000000000000000..f98c133bf96cdfbd61c5d76549a9d2b8f17d494f --- /dev/null +++ b/franka_description/robots/dual_panda_example.urdf.xacro @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="panda"> + <xacro:arg name="arm_id_1" default="panda_1" /> + <xacro:arg name="arm_id_2" default="panda_2" /> + + <xacro:include filename="$(find franka_description)/robots/panda_arm.xacro"/> + <xacro:include filename="$(find franka_description)/robots/hand.xacro"/> + + <!-- box shaped table as base for the 2 Pandas --> + <link name="base"> + <visual> + <origin xyz="0 0 0.5" rpy="0 0 0"/> + <geometry> + <box size="1 2 1" /> + </geometry> + <material name="White"> + <color rgba="1.0 1.0 1.0 1.0"/> + </material> + </visual> + <collision> + <origin xyz="0 0 0.5" rpy="0 0 0"/> + <geometry> + <box size="1 2 1" /> + </geometry> + </collision> + </link> + + <!-- right arm with gripper --> + <xacro:panda_arm arm_id="$(arg arm_id_1)" connected_to="base" xyz="0 -0.5 1" safety_distance="0.03"/> + <xacro:hand ns="$(arg arm_id_1)" rpy="0 0 ${-pi/4}" connected_to="$(arg arm_id_1)_link8" safety_distance="0.03"/> + + <!-- left arm with gripper --> + <xacro:panda_arm arm_id="$(arg arm_id_2)" connected_to="base" xyz="0 0.5 1" safety_distance="0.03"/> + <xacro:hand ns="$(arg arm_id_2)" rpy="0 0 ${-pi/4}" connected_to="$(arg arm_id_2)_link8" safety_distance="0.03"/> + +</robot> diff --git a/franka_example_controllers/CMakeLists.txt b/franka_example_controllers/CMakeLists.txt index 5a14e23b6b4c0c962d970f0458e87f23bf6580c6..3f935f9538d4d248c68678f8f5e3273a7968aac7 100644 --- a/franka_example_controllers/CMakeLists.txt +++ b/franka_example_controllers/CMakeLists.txt @@ -2,16 +2,18 @@ cmake_minimum_required(VERSION 3.4) project(franka_example_controllers) set(CMAKE_BUILD_TYPE Release) - set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(catkin REQUIRED COMPONENTS controller_interface dynamic_reconfigure + eigen_conversions franka_hw geometry_msgs hardware_interface + tf + tf_conversions message_generation pluginlib realtime_tools @@ -20,7 +22,7 @@ find_package(catkin REQUIRED COMPONENTS ) find_package(Eigen3 REQUIRED) -find_package(Franka 0.5.0 REQUIRED) +find_package(Franka 0.7.0 REQUIRED) add_message_files(FILES JointTorqueComparison.msg @@ -31,16 +33,21 @@ generate_messages() generate_dynamic_reconfigure_options( cfg/compliance_param.cfg cfg/desired_mass_param.cfg + cfg/dual_arm_compliance_param.cfg ) catkin_package( + INCLUDE_DIRS include LIBRARIES franka_example_controllers CATKIN_DEPENDS controller_interface dynamic_reconfigure + eigen_conversions franka_hw geometry_msgs hardware_interface + tf + tf_conversions message_runtime pluginlib realtime_tools @@ -58,6 +65,7 @@ add_library(franka_example_controllers src/joint_impedance_example_controller.cpp src/cartesian_impedance_example_controller.cpp src/force_example_controller.cpp + src/dual_arm_cartesian_impedance_example_controller.cpp ) add_dependencies(franka_example_controllers @@ -98,7 +106,7 @@ install(FILES franka_example_controllers_plugin.xml DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} ) catkin_install_python( - PROGRAMS scripts/interactive_marker.py scripts/move_to_start.py + PROGRAMS scripts/interactive_marker.py scripts/move_to_start.py scripts/dual_arm_interactive_marker.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} ) diff --git a/franka_example_controllers/cfg/dual_arm_compliance_param.cfg b/franka_example_controllers/cfg/dual_arm_compliance_param.cfg new file mode 100755 index 0000000000000000000000000000000000000000..ef80c4138f5093d77a42ab4b2a45070ff045545d --- /dev/null +++ b/franka_example_controllers/cfg/dual_arm_compliance_param.cfg @@ -0,0 +1,16 @@ +#!/usr/bin/env python +PACKAGE = "franka_combined_example_controllers" + +from dynamic_reconfigure.parameter_generator_catkin import * + +gen = ParameterGenerator() + +gen.add("left_translational_stiffness", double_t, 0, "Cartesian translational stiffness", 200, 0, 400) +gen.add("left_rotational_stiffness", double_t, 0, "Cartesian rotational stiffness", 10, 0, 30) +gen.add("left_nullspace_stiffness", double_t, 0, "Stiffness of the joint space nullspace controller (the desired configuration is the one at startup)", 0, 0, 100) + +gen.add("right_translational_stiffness", double_t, 0, "Cartesian translational stiffness", 200, 0, 400) +gen.add("right_rotational_stiffness", double_t, 0, "Cartesian rotational stiffness", 10, 0, 30) +gen.add("right_nullspace_stiffness", double_t, 0, "Stiffness of the joint space nullspace controller (the desired configuration is the one at startup)", 0, 0, 100) + +exit(gen.generate(PACKAGE, "dynamic_compliance", "dual_arm_compliance_param")) diff --git a/franka_example_controllers/config/franka_example_controllers.yaml b/franka_example_controllers/config/franka_example_controllers.yaml index 4b7707efc53ad63104834189ee45cdd48d510c33..b82b7efa8cebbf5563287afa94962c2ef4ca4e8b 100644 --- a/franka_example_controllers/config/franka_example_controllers.yaml +++ b/franka_example_controllers/config/franka_example_controllers.yaml @@ -92,3 +92,26 @@ cartesian_impedance_example_controller: - panda_joint5 - panda_joint6 - panda_joint7 + +dual_arm_cartesian_impedance_example_controller: + type: franka_example_controllers/DualArmCartesianImpedanceExampleController + right: + arm_id: panda_1 + joint_names: + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + left: + arm_id: panda_2 + joint_names: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 diff --git a/franka_example_controllers/franka_example_controllers_plugin.xml b/franka_example_controllers/franka_example_controllers_plugin.xml index 559a9f63eccaa1f924094702ffe1d38ec5dd9789..e770f046880f32432fe13f282b05e3f103cb2b65 100644 --- a/franka_example_controllers/franka_example_controllers_plugin.xml +++ b/franka_example_controllers/franka_example_controllers_plugin.xml @@ -36,13 +36,17 @@ </class> <class name="franka_example_controllers/CartesianImpedanceExampleController" type="franka_example_controllers::CartesianImpedanceExampleController" base_class_type="controller_interface::ControllerBase"> <description> - A controller that renders a spring damper system in cartesian space. Compliance parameters and the equilibrium pose can be modified online with dynamic reconfigure and an interactive marker respectively. + A controller that renders a spring damper system in cartesian space. Compliance parameters and the equilibrium pose can be modified online with dynamic reconfigure and an interactive marker respectively. </description> </class> <class name="franka_example_controllers/ForceExampleController" type="franka_example_controllers::ForceExampleController" base_class_type="controller_interface::ControllerBase"> <description> - A PI controller that applies a force corresponding to a user-provided desired mass in the z axis. The desired mass value can be modified online with dynamic reconfigure. + A PI controller that applies a force corresponding to a user-provided desired mass in the z axis. The desired mass value can be modified online with dynamic reconfigure. </description> </class> - + <class name="franka_example_controllers/DualArmCartesianImpedanceExampleController" type="franka_example_controllers::DualArmCartesianImpedanceExampleController" base_class_type="controller_interface::ControllerBase"> + <description> + A controller that renders Cartesian impedance based tracking of separate target poses for 2 panda arms. + </description> + </class> </library> diff --git a/franka_example_controllers/include/franka_example_controllers/dual_arm_cartesian_impedance_example_controller.h b/franka_example_controllers/include/franka_example_controllers/dual_arm_cartesian_impedance_example_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..3aab56f0206e39873acb7505c9670dafec81064b --- /dev/null +++ b/franka_example_controllers/include/franka_example_controllers/dual_arm_cartesian_impedance_example_controller.h @@ -0,0 +1,182 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include <controller_interface/multi_interface_controller.h> +#include <dynamic_reconfigure/server.h> +#include <geometry_msgs/PoseStamped.h> +#include <hardware_interface/joint_command_interface.h> +#include <hardware_interface/robot_hw.h> +#include <realtime_tools/realtime_publisher.h> +#include <ros/node_handle.h> +#include <ros/time.h> +#include <Eigen/Dense> + +#include <franka_example_controllers/dual_arm_compliance_paramConfig.h> +#include <franka_hw/franka_model_interface.h> +#include <franka_hw/franka_state_interface.h> +#include <franka_hw/trigger_rate.h> + +namespace franka_example_controllers { + +/** + * This container holds all data and parameters used to control one panda arm with a Cartesian + * impedance control law tracking a desired target pose. + */ +struct FrankaDataContainer { + std::unique_ptr<franka_hw::FrankaStateHandle> + state_handle_; ///< To read to complete robot state. + std::unique_ptr<franka_hw::FrankaModelHandle> + model_handle_; ///< To have access to e.g. jacobians. + std::vector<hardware_interface::JointHandle> joint_handles_; ///< To command joint torques. + double filter_params_{0.005}; ///< [-] PT1-Filter constant to smooth target values set + ///< by dynamic reconfigure servers (stiffness/damping) + ///< or interactive markers for the target poses. + double nullspace_stiffness_{20.0}; ///< [Nm/rad] To track the initial joint configuration in + ///< the nullspace of the Cartesian motion. + double nullspace_stiffness_target_{20.0}; ///< [Nm/rad] Unfiltered raw value. + const double delta_tau_max_{1.0}; ///< [Nm/ms] Maximum difference in joint-torque per + ///< timestep. Used to saturated torque rates to ensure + ///< feasible commands. + Eigen::Matrix<double, 6, 6> cartesian_stiffness_; ///< To track the target pose. + Eigen::Matrix<double, 6, 6> cartesian_stiffness_target_; ///< Unfiltered raw value. + Eigen::Matrix<double, 6, 6> cartesian_damping_; ///< To damp cartesian motions. + Eigen::Matrix<double, 6, 6> cartesian_damping_target_; ///< Unfiltered raw value. + Eigen::Matrix<double, 7, 1> q_d_nullspace_; ///< Target joint pose for nullspace + ///< motion. For now we track the + ///< initial joint pose. + Eigen::Vector3d position_d_; ///< Target position of the end effector. + Eigen::Quaterniond orientation_d_; ///< Target orientation of the end effector. + Eigen::Vector3d position_d_target_; ///< Unfiltered raw value. + Eigen::Quaterniond orientation_d_target_; ///< Unfiltered raw value. +}; + +/** + * Controller class for ros_control that renders two decoupled Cartesian impedances for the + * tracking of two target poses for the two endeffectors. The controller can be reparameterized at + * runtime via dynamic reconfigure servers. + */ +class DualArmCartesianImpedanceExampleController + : public controller_interface::MultiInterfaceController< + franka_hw::FrankaModelInterface, + hardware_interface::EffortJointInterface, + franka_hw::FrankaStateInterface> { + public: + /** + * Initializes the controller class to be ready to run. + * + * @param[in] robot_hw Pointer to a RobotHW class to get interfaces and resource handles. + * @param[in] node_handle Nodehandle that allows getting parameterizations from the server and + * starting subscribers. + * @return True if the controller was initialized successfully, false otherwise. + */ + bool init(hardware_interface::RobotHW* robot_hw, ros::NodeHandle& node_handle) override; + + /** + * Prepares the controller for the real-time execution. This method is executed once every time + * the controller is started and runs in real-time. + */ + void starting(const ros::Time&) override; + + /** + * Computes the control-law and commands the resulting joint torques to the robot. + * + * @param[in] period The control period (here 0.001s). + */ + void update(const ros::Time&, const ros::Duration& period) override; + + private: + std::map<std::string, FrankaDataContainer> + arms_data_; ///< Holds all relevant data for both arms. + std::string left_arm_id_; ///< Name of the left arm, retrieved from the parameter server. + std::string right_arm_id_; ///< Name of the right arm, retrieved from the parameter server. + + ///< Transformation between base frames of the robots. + Eigen::Affine3d Ol_T_Or_; // NOLINT (readability-identifier-naming) + ///< Target transformation between the two endeffectors. + Eigen::Affine3d EEr_T_EEl_; // NOLINT (readability-identifier-naming) + ///< Transformation from the centering frame to the left end effector. + Eigen::Affine3d EEl_T_C_; + + ///< Publisher for the centering tracking frame of the coordinated motion. + realtime_tools::RealtimePublisher<geometry_msgs::PoseStamped> center_frame_pub_; + ///< Rate to trigger publishing the current pose of the centering frame. + franka_hw::TriggerRate publish_rate_; + + /** + * Saturates torque commands to ensure feasibility. + * + * @param[in] arm_data The data container of the arm. + * @param[in] tau_d_calculated The raw command according to the control law. + * @param[in] tau_J_d The current desired torque, read from the robot state. + * @return The saturated torque command for the 7 joints of one arm. + */ + Eigen::Matrix<double, 7, 1> saturateTorqueRate( + const FrankaDataContainer& arm_data, + const Eigen::Matrix<double, 7, 1>& tau_d_calculated, + const Eigen::Matrix<double, 7, 1>& tau_J_d); // NOLINT (readability-identifier-naming) + + /** + * Initializes a single Panda robot arm. + * + * @param[in] robot_hw A pointer the RobotHW class for getting interfaces and resource handles. + * @param[in] arm_id The name of the panda arm. + * @param[in] joint_names The names of all joints of the panda. + * @return True if successful, false otherwise. + */ + bool initArm(hardware_interface::RobotHW* robot_hw, + const std::string& arm_id, + const std::vector<std::string>& joint_names); + + /** + * Computes the decoupled controller update for a single arm. + * + * @param[in] arm_data The data container of the arm to control. + */ + void updateArm(FrankaDataContainer& arm_data); + + /** + * Prepares all internal states to be ready to run the real-time control for one arm. + * + * @param[in] arm_data The data container of the arm to prepare for the control loop. + */ + void startingArm(FrankaDataContainer& arm_data); + + ///< Dynamic reconfigure server + std::unique_ptr<dynamic_reconfigure::Server< + franka_combined_example_controllers::dual_arm_compliance_paramConfig>> + dynamic_server_compliance_param_; + + ///< Nodehandle for the dynamic reconfigure namespace + ros::NodeHandle dynamic_reconfigure_compliance_param_node_; + + /** + * Callback for updates on the parameterization of the controller in terms of stiffnesses. + * + * @param[in] config Data container for configuration updates. + */ + void complianceParamCallback( + franka_combined_example_controllers::dual_arm_compliance_paramConfig& config, + uint32_t /*level*/); + + ///< Target pose subscriber + ros::Subscriber sub_target_pose_left_; + + /** + * Callback method that handles updates of the target poses. + * + * @param[in] msg New target pose. + */ + void targetPoseCallback(const geometry_msgs::PoseStamped::ConstPtr& msg); + + /** + * Publishes a Pose Stamped for visualization of the current centering pose. + */ + void publishCenteringPose(); +}; + +} // namespace franka_example_controllers diff --git a/franka_example_controllers/src/pseudo_inversion.h b/franka_example_controllers/include/franka_example_controllers/pseudo_inversion.h similarity index 92% rename from franka_example_controllers/src/pseudo_inversion.h rename to franka_example_controllers/include/franka_example_controllers/pseudo_inversion.h index c608b30f906c2b029b4c278f852dd60e124e9790..c820927483012b31290f30226806b70a3c917937 100644 --- a/franka_example_controllers/src/pseudo_inversion.h +++ b/franka_example_controllers/include/franka_example_controllers/pseudo_inversion.h @@ -11,6 +11,8 @@ #include <Eigen/LU> #include <Eigen/SVD> +namespace franka_example_controllers { + inline void pseudoInverse(const Eigen::MatrixXd& M_, Eigen::MatrixXd& M_pinv_, bool damped = true) { double lambda_ = damped ? 0.2 : 0.0; @@ -24,3 +26,5 @@ inline void pseudoInverse(const Eigen::MatrixXd& M_, Eigen::MatrixXd& M_pinv_, b M_pinv_ = Eigen::MatrixXd(svd.matrixV() * S_.transpose() * svd.matrixU().transpose()); } + +} // namespace franka_example_controllers diff --git a/franka_example_controllers/launch/dual_arm_cartesian_impedance_example_controller.launch b/franka_example_controllers/launch/dual_arm_cartesian_impedance_example_controller.launch new file mode 100644 index 0000000000000000000000000000000000000000..9dafe440f537a5e02f5c423c5730464a9e4ed84f --- /dev/null +++ b/franka_example_controllers/launch/dual_arm_cartesian_impedance_example_controller.launch @@ -0,0 +1,23 @@ +<?xml version="1.0" ?> +<launch> + <!--Be sure to pass the IPs of your pandas like robot_ips:={panda_1/robot_ip: <my_ip_1>, panda_2/robot_ip: <my_ip_2>} --> + <arg name="robot_ips" /> + + <arg name="robot_id" default="panda_dual" /> + <arg name="rviz" default="true" /> + <arg name="rqt" default="true" /> + + <include file="$(find franka_control)/launch/franka_combined_control.launch" > + <arg name="robot_id" value="$(arg robot_id)" /> + <arg name="robot_ips" value="$(arg robot_ips)" /> + </include> + + <group ns="$(arg robot_id)"> + <rosparam command="load" file="$(find franka_example_controllers)/config/franka_example_controllers.yaml" /> + <node name="controller_spawner" pkg="controller_manager" type="spawner" respawn="false" output="screen" args="dual_arm_cartesian_impedance_example_controller"/> + <node name="interactive_marker_left" pkg="franka_example_controllers" type="dual_arm_interactive_marker.py" + args="--right_arm_id panda_1 --left_arm_id panda_2" required="false" output="screen" /> + <node name="rqt_reconfigure" pkg="rqt_reconfigure" type="rqt_reconfigure" required="false" if="$(arg rqt)"/> + <node pkg="rviz" type="rviz" output="screen" name="rviz" args="-d $(find franka_example_controllers)/launch/rviz/franka_dual_description_with_marker.rviz" if="$(arg rviz)"/> + </group> +</launch> diff --git a/franka_example_controllers/launch/rviz/franka_dual_description_with_marker.rviz b/franka_example_controllers/launch/rviz/franka_dual_description_with_marker.rviz new file mode 100644 index 0000000000000000000000000000000000000000..bdd4b593a530fb3c89e7423473e06f7e7635fa04 --- /dev/null +++ b/franka_example_controllers/launch/rviz/franka_dual_description_with_marker.rviz @@ -0,0 +1,281 @@ +Panels: + - Class: rviz/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /RobotModel1 + - /InteractiveMarkers1 + Splitter Ratio: 0.655555546 + Tree Height: 820 + - Class: rviz/Selection + Name: Selection + - Class: rviz/Tool Properties + Expanded: + - /2D Pose Estimate1 + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.588679016 + - Class: rviz/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: "" +Toolbars: + toolButtonStyle: 2 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.0299999993 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: <Fixed Frame> + Value: true + - Class: rviz/TF + Enabled: false + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: true + Tree: + {} + Update Interval: 0 + Value: false + - Alpha: 1 + Class: rviz/RobotModel + Collision Enabled: false + Enabled: true + Links: + All Links Enabled: true + Expand Joint Details: false + Expand Link Details: false + Expand Tree: false + Link Tree Style: Links in Alphabetic Order + base: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_hand: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_leftfinger: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link0: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link1: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link2: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link3: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link4: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link5: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link6: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link7: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_link8: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_1_rightfinger: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_hand: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_leftfinger: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link0: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link1: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link2: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link3: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link4: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link5: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link6: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link7: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_link8: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + panda_2_rightfinger: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + Name: RobotModel + Robot Description: panda_dual/robot_description + TF Prefix: "" + Update Interval: 0 + Value: true + Visual Enabled: true + - Class: rviz/InteractiveMarkers + Enable Transparency: true + Enabled: true + Name: InteractiveMarkers + Show Axes: true + Show Descriptions: true + Show Visual Aids: true + Update Topic: /panda_dual/target_pose_marker/update + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Default Light: true + Fixed Frame: base + Frame Rate: 30 + Name: root + Tools: + - Class: rviz/Interact + Hide Inactive Objects: true + - Class: rviz/MoveCamera + - Class: rviz/Select + - Class: rviz/FocusCamera + - Class: rviz/Measure + - Class: rviz/SetInitialPose + Topic: /initialpose + - Class: rviz/SetGoal + Topic: /move_base_simple/goal + - Class: rviz/PublishPoint + Single click: true + Topic: /clicked_point + Value: true + Views: + Current: + Class: rviz/Orbit + Distance: 2.63399172 + Enable Stereo Rendering: + Stereo Eye Separation: 0.0599999987 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0.42157951 + Y: -0.0423400775 + Z: 1.508255 + Focal Shape Fixed Size: true + Focal Shape Size: 0.0500000007 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.00999999978 + Pitch: 0.429795891 + Target Frame: <Fixed Frame> + Value: Orbit (rviz) + Yaw: 6.16679144 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1020 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd00000004000000000000016a00000375fc020000000dfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006100fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000002800000375000000d700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000001bc000001e20000000000000000fb0000002e004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d00200053006c00690064006500720000000000ffffffff0000000000000000fb000000120020002d00200053006c00690064006500720000000000ffffffff0000000000000000fb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000001ce000001d10000000000000000fb0000000a0049006d00610067006500000001a9000001f50000000000000000000000010000010f00000365fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003f00000365000000ad00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000073f0000003bfc0100000002fb0000000800540069006d006501000000000000073f0000030000fffffffb0000000800540069006d00650100000000000004500000000000000000000005cf0000037500000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1855 + X: 65 + Y: 28 diff --git a/franka_example_controllers/package.xml b/franka_example_controllers/package.xml index a382e13c87eb07e8320ed21682350db9d58d0f9b..65636db264699a3852f5d9f3bba7f9311424c0fc 100644 --- a/franka_example_controllers/package.xml +++ b/franka_example_controllers/package.xml @@ -20,9 +20,12 @@ <depend>controller_interface</depend> <depend>dynamic_reconfigure</depend> + <depend>eigen_conversions</depend> <depend>franka_hw</depend> <depend>geometry_msgs</depend> <depend>hardware_interface</depend> + <depend>tf</depend> + <depend>tf_conversions</depend> <depend>libfranka</depend> <depend>pluginlib</depend> <depend>realtime_tools</depend> diff --git a/franka_example_controllers/scripts/dual_arm_interactive_marker.py b/franka_example_controllers/scripts/dual_arm_interactive_marker.py new file mode 100755 index 0000000000000000000000000000000000000000..d971629073c15809160441b37e53ddfc3a228286 --- /dev/null +++ b/franka_example_controllers/scripts/dual_arm_interactive_marker.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +""" This simple script creates an interactive marker for changing desired centering pose of + two the dual_panda_cartesian_impedance_example_controller. It features also resetting the + marker to current centering pose between the left and the right endeffector. +""" + +import rospy +import argparse + +from interactive_markers.interactive_marker_server import \ + InteractiveMarkerServer, InteractiveMarkerFeedback +from visualization_msgs.msg import InteractiveMarker, \ + InteractiveMarkerControl, Marker +from franka_msgs.msg import FrankaState +from geometry_msgs.msg import PoseStamped + +marker_pose = PoseStamped() + +centering_frame_ready = False + +has_error = False +left_has_error = False +right_has_error = False + +pose_pub = None + + +def make_sphere(scale=0.3): + """ + This function returns sphere marker for 3D translational movements. + :param scale: scales the size of the sphere + :return: sphere marker + """ + marker = Marker() + marker.type = Marker.SPHERE + marker.scale.x = scale * 0.45 + marker.scale.y = scale * 0.45 + marker.scale.z = scale * 0.45 + marker.color.r = 0.5 + marker.color.g = 0.5 + marker.color.b = 0.5 + marker.color.a = 1.0 + return marker + + +def publish_target_pose(): + """ + This function publishes desired centering pose which the controller will subscribe to. + :return: None + """ + marker_pose.header.stamp = rospy.Time(0) + pose_pub.publish(marker_pose) + + +def left_franka_state_callback(msg): + """ + This callback function set `has_error` variable to True if the left arm is having an error. + :param msg: FrankaState msg data + :return: None + """ + global has_error, left_has_error + if msg.robot_mode == FrankaState.ROBOT_MODE_MOVE: + left_has_error = False + else: + left_has_error = True + has_error = left_has_error or right_has_error + + +def right_franka_state_callback(msg): + """ + This callback function set `has_error` variable to True if the right arm is having an error. + :param msg: FrankaState msg data + :return: None + """ + global has_error, right_has_error + if msg.robot_mode == FrankaState.ROBOT_MODE_MOVE: + right_has_error = False + else: + right_has_error = True + has_error = left_has_error or right_has_error + + +def centering_pose_callback(msg): + """ + This callback function sets the marker pose to the current centering pose from a subscribed topic. + :return: None + """ + global centering_frame_ready + global marker_pose + + marker_pose = msg + centering_frame_ready = True + + +def reset_marker_pose_blocking(): + """ + This function resets the marker pose to current "middle pose" of left and right arm EEs. + :return: None + """ + + global centering_frame_ready + global marker_pose + + centering_frame_ready = False + + centering_frame_pose_sub = rospy.Subscriber( + "dual_arm_cartesian_impedance_example_controller/centering_frame", + PoseStamped, centering_pose_callback) + + # Get initial pose for the interactive marker + while not centering_frame_ready: + rospy.sleep(0.1) + + centering_frame_pose_sub.unregister() + + +def process_feedback(feedback): + """ + This callback function clips the marker_pose inside a predefined box to prevent misuse of the marker. + :param feedback: feedback data of interactive marker + :return: None + """ + global marker_pose + if feedback.event_type == InteractiveMarkerFeedback.POSE_UPDATE: + marker_pose.pose.position.x = feedback.pose.position.x + marker_pose.pose.position.y = feedback.pose.position.y + marker_pose.pose.position.z = feedback.pose.position.z + marker_pose.pose.orientation = feedback.pose.orientation + server.applyChanges() + + +if __name__ == "__main__": + rospy.init_node("target_pose_node") + + parser = argparse.ArgumentParser("dual_panda_interactive_marker.py") + parser.add_argument("--left_arm_id", + help="The id of the left arm.", + required=True) + parser.add_argument("--right_arm_id", + help="The id of the right arm.", + required=True) + parser.add_argument('args', nargs=argparse.REMAINDER) + args = parser.parse_args() + + # Arm IDs of left and right arms + left_arm_id = args.left_arm_id + right_arm_id = args.right_arm_id + + # Initialize subscribers for error states of the arms + left_state_sub = rospy.Subscriber(left_arm_id + "_state_controller/franka_states", + FrankaState, left_franka_state_callback) + + right_state_sub = rospy.Subscriber(right_arm_id + "_state_controller/franka_states", + FrankaState, right_franka_state_callback) + + # Set marker pose to be the current "middle pose" of both EEs + reset_marker_pose_blocking() + + # Initialize publisher for publishing desired centering pose + pose_pub = rospy.Publisher( + "dual_arm_cartesian_impedance_example_controller/centering_frame_target_pose", + PoseStamped, + queue_size=1) + + # Interactive marker settings + server = InteractiveMarkerServer("target_pose_marker") + int_marker = InteractiveMarker() + int_marker.header.frame_id = marker_pose.header.frame_id + int_marker.scale = 0.3 + int_marker.name = "centering_frame_pose" + int_marker.description = ("Target centering pose\n" + "BE CAREFUL! \n" + "If you move the target marker\n" + "both robots will follow it \n" + "as the center between the two\n" + "endeffectors. Be aware of\n" + "potential collisions!") + int_marker.pose = marker_pose.pose + + # insert a box + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 1 + control.orientation.y = 0 + control.orientation.z = 0 + control.name = "rotate_x" + control.interaction_mode = InteractiveMarkerControl.ROTATE_AXIS + int_marker.controls.append(control) + + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 1 + control.orientation.y = 0 + control.orientation.z = 0 + control.name = "move_x" + control.interaction_mode = InteractiveMarkerControl.MOVE_AXIS + int_marker.controls.append(control) + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 0 + control.orientation.y = 1 + control.orientation.z = 0 + control.name = "rotate_y" + control.interaction_mode = InteractiveMarkerControl.ROTATE_AXIS + int_marker.controls.append(control) + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 0 + control.orientation.y = 1 + control.orientation.z = 0 + control.name = "move_y" + control.interaction_mode = InteractiveMarkerControl.MOVE_AXIS + int_marker.controls.append(control) + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 0 + control.orientation.y = 0 + control.orientation.z = 1 + control.name = "rotate_z" + control.interaction_mode = InteractiveMarkerControl.ROTATE_AXIS + int_marker.controls.append(control) + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 0 + control.orientation.y = 0 + control.orientation.z = 1 + control.name = "move_z" + control.interaction_mode = InteractiveMarkerControl.MOVE_AXIS + int_marker.controls.append(control) + control = InteractiveMarkerControl() + control.orientation.w = 1 + control.orientation.x = 1 + control.orientation.y = 1 + control.orientation.z = 1 + control.name = "move_3D" + control.always_visible = True + control.markers.append(make_sphere()) + control.interaction_mode = InteractiveMarkerControl.MOVE_3D + int_marker.controls.append(control) + + server.insert(int_marker, process_feedback) + server.applyChanges() + + # main loop + while not rospy.is_shutdown(): + publish_target_pose() + if has_error: + reset_marker_pose_blocking() + publish_target_pose() + server.setPose("centering_frame_pose", marker_pose.pose) + server.applyChanges() + rospy.sleep(0.5) + rospy.sleep(0.1) diff --git a/franka_example_controllers/src/cartesian_impedance_example_controller.cpp b/franka_example_controllers/src/cartesian_impedance_example_controller.cpp index e93daf9d7937c615da602f0b1a03078641225560..525b7851de43e0ba70e75e84e9d721d9c10b950e 100644 --- a/franka_example_controllers/src/cartesian_impedance_example_controller.cpp +++ b/franka_example_controllers/src/cartesian_impedance_example_controller.cpp @@ -10,7 +10,7 @@ #include <pluginlib/class_list_macros.h> #include <ros/ros.h> -#include "pseudo_inversion.h" +#include <franka_example_controllers/pseudo_inversion.h> namespace franka_example_controllers { @@ -113,8 +113,6 @@ void CartesianImpedanceExampleController::starting(const ros::Time& /*time*/) { std::array<double, 42> jacobian_array = model_handle_->getZeroJacobian(franka::Frame::kEndEffector); // convert to eigen - Eigen::Map<Eigen::Matrix<double, 6, 7>> jacobian(jacobian_array.data()); - Eigen::Map<Eigen::Matrix<double, 7, 1>> dq_initial(initial_state.dq.data()); Eigen::Map<Eigen::Matrix<double, 7, 1>> q_initial(initial_state.q.data()); Eigen::Affine3d initial_transform(Eigen::Matrix4d::Map(initial_state.O_T_EE.data())); diff --git a/franka_example_controllers/src/dual_arm_cartesian_impedance_example_controller.cpp b/franka_example_controllers/src/dual_arm_cartesian_impedance_example_controller.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aee01b63bd1f33392754375ca340e20e1d2ed265 --- /dev/null +++ b/franka_example_controllers/src/dual_arm_cartesian_impedance_example_controller.cpp @@ -0,0 +1,412 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#include <franka_example_controllers/dual_arm_cartesian_impedance_example_controller.h> + +#include <cmath> +#include <functional> +#include <memory> + +#include <controller_interface/controller_base.h> +#include <eigen_conversions/eigen_msg.h> +#include <franka/robot_state.h> +#include <franka_example_controllers/pseudo_inversion.h> +#include <franka_hw/trigger_rate.h> +#include <geometry_msgs/PoseStamped.h> +#include <pluginlib/class_list_macros.h> +#include <ros/ros.h> +#include <ros/transport_hints.h> +#include <tf/transform_listener.h> +#include <tf_conversions/tf_eigen.h> + +namespace franka_example_controllers { + +bool DualArmCartesianImpedanceExampleController::initArm( + hardware_interface::RobotHW* robot_hw, + const std::string& arm_id, + const std::vector<std::string>& joint_names) { + FrankaDataContainer arm_data; + auto* model_interface = robot_hw->get<franka_hw::FrankaModelInterface>(); + if (model_interface == nullptr) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Error getting model interface from hardware"); + return false; + } + try { + arm_data.model_handle_ = std::make_unique<franka_hw::FrankaModelHandle>( + model_interface->getHandle(arm_id + "_model")); + } catch (hardware_interface::HardwareInterfaceException& ex) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Exception getting model handle from " + "interface: " + << ex.what()); + return false; + } + + auto* state_interface = robot_hw->get<franka_hw::FrankaStateInterface>(); + if (state_interface == nullptr) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Error getting state interface from hardware"); + return false; + } + try { + arm_data.state_handle_ = std::make_unique<franka_hw::FrankaStateHandle>( + state_interface->getHandle(arm_id + "_robot")); + } catch (hardware_interface::HardwareInterfaceException& ex) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Exception getting state handle from " + "interface: " + << ex.what()); + return false; + } + + auto* effort_joint_interface = robot_hw->get<hardware_interface::EffortJointInterface>(); + if (effort_joint_interface == nullptr) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Error getting effort joint interface from " + "hardware"); + return false; + } + for (size_t i = 0; i < 7; ++i) { + try { + arm_data.joint_handles_.push_back(effort_joint_interface->getHandle(joint_names[i])); + } catch (const hardware_interface::HardwareInterfaceException& ex) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Exception getting joint handles: " + << ex.what()); + return false; + } + } + + arm_data.position_d_.setZero(); + arm_data.orientation_d_.coeffs() << 0.0, 0.0, 0.0, 1.0; + arm_data.position_d_target_.setZero(); + arm_data.orientation_d_target_.coeffs() << 0.0, 0.0, 0.0, 1.0; + + arm_data.cartesian_stiffness_.setZero(); + arm_data.cartesian_damping_.setZero(); + + arms_data_.emplace(std::make_pair(arm_id, std::move(arm_data))); + + return true; +} + +bool DualArmCartesianImpedanceExampleController::init(hardware_interface::RobotHW* robot_hw, + ros::NodeHandle& node_handle) { + std::vector<double> cartesian_stiffness_vector; + std::vector<double> cartesian_damping_vector; + + if (!node_handle.getParam("left/arm_id", left_arm_id_)) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Could not read parameter left_arm_id_"); + return false; + } + std::vector<std::string> left_joint_names; + if (!node_handle.getParam("left/joint_names", left_joint_names) || left_joint_names.size() != 7) { + ROS_ERROR( + "DualArmCartesianImpedanceExampleController: Invalid or no left_joint_names parameters " + "provided, " + "aborting controller init!"); + return false; + } + + if (!node_handle.getParam("right/arm_id", right_arm_id_)) { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Could not read parameter right_arm_id_"); + return false; + } + + boost::function<void(const geometry_msgs::PoseStamped::ConstPtr&)> callback = + boost::bind(&DualArmCartesianImpedanceExampleController::targetPoseCallback, this, _1); + + ros::SubscribeOptions subscribe_options; + subscribe_options.init("centering_frame_target_pose", 1, callback); + subscribe_options.transport_hints = ros::TransportHints().reliable().tcpNoDelay(); + sub_target_pose_left_ = node_handle.subscribe(subscribe_options); + + std::vector<std::string> right_joint_names; + if (!node_handle.getParam("right/joint_names", right_joint_names) || + right_joint_names.size() != 7) { + ROS_ERROR( + "DualArmCartesianImpedanceExampleController: Invalid or no right_joint_names parameters " + "provided, " + "aborting controller init!"); + return false; + } + + bool left_success = initArm(robot_hw, left_arm_id_, left_joint_names); + bool right_success = initArm(robot_hw, right_arm_id_, right_joint_names); + + dynamic_reconfigure_compliance_param_node_ = + ros::NodeHandle("dynamic_reconfigure_compliance_param_node"); + + dynamic_server_compliance_param_ = std::make_unique<dynamic_reconfigure::Server< + franka_combined_example_controllers::dual_arm_compliance_paramConfig>>( + dynamic_reconfigure_compliance_param_node_); + + dynamic_server_compliance_param_->setCallback(boost::bind( + &DualArmCartesianImpedanceExampleController::complianceParamCallback, this, _1, _2)); + + // Get the transformation from right_O_frame to left_O_frame + tf::StampedTransform transform; + tf::TransformListener listener; + try { + if (listener.waitForTransform(left_arm_id_ + "_link0", right_arm_id_ + "_link0", ros::Time(0), + ros::Duration(4.0))) { + listener.lookupTransform(left_arm_id_ + "_link0", right_arm_id_ + "_link0", ros::Time(0), + transform); + } else { + ROS_ERROR( + "DualArmCartesianImpedanceExampleController: Failed to read transform from %s to %s. " + "Aborting init!", + (right_arm_id_ + "_link0").c_str(), (left_arm_id_ + "_link0").c_str()); + return false; + } + } catch (tf::TransformException& ex) { + ROS_ERROR("DualArmCartesianImpedanceExampleController: %s", ex.what()); + return false; + } + tf::transformTFToEigen(transform, Ol_T_Or_); // NOLINT (readability-identifier-naming) + + // Setup publisher for the centering frame. + publish_rate_ = franka_hw::TriggerRate(30.0); + center_frame_pub_.init(node_handle, "centering_frame", 1, true); + + return left_success && right_success; +} + +void DualArmCartesianImpedanceExampleController::startingArm(FrankaDataContainer& arm_data) { + // compute initial velocity with jacobian and set x_attractor and q_d_nullspace + // to initial configuration + franka::RobotState initial_state = arm_data.state_handle_->getRobotState(); + // get jacobian + std::array<double, 42> jacobian_array = + arm_data.model_handle_->getZeroJacobian(franka::Frame::kEndEffector); + // convert to eigen + Eigen::Map<Eigen::Matrix<double, 6, 7>> jacobian(jacobian_array.data()); + Eigen::Map<Eigen::Matrix<double, 7, 1>> dq_initial(initial_state.dq.data()); + Eigen::Map<Eigen::Matrix<double, 7, 1>> q_initial(initial_state.q.data()); + Eigen::Affine3d initial_transform(Eigen::Matrix4d::Map(initial_state.O_T_EE.data())); + + // set target point to current state + arm_data.position_d_ = initial_transform.translation(); + arm_data.orientation_d_ = Eigen::Quaterniond(initial_transform.linear()); + arm_data.position_d_target_ = initial_transform.translation(); + arm_data.orientation_d_target_ = Eigen::Quaterniond(initial_transform.linear()); + + // set nullspace target configuration to initial q + arm_data.q_d_nullspace_ = q_initial; +} + +void DualArmCartesianImpedanceExampleController::starting(const ros::Time& /*time*/) { + for (auto& arm_data : arms_data_) { + startingArm(arm_data.second); + } + franka::RobotState robot_state_right = + arms_data_.at(right_arm_id_).state_handle_->getRobotState(); + franka::RobotState robot_state_left = arms_data_.at(left_arm_id_).state_handle_->getRobotState(); + Eigen::Affine3d Ol_T_EEl(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming) + robot_state_left.O_T_EE.data())); // NOLINT (readability-identifier-naming) + Eigen::Affine3d Or_T_EEr(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming) + robot_state_right.O_T_EE.data())); // NOLINT (readability-identifier-naming) + EEr_T_EEl_ = + Or_T_EEr.inverse() * Ol_T_Or_.inverse() * Ol_T_EEl; // NOLINT (readability-identifier-naming) + EEl_T_C_.setIdentity(); + Eigen::Vector3d EEr_r_EEr_EEl = // NOLINT (readability-identifier-naming) + EEr_T_EEl_.translation(); // NOLINT (readability-identifier-naming) + EEl_T_C_.translation() = -0.5 * EEr_T_EEl_.inverse().rotation() * EEr_r_EEr_EEl; +} + +void DualArmCartesianImpedanceExampleController::update(const ros::Time& /*time*/, + const ros::Duration& /*period*/) { + for (auto& arm_data : arms_data_) { + updateArm(arm_data.second); + } + if (publish_rate_()) { + publishCenteringPose(); + } +} + +void DualArmCartesianImpedanceExampleController::updateArm(FrankaDataContainer& arm_data) { + // get state variables + franka::RobotState robot_state = arm_data.state_handle_->getRobotState(); + std::array<double, 49> inertia_array = arm_data.model_handle_->getMass(); + std::array<double, 7> coriolis_array = arm_data.model_handle_->getCoriolis(); + std::array<double, 42> jacobian_array = + arm_data.model_handle_->getZeroJacobian(franka::Frame::kEndEffector); + + // convert to Eigen + Eigen::Map<Eigen::Matrix<double, 7, 1>> coriolis(coriolis_array.data()); + Eigen::Map<Eigen::Matrix<double, 6, 7>> jacobian(jacobian_array.data()); + Eigen::Map<Eigen::Matrix<double, 7, 1>> q(robot_state.q.data()); + Eigen::Map<Eigen::Matrix<double, 7, 1>> dq(robot_state.dq.data()); + Eigen::Map<Eigen::Matrix<double, 7, 1>> tau_J_d( // NOLINT (readability-identifier-naming) + robot_state.tau_J_d.data()); + Eigen::Affine3d transform(Eigen::Matrix4d::Map(robot_state.O_T_EE.data())); + Eigen::Vector3d position(transform.translation()); + Eigen::Quaterniond orientation(transform.linear()); + + // compute error to desired pose + // position error + Eigen::Matrix<double, 6, 1> error; + error.head(3) << position - arm_data.position_d_; + + // orientation error + if (arm_data.orientation_d_.coeffs().dot(orientation.coeffs()) < 0.0) { + orientation.coeffs() << -orientation.coeffs(); + } + // "difference" quaternion + Eigen::Quaterniond error_quaternion(orientation * arm_data.orientation_d_.inverse()); + // convert to axis angle + Eigen::AngleAxisd error_quaternion_angle_axis(error_quaternion); + // compute "orientation error" + error.tail(3) << error_quaternion_angle_axis.axis() * error_quaternion_angle_axis.angle(); + + // compute control + // allocate variables + Eigen::VectorXd tau_task(7), tau_nullspace(7), tau_d(7); + + // pseudoinverse for nullspace handling + // kinematic pseuoinverse + Eigen::MatrixXd jacobian_transpose_pinv; + franka_example_controllers::pseudoInverse(jacobian.transpose(), jacobian_transpose_pinv); + + // Cartesian PD control with damping ratio = 1 + tau_task << jacobian.transpose() * (-arm_data.cartesian_stiffness_ * error - + arm_data.cartesian_damping_ * (jacobian * dq)); + // nullspace PD control with damping ratio = 1 + tau_nullspace << (Eigen::MatrixXd::Identity(7, 7) - + jacobian.transpose() * jacobian_transpose_pinv) * + (arm_data.nullspace_stiffness_ * (arm_data.q_d_nullspace_ - q) - + (2.0 * sqrt(arm_data.nullspace_stiffness_)) * dq); + // Desired torque + tau_d << tau_task + tau_nullspace + coriolis; + // Saturate torque rate to avoid discontinuities + tau_d << saturateTorqueRate(arm_data, tau_d, tau_J_d); + for (size_t i = 0; i < 7; ++i) { + arm_data.joint_handles_[i].setCommand(tau_d(i)); + } + + // update parameters changed online either through dynamic reconfigure or through the interactive + // target by filtering + arm_data.cartesian_stiffness_ = arm_data.filter_params_ * arm_data.cartesian_stiffness_target_ + + (1.0 - arm_data.filter_params_) * arm_data.cartesian_stiffness_; + arm_data.cartesian_damping_ = arm_data.filter_params_ * arm_data.cartesian_damping_target_ + + (1.0 - arm_data.filter_params_) * arm_data.cartesian_damping_; + arm_data.nullspace_stiffness_ = arm_data.filter_params_ * arm_data.nullspace_stiffness_target_ + + (1.0 - arm_data.filter_params_) * arm_data.nullspace_stiffness_; + arm_data.position_d_ = arm_data.filter_params_ * arm_data.position_d_target_ + + (1.0 - arm_data.filter_params_) * arm_data.position_d_; + arm_data.orientation_d_ = + arm_data.orientation_d_.slerp(arm_data.filter_params_, arm_data.orientation_d_target_); +} + +Eigen::Matrix<double, 7, 1> DualArmCartesianImpedanceExampleController::saturateTorqueRate( + const FrankaDataContainer& arm_data, + const Eigen::Matrix<double, 7, 1>& tau_d_calculated, + const Eigen::Matrix<double, 7, 1>& tau_J_d) { // NOLINT (readability-identifier-naming) + Eigen::Matrix<double, 7, 1> tau_d_saturated{}; + for (size_t i = 0; i < 7; i++) { + double difference = tau_d_calculated[i] - tau_J_d[i]; + tau_d_saturated[i] = tau_J_d[i] + std::max(std::min(difference, arm_data.delta_tau_max_), + -arm_data.delta_tau_max_); + } + return tau_d_saturated; +} + +void DualArmCartesianImpedanceExampleController::complianceParamCallback( + franka_combined_example_controllers::dual_arm_compliance_paramConfig& config, + uint32_t /*level*/) { + auto& left_arm_data = arms_data_.at(left_arm_id_); + left_arm_data.cartesian_stiffness_target_.setIdentity(); + left_arm_data.cartesian_stiffness_target_.topLeftCorner(3, 3) + << config.left_translational_stiffness * Eigen::Matrix3d::Identity(); + left_arm_data.cartesian_stiffness_target_.bottomRightCorner(3, 3) + << config.left_rotational_stiffness * Eigen::Matrix3d::Identity(); + left_arm_data.cartesian_damping_target_.setIdentity(); + + left_arm_data.cartesian_damping_target_.topLeftCorner(3, 3) + << 2 * sqrt(config.left_translational_stiffness) * Eigen::Matrix3d::Identity(); + left_arm_data.cartesian_damping_target_.bottomRightCorner(3, 3) + << 2 * sqrt(config.left_rotational_stiffness) * Eigen::Matrix3d::Identity(); + left_arm_data.nullspace_stiffness_target_ = config.left_nullspace_stiffness; + + auto& right_arm_data = arms_data_.at(right_arm_id_); + right_arm_data.cartesian_stiffness_target_.setIdentity(); + right_arm_data.cartesian_stiffness_target_.topLeftCorner(3, 3) + << config.right_translational_stiffness * Eigen::Matrix3d::Identity(); + right_arm_data.cartesian_stiffness_target_.bottomRightCorner(3, 3) + << config.right_rotational_stiffness * Eigen::Matrix3d::Identity(); + right_arm_data.cartesian_damping_target_.setIdentity(); + + right_arm_data.cartesian_damping_target_.topLeftCorner(3, 3) + << 2 * sqrt(config.right_translational_stiffness) * Eigen::Matrix3d::Identity(); + right_arm_data.cartesian_damping_target_.bottomRightCorner(3, 3) + << 2 * sqrt(config.right_rotational_stiffness) * Eigen::Matrix3d::Identity(); + right_arm_data.nullspace_stiffness_target_ = config.right_nullspace_stiffness; +} + +void DualArmCartesianImpedanceExampleController::targetPoseCallback( + const geometry_msgs::PoseStamped::ConstPtr& msg) { + try { + if (msg->header.frame_id != left_arm_id_ + "_link0") { + ROS_ERROR_STREAM( + "DualArmCartesianImpedanceExampleController: Got pose target with invalid" + " frame_id " + << msg->header.frame_id << ". Expected " << left_arm_id_ + "_link0"); + return; + } + + // Set target for the left robot. + auto& left_arm_data = arms_data_.at(left_arm_id_); + Eigen::Affine3d Ol_T_C; // NOLINT (readability-identifier-naming) + tf::poseMsgToEigen(msg->pose, Ol_T_C); + Eigen::Affine3d Ol_T_EEl_d = // NOLINT (readability-identifier-naming) + Ol_T_C * EEl_T_C_.inverse(); // NOLINT (readability-identifier-naming) + left_arm_data.position_d_target_ = Ol_T_EEl_d.translation(); + Eigen::Quaterniond last_orientation_d_target(left_arm_data.orientation_d_target_); + Eigen::Quaterniond new_orientation_target(Ol_T_EEl_d.linear()); + if (last_orientation_d_target.coeffs().dot(new_orientation_target.coeffs()) < 0.0) { + new_orientation_target.coeffs() << -new_orientation_target.coeffs(); + } + Ol_T_EEl_d.linear() = new_orientation_target.matrix(); + left_arm_data.orientation_d_target_ = Ol_T_EEl_d.linear(); + + // Compute target for the right endeffector given the static desired transform from left to + // right endeffector. + Eigen::Affine3d Or_T_EEr_d = Ol_T_Or_.inverse() // NOLINT (readability-identifier-naming) + * Ol_T_EEl_d * // NOLINT (readability-identifier-naming) + EEr_T_EEl_.inverse(); // NOLINT (readability-identifier-naming) + auto& right_arm_data = arms_data_.at(right_arm_id_); + right_arm_data.position_d_target_ = + Or_T_EEr_d.translation(); // NOLINT (readability-identifier-naming) + last_orientation_d_target = Eigen::Quaterniond(right_arm_data.orientation_d_target_); + right_arm_data.orientation_d_target_ = + Or_T_EEr_d.linear(); // NOLINT (readability-identifier-naming) + if (last_orientation_d_target.coeffs().dot(right_arm_data.orientation_d_target_.coeffs()) < + 0.0) { + right_arm_data.orientation_d_target_.coeffs() + << -right_arm_data.orientation_d_target_.coeffs(); + } + + } catch (std::out_of_range& ex) { + ROS_ERROR_STREAM("DualArmCartesianImpedanceExampleController: Exception setting target poses."); + } +} + +void DualArmCartesianImpedanceExampleController::publishCenteringPose() { + if (center_frame_pub_.trylock()) { + franka::RobotState robot_state_left = + arms_data_.at(left_arm_id_).state_handle_->getRobotState(); + Eigen::Affine3d Ol_T_EEl(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming) + robot_state_left.O_T_EE.data())); // NOLINT (readability-identifier-naming) + Eigen::Affine3d Ol_T_C = Ol_T_EEl * EEl_T_C_; // NOLINT (readability-identifier-naming) + tf::poseEigenToMsg(Ol_T_C, center_frame_pub_.msg_.pose); + center_frame_pub_.msg_.header.frame_id = left_arm_id_ + "_link0"; + center_frame_pub_.unlockAndPublish(); + } +} + +} // namespace franka_example_controllers + +PLUGINLIB_EXPORT_CLASS(franka_example_controllers::DualArmCartesianImpedanceExampleController, + controller_interface::ControllerBase) diff --git a/franka_gripper/CMakeLists.txt b/franka_gripper/CMakeLists.txt index 38070193399771b33f4ab6ac43cecb56e05dbdf8..14dd8524315cf9d7aa8d2b446d93010545fa1096 100644 --- a/franka_gripper/CMakeLists.txt +++ b/franka_gripper/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(catkin REQUIRED COMPONENTS actionlib_msgs ) -find_package(Franka 0.5.0 REQUIRED) +find_package(Franka 0.7.0 REQUIRED) add_action_files( DIRECTORY action diff --git a/franka_gripper/config/franka_gripper_node.yaml b/franka_gripper/config/franka_gripper_node.yaml index 195ec8f399bacbcdf43994ab03b5d9bc8694a023..687e5d3db2cbfb5afd3c3525e3b6f3efb5ca7180 100644 --- a/franka_gripper/config/franka_gripper_node.yaml +++ b/franka_gripper/config/franka_gripper_node.yaml @@ -1,7 +1,4 @@ publish_rate: 30 # [Hz] -joint_names: - - panda_finger_joint1 - - panda_finger_joint2 default_speed: 0.1 # [m/s] default_grasp_epsilon: inner: 0.005 # [m] diff --git a/franka_gripper/launch/franka_gripper.launch b/franka_gripper/launch/franka_gripper.launch index 049bc3d54d434806f6c8abdc04cb7dbaff8f5fb2..d56656352efaf03356d7d3a82ccfe528f2003013 100644 --- a/franka_gripper/launch/franka_gripper.launch +++ b/franka_gripper/launch/franka_gripper.launch @@ -1,9 +1,13 @@ <?xml version="1.0" ?> <launch> <arg name="robot_ip" /> + <arg name="arm_id" default="panda" /> + <arg name="joint_names" default="[$(arg arm_id)_finger_joint1, $(arg arm_id)_finger_joint2]" /> <node name="franka_gripper" pkg="franka_gripper" type="franka_gripper_node" output="screen"> <param name="robot_ip" value="$(arg robot_ip)"/> <rosparam command="load" file="$(find franka_gripper)/config/franka_gripper_node.yaml" /> + <rosparam param="joint_names" subst_value="true">$(arg joint_names)</rosparam> </node> + </launch> diff --git a/franka_hw/CMakeLists.txt b/franka_hw/CMakeLists.txt index db3f0916e77478974fbd3e4cbd2f615471d83665..417b816f8f529274f51081db88926bfc0b32bba9 100644 --- a/franka_hw/CMakeLists.txt +++ b/franka_hw/CMakeLists.txt @@ -5,25 +5,42 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(catkin REQUIRED COMPONENTS + actionlib controller_interface + combined_robot_hw hardware_interface joint_limits_interface roscpp + pluginlib urdf + franka_msgs + message_generation ) -find_package(Franka 0.5.0 REQUIRED) +find_package(Franka 0.7.0 REQUIRED) catkin_package( INCLUDE_DIRS include - LIBRARIES franka_hw - CATKIN_DEPENDS controller_interface hardware_interface joint_limits_interface roscpp urdf + LIBRARIES franka_hw franka_control_services + CATKIN_DEPENDS + actionlib_msgs + actionlib + controller_interface + combined_robot_hw + hardware_interface + joint_limits_interface + roscpp + pluginlib + urdf + franka_msgs DEPENDS Franka ) add_library(franka_hw src/control_mode.cpp src/franka_hw.cpp + src/franka_combinable_hw.cpp + src/franka_combined_hw.cpp src/resource_helpers.cpp src/trigger_rate.cpp ) @@ -36,6 +53,7 @@ add_dependencies(franka_hw target_link_libraries(franka_hw ${Franka_LIBRARIES} ${catkin_LIBRARIES} + franka_control_services ) target_include_directories(franka_hw SYSTEM PUBLIC @@ -56,6 +74,29 @@ install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} ) +## franka_control_services +add_library(franka_control_services + src/services.cpp +) + +add_dependencies(franka_control_services + ${${PROJECT_NAME}_EXPORTED_TARGETS} + ${catkin_EXPORTED_TARGETS} +) + +target_link_libraries(franka_control_services + ${Franka_LIBRARIES} + ${catkin_LIBRARIES} +) + +target_include_directories(franka_control_services SYSTEM PUBLIC + ${Franka_INCLUDE_DIRS} + ${catkin_INCLUDE_DIRS} +) +target_include_directories(franka_control_services PUBLIC + include +) + if(CATKIN_ENABLE_TESTING) add_subdirectory(test) endif() diff --git a/franka_hw/franka_combinable_hw_plugin.xml b/franka_hw/franka_combinable_hw_plugin.xml new file mode 100644 index 0000000000000000000000000000000000000000..12763ff6b121246a221708af40c67016e5cde51b --- /dev/null +++ b/franka_hw/franka_combinable_hw_plugin.xml @@ -0,0 +1,7 @@ +<library path="lib/libfranka_hw"> + <class name="franka_hw/FrankaCombinableHW" type="franka_hw::FrankaCombinableHW" base_class_type="hardware_interface::RobotHW"> + <description> + A robot hardware interface class for franka robots that is combinable with other FrankaCombinableHW. + </description> + </class> +</library> diff --git a/franka_hw/include/franka_hw/franka_combinable_hw.h b/franka_hw/include/franka_hw/franka_combinable_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..bc2dffd8da5f550d041bf5204b9d318fe3f20f3b --- /dev/null +++ b/franka_hw/include/franka_hw/franka_combinable_hw.h @@ -0,0 +1,170 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#pragma once + +#include <atomic> +#include <functional> +#include <memory> +#include <string> +#include <thread> + +#include <franka/exception.h> +#include <franka/robot_state.h> + +#include <actionlib/server/simple_action_server.h> +#include <franka_hw/franka_hw.h> +#include <franka_hw/services.h> +#include <franka_msgs/ErrorRecoveryAction.h> +#include <ros/ros.h> + +namespace franka_hw { + +/** + * A hardware class for a Panda robot based on the ros_control framework. + * This class is ready to be combined with other hardware classes e.g. to + * control multiple robots from a single controller. + * Note: This class allows for torque (effort) control only due to the lack of synchronization + * between master controllers of different robots. For more information see the documentation at + * https://frankaemika.github.io/docs/franka_ros.html . + */ +class FrankaCombinableHW : public FrankaHW { + public: + /** + * Creates an instance of FrankaCombinableHW. + */ + FrankaCombinableHW(); + + /** + * Initializes the class in terms of ros_control interfaces. + * Note: You have to call initParameters beforehand. Use the complete initialization routine + * \ref init() method to control robots. + * + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + * @return True if successful, false otherwise. + */ + void initROSInterfaces(ros::NodeHandle& robot_hw_nh) override; + + /** + * Runs the currently active controller in a realtime loop. If no controller is active, the + * function immediately exits. + * + * @param[in] ros_callback A callback function that is executed at each time step. + * + * @throw franka::ControlException if an error related to torque control occurred. + * @throw franka::InvalidOperationException if a conflicting operation is already running. + * @throw franka::NetworkException if the connection is lost, e.g. after a timeout. + * @throw franka::RealtimeException if realtime priority cannot be set for the current thread. + */ + void control(const std::function<bool(const ros::Time&, const ros::Duration&)>& + ros_callback = // NOLINT (google-default-arguments) + [](const ros::Time&, const ros::Duration&) { + return true; + }) const override; // NOLINT (google-default-arguments) + + /** + * Checks whether a requested controller can be run, based on the resources and interfaces it + * claims. Note: FrankaCombinableHW allows torque control only. + * + * @param[in] info Controllers to be running at the same time. + * + * @return True in case of a conflict, false in case of valid controllers. + */ + bool checkForConflict(const std::list<hardware_interface::ControllerInfo>& info) const override; + + /** + * Reads data from the franka robot. + * + * @param[in] time The current time. Not used in this class. + * @param[in] period The time passed since the last call to \ref read. Not used in this class. + */ + void read(const ros::Time& /*time*/, const ros::Duration& /*period*/) override; + + /** + * Writes data to the franka robot. + * + * @param[in] time The current time. Not used in this class. + * @param[in] period The time passed since the last call to \ref write. + */ + void write(const ros::Time& /*time*/, const ros::Duration& period) override; + + /** + * Getter method for the arm_id which is used to distinguish between multiple + * instances of FrankaCombinableHW. + * + * @return A copy of the arm_id string identifying the class instance. + */ + std::string getArmID() const noexcept; + + /** + * Triggers a stop of the controlLoop. This interface is used to stop all combined + * robots together when at one robot encounters an error. + */ + void triggerError(); + + /** + * Getter for the error flag of the class. + * + * @return True in case of an error false otherwise. + */ + bool hasError() const noexcept; + + /** + * Recovers the libfranka robot, resets the error flag and publishes the error state. + */ + void resetError(); + + /** + * Returns whether the controller needs to be reset e.g. after error recovery. + * + * @return A copy of the controller_needs_reset flag. + */ + bool controllerNeedsReset() const noexcept; + + private: + template <typename T> + T libfrankaUpdateCallback(const T& command, + const franka::RobotState& robot_state, + franka::Duration time_step) { + if (commandHasNaN(command)) { + std::string error_message = "FrankaCombinableHW: Got NaN value in command!"; + ROS_FATAL("%s", error_message.c_str()); + throw std::invalid_argument(error_message); + } + checkJointLimits(); + { + std::lock_guard<std::mutex> state_lock(libfranka_state_mutex_); + robot_state_libfranka_ = robot_state; + } + + std::lock_guard<std::mutex> command_lock(libfranka_cmd_mutex_); + T current_cmd = command; + if (has_error_ || !controller_active_) { + return franka::MotionFinished(current_cmd); + } + return current_cmd; + } + + void publishErrorState(bool error); + + void setupServicesAndActionServers(ros::NodeHandle& node_handle); + + void initRobot() override; + + bool setRunFunction(const ControlMode& requested_control_mode, + bool limit_rate, + double cutoff_frequency, + franka::ControllerMode internal_controller) override; + + void controlLoop(); + + std::unique_ptr<std::thread> control_loop_thread_; + ServiceContainer services_; + std::unique_ptr<actionlib::SimpleActionServer<franka_msgs::ErrorRecoveryAction>> + recovery_action_server_; + std::atomic_bool has_error_{false}; + ros::Publisher has_error_pub_; + std::atomic_bool error_recovered_{false}; + std::atomic_bool controller_needs_reset_{false}; +}; + +} // namespace franka_hw diff --git a/franka_hw/include/franka_hw/franka_combined_hw.h b/franka_hw/include/franka_hw/franka_combined_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..3072f31429090fad85c805134dddd4b5845edd7e --- /dev/null +++ b/franka_hw/include/franka_hw/franka_combined_hw.h @@ -0,0 +1,64 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#pragma once + +#include <combined_robot_hw/combined_robot_hw.h> +#include <franka_hw/franka_combinable_hw.h> +#include <franka_msgs/ErrorRecoveryAction.h> + +#include <actionlib/server/simple_action_server.h> +#include <ros/node_handle.h> +#include <ros/time.h> + +#include <memory> + +namespace franka_hw { + +class FrankaCombinedHW : public combined_robot_hw::CombinedRobotHW { + public: + /** + * Creates an instance of CombinedFrankaHW. + * + */ + FrankaCombinedHW(); + + ~FrankaCombinedHW() override = default; // NOLINT (clang-analyzer-optin.cplusplus.VirtualCall) + + /** + * The init function is called to initialize the CombinedFrankaHW from a + * non-realtime thread. + * + * @param[in] root_nh A NodeHandle in the root of the caller namespace. + * @param[in] robot_hw_nh A NodeHandle in the namespace from which the RobotHW. + * should read its configuration. + * @return True if initialization was successful. + */ + bool init(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh) override; + + /** + * Reads data from the robot HW + * + * @param[in] time The current time. + * @param[in] period The time passed since the last call to \ref read. + */ + void read(const ros::Time& time, const ros::Duration& period) override; + + /** + * Checks whether the controller needs to be reset. + * + * @return True if the controllers needs to be reset, false otherwise. + */ + bool controllerNeedsReset(); + + protected: + std::unique_ptr<actionlib::SimpleActionServer<franka_msgs::ErrorRecoveryAction>> + combined_recovery_action_server_; + + private: + void handleError(); + bool hasError(); + void triggerError(); + bool is_recovering_{false}; +}; + +} // namespace franka_hw diff --git a/franka_hw/include/franka_hw/franka_hw.h b/franka_hw/include/franka_hw/franka_hw.h index 3950d81fbca89d91b2ef96dff788c26021b5acef..2b6ee5eb2b17be4d2dd44bf9493e29647c00daf0 100644 --- a/franka_hw/include/franka_hw/franka_hw.h +++ b/franka_hw/include/franka_hw/franka_hw.h @@ -4,77 +4,87 @@ #include <array> #include <atomic> +#include <exception> #include <functional> +#include <list> #include <string> +#include <franka/control_types.h> #include <franka/duration.h> #include <franka/model.h> +#include <franka/rate_limiting.h> #include <franka/robot.h> #include <franka/robot_state.h> -#include <franka_hw/control_mode.h> -#include <franka_hw/franka_cartesian_command_interface.h> -#include <franka_hw/franka_model_interface.h> -#include <franka_hw/franka_state_interface.h> #include <hardware_interface/joint_command_interface.h> #include <hardware_interface/joint_state_interface.h> #include <hardware_interface/robot_hw.h> #include <joint_limits_interface/joint_limits_interface.h> +#include <joint_limits_interface/joint_limits_urdf.h> #include <ros/time.h> #include <urdf/model.h> +#include <franka_hw/control_mode.h> +#include <franka_hw/franka_cartesian_command_interface.h> +#include <franka_hw/franka_model_interface.h> +#include <franka_hw/franka_state_interface.h> +#include <franka_hw/resource_helpers.h> + namespace franka_hw { +/** + * This class wraps the functionality of libfranka for controlling Panda robots into the ros_control + * framework. + */ class FrankaHW : public hardware_interface::RobotHW { public: - FrankaHW() = delete; - - /** - * Creates an instance of FrankaHW that does not provide a model interface. - * - * @param[in] joint_names An array of joint names being controlled. - * @param[in] arm_id Unique identifier for the robot being controlled. - * @param[in] urdf_model A URDF model to initialize joint limits from. - * @param[in] get_limit_rate Getter that should return true if the rate limiter - * should be used, false otherwise. Defaults to true. - * @param[in] get_cutoff_frequency Getter for cutoff frequency for the low-pass filter. - * Defaults to franka::kDefaultCutoffFrequency. - * @param[in] get_internal_controller Getter for an internal controller to - * be used for control loops using only motion generation. Defaults to joint impedance. - */ - FrankaHW(const std::array<std::string, 7>& joint_names, - const std::string& arm_id, - const urdf::Model& urdf_model, - std::function<bool()> get_limit_rate = []() { return true; }, - std::function<double()> get_cutoff_frequency = - []() { return franka::kDefaultCutoffFrequency; }, - std::function<franka::ControllerMode()> get_internal_controller = - []() { return franka::ControllerMode::kJointImpedance; }); - - /** - * Creates an instance of FrankaHW that provides a model interface. - * - * @param[in] joint_names An array of joint names being controlled. - * @param[in] arm_id Unique identifier for the robot being controlled. - * @param[in] urdf_model A URDF model to initialize joint limits from. - * @param[in] model Robot model. - * @param[in] get_limit_rate Getter that should return true if the rate limiter - * should be used, false otherwise. Defaults to true. - * @param[in] get_cutoff_frequency Getter for cutoff frequency for the low-pass filter. - * Defaults to franka::kDefaultCutoffFrequency. - * @param[in] get_internal_controller Getter for an internal controller to - * be used for control loops using only motion generation. Defaults to joint impedance. - */ - FrankaHW(const std::array<std::string, 7>& joint_names, - const std::string& arm_id, - const urdf::Model& urdf_model, - franka::Model& model, - std::function<bool()> get_limit_rate = []() { return true; }, - std::function<double()> get_cutoff_frequency = - []() { return franka::kDefaultCutoffFrequency; }, - std::function<franka::ControllerMode()> get_internal_controller = - []() { return franka::ControllerMode::kJointImpedance; }); - - ~FrankaHW() override = default; + /** + * Default constructor. + * Note: Be sure to call the init() method before operation. + */ + FrankaHW(); + + virtual ~FrankaHW() override = default; + + /** + * Initializes the FrankaHW class to be fully operational. This involves parsing required + * configurations from the ROS parameter server, connecting to the robot and setting up interfaces + * for the ros_control framework. + * + * @param[in] root_nh A node handle in the root namespace of the control node. + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + * + * @return True if successful, false otherwise. + */ + virtual bool init(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh) override; + + /** + * Reads the parameterization of the hardware class from the ROS parameter server + * (e.g. arm_id, robot_ip joint_names etc.) + * + * @param[in] root_nh A node handle in the root namespace of the control node. + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + * + * @return True if successful, false otherwise. + */ + virtual bool initParameters(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh); + + /** + * Initializes the class in terms of ros_control interfaces. + * Note: You have to call initParameters beforehand. Use the complete initialization routine + * \ref init() method to control robots. + * + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + * @return True if successful, false otherwise. + */ + virtual void initROSInterfaces(ros::NodeHandle& robot_hw_nh); + + /** + * Initializes the callbacks for on-the-fly reading the parameters for rate limiting, + * internal controllers and cutoff frequency. + * + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + */ + virtual void setupParameterCallbacks(ros::NodeHandle& robot_hw_nh); /** * Runs the currently active controller in a realtime loop. @@ -82,7 +92,6 @@ class FrankaHW : public hardware_interface::RobotHW { * If no controller is active, the function immediately exits. When running a controller, * the function only exits when ros_callback returns false. * - * @param[in] robot Robot instance. * @param[in] ros_callback A callback function that is executed at each time step and runs * all ROS-side functionality of the hardware. Execution is stopped if it returns false. * @@ -92,25 +101,22 @@ class FrankaHW : public hardware_interface::RobotHW { * @throw franka::NetworkException if the connection is lost, e.g. after a timeout. * @throw franka::RealtimeException if realtime priority cannot be set for the current thread. */ - void control(franka::Robot& robot, - const std::function<bool(const ros::Time&, const ros::Duration&)>& ros_callback); + virtual void control( + const std::function<bool(const ros::Time&, const ros::Duration&)>& ros_callback) const; /** * Updates the controller interfaces from the given robot state. * * @param[in] robot_state Current robot state. - * - * @throw franka::InvalidOperationException if a conflicting operation is already running. - * @throw franka::NetworkException if the connection is lost. */ - void update(const franka::RobotState& robot_state); + virtual void update(const franka::RobotState& robot_state); /** * Indicates whether there is an active controller. * * @return True if a controller is currently active, false otherwise. */ - bool controllerActive() const noexcept; + virtual bool controllerActive() const noexcept; /** * Checks whether a requested controller can be run, based on the resources @@ -120,13 +126,15 @@ class FrankaHW : public hardware_interface::RobotHW { * * @return True in case of a conflict, false in case of valid controllers. */ - bool checkForConflict(const std::list<hardware_interface::ControllerInfo>& info) const override; + virtual bool checkForConflict( + const std::list<hardware_interface::ControllerInfo>& info) const override; /** * Performs controller switching (real-time capable). */ - void doSwitch(const std::list<hardware_interface::ControllerInfo>&, - const std::list<hardware_interface::ControllerInfo>&) override; + virtual void doSwitch( + const std::list<hardware_interface::ControllerInfo>& /*start_list*/, + const std::list<hardware_interface::ControllerInfo>& /*stop_list*/) override; /** * Prepares switching between controllers (not real-time capable). @@ -136,88 +144,373 @@ class FrankaHW : public hardware_interface::RobotHW { * * @return True if the preparation has been successful, false otherwise. */ - bool prepareSwitch(const std::list<hardware_interface::ControllerInfo>& start_list, - const std::list<hardware_interface::ControllerInfo>& stop_list) override; + virtual bool prepareSwitch( + const std::list<hardware_interface::ControllerInfo>& start_list, + const std::list<hardware_interface::ControllerInfo>& stop_list) override; /** * Gets the current joint position command. * - * @return Current joint position command. + * @return The current joint position command. */ - std::array<double, 7> getJointPositionCommand() const noexcept; + virtual std::array<double, 7> getJointPositionCommand() const noexcept; /** * Gets the current joint velocity command. * - * @return Current joint velocity command. + * @return The current joint velocity command. */ - std::array<double, 7> getJointVelocityCommand() const noexcept; + virtual std::array<double, 7> getJointVelocityCommand() const noexcept; /** * Gets the current joint torque command. * - * @return Current joint torque command. + * @return The current joint torque command. */ - std::array<double, 7> getJointEffortCommand() const noexcept; + virtual std::array<double, 7> getJointEffortCommand() const noexcept; /** * Enforces limits on position, velocity, and torque level. * - * @param[in] period Duration of the current cycle. + * @param[in] period The duration of the current cycle. */ - void enforceLimits(const ros::Duration& period); + virtual void enforceLimits(const ros::Duration& period); /** * Resets the limit interfaces. */ - void reset(); + virtual void reset(); + + /** + * Checks the proximity of each joint to its joint position limits and prints + * a warning whenever a joint is close to a limit. + */ + virtual void checkJointLimits(); + + /** + * Reads data from the franka robot. + * + * @param[in] time The current time. + * @param[in] period The time passed since the last call to \ref read. + */ + virtual void read(const ros::Time& time, const ros::Duration& period) override; + + /** + * Writes data to the franka robot. + * + * @param[in] time The current time. + * @param[in] period The time passed since the last call to \ref write. + */ + virtual void write(const ros::Time& time, const ros::Duration& period) override; + + /** + * Getter for the libfranka robot instance. + */ + virtual franka::Robot& robot() const; + + /** + * Checks a command for NaN values. + * + * @param[in] command The command to check. + * + * @return True if the command contains NaN, false otherwise. + */ + static bool commandHasNaN(const franka::Torques& command); + + /** + * Checks a command for NaN values. + * + * @param[in] command The command to check. + * + * @return True if the command contains NaN, false otherwise. + */ + static bool commandHasNaN(const franka::JointPositions& command); + + /** + * Checks a command for NaN values. + * + * @param[in] command The command to check. + * + * @return True if the command contains NaN, false otherwise. + */ + static bool commandHasNaN(const franka::JointVelocities& command); + + /** + * Checks a command for NaN values. + * + * @param[in] command The command to check. + * + * @return True if the command contains NaN, false otherwise. + */ + static bool commandHasNaN(const franka::CartesianPose& command); + + /** + * Checks a command for NaN values. + * + * @param[in] command The command to check. + * + * @return True if the command contains NaN, false otherwise. + */ + static bool commandHasNaN(const franka::CartesianVelocities& command); + + protected: + /** + * Checks whether an array of doubles contains NaN values. + * + * @param[in] command array The array to check. + * + * @return True if the array contains NaN values, false otherwise. + */ + template <size_t size> + static bool arrayHasNaN(const std::array<double, size>& array) { + return std::any_of(array.begin(), array.end(), [](const double& e) { return std::isnan(e); }); + } - private: using Callback = std::function<bool(const franka::RobotState&, franka::Duration)>; + /** + * Callback for the libfranka control loop. This method is designed to incorporate a + * second callback named ros_callback that will be called on each iteration of the callback. + * + * @param[in] command The datafield containing the command to send to the robot. + * @param[in] ros_callback An additional callback that is executed every time step. + * @param[in] robot_state The current robot state to compute commands with. + * @param[in] time_step Time since last call to the callback. + * @throw std::invalid_argument When a command contains NaN values. + * + * @return The command to be sent to the robot via libfranka. + */ template <typename T> T controlCallback(const T& command, Callback ros_callback, const franka::RobotState& robot_state, franka::Duration time_step) { - robot_state_ = robot_state; + robot_state_libfranka_ = robot_state; + ros::Time now = ros::Time(0); + read(now, ros::Duration(time_step.toSec())); + if (!controller_active_ || (ros_callback && !ros_callback(robot_state, time_step))) { return franka::MotionFinished(command); } + + write(now, ros::Duration(time_step.toSec())); + if (commandHasNaN(command)) { + std::string error_message = "FrankaHW::controlCallback: Got NaN command!"; + ROS_FATAL("%s", error_message.c_str()); + throw std::invalid_argument(error_message); + } + return command; } + /** + * Configures a limit interface to enforce limits on effort, velocity or position level + * on joint commands. + * + * @param[out] limit_interface The limit interface to set up. + * @param[out] command_interface The command interface to hook the limit interface to. + */ + template <typename T> + void setupLimitInterface(joint_limits_interface::JointLimitsInterface<T>& limit_interface, + hardware_interface::JointCommandInterface& command_interface) { + joint_limits_interface::SoftJointLimits soft_limits; + joint_limits_interface::JointLimits joint_limits; + for (size_t i = 0; i < joint_names_.size(); i++) { + const std::string& joint_name = joint_names_[i]; + auto urdf_joint = urdf_model_.getJoint(joint_name); + if (!urdf_joint || !urdf_joint->safety || !urdf_joint->limits) { + ROS_WARN( + "FrankaHW: Joint %s has incomplete limits and safety specs. Skipping it in the joint " + "limit interface!", + joint_name.c_str()); + continue; + } + if (joint_limits_interface::getSoftJointLimits(urdf_joint, soft_limits)) { + if (joint_limits_interface::getJointLimits(urdf_joint, joint_limits)) { + joint_limits.max_acceleration = franka::kMaxJointAcceleration[i]; + joint_limits.has_acceleration_limits = true; + joint_limits.max_jerk = franka::kMaxJointJerk[i]; + joint_limits.has_jerk_limits = true; + T limit_handle(command_interface.getHandle(joint_name), joint_limits, soft_limits); + limit_interface.registerHandle(limit_handle); + } else { + ROS_ERROR( + "FrankaHW: Could not parse joint limit for joint: %s for joint limit interfaces", + joint_name.c_str()); + } + } else { + ROS_ERROR( + "FrankaHW: Could not parse soft joint limit for joint %s for joint limit interfaces", + joint_name.c_str()); + } + } + } + + /** + * Configures and registers the joint state interface in ros_control. + * + * @param[in] robot_state The data field holding the updated robot state. + */ + virtual void setupJointStateInterface(franka::RobotState& robot_state); + + /** + * Configures and registers a joint command interface for positions velocities or efforts in + * ros_control. + * + * @param[in] command The data field holding the command to execute. + * @param[in] state The data field holding the updated robot state. + * @param[in] use_q_d Flag to configure using desired values as joint_states. + * @param[out] interface The command interface to configure. + */ + template <typename T> + void setupJointCommandInterface(std::array<double, 7>& command, + franka::RobotState& state, + bool use_q_d, + T& interface) { + for (size_t i = 0; i < joint_names_.size(); i++) { + hardware_interface::JointStateHandle state_handle; + if (use_q_d) { + state_handle = hardware_interface::JointStateHandle(joint_names_[i], &state.q_d[i], + &state.dq_d[i], &state.tau_J[i]); + } else { + state_handle = hardware_interface::JointStateHandle(joint_names_[i], &state.q[i], + &state.dq[i], &state.tau_J[i]); + } + hardware_interface::JointHandle joint_handle(state_handle, &command[i]); + interface.registerHandle(joint_handle); + } + registerInterface(&interface); + } + + /** + * Configures and registers the state interface offering the full franka::RobotState in + * ros_control. + * + * @param[in] robot_state The data field holding the updated robot state. + */ + virtual void setupFrankaStateInterface(franka::RobotState& robot_state); + + /** + * Configures and registers the command interface for Cartesian poses in ros_control. + * + * @param[in] pose_cartesian_command The data field holding the command to execute. + */ + virtual void setupFrankaCartesianPoseInterface(franka::CartesianPose& pose_cartesian_command); + + /** + * Configures and registers the command interface for Cartesian velocities in ros_control. + * + * @param[in] velocity_cartesian_command The data field holding the command to execute. + */ + virtual void setupFrankaCartesianVelocityInterface( + franka::CartesianVelocities& velocity_cartesian_command); + + /** + * Configures and registers the model interface offering kinematics and dynamics in ros_control. + * + * @param[in] robot_state A reference to the data field storing the current robot state. This + * state is used to evaluate model qunatities (by default) at the current state. + */ + virtual void setupFrankaModelInterface(franka::RobotState& robot_state); + + /** + * Configures the run function which is used as libfranka control callback based on the requested + * control mode. + * + * @param[in] requested_control_mode The control mode to configure (e.g. torque/position/velocity + * etc.) + * @param[in] limit_rate Flag to enable/disable rate limiting to smoothen the commands. + * @param[in] cutoff_frequency The cutoff frequency applied for command smoothing. + * @param[in] internal_controller The internal controller to use when using position or velocity + * modes. + * + * @return True if successful, false otherwise. + */ + virtual bool setRunFunction(const ControlMode& requested_control_mode, + bool limit_rate, + double cutoff_frequency, + franka::ControllerMode internal_controller); + /** + * Uses the robot_ip_ to connect to the robot via libfranka and loads the libfranka model. + */ + virtual void initRobot(); + + /** + * Parses a set of collision thresholds from the parameter server. The methods returns + * the default values if no parameter was found or the size of the array did not match + * the defaults dimension. + * + * @param[in] name The name of the parameter to look for. + * @param[in] robot_hw_nh A node handle in the namespace of the robot hardware. + * @param[in] defaults A set of default values that also specify the size the parameter must have + * to be valid. + * @return A set parsed parameters if valid parameters where found, the default values otherwise. + */ + static std::vector<double> getCollisionThresholds(const std::string& name, + ros::NodeHandle& robot_hw_nh, + const std::vector<double>& defaults); + + struct CollisionConfig { + std::array<double, 7> lower_torque_thresholds_acceleration; + std::array<double, 7> upper_torque_thresholds_acceleration; + std::array<double, 7> lower_torque_thresholds_nominal; + std::array<double, 7> upper_torque_thresholds_nominal; + std::array<double, 6> lower_force_thresholds_acceleration; + std::array<double, 6> upper_force_thresholds_acceleration; + std::array<double, 6> lower_force_thresholds_nominal; + std::array<double, 6> upper_force_thresholds_nominal; + }; + + CollisionConfig collision_config_; hardware_interface::JointStateInterface joint_state_interface_{}; - franka_hw::FrankaStateInterface franka_state_interface_{}; + FrankaStateInterface franka_state_interface_{}; hardware_interface::PositionJointInterface position_joint_interface_{}; hardware_interface::VelocityJointInterface velocity_joint_interface_{}; hardware_interface::EffortJointInterface effort_joint_interface_{}; - franka_hw::FrankaPoseCartesianInterface franka_pose_cartesian_interface_{}; - franka_hw::FrankaVelocityCartesianInterface franka_velocity_cartesian_interface_{}; - franka_hw::FrankaModelInterface franka_model_interface_{}; - + FrankaPoseCartesianInterface franka_pose_cartesian_interface_{}; + FrankaVelocityCartesianInterface franka_velocity_cartesian_interface_{}; + FrankaModelInterface franka_model_interface_{}; joint_limits_interface::PositionJointSoftLimitsInterface position_joint_limit_interface_{}; joint_limits_interface::VelocityJointSoftLimitsInterface velocity_joint_limit_interface_{}; joint_limits_interface::EffortJointSoftLimitsInterface effort_joint_limit_interface_{}; - franka::RobotState robot_state_{}; + std::mutex libfranka_state_mutex_; + std::mutex ros_state_mutex_; + franka::RobotState robot_state_libfranka_{}; + franka::RobotState robot_state_ros_{}; - std::array<std::string, 7> joint_names_; - const std::string arm_id_; - std::function<franka::ControllerMode()> get_internal_controller_; - std::function<bool()> get_limit_rate_; - std::function<double()> get_cutoff_frequency_; + std::mutex libfranka_cmd_mutex_; + franka::JointPositions position_joint_command_libfranka_; + franka::JointVelocities velocity_joint_command_libfranka_; + franka::Torques effort_joint_command_libfranka_; + franka::CartesianPose pose_cartesian_command_libfranka_; + franka::CartesianVelocities velocity_cartesian_command_libfranka_; - franka::JointPositions position_joint_command_; - franka::JointVelocities velocity_joint_command_; - franka::Torques effort_joint_command_; - franka::CartesianPose pose_cartesian_command_; - franka::CartesianVelocities velocity_cartesian_command_; + std::mutex ros_cmd_mutex_; + franka::JointPositions position_joint_command_ros_; + franka::JointVelocities velocity_joint_command_ros_; + franka::Torques effort_joint_command_ros_; + franka::CartesianPose pose_cartesian_command_ros_; + franka::CartesianVelocities velocity_cartesian_command_ros_; - std::function<void(franka::Robot&, Callback)> run_function_; + std::unique_ptr<franka::Robot> robot_; + std::unique_ptr<franka::Model> model_; + std::array<std::string, 7> joint_names_; + std::string arm_id_; + std::string robot_ip_; + urdf::Model urdf_model_; + double joint_limit_warning_threshold_{0.1}; + franka::RealtimeConfig realtime_config_; + + bool initialized_{false}; std::atomic_bool controller_active_{false}; ControlMode current_control_mode_ = ControlMode::None; + + std::function<franka::ControllerMode()> get_internal_controller_; + std::function<bool()> get_limit_rate_; + std::function<double()> get_cutoff_frequency_; + std::function<void(franka::Robot&, Callback)> run_function_; }; } // namespace franka_hw diff --git a/franka_hw/src/resource_helpers.h b/franka_hw/include/franka_hw/resource_helpers.h similarity index 72% rename from franka_hw/src/resource_helpers.h rename to franka_hw/include/franka_hw/resource_helpers.h index 15f00b9df93252519e7e953a76c9e2d56b059030..f2231ba58d3d3867f2942b6e22224d19087e6e46 100644 --- a/franka_hw/src/resource_helpers.h +++ b/franka_hw/include/franka_hw/resource_helpers.h @@ -33,4 +33,13 @@ bool getArmClaimedMap(ResourceWithClaimsMap& resource_map, ArmClaimedMap& arm_cl ControlMode getControlMode(const std::string& arm_id, ArmClaimedMap& arm_claim_map); +bool hasConflictingMultiClaim(const ResourceWithClaimsMap& resource_map); + +bool hasConflictingJointAndCartesianClaim(const ArmClaimedMap& arm_claim_map, + const std::string& arm_id); + +bool partiallyClaimsArmJoints(const ArmClaimedMap& arm_claim_map, const std::string& arm_id); + +bool hasTrajectoryClaim(const ArmClaimedMap& arm_claim_map, const std::string& arm_id); + } // namespace franka_hw diff --git a/franka_hw/include/franka_hw/services.h b/franka_hw/include/franka_hw/services.h new file mode 100644 index 0000000000000000000000000000000000000000..5d010b31ba67a3e292ff9777af61f22e0fad40e2 --- /dev/null +++ b/franka_hw/include/franka_hw/services.h @@ -0,0 +1,160 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#pragma once +#include <vector> + +#include <franka/exception.h> +#include <franka/robot.h> +#include <ros/console.h> +#include <ros/node_handle.h> +#include <ros/service_server.h> + +#include <franka_msgs/SetCartesianImpedance.h> +#include <franka_msgs/SetEEFrame.h> +#include <franka_msgs/SetForceTorqueCollisionBehavior.h> +#include <franka_msgs/SetFullCollisionBehavior.h> +#include <franka_msgs/SetJointImpedance.h> +#include <franka_msgs/SetKFrame.h> +#include <franka_msgs/SetLoad.h> + +namespace franka_hw { + +/** + * Advertises a service that acts according to the handler function which is used in the service + * callback. + * + * @param[in] node_handle The NodeHandle in the namespace at which to advertise the service. + * @param[in] name The name of the service. + * @param[in] handler The callback method for the service. + * @return The service server. + */ +template <typename T> +ros::ServiceServer advertiseService( + ros::NodeHandle& node_handle, + const std::string& name, + std::function<void(typename T::Request&, typename T::Response&)> handler) { + return node_handle.advertiseService<typename T::Request, typename T::Response>( + name, [name, handler](typename T::Request& request, typename T::Response& response) { + try { + handler(request, response); + response.success = true; + ROS_DEBUG_STREAM(name << " succeeded."); + } catch (const franka::Exception& ex) { + ROS_ERROR_STREAM(name << " failed: " << ex.what()); + response.success = false; + response.error = ex.what(); + } + return true; + }); +} + +/** + * This class serves as container that gathers all possible service interfaces to a libfranka robot + * instance. + */ +class ServiceContainer { + public: + /** + * Advertises and adds a service to the container class. + * + * @return A reference to the service container to allow a fluent API. + */ + template <typename T, typename... TArgs> + ServiceContainer& advertiseService(TArgs&&... args) { + ros::ServiceServer server = franka_hw::advertiseService<T>(std::forward<TArgs>(args)...); + services_.push_back(server); + return *this; + } + + private: + std::vector<ros::ServiceServer> services_; +}; + +/** + * Sets up all services relevant for a libfranka robot inside a service container. + * + * @param[in] robot The libfranka robot for which to set up services interfaces. + * @param[in] node_handle The NodeHandle in the namespace at which to advertise the services. + * @param[in] services The container to store the service servers. + */ +void setupServices(franka::Robot& robot, ros::NodeHandle& node_handle, ServiceContainer& services); + +/** + * Callback for the service interface to franka::robot::setCartesianImpedance. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setCartesianImpedance(franka::Robot& robot, + const franka_msgs::SetCartesianImpedance::Request& req, + franka_msgs::SetCartesianImpedance::Response& res); + +/** + * Callback for the service interface to franka::robot::setJointImpedance. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setJointImpedance(franka::Robot& robot, + const franka_msgs::SetJointImpedance::Request& req, + franka_msgs::SetJointImpedance::Response& res); + +/** + * Callback for the service interface to franka::robot::setEEFrame. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setEEFrame(franka::Robot& robot, + const franka_msgs::SetEEFrame::Request& req, + franka_msgs::SetEEFrame::Response& res); + +/** + * Callback for the service interface to franka::robot::setKFrame. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setKFrame(franka::Robot& robot, + const franka_msgs::SetKFrame::Request& req, + franka_msgs::SetKFrame::Response& res); + +/** + * Callback for the service interface to franka::robot::setForceTorqueCollisionBehavior. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setForceTorqueCollisionBehavior( + franka::Robot& robot, + const franka_msgs::SetForceTorqueCollisionBehavior::Request& req, + franka_msgs::SetForceTorqueCollisionBehavior::Response& res); + +/** + * Callback for the service interface to franka::robot::setFullCollisionBehavior. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setFullCollisionBehavior(franka::Robot& robot, + const franka_msgs::SetFullCollisionBehavior::Request& req, + franka_msgs::SetFullCollisionBehavior::Response& res); + +/** + * Callback for the service interface to franka::robot::setLoad. + * + * @param[in] robot The libfranka robot for which to set up the service. + * @param[in] req The service request. + * @param[out] res The service response. + */ +void setLoad(franka::Robot& robot, + const franka_msgs::SetLoad::Request& req, + franka_msgs::SetLoad::Response& res); + +} // namespace franka_hw diff --git a/franka_hw/package.xml b/franka_hw/package.xml index 0ff25b93cf76894735bb1950e059774ef0a374d6..a61cec93a6301996d8073a7fe3c23025c099bc52 100644 --- a/franka_hw/package.xml +++ b/franka_hw/package.xml @@ -13,14 +13,25 @@ <buildtool_depend>catkin</buildtool_depend> + <build_depend>message_generation</build_depend> + + <depend>actionlib_msgs</depend> + <depend>actionlib</depend> <depend>controller_interface</depend> + <depend>combined_robot_hw</depend> <depend>hardware_interface</depend> <depend>joint_limits_interface</depend> <depend>libfranka</depend> <depend>roscpp</depend> <depend>urdf</depend> + <depend>pluginlib</depend> + <depend>franka_msgs</depend> <test_depend>franka_description</test_depend> <test_depend>gtest</test_depend> <test_depend>rostest</test_depend> + + <export> + <hardware_interface plugin="${prefix}/franka_combinable_hw_plugin.xml"/> + </export> </package> diff --git a/franka_hw/src/franka_combinable_hw.cpp b/franka_hw/src/franka_combinable_hw.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b2b1650a195d730baa9766cf071151153e1af8e --- /dev/null +++ b/franka_hw/src/franka_combinable_hw.cpp @@ -0,0 +1,218 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#include <franka_hw/franka_combinable_hw.h> + +#include <hardware_interface/joint_command_interface.h> +#include <joint_limits_interface/joint_limits_interface.h> +#include <pluginlib/class_list_macros.h> +#include <std_msgs/Bool.h> + +#include <franka_hw/services.h> + +namespace franka_hw { + +FrankaCombinableHW::FrankaCombinableHW() : has_error_(false), error_recovered_(false) {} + +void FrankaCombinableHW::initROSInterfaces(ros::NodeHandle& robot_hw_nh) { + setupJointStateInterface(robot_state_ros_); + setupJointCommandInterface(effort_joint_command_ros_.tau_J, robot_state_ros_, false, + effort_joint_interface_); + setupLimitInterface<joint_limits_interface::EffortJointSoftLimitsHandle>( + effort_joint_limit_interface_, effort_joint_interface_); + setupFrankaStateInterface(robot_state_ros_); + setupFrankaModelInterface(robot_state_ros_); + + has_error_pub_ = robot_hw_nh.advertise<std_msgs::Bool>("has_error", 1, true); + publishErrorState(has_error_); + + setupServicesAndActionServers(robot_hw_nh); +} + +void FrankaCombinableHW::initRobot() { + FrankaHW::initRobot(); + control_loop_thread_ = std::make_unique<std::thread>(&FrankaCombinableHW::controlLoop, this); +} + +void FrankaCombinableHW::publishErrorState(const bool error) { + std_msgs::Bool msg; + msg.data = static_cast<int>(error); + has_error_pub_.publish(msg); +} + +void FrankaCombinableHW::controlLoop() { + while (ros::ok()) { + ros::Time last_time = ros::Time::now(); + + // Wait until controller has been activated or error has been recovered + while (!controllerActive() || has_error_) { + if (!controllerActive()) { + ROS_DEBUG_THROTTLE(1, "FrankaCombinableHW::%s::control_loop(): controller is not active.", + arm_id_.c_str()); + } + if (has_error_) { + ROS_DEBUG_THROTTLE(1, "FrankaCombinableHW::%s::control_loop(): an error has occured.", + arm_id_.c_str()); + } + + checkJointLimits(); + + { + std::lock_guard<std::mutex> ros_state_lock(ros_state_mutex_); + std::lock_guard<std::mutex> libfranka_state_lock(libfranka_state_mutex_); + robot_state_libfranka_ = robot_->readOnce(); + robot_state_ros_ = robot_->readOnce(); + } + + if (!ros::ok()) { + return; + } + } + ROS_INFO("FrankaCombinableHW::%s::control_loop(): controller is active.", arm_id_.c_str()); + + // Reset commands + { + std::lock_guard<std::mutex> command_lock(libfranka_cmd_mutex_); + effort_joint_command_libfranka_ = franka::Torques({0., 0., 0., 0., 0., 0., 0.}); + } + + try { + control(); + } catch (const franka::ControlException& e) { + // Reflex could be caught and it needs to wait for automatic error recovery + ROS_ERROR("%s: %s", arm_id_.c_str(), e.what()); + has_error_ = true; + publishErrorState(has_error_); + } + } +} + +void FrankaCombinableHW::setupServicesAndActionServers(ros::NodeHandle& node_handle) { + setupServices(*robot_, node_handle, services_); + recovery_action_server_ = + std::make_unique<actionlib::SimpleActionServer<franka_msgs::ErrorRecoveryAction>>( + node_handle, "error_recovery", + [&](const franka_msgs::ErrorRecoveryGoalConstPtr&) { + try { + robot_->automaticErrorRecovery(); + // error recovered => reset controller + if (has_error_) { + error_recovered_ = true; + } + has_error_ = false; + publishErrorState(has_error_); + recovery_action_server_->setSucceeded(); + } catch (const franka::Exception& ex) { + recovery_action_server_->setAborted(franka_msgs::ErrorRecoveryResult(), ex.what()); + } + }, + false); + recovery_action_server_->start(); +} + +void FrankaCombinableHW::control( // NOLINT (google-default-arguments) + const std::function<bool(const ros::Time&, const ros::Duration&)>& /*ros_callback*/) const { + if (!controller_active_) { + return; + } + auto empty_method = [](const franka::RobotState&, franka::Duration) { return true; }; + run_function_(*robot_, empty_method); +} + +bool FrankaCombinableHW::checkForConflict( + const std::list<hardware_interface::ControllerInfo>& info) const { + ResourceWithClaimsMap resource_map = getResourceMap(info); + + if (hasConflictingMultiClaim(resource_map)) { + return true; + } + + ArmClaimedMap arm_claim_map; + if (!getArmClaimedMap(resource_map, arm_claim_map)) { + ROS_ERROR("FrankaCombinableHW: Unknown interface claimed. Conflict!"); + return true; + } + + // check for any claim to trajectory interfaces (non-torque) which are not supported. + if (hasTrajectoryClaim(arm_claim_map, arm_id_)) { + ROS_ERROR_STREAM("FrankaCombinableHW: Invalid claim joint position or velocity interface." + << "Note: joint position and joint velocity interfaces are not supported" + << " in FrankaCombinableHW. Arm:" << arm_id_ << ". Conflict!"); + return true; + } + + return partiallyClaimsArmJoints(arm_claim_map, arm_id_); +} + +void FrankaCombinableHW::read(const ros::Time& time, const ros::Duration& period) { + controller_needs_reset_ = bool(error_recovered_); + FrankaHW::read(time, period); +} + +void FrankaCombinableHW::write(const ros::Time& time, const ros::Duration& period) { + // if flag `controller_needs_reset_` was updated, then controller_manager. update(..., + // reset_controller) must + // have been executed to reset the controller. + if (controller_needs_reset_ && error_recovered_) { + controller_needs_reset_ = false; + error_recovered_ = false; + } + + enforceLimits(period); + + FrankaHW::write(time, period); +} + +std::string FrankaCombinableHW::getArmID() const noexcept { + return arm_id_; +} + +void FrankaCombinableHW::triggerError() { + has_error_ = true; + publishErrorState(has_error_); +} + +bool FrankaCombinableHW::hasError() const noexcept { + return has_error_; +} + +void FrankaCombinableHW::resetError() { + robot_->automaticErrorRecovery(); + // error recovered => reset controller + if (has_error_) { + error_recovered_ = true; + } + has_error_ = false; + publishErrorState(has_error_); +} + +bool FrankaCombinableHW::controllerNeedsReset() const noexcept { + return controller_needs_reset_; +} + +bool FrankaCombinableHW::setRunFunction(const ControlMode& requested_control_mode, + const bool limit_rate, + const double cutoff_frequency, + const franka::ControllerMode /*internal_controller*/) { + using Callback = std::function<bool(const franka::RobotState&, franka::Duration)>; + + if (requested_control_mode == ControlMode::None) { + return true; + } + if (requested_control_mode == ControlMode::JointTorque) { + run_function_ = [this, limit_rate, cutoff_frequency](franka::Robot& robot, + Callback /*callback*/) { + robot.control(std::bind(&FrankaCombinableHW::libfrankaUpdateCallback<franka::Torques>, this, + std::cref(effort_joint_command_libfranka_), std::placeholders::_1, + std::placeholders::_2), + limit_rate, cutoff_frequency); + }; + return true; + } + + ROS_ERROR("FrankaCombinableHW: No valid control mode selected; cannot set run function."); + return false; +} + +} // namespace franka_hw + +PLUGINLIB_EXPORT_CLASS(franka_hw::FrankaCombinableHW, hardware_interface::RobotHW) diff --git a/franka_hw/src/franka_combined_hw.cpp b/franka_hw/src/franka_combined_hw.cpp new file mode 100644 index 0000000000000000000000000000000000000000..357b5bb224e4636b6b3b3f701c7238b215e8d9d0 --- /dev/null +++ b/franka_hw/src/franka_combined_hw.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#include <franka_hw/franka_combined_hw.h> + +#include <algorithm> +#include <memory> + +#include <actionlib/server/simple_action_server.h> +#include <ros/node_handle.h> +#include <ros/time.h> + +#include <franka_hw/franka_combinable_hw.h> +#include <franka_hw/franka_hw.h> +#include <franka_msgs/ErrorRecoveryAction.h> + +namespace franka_hw { + +FrankaCombinedHW::FrankaCombinedHW() = default; + +bool FrankaCombinedHW::init(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh) { + bool success = CombinedRobotHW::init(root_nh, robot_hw_nh); + // Error recovery server for all FrankaHWs + combined_recovery_action_server_ = + std::make_unique<actionlib::SimpleActionServer<franka_msgs::ErrorRecoveryAction>>( + robot_hw_nh, "error_recovery", + [&](const franka_msgs::ErrorRecoveryGoalConstPtr&) { + try { + is_recovering_ = true; + for (const auto& robot_hw : robot_hw_list_) { + auto* franka_combinable_hw_ptr = + dynamic_cast<franka_hw::FrankaCombinableHW*>(robot_hw.get()); + if (franka_combinable_hw_ptr != nullptr) { + franka_combinable_hw_ptr->resetError(); + } else { + ROS_ERROR( + "FrankaCombinedHW: dynamic_cast from RobotHW to FrankaCombinableHW failed."); + is_recovering_ = false; + combined_recovery_action_server_->setAborted( + franka_msgs::ErrorRecoveryResult(), + "dynamic_cast from RobotHW to FrankaCombinableHW failed"); + return; + } + } + is_recovering_ = false; + combined_recovery_action_server_->setSucceeded(); + } catch (const franka::Exception& ex) { + is_recovering_ = false; + combined_recovery_action_server_->setAborted(franka_msgs::ErrorRecoveryResult(), + ex.what()); + } + }, + false); + combined_recovery_action_server_->start(); + return success; +} + +void FrankaCombinedHW::read(const ros::Time& time, const ros::Duration& period) { + // Call the read method of the single RobotHW objects. + CombinedRobotHW::read(time, period); + handleError(); +} + +bool FrankaCombinedHW::controllerNeedsReset() { + // Check if any of the RobotHW object needs a controller reset + bool controller_reset = false; + for (const auto& robot_hw : robot_hw_list_) { + auto* franka_combinable_hw_ptr = dynamic_cast<franka_hw::FrankaCombinableHW*>(robot_hw.get()); + if (franka_combinable_hw_ptr != nullptr) { + controller_reset = controller_reset || franka_combinable_hw_ptr->controllerNeedsReset(); + } else { + ROS_ERROR("FrankaCombinedHW: dynamic_cast from RobotHW to FrankaCombinableHW failed."); + return false; + } + } + return controller_reset; +} + +void FrankaCombinedHW::handleError() { + // Trigger error state of all other RobotHW objects when one of them has a error. + if (hasError() && !is_recovering_) { + triggerError(); + } +} + +bool FrankaCombinedHW::hasError() { + bool has_error = false; + for (const auto& robot_hw : robot_hw_list_) { + auto* franka_combinable_hw_ptr = dynamic_cast<franka_hw::FrankaCombinableHW*>(robot_hw.get()); + if (franka_combinable_hw_ptr != nullptr) { + has_error = has_error || franka_combinable_hw_ptr->hasError(); + } else { + ROS_ERROR("FrankaCombinedHW: dynamic_cast from RobotHW to FrankaCombinableHW failed."); + return false; + } + } + return has_error; +} + +void FrankaCombinedHW::triggerError() { + // Trigger error state of all RobotHW objects. + for (const auto& robot_hw : robot_hw_list_) { + auto* franka_combinable_hw_ptr = dynamic_cast<franka_hw::FrankaCombinableHW*>(robot_hw.get()); + if (franka_combinable_hw_ptr != nullptr) { + franka_combinable_hw_ptr->triggerError(); + } else { + ROS_ERROR("FrankaCombinedHW: dynamic_cast from RobotHW to FrankaCombinableHW failed."); + } + } +} + +} // namespace franka_hw diff --git a/franka_hw/src/franka_hw.cpp b/franka_hw/src/franka_hw.cpp index 75ca901da3be4255154d9c4090a7719349702a21..2fce7191d981bcde5dda8e6fb86006a7b6217eaf 100644 --- a/franka_hw/src/franka_hw.cpp +++ b/franka_hw/src/franka_hw.cpp @@ -1,16 +1,24 @@ // Copyright (c) 2017 Franka Emika GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE #include <franka_hw/franka_hw.h> +#include <franka_hw/resource_helpers.h> +#include <array> #include <cstdint> +#include <exception> +#include <functional> +#include <list> +#include <mutex> #include <ostream> +#include <sstream> +#include <string> #include <utility> +#include <franka/control_types.h> #include <franka/rate_limiting.h> +#include <franka/robot.h> #include <joint_limits_interface/joint_limits_urdf.h> -#include "resource_helpers.h" - namespace franka_hw { namespace { @@ -24,133 +32,153 @@ std::ostream& operator<<(std::ostream& ostream, franka::ControllerMode mode) { } return ostream; } + +std::string toStringWithPrecision(const double value, const size_t precision = 6) { + std::ostringstream out; + out.precision(precision); + out << std::fixed << value; + return out.str(); +} + } // anonymous namespace -FrankaHW::FrankaHW(const std::array<std::string, 7>& joint_names, - const std::string& arm_id, - const urdf::Model& urdf_model, - std::function<bool()> get_limit_rate, - std::function<double()> get_cutoff_frequency, - std::function<franka::ControllerMode()> get_internal_controller) - : joint_names_(joint_names), - arm_id_(arm_id), - get_internal_controller_(std::move(get_internal_controller)), - get_limit_rate_(std::move(get_limit_rate)), - get_cutoff_frequency_(std::move(get_cutoff_frequency)), - position_joint_command_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), - velocity_joint_command_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), - effort_joint_command_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), - pose_cartesian_command_( +FrankaHW::FrankaHW() + : position_joint_command_ros_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + position_joint_command_libfranka_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + velocity_joint_command_ros_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + velocity_joint_command_libfranka_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + effort_joint_command_ros_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + effort_joint_command_libfranka_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + pose_cartesian_command_ros_( {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0}), - velocity_cartesian_command_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0}) { - for (size_t i = 0; i < joint_names_.size(); i++) { - hardware_interface::JointStateHandle joint_handle_q_d( - joint_names_[i], &robot_state_.q_d[i], &robot_state_.dq_d[i], &robot_state_.tau_J[i]); + pose_cartesian_command_libfranka_( + {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0}), + velocity_cartesian_command_ros_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0}), + velocity_cartesian_command_libfranka_({0.0, 0.0, 0.0, 0.0, 0.0, 0.0}) {} - hardware_interface::JointStateHandle joint_handle_q( - joint_names_[i], &robot_state_.q[i], &robot_state_.dq[i], &robot_state_.tau_J[i]); +bool FrankaHW::init(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh) { + if (initialized_) { + ROS_ERROR("FrankaHW: Cannot be initialized twice."); + return false; + } - joint_state_interface_.registerHandle(joint_handle_q); + if (!initParameters(root_nh, robot_hw_nh)) { + ROS_ERROR("FrankaHW: Failed to parse all required parameters."); + return false; + } + try { + initRobot(); + } catch (const std::runtime_error& error) { + ROS_ERROR("FrankaHW: Failed to initialize libfranka robot. %s", error.what()); + return false; + } + initROSInterfaces(robot_hw_nh); + setupParameterCallbacks(robot_hw_nh); - hardware_interface::JointHandle position_joint_handle(joint_handle_q_d, - &position_joint_command_.q[i]); - position_joint_interface_.registerHandle(position_joint_handle); + initialized_ = true; + return true; +} - hardware_interface::JointHandle velocity_joint_handle(joint_handle_q_d, - &velocity_joint_command_.dq[i]); - velocity_joint_interface_.registerHandle(velocity_joint_handle); +bool FrankaHW::initParameters(ros::NodeHandle& root_nh, ros::NodeHandle& robot_hw_nh) { + std::vector<std::string> joint_names_vector; + if (!robot_hw_nh.getParam("joint_names", joint_names_vector) || joint_names_vector.size() != 7) { + ROS_ERROR("Invalid or no joint_names parameters provided"); + return false; + } + std::copy(joint_names_vector.cbegin(), joint_names_vector.cend(), joint_names_.begin()); - hardware_interface::JointHandle effort_joint_handle(joint_handle_q, - &effort_joint_command_.tau_J[i]); - effort_joint_interface_.registerHandle(effort_joint_handle); + bool rate_limiting; + if (!robot_hw_nh.getParamCached("rate_limiting", rate_limiting)) { + ROS_ERROR("Invalid or no rate_limiting parameter provided"); + return false; } - joint_limits_interface::SoftJointLimits soft_limits; - joint_limits_interface::JointLimits joint_limits; + double cutoff_frequency; + if (!robot_hw_nh.getParamCached("cutoff_frequency", cutoff_frequency)) { + ROS_ERROR("Invalid or no cutoff_frequency parameter provided"); + return false; + } - for (size_t i = 0; i < joint_names_.size(); i++) { - const std::string& joint_name = joint_names_[i]; - auto urdf_joint = urdf_model.getJoint(joint_name); - if (!urdf_joint) { - ROS_ERROR_STREAM("FrankaHW: Could not get joint " << joint_name << " from urdf"); - continue; - } - if (!urdf_joint->safety) { - ROS_ERROR_STREAM("FrankaHW: Joint " << joint_name << " has no safety"); - continue; - } - if (!urdf_joint->limits) { - ROS_ERROR_STREAM("FrankaHW: Joint " << joint_name << " has no limits"); - continue; - } + std::string internal_controller; + if (!robot_hw_nh.getParam("internal_controller", internal_controller)) { + ROS_ERROR("No internal_controller parameter provided"); + return false; + } - if (joint_limits_interface::getSoftJointLimits(urdf_joint, soft_limits)) { - if (joint_limits_interface::getJointLimits(urdf_joint, joint_limits)) { - joint_limits.max_acceleration = franka::kMaxJointAcceleration[i]; - joint_limits.has_acceleration_limits = true; - joint_limits.max_jerk = franka::kMaxJointJerk[i]; - joint_limits.has_jerk_limits = true; - joint_limits_interface::PositionJointSoftLimitsHandle position_limit_handle( - position_joint_interface_.getHandle(joint_name), joint_limits, soft_limits); - position_joint_limit_interface_.registerHandle(position_limit_handle); - - joint_limits_interface::VelocityJointSoftLimitsHandle velocity_limit_handle( - velocity_joint_interface_.getHandle(joint_name), joint_limits, soft_limits); - velocity_joint_limit_interface_.registerHandle(velocity_limit_handle); - - joint_limits_interface::EffortJointSoftLimitsHandle effort_limit_handle( - effort_joint_interface_.getHandle(joint_name), joint_limits, soft_limits); - effort_joint_limit_interface_.registerHandle(effort_limit_handle); - } else { - ROS_ERROR_STREAM("FrankaHW: Could not parse joint limit for joint " - << joint_name << " for joint limit interfaces"); - } - } else { - ROS_ERROR_STREAM("FrankaHW: Could not parse soft joint limit for joint " - << joint_name << " for joint limit interfaces"); - } + if (!robot_hw_nh.getParam("arm_id", arm_id_)) { + ROS_ERROR("Invalid or no arm_id parameter provided"); + return false; } - FrankaStateHandle franka_state_handle(arm_id_ + "_robot", robot_state_); - franka_state_interface_.registerHandle(franka_state_handle); - FrankaCartesianPoseHandle franka_cartesian_pose_handle( - franka_state_handle, pose_cartesian_command_.O_T_EE, pose_cartesian_command_.elbow); - franka_pose_cartesian_interface_.registerHandle(franka_cartesian_pose_handle); - FrankaCartesianVelocityHandle franka_cartesian_velocity_handle( - franka_state_handle, velocity_cartesian_command_.O_dP_EE, velocity_cartesian_command_.elbow); - franka_velocity_cartesian_interface_.registerHandle(franka_cartesian_velocity_handle); + if (!urdf_model_.initParamWithNodeHandle("robot_description", root_nh)) { + ROS_ERROR("Could not initialize URDF model from robot_description"); + return false; + } - registerInterface(&franka_state_interface_); - registerInterface(&joint_state_interface_); - registerInterface(&position_joint_interface_); - registerInterface(&velocity_joint_interface_); - registerInterface(&effort_joint_interface_); - registerInterface(&franka_pose_cartesian_interface_); - registerInterface(&franka_velocity_cartesian_interface_); -} + if (!robot_hw_nh.getParam("robot_ip", robot_ip_)) { + ROS_ERROR("Invalid or no robot_ip parameter provided"); + return false; + } + + if (!robot_hw_nh.getParam("joint_limit_warning_threshold", joint_limit_warning_threshold_)) { + ROS_INFO( + "No parameter joint_limit_warning_threshold is found, using default " + "value %f", + joint_limit_warning_threshold_); + } -FrankaHW::FrankaHW(const std::array<std::string, 7>& joint_names, - const std::string& arm_id, - const urdf::Model& urdf_model, - franka::Model& model, - std::function<bool()> get_limit_rate, - std::function<double()> get_cutoff_frequency, - std::function<franka::ControllerMode()> get_internal_controller) - : FrankaHW(joint_names, - arm_id, - urdf_model, - std::move(get_limit_rate), - std::move(get_cutoff_frequency), - std::move(get_internal_controller)) { - franka_hw::FrankaModelHandle model_handle(arm_id_ + "_model", model, robot_state_); + std::string realtime_config_param = robot_hw_nh.param("realtime_config", std::string("enforce")); + if (realtime_config_param == "enforce") { + realtime_config_ = franka::RealtimeConfig::kEnforce; + } else if (realtime_config_param == "ignore") { + realtime_config_ = franka::RealtimeConfig::kIgnore; + } else { + ROS_ERROR("Invalid realtime_config parameter provided. Valid values are 'enforce', 'ignore'."); + return false; + } - franka_model_interface_.registerHandle(model_handle); + // Get full collision behavior config from the parameter server. + std::vector<double> thresholds = + getCollisionThresholds("lower_torque_thresholds_acceleration", robot_hw_nh, + {20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.lower_torque_thresholds_acceleration.begin()); + thresholds = getCollisionThresholds("upper_torque_thresholds_acceleration", robot_hw_nh, + {20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.upper_torque_thresholds_acceleration.begin()); + thresholds = getCollisionThresholds("lower_torque_thresholds_nominal", robot_hw_nh, + {20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.lower_torque_thresholds_nominal.begin()); + thresholds = getCollisionThresholds("upper_torque_thresholds_nominal", robot_hw_nh, + {20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.upper_torque_thresholds_nominal.begin()); + thresholds.resize(6); + thresholds = getCollisionThresholds("lower_force_thresholds_acceleration", robot_hw_nh, + {20.0, 20.0, 20.0, 25.0, 25.0, 25.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.lower_force_thresholds_acceleration.begin()); + thresholds = getCollisionThresholds("upper_force_thresholds_acceleration", robot_hw_nh, + {20.0, 20.0, 20.0, 25.0, 25.0, 25.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.upper_force_thresholds_acceleration.begin()); + thresholds = getCollisionThresholds("lower_force_thresholds_nominal", robot_hw_nh, + {20.0, 20.0, 20.0, 25.0, 25.0, 25.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.lower_force_thresholds_nominal.begin()); + thresholds = getCollisionThresholds("upper_force_thresholds_nominal", robot_hw_nh, + {20.0, 20.0, 20.0, 25.0, 25.0, 25.0}); + std::copy(thresholds.begin(), thresholds.end(), + collision_config_.upper_force_thresholds_nominal.begin()); - registerInterface(&franka_model_interface_); + return true; } void FrankaHW::update(const franka::RobotState& robot_state) { - robot_state_ = robot_state; + std::lock_guard<std::mutex> ros_lock(ros_state_mutex_); + robot_state_ros_ = robot_state; } bool FrankaHW::controllerActive() const noexcept { @@ -158,18 +186,22 @@ bool FrankaHW::controllerActive() const noexcept { } void FrankaHW::control( - franka::Robot& robot, - const std::function<bool(const ros::Time&, const ros::Duration&)>& ros_callback) { + const std::function<bool(const ros::Time&, const ros::Duration&)>& ros_callback) const { + if (!initialized_) { + ROS_ERROR("FrankaHW: Call to control before initialization!"); + return; + } if (!controller_active_) { return; } - franka::Duration last_time = robot_state_.time; + franka::Duration last_time = robot_state_ros_.time; - run_function_(robot, [this, ros_callback, &last_time](const franka::RobotState& robot_state, - franka::Duration time_step) { + run_function_(*robot_, [this, ros_callback, &last_time](const franka::RobotState& robot_state, + franka::Duration time_step) { if (last_time != robot_state.time) { last_time = robot_state.time; + return ros_callback(ros::Time::now(), ros::Duration(time_step.toSec())); } return true; @@ -186,68 +218,19 @@ void FrankaHW::enforceLimits(const ros::Duration& period) { bool FrankaHW::checkForConflict(const std::list<hardware_interface::ControllerInfo>& info) const { ResourceWithClaimsMap resource_map = getResourceMap(info); - // check for conflicts in single resources: no triple claims, - // for 2 claims it must be one torque and one non-torque claim - for (auto map_it = resource_map.begin(); map_it != resource_map.end(); map_it++) { - if (map_it->second.size() > 2) { - ROS_ERROR_STREAM("FrankaHW: Resource " - << map_it->first << " claimed with more than two interfaces. Conflict!"); - return true; - } - uint8_t torque_claims = 0; - uint8_t other_claims = 0; - if (map_it->second.size() == 2) { - for (auto& claimed_by : map_it->second) { - if (claimed_by[2] == "hardware_interface::EffortJointInterface") { - torque_claims++; - } else { - other_claims++; - } - } - if (torque_claims != 1) { - ROS_ERROR_STREAM("FrankaHW: Resource " - << map_it->first - << " is claimed with two non-compatible interfaces. Conflict!"); - return true; - } - } + if (hasConflictingMultiClaim(resource_map)) { + return true; } - ArmClaimedMap arm_claim_map; if (!getArmClaimedMap(resource_map, arm_claim_map)) { ROS_ERROR_STREAM("FrankaHW: Unknown interface claimed. Conflict!"); return true; } - - // check for conflicts between joint and cartesian level for each arm. - // Valid claims are torque claims on joint level in combination with either - // 7 non-torque claims on joint_level or one claim on cartesian level. - if (arm_claim_map.find(arm_id_) != arm_claim_map.end()) { - if ((arm_claim_map[arm_id_].cartesian_velocity_claims + - arm_claim_map[arm_id_].cartesian_pose_claims > - 0 && - arm_claim_map[arm_id_].joint_position_claims + - arm_claim_map[arm_id_].joint_velocity_claims > - 0)) { - ROS_ERROR_STREAM("FrankaHW: Invalid claims on joint AND cartesian level on arm " - << arm_id_ << ". Conflict!"); - return true; - } - if ((arm_claim_map[arm_id_].joint_position_claims > 0 && - arm_claim_map[arm_id_].joint_position_claims != 7) || - (arm_claim_map[arm_id_].joint_velocity_claims > 0 && - arm_claim_map[arm_id_].joint_velocity_claims != 7) || - (arm_claim_map[arm_id_].joint_torque_claims > 0 && - arm_claim_map[arm_id_].joint_torque_claims != 7)) { - ROS_ERROR_STREAM("FrankaHW: Non-consistent claims on the joints of " - << arm_id_ << ". Not supported. Conflict!"); - return true; - } - } - return false; + return hasConflictingJointAndCartesianClaim(arm_claim_map, arm_id_) || + partiallyClaimsArmJoints(arm_claim_map, arm_id_); } -// doSwitch runs on the main realtime thread +// doSwitch runs on the main realtime thread. void FrankaHW::doSwitch(const std::list<hardware_interface::ControllerInfo>& /* start_list */, const std::list<hardware_interface::ControllerInfo>& /* stop_list */) { if (current_control_mode_ != ControlMode::None) { @@ -256,7 +239,7 @@ void FrankaHW::doSwitch(const std::list<hardware_interface::ControllerInfo>& /* } } -// prepareSwitch runs on the background message handling thread +// prepareSwitch runs on the background message handling thread. bool FrankaHW::prepareSwitch(const std::list<hardware_interface::ControllerInfo>& start_list, const std::list<hardware_interface::ControllerInfo>& stop_list) { ResourceWithClaimsMap start_resource_map = getResourceMap(start_list); @@ -265,6 +248,7 @@ bool FrankaHW::prepareSwitch(const std::list<hardware_interface::ControllerInfo> ROS_ERROR("FrankaHW: Unknown interface claimed for starting!"); return false; } + ControlMode start_control_mode = getControlMode(arm_id_, start_arm_claim_map); ResourceWithClaimsMap stop_resource_map = getResourceMap(stop_list); @@ -279,12 +263,146 @@ bool FrankaHW::prepareSwitch(const std::list<hardware_interface::ControllerInfo> requested_control_mode &= ~stop_control_mode; requested_control_mode |= start_control_mode; - auto limit_rate = get_limit_rate_(); - auto cutoff_frequency = get_cutoff_frequency_(); - auto internal_controller = get_internal_controller_(); + if (!setRunFunction(requested_control_mode, get_limit_rate_(), get_cutoff_frequency_(), + get_internal_controller_())) { + return false; + } + + if (current_control_mode_ != requested_control_mode) { + ROS_INFO_STREAM("FrankaHW: Prepared switching controllers to " + << requested_control_mode << " with parameters " + << "limit_rate=" << get_limit_rate_() + << ", cutoff_frequency=" << get_cutoff_frequency_() + << ", internal_controller=" << get_internal_controller_()); + current_control_mode_ = requested_control_mode; + controller_active_ = false; + } + + return true; +} + +std::array<double, 7> FrankaHW::getJointPositionCommand() const noexcept { + return position_joint_command_ros_.q; +} + +std::array<double, 7> FrankaHW::getJointVelocityCommand() const noexcept { + return velocity_joint_command_ros_.dq; +} + +std::array<double, 7> FrankaHW::getJointEffortCommand() const noexcept { + return effort_joint_command_ros_.tau_J; +} + +void FrankaHW::reset() { + position_joint_limit_interface_.reset(); +} + +void FrankaHW::checkJointLimits() { + std::string joint_limits_warning; + for (const auto& k_joint_name : joint_names_) { + try { + auto joint_handle = joint_state_interface_.getHandle(k_joint_name); + auto urdf_joint = urdf_model_.getJoint(k_joint_name); + joint_limits_interface::JointLimits joint_limits; + if (getJointLimits(urdf_joint, joint_limits)) { + double joint_lower = joint_limits.min_position; + double joint_upper = joint_limits.max_position; + double joint_position = joint_handle.getPosition(); + double dist = fmin(fabs(joint_position - joint_lower), fabs(joint_position - joint_upper)); + if (dist < joint_limit_warning_threshold_) { + joint_limits_warning += + "\n\t" + k_joint_name + ": " + toStringWithPrecision(dist * 180 / M_PI) + + " degrees to joint limits (limits: [" + toStringWithPrecision(joint_lower) + ", " + + toStringWithPrecision(joint_upper) + "]" + + " q: " + toStringWithPrecision(joint_position) + ") "; + } + } else { + ROS_ERROR_STREAM_ONCE("FrankaHW: Could not parse joint limit for joint " + << k_joint_name << " for joint limit interfaces"); + } + } catch (const hardware_interface::HardwareInterfaceException& ex) { + ROS_ERROR_STREAM_ONCE("FrankaHW: Could not get joint handle " << k_joint_name << " .\n" + << ex.what()); + return; + } + } + if (!joint_limits_warning.empty()) { + ROS_WARN_THROTTLE(5, "FrankaHW: %s", joint_limits_warning.c_str()); + } +} + +franka::Robot& FrankaHW::robot() const { + if (!initialized_ || !robot_) { + std::string error_message = "FrankaHW: Attempt to access robot before initialization!"; + ROS_ERROR("%s", error_message.c_str()); + throw std::logic_error(error_message); + } + return *robot_; +} + +void FrankaHW::read(const ros::Time& /*time*/, const ros::Duration& /*period*/) { + std::lock_guard<std::mutex> ros_lock(ros_state_mutex_); + std::lock_guard<std::mutex> libfranka_lock(libfranka_state_mutex_); + robot_state_ros_ = robot_state_libfranka_; +} + +void FrankaHW::write(const ros::Time& /*time*/, const ros::Duration& /*period*/) { + std::lock_guard<std::mutex> ros_lock(ros_cmd_mutex_); + std::lock_guard<std::mutex> libfranka_lock(libfranka_cmd_mutex_); + pose_cartesian_command_libfranka_ = pose_cartesian_command_ros_; + velocity_cartesian_command_libfranka_ = velocity_cartesian_command_ros_; + effort_joint_command_libfranka_ = effort_joint_command_ros_; + position_joint_command_libfranka_ = position_joint_command_ros_; + velocity_joint_command_libfranka_ = velocity_joint_command_ros_; +} + +void FrankaHW::setupJointStateInterface(franka::RobotState& robot_state) { + for (size_t i = 0; i < joint_names_.size(); i++) { + hardware_interface::JointStateHandle joint_handle_q(joint_names_[i], &robot_state.q[i], + &robot_state.dq[i], &robot_state.tau_J[i]); + joint_state_interface_.registerHandle(joint_handle_q); + } + registerInterface(&joint_state_interface_); +} + +void FrankaHW::setupFrankaStateInterface(franka::RobotState& robot_state) { + FrankaStateHandle franka_state_handle(arm_id_ + "_robot", robot_state); + franka_state_interface_.registerHandle(franka_state_handle); + registerInterface(&franka_state_interface_); +} + +void FrankaHW::setupFrankaCartesianPoseInterface(franka::CartesianPose& pose_cartesian_command) { + FrankaCartesianPoseHandle franka_cartesian_pose_handle( + franka_state_interface_.getHandle(arm_id_ + "_robot"), pose_cartesian_command.O_T_EE, + pose_cartesian_command.elbow); + franka_pose_cartesian_interface_.registerHandle(franka_cartesian_pose_handle); + registerInterface(&franka_pose_cartesian_interface_); +} + +void FrankaHW::setupFrankaCartesianVelocityInterface( + franka::CartesianVelocities& velocity_cartesian_command) { + FrankaCartesianVelocityHandle franka_cartesian_velocity_handle( + franka_state_interface_.getHandle(arm_id_ + "_robot"), velocity_cartesian_command.O_dP_EE, + velocity_cartesian_command.elbow); + franka_velocity_cartesian_interface_.registerHandle(franka_cartesian_velocity_handle); + registerInterface(&franka_velocity_cartesian_interface_); +} + +void FrankaHW::setupFrankaModelInterface(franka::RobotState& robot_state) { + if (model_) { + franka_hw::FrankaModelHandle model_handle(arm_id_ + "_model", *model_, robot_state); + franka_model_interface_.registerHandle(model_handle); + registerInterface(&franka_model_interface_); + } +} +bool FrankaHW::setRunFunction(const ControlMode& requested_control_mode, + const bool limit_rate, + const double cutoff_frequency, + const franka::ControllerMode internal_controller) { using std::placeholders::_1; using std::placeholders::_2; + using Callback = std::function<bool(const franka::RobotState&, franka::Duration)>; switch (requested_control_mode) { case ControlMode::None: @@ -292,106 +410,181 @@ bool FrankaHW::prepareSwitch(const std::list<hardware_interface::ControllerInfo> case ControlMode::JointTorque: run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::Torques>, this, - std::cref(effort_joint_command_), ros_callback, _1, _2), + std::cref(effort_joint_command_libfranka_), ros_callback, _1, _2), limit_rate, cutoff_frequency); }; break; case ControlMode::JointPosition: run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::JointPositions>, this, - std::cref(position_joint_command_), ros_callback, _1, _2), + std::cref(position_joint_command_libfranka_), ros_callback, _1, _2), internal_controller, limit_rate, cutoff_frequency); }; break; case ControlMode::JointVelocity: run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::JointVelocities>, this, - std::cref(velocity_joint_command_), ros_callback, _1, _2), + std::cref(velocity_joint_command_libfranka_), ros_callback, _1, _2), internal_controller, limit_rate, cutoff_frequency); }; break; case ControlMode::CartesianPose: run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::CartesianPose>, this, - std::cref(pose_cartesian_command_), ros_callback, _1, _2), + std::cref(pose_cartesian_command_libfranka_), ros_callback, _1, _2), internal_controller, limit_rate, cutoff_frequency); }; break; case ControlMode::CartesianVelocity: run_function_ = [=](franka::Robot& robot, Callback ros_callback) { - robot.control(std::bind(&FrankaHW::controlCallback<franka::CartesianVelocities>, this, - std::cref(velocity_cartesian_command_), ros_callback, _1, _2), - internal_controller, limit_rate, cutoff_frequency); + robot.control( + std::bind(&FrankaHW::controlCallback<franka::CartesianVelocities>, this, + std::cref(velocity_cartesian_command_libfranka_), ros_callback, _1, _2), + internal_controller, limit_rate, cutoff_frequency); }; break; case (ControlMode::JointTorque | ControlMode::JointPosition): run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::Torques>, this, - std::cref(effort_joint_command_), ros_callback, _1, _2), + std::cref(effort_joint_command_libfranka_), ros_callback, _1, _2), std::bind(&FrankaHW::controlCallback<franka::JointPositions>, this, - std::cref(position_joint_command_), ros_callback, _1, _2), + std::cref(position_joint_command_libfranka_), ros_callback, _1, _2), limit_rate, cutoff_frequency); }; break; case (ControlMode::JointTorque | ControlMode::JointVelocity): run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::Torques>, this, - std::cref(effort_joint_command_), ros_callback, _1, _2), + std::cref(effort_joint_command_libfranka_), ros_callback, _1, _2), std::bind(&FrankaHW::controlCallback<franka::JointVelocities>, this, - std::cref(velocity_joint_command_), ros_callback, _1, _2), + std::cref(velocity_joint_command_libfranka_), ros_callback, _1, _2), limit_rate, cutoff_frequency); }; break; case (ControlMode::JointTorque | ControlMode::CartesianPose): run_function_ = [=](franka::Robot& robot, Callback ros_callback) { robot.control(std::bind(&FrankaHW::controlCallback<franka::Torques>, this, - std::cref(effort_joint_command_), ros_callback, _1, _2), + std::cref(effort_joint_command_libfranka_), ros_callback, _1, _2), std::bind(&FrankaHW::controlCallback<franka::CartesianPose>, this, - std::cref(pose_cartesian_command_), ros_callback, _1, _2), + std::cref(pose_cartesian_command_libfranka_), ros_callback, _1, _2), limit_rate, cutoff_frequency); }; break; case (ControlMode::JointTorque | ControlMode::CartesianVelocity): run_function_ = [=](franka::Robot& robot, Callback ros_callback) { - robot.control(std::bind(&FrankaHW::controlCallback<franka::Torques>, this, - std::cref(effort_joint_command_), ros_callback, _1, _2), - std::bind(&FrankaHW::controlCallback<franka::CartesianVelocities>, this, - std::cref(velocity_cartesian_command_), ros_callback, _1, _2), - limit_rate, cutoff_frequency); + robot.control( + std::bind(&FrankaHW::controlCallback<franka::Torques>, this, + std::cref(effort_joint_command_libfranka_), ros_callback, _1, _2), + std::bind(&FrankaHW::controlCallback<franka::CartesianVelocities>, this, + std::cref(velocity_cartesian_command_libfranka_), ros_callback, _1, _2), + limit_rate, cutoff_frequency); }; break; default: ROS_WARN("FrankaHW: No valid control mode selected; cannot switch controllers."); return false; } + return true; +} - if (current_control_mode_ != requested_control_mode) { - ROS_INFO_STREAM("FrankaHW: Prepared switching controllers to " - << requested_control_mode << " with parameters " - << "limit_rate=" << limit_rate << ", cutoff_frequency=" << cutoff_frequency - << ", internal_controller=" << internal_controller); - current_control_mode_ = requested_control_mode; +void FrankaHW::initROSInterfaces(ros::NodeHandle& /*robot_hw_nh*/) { + setupJointStateInterface(robot_state_ros_); + setupJointCommandInterface(position_joint_command_ros_.q, robot_state_ros_, true, + position_joint_interface_); + setupJointCommandInterface(velocity_joint_command_ros_.dq, robot_state_ros_, true, + velocity_joint_interface_); + setupJointCommandInterface(effort_joint_command_ros_.tau_J, robot_state_ros_, false, + effort_joint_interface_); + setupLimitInterface<joint_limits_interface::PositionJointSoftLimitsHandle>( + position_joint_limit_interface_, position_joint_interface_); + setupLimitInterface<joint_limits_interface::VelocityJointSoftLimitsHandle>( + velocity_joint_limit_interface_, velocity_joint_interface_); + setupLimitInterface<joint_limits_interface::EffortJointSoftLimitsHandle>( + effort_joint_limit_interface_, effort_joint_interface_); + setupFrankaStateInterface(robot_state_ros_); + setupFrankaCartesianPoseInterface(pose_cartesian_command_ros_); + setupFrankaCartesianVelocityInterface(velocity_cartesian_command_ros_); + setupFrankaModelInterface(robot_state_ros_); +} - controller_active_ = false; - } +void FrankaHW::initRobot() { + robot_ = std::make_unique<franka::Robot>(robot_ip_, realtime_config_); + model_ = std::make_unique<franka::Model>(robot_->loadModel()); + robot_->setCollisionBehavior(collision_config_.lower_torque_thresholds_acceleration, + collision_config_.upper_torque_thresholds_acceleration, + collision_config_.lower_torque_thresholds_nominal, + collision_config_.upper_torque_thresholds_nominal, + collision_config_.lower_force_thresholds_acceleration, + collision_config_.upper_force_thresholds_acceleration, + collision_config_.lower_force_thresholds_nominal, + collision_config_.upper_force_thresholds_nominal); + update(robot_->readOnce()); +} - return true; +void FrankaHW::setupParameterCallbacks(ros::NodeHandle& robot_hw_nh) { + get_limit_rate_ = [robot_hw_nh]() { + bool rate_limiting; + robot_hw_nh.getParamCached("rate_limiting", rate_limiting); + return rate_limiting; + }; + + get_internal_controller_ = [robot_hw_nh]() { + std::string internal_controller; + robot_hw_nh.getParamCached("internal_controller", internal_controller); + franka::ControllerMode controller_mode; + if (internal_controller == "joint_impedance") { + controller_mode = franka::ControllerMode::kJointImpedance; + } else if (internal_controller == "cartesian_impedance") { + controller_mode = franka::ControllerMode::kCartesianImpedance; + } else { + ROS_WARN("Invalid internal_controller parameter provided, falling back to joint impedance"); + controller_mode = franka::ControllerMode::kJointImpedance; + } + return controller_mode; + }; + + get_cutoff_frequency_ = [robot_hw_nh]() { + double cutoff_frequency; + robot_hw_nh.getParamCached("cutoff_frequency", cutoff_frequency); + return cutoff_frequency; + }; } -std::array<double, 7> FrankaHW::getJointPositionCommand() const noexcept { - return position_joint_command_.q; +bool FrankaHW::commandHasNaN(const franka::Torques& command) { + return arrayHasNaN(command.tau_J); } -std::array<double, 7> FrankaHW::getJointVelocityCommand() const noexcept { - return velocity_joint_command_.dq; +bool FrankaHW::commandHasNaN(const franka::JointPositions& command) { + return arrayHasNaN(command.q); } -std::array<double, 7> FrankaHW::getJointEffortCommand() const noexcept { - return effort_joint_command_.tau_J; +bool FrankaHW::commandHasNaN(const franka::JointVelocities& command) { + return arrayHasNaN(command.dq); } -void FrankaHW::reset() { - position_joint_limit_interface_.reset(); +bool FrankaHW::commandHasNaN(const franka::CartesianPose& command) { + return arrayHasNaN(command.elbow) || arrayHasNaN(command.O_T_EE); +} + +bool FrankaHW::commandHasNaN(const franka::CartesianVelocities& command) { + return arrayHasNaN(command.elbow) || arrayHasNaN(command.O_dP_EE); +} + +std::vector<double> FrankaHW::getCollisionThresholds(const std::string& name, + ros::NodeHandle& robot_hw_nh, + const std::vector<double>& defaults) { + std::vector<double> thresholds; + if (!robot_hw_nh.getParam("collision_config/" + name, thresholds) || + thresholds.size() != defaults.size()) { + std::string message; + for (const double& threshold : defaults) { + message += std::to_string(threshold); + message += " "; + } + ROS_INFO("No parameter %s found, using default values: %s", name.c_str(), message.c_str()); + return defaults; + } + return thresholds; } } // namespace franka_hw diff --git a/franka_hw/src/resource_helpers.cpp b/franka_hw/src/resource_helpers.cpp index d8a2166bcfe9d88c7b5e6ddca243987f38060b0c..f9219967f7560a1b83b53ae7de396c91ecfbb317 100644 --- a/franka_hw/src/resource_helpers.cpp +++ b/franka_hw/src/resource_helpers.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2017 Franka Emika GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE -#include "resource_helpers.h" +#include <franka_hw/resource_helpers.h> #include <ros/console.h> @@ -46,9 +46,8 @@ bool getArmClaimedMap(ResourceWithClaimsMap& resource_map, ArmClaimedMap& arm_cl // 7 non-torque claims on joint_level or one claim on cartesian level. for (auto map_it = resource_map.begin(); map_it != resource_map.end(); map_it++) { if (!findArmIdInResourceId(map_it->first, ¤t_arm_id)) { - ROS_ERROR_STREAM("Could not find arm_id in resource " - << map_it->first - << ". Conflict! Name joints as '<robot_arm_id>_joint<jointnumber>'"); + ROS_ERROR_STREAM("Resource conflict: Could not find arm_id in resource " + << map_it->first << ". Name joints as '<robot_arm_id>_joint<jointnumber>'"); return false; } ResourceClaims new_claim; @@ -136,4 +135,85 @@ ControlMode getControlMode(const std::string& arm_id, ArmClaimedMap& arm_claim_m return control_mode; } +bool hasConflictingMultiClaim(const ResourceWithClaimsMap& resource_map) { + for (const auto& resource : resource_map) { + if (resource.second.size() > 2) { + ROS_ERROR_STREAM("Resource conflict: " << resource.first + << " is claimed with more than two command interfaces " + "which is not supported."); + return true; + } + uint8_t torque_claims = 0; + uint8_t other_claims = 0; + if (resource.second.size() == 2) { + for (const auto& claimed_by : resource.second) { + if (claimed_by.at(2) == "hardware_interface::EffortJointInterface") { + torque_claims++; + } else { + other_claims++; + } + } + if (torque_claims != 1) { + ROS_ERROR_STREAM("Resource conflict: " + << resource.first + << " is claimed with a combination of two interfaces that is not " + "supported."); + return true; + } + } + } + return false; +} + +bool hasConflictingJointAndCartesianClaim(const ArmClaimedMap& arm_claim_map, + const std::string& arm_id) { + // check for conflicts between joint and cartesian level for each arm. + if (arm_claim_map.find(arm_id) != arm_claim_map.end()) { + if ((arm_claim_map.at(arm_id).cartesian_velocity_claims + + arm_claim_map.at(arm_id).cartesian_pose_claims > + 0 && + arm_claim_map.at(arm_id).joint_position_claims + + arm_claim_map.at(arm_id).joint_velocity_claims > + 0)) { + ROS_ERROR_STREAM( + "Resource conflict: Invalid combination of claims on joint AND cartesian level on arm " + << arm_id << " which is not supported."); + return true; + } + } + return false; +} + +bool partiallyClaimsArmJoints(const ArmClaimedMap& arm_claim_map, const std::string& arm_id) { + // Valid claims are torque claims on joint level in combination with either + // 7 non-torque claims on joint_level or one claim on cartesian level. + if (arm_claim_map.find(arm_id) != arm_claim_map.end()) { + if ((arm_claim_map.at(arm_id).joint_position_claims > 0 && + arm_claim_map.at(arm_id).joint_position_claims != 7) || + (arm_claim_map.at(arm_id).joint_velocity_claims > 0 && + arm_claim_map.at(arm_id).joint_velocity_claims != 7) || + (arm_claim_map.at(arm_id).joint_torque_claims > 0 && + arm_claim_map.at(arm_id).joint_torque_claims != 7)) { + ROS_ERROR_STREAM("Resource conflict: Partially claiming joints of arm " + << arm_id + << " is not supported. Make sure to claim all 7 joints of the robot."); + return true; + } + } + return false; +} + +bool hasTrajectoryClaim(const ArmClaimedMap& arm_claim_map, const std::string& arm_id) { + if (arm_claim_map.find(arm_id) != arm_claim_map.end()) { + if (arm_claim_map.at(arm_id).joint_position_claims + + arm_claim_map.at(arm_id).joint_velocity_claims + + arm_claim_map.at(arm_id).cartesian_velocity_claims + + arm_claim_map.at(arm_id).cartesian_pose_claims > + 0) { + return true; + } + } + return false; +} + } // namespace franka_hw diff --git a/franka_control/src/services.cpp b/franka_hw/src/services.cpp similarity index 61% rename from franka_control/src/services.cpp rename to franka_hw/src/services.cpp index c7a47c91b6f8a98d5e2a76cf86b07e5a4c9cafcf..355797756959febc2350cdaf7f304dcd9ef42acd 100644 --- a/franka_control/src/services.cpp +++ b/franka_hw/src/services.cpp @@ -1,12 +1,45 @@ // Copyright (c) 2017 Franka Emika GmbH // Use of this source code is governed by the Apache-2.0 license, see LICENSE -#include <franka_control/services.h> +#include <franka_hw/services.h> -namespace franka_control { +namespace franka_hw { + +void setupServices(franka::Robot& robot, ros::NodeHandle& node_handle, ServiceContainer& services) { + services + .advertiseService<franka_msgs::SetJointImpedance>(node_handle, "set_joint_impedance", + [&robot](auto&& req, auto&& res) { + return franka_hw::setJointImpedance( + robot, req, res); + }) + .advertiseService<franka_msgs::SetCartesianImpedance>( + node_handle, "set_cartesian_impedance", + [&robot](auto&& req, auto&& res) { + return franka_hw::setCartesianImpedance(robot, req, res); + }) + .advertiseService<franka_msgs::SetEEFrame>( + node_handle, "set_EE_frame", + [&robot](auto&& req, auto&& res) { return franka_hw::setEEFrame(robot, req, res); }) + .advertiseService<franka_msgs::SetKFrame>( + node_handle, "set_K_frame", + [&robot](auto&& req, auto&& res) { return franka_hw::setKFrame(robot, req, res); }) + .advertiseService<franka_msgs::SetForceTorqueCollisionBehavior>( + node_handle, "set_force_torque_collision_behavior", + [&robot](auto&& req, auto&& res) { + return franka_hw::setForceTorqueCollisionBehavior(robot, req, res); + }) + .advertiseService<franka_msgs::SetFullCollisionBehavior>( + node_handle, "set_full_collision_behavior", + [&robot](auto&& req, auto&& res) { + return franka_hw::setFullCollisionBehavior(robot, req, res); + }) + .advertiseService<franka_msgs::SetLoad>( + node_handle, "set_load", + [&robot](auto&& req, auto&& res) { return franka_hw::setLoad(robot, req, res); }); +} void setCartesianImpedance(franka::Robot& robot, - const SetCartesianImpedance::Request& req, - SetCartesianImpedance::Response& /* res */) { + const franka_msgs::SetCartesianImpedance::Request& req, + franka_msgs::SetCartesianImpedance::Response& /* res */) { std::array<double, 6> cartesian_stiffness; std::copy(req.cartesian_stiffness.cbegin(), req.cartesian_stiffness.cend(), cartesian_stiffness.begin()); @@ -14,32 +47,33 @@ void setCartesianImpedance(franka::Robot& robot, } void setJointImpedance(franka::Robot& robot, - const SetJointImpedance::Request& req, - SetJointImpedance::Response& /* res */) { + const franka_msgs::SetJointImpedance::Request& req, + franka_msgs::SetJointImpedance::Response& /* res */) { std::array<double, 7> joint_stiffness; std::copy(req.joint_stiffness.cbegin(), req.joint_stiffness.cend(), joint_stiffness.begin()); robot.setJointImpedance(joint_stiffness); } void setEEFrame(franka::Robot& robot, - const SetEEFrame::Request& req, - SetEEFrame::Response& /* res */) { + const franka_msgs::SetEEFrame::Request& req, + franka_msgs::SetEEFrame::Response& /* res */) { std::array<double, 16> F_T_EE; // NOLINT [readability-identifier-naming] std::copy(req.F_T_EE.cbegin(), req.F_T_EE.cend(), F_T_EE.begin()); robot.setEE(F_T_EE); } void setKFrame(franka::Robot& robot, - const SetKFrame::Request& req, - SetKFrame::Response& /* res */) { + const franka_msgs::SetKFrame::Request& req, + franka_msgs::SetKFrame::Response& /* res */) { std::array<double, 16> EE_T_K; // NOLINT [readability-identifier-naming] std::copy(req.EE_T_K.cbegin(), req.EE_T_K.cend(), EE_T_K.begin()); robot.setK(EE_T_K); } -void setForceTorqueCollisionBehavior(franka::Robot& robot, - const SetForceTorqueCollisionBehavior::Request& req, - SetForceTorqueCollisionBehavior::Response& /* res */ +void setForceTorqueCollisionBehavior( + franka::Robot& robot, + const franka_msgs::SetForceTorqueCollisionBehavior::Request& req, + franka_msgs::SetForceTorqueCollisionBehavior::Response& /* res */ ) { std::array<double, 7> lower_torque_thresholds_nominal; std::copy(req.lower_torque_thresholds_nominal.cbegin(), @@ -59,8 +93,8 @@ void setForceTorqueCollisionBehavior(franka::Robot& robot, } void setFullCollisionBehavior(franka::Robot& robot, - const SetFullCollisionBehavior::Request& req, - SetFullCollisionBehavior::Response& /* res */) { + const franka_msgs::SetFullCollisionBehavior::Request& req, + franka_msgs::SetFullCollisionBehavior::Response& /* res */) { std::array<double, 7> lower_torque_thresholds_acceleration; std::copy(req.lower_torque_thresholds_acceleration.cbegin(), req.lower_torque_thresholds_acceleration.cend(), @@ -96,7 +130,9 @@ void setFullCollisionBehavior(franka::Robot& robot, upper_force_thresholds_nominal); } -void setLoad(franka::Robot& robot, const SetLoad::Request& req, SetLoad::Response& /* res */) { +void setLoad(franka::Robot& robot, + const franka_msgs::SetLoad::Request& req, + franka_msgs::SetLoad::Response& /* res */) { double mass(req.mass); std::array<double, 3> F_x_center_load; // NOLINT [readability-identifier-naming] std::copy(req.F_x_center_load.cbegin(), req.F_x_center_load.cend(), F_x_center_load.begin()); @@ -106,4 +142,4 @@ void setLoad(franka::Robot& robot, const SetLoad::Request& req, SetLoad::Respons robot.setLoad(mass, F_x_center_load, load_inertia); } -} // namespace franka_control +} // namespace franka_hw diff --git a/franka_hw/test/CMakeLists.txt b/franka_hw/test/CMakeLists.txt index d14888ffa734fc0d23b25caa70a84dd4ce49192b..3601d19e9f2a5e7780d338b2ae5cdbadc76ad3d3 100644 --- a/franka_hw/test/CMakeLists.txt +++ b/franka_hw/test/CMakeLists.txt @@ -5,6 +5,7 @@ add_rostest_gtest(franka_hw_test main.cpp franka_hw_controller_switching_test.cpp franka_hw_interfaces_test.cpp + franka_combinable_hw_controller_switching_test.cpp ) add_dependencies(franka_hw_test @@ -21,4 +22,3 @@ target_link_libraries(franka_hw_test target_include_directories(franka_hw_test PUBLIC ${catkin_INCLUDE_DIRS} ) - diff --git a/franka_hw/test/franka_combinable_hw_controller_switching_test.cpp b/franka_hw/test/franka_combinable_hw_controller_switching_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f2837fb301f2b4e897ecdd4d48b4526b8649dff --- /dev/null +++ b/franka_hw/test/franka_combinable_hw_controller_switching_test.cpp @@ -0,0 +1,198 @@ +// Copyright (c) 2019 Franka Emika GmbH +// Use of this source code is governed by the Apache-2.0 license, see LICENSE +#include <array> +#include <list> +#include <memory> +#include <random> +#include <set> +#include <string> + +#include <gtest/gtest.h> +#include <hardware_interface/controller_info.h> +#include <hardware_interface/joint_command_interface.h> +#include <joint_limits_interface/joint_limits.h> +#include <joint_limits_interface/joint_limits_urdf.h> +#include <ros/ros.h> +#include <urdf/model.h> + +#include <franka_hw/franka_cartesian_command_interface.h> +#include <franka_hw/franka_combinable_hw.h> + +extern std::string arm_id; +extern std::array<std::string, 7> joint_names; + +hardware_interface::ControllerInfo newInfo( + const std::string& name, + const std::string& type, + const hardware_interface::InterfaceResources& resource1) { + hardware_interface::ControllerInfo info; + info.claimed_resources.clear(); + info.name = name; + info.type = type; + info.claimed_resources.push_back(resource1); + return info; +} + +hardware_interface::ControllerInfo newInfo( + const std::string& name, + const std::string& type, + const hardware_interface::InterfaceResources& resource1, + const hardware_interface::InterfaceResources& resource2) { + hardware_interface::ControllerInfo info = newInfo(name, type, resource1); + info.claimed_resources.push_back(resource2); + return info; +} + +hardware_interface::ControllerInfo newInfo( + const std::string& name, + const std::string& type, + const hardware_interface::InterfaceResources& resource1, + const hardware_interface::InterfaceResources& resource2, + const hardware_interface::InterfaceResources& resource3) { + hardware_interface::ControllerInfo info = newInfo(name, type, resource1, resource2); + info.claimed_resources.push_back(resource3); + return info; +} + +class CombinableControllerConflict + : public ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo>> { + public: + CombinableControllerConflict() : robot_(std::make_unique<franka_hw::FrankaCombinableHW>()) { + ros::NodeHandle root_nh; + ros::NodeHandle robot_hw_nh("~"); + robot_->initParameters(root_nh, robot_hw_nh); + robot_->initROSInterfaces(robot_hw_nh); + robot_->setupParameterCallbacks(robot_hw_nh); + } + bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->checkForConflict(info_list); + } + bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->prepareSwitch(info_list, info_list); + } + + private: + std::unique_ptr<franka_hw::FrankaCombinableHW> robot_; +}; + +class CombinableNoControllerConflict + : public ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo>> { + public: + CombinableNoControllerConflict() : robot_(std::make_unique<franka_hw::FrankaCombinableHW>()) { + ros::NodeHandle root_nh; + ros::NodeHandle robot_hw_nh("~"); + robot_->initParameters(root_nh, robot_hw_nh); + robot_->initROSInterfaces(robot_hw_nh); + robot_->setupParameterCallbacks(robot_hw_nh); + } + bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->checkForConflict(info_list); + } + bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->prepareSwitch(info_list, info_list); + } + + private: + std::unique_ptr<franka_hw::FrankaCombinableHW> robot_; +}; + +std::string arm_id2("panda2"); +std::string jp_iface_str("hardware_interface::PositionJointInterface"); +std::string jv_iface_str("hardware_interface::VelocityJointInterface"); +std::string jt_iface_str("hardware_interface::EffortJointInterface"); +std::string cv_iface_str("franka_hw::FrankaVelocityCartesianInterface"); +std::string cp_iface_str("franka_hw::FrankaPoseCartesianInterface"); +std::string unknown_iface_str("hardware_interface::UnknownInterface"); +std::string name_str("some_controller"); +std::string type_str("SomeControllerClass"); +std::set<std::string> joints_set = {joint_names[0], joint_names[1], joint_names[2], joint_names[3], + joint_names[4], joint_names[5], joint_names[6]}; +std::set<std::string> cartesian_set = {arm_id + "_robot"}; +std::set<std::string> cartesian_arm2_set = {arm_id2 + "_robot"}; +std::set<std::string> no_id_set = {"joint1"}; +hardware_interface::InterfaceResources no_id_res(jp_iface_str, no_id_set); +hardware_interface::InterfaceResources unknown_iface_res(unknown_iface_str, joints_set); +hardware_interface::InterfaceResources jp_res(jp_iface_str, joints_set); +hardware_interface::InterfaceResources jv_res(jv_iface_str, joints_set); +hardware_interface::InterfaceResources jt_res(jt_iface_str, joints_set); +hardware_interface::InterfaceResources cv_res(cv_iface_str, cartesian_set); +hardware_interface::InterfaceResources cp_res(cp_iface_str, cartesian_set); +hardware_interface::InterfaceResources cp_arm2_res(cp_iface_str, cartesian_arm2_set); +hardware_interface::ControllerInfo cp_arm2_info = newInfo(name_str, type_str, cp_arm2_res); +hardware_interface::ControllerInfo no_id_info = newInfo(name_str, type_str, no_id_res); +hardware_interface::ControllerInfo unknown_iface_info = + newInfo(name_str, type_str, unknown_iface_res); +hardware_interface::ControllerInfo jt_jt_info = newInfo(name_str, type_str, jt_res, jt_res); +hardware_interface::ControllerInfo jp_jp_info = newInfo(name_str, type_str, jp_res, jp_res); +hardware_interface::ControllerInfo jp_jv_info = newInfo(name_str, type_str, jp_res, jv_res); +hardware_interface::ControllerInfo jp_cv_info = newInfo(name_str, type_str, jp_res, cv_res); +hardware_interface::ControllerInfo jp_cp_info = newInfo(name_str, type_str, jp_res, cp_res); +hardware_interface::ControllerInfo jv_jv_info = newInfo(name_str, type_str, jv_res, jv_res); +hardware_interface::ControllerInfo jv_cv_info = newInfo(name_str, type_str, jv_res, cv_res); +hardware_interface::ControllerInfo jv_cp_info = newInfo(name_str, type_str, jv_res, cp_res); +hardware_interface::ControllerInfo cv_cv_info = newInfo(name_str, type_str, cv_res, cv_res); +hardware_interface::ControllerInfo cv_cp_info = newInfo(name_str, type_str, cv_res, cp_res); +hardware_interface::ControllerInfo cp_cp_info = newInfo(name_str, type_str, cp_res, cp_res); +hardware_interface::ControllerInfo jp_jp_jp_info = + newInfo(name_str, type_str, jp_res, jp_res, jp_res); +hardware_interface::ControllerInfo jp_info = newInfo(name_str, type_str, jp_res); +hardware_interface::ControllerInfo jv_info = newInfo(name_str, type_str, jv_res); +hardware_interface::ControllerInfo jt_info = newInfo(name_str, type_str, jt_res); +hardware_interface::ControllerInfo cv_info = newInfo(name_str, type_str, cv_res); +hardware_interface::ControllerInfo cp_info = newInfo(name_str, type_str, cp_res); +hardware_interface::ControllerInfo jt_jp_info = newInfo(name_str, type_str, jt_res, jp_res); +hardware_interface::ControllerInfo jt_jv_info = newInfo(name_str, type_str, jt_res, jv_res); +hardware_interface::ControllerInfo jt_cv_info = newInfo(name_str, type_str, jt_res, cv_res); +hardware_interface::ControllerInfo jt_cp_info = newInfo(name_str, type_str, jt_res, cp_res); + +INSTANTIATE_TEST_CASE_P( + combinableNonAdmissibleRequests, + CombinableControllerConflict, + ::testing::Values(std::list<hardware_interface::ControllerInfo>{no_id_info}, + std::list<hardware_interface::ControllerInfo>{unknown_iface_info}, + std::list<hardware_interface::ControllerInfo>{jp_info}, + std::list<hardware_interface::ControllerInfo>{jv_info}, + std::list<hardware_interface::ControllerInfo>{cv_info}, + std::list<hardware_interface::ControllerInfo>{cp_info}, + std::list<hardware_interface::ControllerInfo>{jt_jp_info}, + std::list<hardware_interface::ControllerInfo>{jt_jv_info}, + std::list<hardware_interface::ControllerInfo>{jt_jt_info}, + std::list<hardware_interface::ControllerInfo>{jp_jp_info}, + std::list<hardware_interface::ControllerInfo>{jp_jv_info}, + std::list<hardware_interface::ControllerInfo>{jp_cv_info}, + std::list<hardware_interface::ControllerInfo>{jp_cp_info}, + std::list<hardware_interface::ControllerInfo>{jv_jv_info}, + std::list<hardware_interface::ControllerInfo>{jv_cv_info}, + std::list<hardware_interface::ControllerInfo>{jv_cp_info}, + std::list<hardware_interface::ControllerInfo>{cv_cv_info}, + std::list<hardware_interface::ControllerInfo>{cv_cp_info}, + std::list<hardware_interface::ControllerInfo>{cp_cp_info}, + std::list<hardware_interface::ControllerInfo>{jp_jp_jp_info}, + std::list<hardware_interface::ControllerInfo>{jt_cv_info}, + std::list<hardware_interface::ControllerInfo>{jt_cp_info})); + +INSTANTIATE_TEST_CASE_P(combinableAdmissibleRequests, + CombinableNoControllerConflict, + ::testing::Values(std::list<hardware_interface::ControllerInfo>{jt_info}, + std::list<hardware_interface::ControllerInfo>{ + jt_info, cp_arm2_info})); + +TEST(FrankaCombinableHWTests, CanInitROSInterfaces) { + franka_hw::FrankaCombinableHW hw; + ros::NodeHandle root_nh; + ros::NodeHandle robot_hw_nh("~"); + EXPECT_TRUE(hw.initParameters(root_nh, robot_hw_nh)); + EXPECT_NO_THROW(hw.initROSInterfaces(robot_hw_nh)); +} + +TEST_P(CombinableControllerConflict, ConflictsForIncompatibleControllers) { + EXPECT_TRUE(callCheckForConflict(GetParam())); +} + +TEST_P(CombinableNoControllerConflict, DoesNotConflictForCompatibleControllers) { + EXPECT_FALSE(callCheckForConflict(GetParam())); +} + +TEST_P(CombinableNoControllerConflict, CanPrepareSwitchForCompatibleControllers) { + EXPECT_TRUE(callPrepareSwitch(GetParam())); +} diff --git a/franka_hw/test/franka_hw_controller_switching_test.cpp b/franka_hw/test/franka_hw_controller_switching_test.cpp index 58639da26544821748e43a479ebf8e04d27e3c78..59646b7c4468cbf69887c907de3afa837d7502c2 100644 --- a/franka_hw/test/franka_hw_controller_switching_test.cpp +++ b/franka_hw/test/franka_hw_controller_switching_test.cpp @@ -24,13 +24,9 @@ using hardware_interface::InterfaceResources; using hardware_interface::ControllerInfo; std::string arm_id("panda"); -std::array<std::string, 7> joint_names = {arm_id + "_joint1", - arm_id + "_joint2", - arm_id + "_joint3", - arm_id + "_joint4", - arm_id + "_joint5", - arm_id + "_joint6", - arm_id + "_joint7"}; +std::array<std::string, 7> joint_names = { + arm_id + "_joint1", arm_id + "_joint2", arm_id + "_joint3", arm_id + "_joint4", + arm_id + "_joint5", arm_id + "_joint6", arm_id + "_joint7"}; namespace franka_hw { @@ -67,32 +63,45 @@ hardware_interface::ControllerInfo newInfo( return info; } -class ControllerConflict : - public ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo> > { -public: - ControllerConflict() : robot_(new FrankaHW(joint_names, arm_id, urdf::Model())) {} - bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { - return robot_->checkForConflict(info_list); - } - bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { - return robot_->prepareSwitch(info_list, info_list); - } +class ControllerConflict + : public ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo>> { + public: + ControllerConflict() : robot_(std::make_unique<FrankaHW>()) { + ros::NodeHandle private_nh("~"); + ros::NodeHandle root_nh; + EXPECT_TRUE(robot_->initParameters(root_nh, private_nh)); + robot_->initROSInterfaces(private_nh); + } + bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->checkForConflict(info_list); + } + bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->prepareSwitch(info_list, info_list); + } + private: - std::unique_ptr<FrankaHW> robot_; + std::unique_ptr<FrankaHW> robot_; }; -class NoControllerConflict : public - ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo> > { -public: - NoControllerConflict() : robot_(new FrankaHW(joint_names, arm_id, urdf::Model())) {} - bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { - return robot_->checkForConflict(info_list); - } - bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { - return robot_->prepareSwitch(info_list, info_list); - } +class NoControllerConflict + : public ::testing::TestWithParam<std::list<hardware_interface::ControllerInfo>> { + public: + NoControllerConflict() : robot_(std::make_unique<FrankaHW>()) { + ros::NodeHandle private_nh("~"); + ros::NodeHandle root_nh; + EXPECT_TRUE(robot_->initParameters(root_nh, private_nh)); + robot_->initROSInterfaces(private_nh); + robot_->setupParameterCallbacks(private_nh); + } + bool callCheckForConflict(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->checkForConflict(info_list); + } + bool callPrepareSwitch(const std::list<hardware_interface::ControllerInfo> info_list) { + return robot_->prepareSwitch(info_list, info_list); + } + private: - std::unique_ptr<FrankaHW> robot_; + std::unique_ptr<FrankaHW> robot_; }; string arm_id2("panda2"); @@ -104,13 +113,8 @@ string cp_iface_str("franka_hw::FrankaPoseCartesianInterface"); string unknown_iface_str("hardware_interface::UnknownInterface"); string name_str("some_controller"); string type_str("SomeControllerClass"); -set<string> joints_set = {joint_names[0], - joint_names[1], - joint_names[2], - joint_names[3], - joint_names[4], - joint_names[5], - joint_names[6]}; +set<string> joints_set = {joint_names[0], joint_names[1], joint_names[2], joint_names[3], + joint_names[4], joint_names[5], joint_names[6]}; set<string> cartesian_set = {arm_id + "_robot"}; set<string> cartesian_arm2_set = {arm_id2 + "_robot"}; set<string> no_id_set = {"joint1"}; @@ -186,7 +190,7 @@ TEST_P(NoControllerConflict, DoesNotConflictForCompatibleControllers) { } TEST_P(NoControllerConflict, CanPrepareSwitchForCompatibleControllers) { - EXPECT_TRUE(callPrepareSwitch(GetParam())); + EXPECT_TRUE(callPrepareSwitch(GetParam())); } } // namespace franka_hw diff --git a/franka_hw/test/franka_hw_interfaces_test.cpp b/franka_hw/test/franka_hw_interfaces_test.cpp index 7984e1e2f71dfb3f43a00c06b42ceaa05c928a5a..7087632c21ef527817e3461ddb5e745e013106ed 100644 --- a/franka_hw/test/franka_hw_interfaces_test.cpp +++ b/franka_hw/test/franka_hw_interfaces_test.cpp @@ -23,21 +23,24 @@ extern std::array<std::string, 7> joint_names; namespace franka_hw { TEST(FrankaHWTests, InterfacesWorkForReadAndCommand) { - std::unique_ptr<FrankaHW> robotptr(new FrankaHW(joint_names, arm_id, urdf::Model())); + auto robot_ptr = std::make_unique<FrankaHW>(); + ros::NodeHandle private_nh("~"); + ros::NodeHandle root_nh; + EXPECT_TRUE(robot_ptr->initParameters(root_nh, private_nh)); + robot_ptr->initROSInterfaces(private_nh); + hardware_interface::JointStateInterface* js_interface = - robotptr->get<hardware_interface::JointStateInterface>(); + robot_ptr->get<hardware_interface::JointStateInterface>(); hardware_interface::PositionJointInterface* pj_interface = - robotptr->get<hardware_interface::PositionJointInterface>(); + robot_ptr->get<hardware_interface::PositionJointInterface>(); hardware_interface::VelocityJointInterface* vj_interface = - robotptr->get<hardware_interface::VelocityJointInterface>(); + robot_ptr->get<hardware_interface::VelocityJointInterface>(); hardware_interface::EffortJointInterface* ej_interface = - robotptr->get<hardware_interface::EffortJointInterface>(); - FrankaPoseCartesianInterface* fpc_interface = - robotptr->get<FrankaPoseCartesianInterface>(); + robot_ptr->get<hardware_interface::EffortJointInterface>(); + FrankaPoseCartesianInterface* fpc_interface = robot_ptr->get<FrankaPoseCartesianInterface>(); FrankaVelocityCartesianInterface* fvc_interface = - robotptr->get<FrankaVelocityCartesianInterface>(); - FrankaStateInterface* fs_interface = - robotptr->get<FrankaStateInterface>(); + robot_ptr->get<FrankaVelocityCartesianInterface>(); + FrankaStateInterface* fs_interface = robot_ptr->get<FrankaStateInterface>(); ASSERT_NE(nullptr, js_interface); ASSERT_NE(nullptr, pj_interface); @@ -48,23 +51,20 @@ TEST(FrankaHWTests, InterfacesWorkForReadAndCommand) { ASSERT_NE(nullptr, fs_interface); // Model interface not available with this signature - FrankaModelInterface* fm_interface = robotptr->get<FrankaModelInterface>(); + FrankaModelInterface* fm_interface = robot_ptr->get<FrankaModelInterface>(); ASSERT_EQ(nullptr, fm_interface); EXPECT_NO_THROW(FrankaCartesianPoseHandle fpc_handle = - fpc_interface->getHandle(arm_id + "_robot")); - FrankaCartesianPoseHandle fpc_handle = - fpc_interface->getHandle(arm_id + "_robot"); - std::array<double, 16> pose_command = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0}; + fpc_interface->getHandle(arm_id + "_robot")); + FrankaCartesianPoseHandle fpc_handle = fpc_interface->getHandle(arm_id + "_robot"); + std::array<double, 16> pose_command = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; fpc_handle.setCommand(pose_command); EXPECT_EQ(pose_command, fpc_handle.getCommand()); EXPECT_NO_THROW(FrankaCartesianVelocityHandle fvc_handle = - fvc_interface->getHandle(arm_id + "_robot")); - FrankaCartesianVelocityHandle fvc_handle = - fvc_interface->getHandle(arm_id + "_robot"); + fvc_interface->getHandle(arm_id + "_robot")); + FrankaCartesianVelocityHandle fvc_handle = fvc_interface->getHandle(arm_id + "_robot"); std::array<double, 6> vel_command = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; fvc_handle.setCommand(vel_command); EXPECT_EQ(vel_command, fvc_handle.getCommand()); @@ -73,10 +73,10 @@ TEST(FrankaHWTests, InterfacesWorkForReadAndCommand) { } TEST(FrankaHWTests, JointLimitInterfacesEnforceLimitsOnCommands) { - urdf::Model urdf_model; - ASSERT_TRUE(urdf_model.initParamWithNodeHandle("robot_description")); - - std::unique_ptr<FrankaHW> robot_ptr(new FrankaHW(joint_names, arm_id, urdf_model)); + auto robot_ptr = std::make_unique<FrankaHW>(); + ros::NodeHandle nh("~"); + ASSERT_TRUE(robot_ptr->initParameters(nh, nh)); + robot_ptr->initROSInterfaces(nh); hardware_interface::PositionJointInterface* pj_interface = robot_ptr->get<hardware_interface::PositionJointInterface>(); @@ -93,16 +93,15 @@ TEST(FrankaHWTests, JointLimitInterfacesEnforceLimitsOnCommands) { std::vector<hardware_interface::JointHandle> velocity_handles(7); std::vector<hardware_interface::JointHandle> effort_handles(7); + urdf::Model urdf_model; + ASSERT_TRUE(urdf_model.initParamWithNodeHandle("robot_description")); + for (size_t i = 0; i < joint_names.size(); ++i) { auto urdf_joint = urdf_model.getJoint(joint_names[i]); - ASSERT_TRUE( - joint_limits_interface::getJointLimits(urdf_joint, joint_limits[i])); - ASSERT_NO_THROW(position_handles[i] = - pj_interface->getHandle(joint_names[i])); - ASSERT_NO_THROW(velocity_handles[i] = - vj_interface->getHandle(joint_names[i])); - ASSERT_NO_THROW(effort_handles[i] = - ej_interface->getHandle(joint_names[i])); + ASSERT_TRUE(joint_limits_interface::getJointLimits(urdf_joint, joint_limits[i])); + ASSERT_NO_THROW(position_handles[i] = pj_interface->getHandle(joint_names[i])); + ASSERT_NO_THROW(velocity_handles[i] = vj_interface->getHandle(joint_names[i])); + ASSERT_NO_THROW(effort_handles[i] = ej_interface->getHandle(joint_names[i])); } std::uniform_real_distribution<double> uniform_distribution(0.1, 3.0); @@ -113,13 +112,12 @@ TEST(FrankaHWTests, JointLimitInterfacesEnforceLimitsOnCommands) { uniform_distribution(random_engine)); velocity_handles[i].setCommand(joint_limits[i].max_velocity + uniform_distribution(random_engine)); - effort_handles[i].setCommand(joint_limits[i].max_effort + - uniform_distribution(random_engine)); + effort_handles[i].setCommand(joint_limits[i].max_effort + uniform_distribution(random_engine)); } robot_ptr->enforceLimits(ros::Duration(0.001)); for (size_t i = 0; i < joint_names.size(); ++i) { EXPECT_LE(position_handles[i].getCommand(), joint_limits[i].max_position); - EXPECT_GE(position_handles[i].getCommand(),joint_limits[i].min_position); + EXPECT_GE(position_handles[i].getCommand(), joint_limits[i].min_position); EXPECT_LE(velocity_handles[i].getCommand(), joint_limits[i].max_velocity); EXPECT_GE(velocity_handles[i].getCommand(), -joint_limits[i].max_velocity); EXPECT_LE(effort_handles[i].getCommand(), joint_limits[i].max_effort); @@ -131,18 +129,17 @@ TEST(FrankaHWTests, JointLimitInterfacesEnforceLimitsOnCommands) { uniform_distribution(random_engine)); velocity_handles[i].setCommand(-joint_limits[i].max_velocity - uniform_distribution(random_engine)); - effort_handles[i].setCommand(-joint_limits[i].max_effort - - uniform_distribution(random_engine)); + effort_handles[i].setCommand(-joint_limits[i].max_effort - uniform_distribution(random_engine)); } robot_ptr->enforceLimits(ros::Duration(0.001)); for (size_t i = 0; i < joint_names.size(); ++i) { - EXPECT_LE(position_handles[i].getCommand(), joint_limits[i].max_position); - EXPECT_GE(position_handles[i].getCommand(),joint_limits[i].min_position); - EXPECT_LE(velocity_handles[i].getCommand(), joint_limits[i].max_velocity); - EXPECT_GE(velocity_handles[i].getCommand(), -joint_limits[i].max_velocity); - EXPECT_LE(effort_handles[i].getCommand(), joint_limits[i].max_effort); - EXPECT_GE(effort_handles[i].getCommand(), -joint_limits[i].max_effort); } + EXPECT_LE(position_handles[i].getCommand(), joint_limits[i].max_position); + EXPECT_GE(position_handles[i].getCommand(), joint_limits[i].min_position); + EXPECT_LE(velocity_handles[i].getCommand(), joint_limits[i].max_velocity); + EXPECT_GE(velocity_handles[i].getCommand(), -joint_limits[i].max_velocity); + EXPECT_LE(effort_handles[i].getCommand(), joint_limits[i].max_effort); + EXPECT_GE(effort_handles[i].getCommand(), -joint_limits[i].max_effort); + } } } // namespace franka_hw - diff --git a/franka_hw/test/launch/franka_hw_test.test b/franka_hw/test/launch/franka_hw_test.test index 53dfa12ed4015436ad3dfc2257c2197e90fa92bb..64ffded744f2e8ff753e3d4464efbc2f3221c5c2 100644 --- a/franka_hw/test/launch/franka_hw_test.test +++ b/franka_hw/test/launch/franka_hw_test.test @@ -1,5 +1,8 @@ <launch> <env name="ROSCONSOLE_CONFIG_FILE" value="$(find franka_hw)/test/config/ros_console_settings_for_tests.conf"/> <param name="robot_description" command="$(find xacro)/xacro --inorder $(find franka_description)/robots/panda_arm.urdf.xacro" /> - <test test-name="franka_hw_test" pkg="franka_hw" type="franka_hw_test" /> + <test test-name="franka_hw_test" pkg="franka_hw" type="franka_hw_test" > + <rosparam command="load" file="$(find franka_control)/config/franka_control_node.yaml" /> + <param name="robot_ip" value="unused_dummy_ip" /> + </test> </launch> diff --git a/franka_msgs/CMakeLists.txt b/franka_msgs/CMakeLists.txt index 49816727b832637c32d3075794185a542341495e..1f6f926597e947e268865a2a7827ea0e30570d8e 100644 --- a/franka_msgs/CMakeLists.txt +++ b/franka_msgs/CMakeLists.txt @@ -1,8 +1,22 @@ cmake_minimum_required(VERSION 3.4) project(franka_msgs) -find_package(catkin REQUIRED COMPONENTS message_generation std_msgs) +find_package(catkin REQUIRED COMPONENTS message_generation std_msgs actionlib_msgs) add_message_files(FILES Errors.msg FrankaState.msg) -generate_messages(DEPENDENCIES std_msgs) -catkin_package(CATKIN_DEPENDS message_runtime std_msgs) + +add_service_files(FILES + SetCartesianImpedance.srv + SetEEFrame.srv + SetForceTorqueCollisionBehavior.srv + SetFullCollisionBehavior.srv + SetJointImpedance.srv + SetKFrame.srv + SetLoad.srv +) + +add_action_files(FILES ErrorRecovery.action) + +generate_messages(DEPENDENCIES std_msgs actionlib_msgs) + +catkin_package(CATKIN_DEPENDS message_runtime std_msgs actionlib_msgs) diff --git a/franka_control/action/ErrorRecovery.action b/franka_msgs/action/ErrorRecovery.action similarity index 100% rename from franka_control/action/ErrorRecovery.action rename to franka_msgs/action/ErrorRecovery.action diff --git a/franka_msgs/package.xml b/franka_msgs/package.xml index e5b2e8dc330ee85bec6bd1b14fbb50ec117cba46..c8bcef0cbad6707c4b4867f2b36792de5e0f7bb1 100644 --- a/franka_msgs/package.xml +++ b/franka_msgs/package.xml @@ -13,9 +13,13 @@ <buildtool_depend>catkin</buildtool_depend> - <depend>std_msgs</depend> <build_depend>message_generation</build_depend> + + <depend>std_msgs</depend> + <depend>actionlib_msgs</depend> + <exec_depend>message_runtime</exec_depend> + <build_export_depend>message_runtime</build_export_depend> <export> diff --git a/franka_control/srv/SetCartesianImpedance.srv b/franka_msgs/srv/SetCartesianImpedance.srv similarity index 100% rename from franka_control/srv/SetCartesianImpedance.srv rename to franka_msgs/srv/SetCartesianImpedance.srv diff --git a/franka_control/srv/SetEEFrame.srv b/franka_msgs/srv/SetEEFrame.srv similarity index 100% rename from franka_control/srv/SetEEFrame.srv rename to franka_msgs/srv/SetEEFrame.srv diff --git a/franka_control/srv/SetForceTorqueCollisionBehavior.srv b/franka_msgs/srv/SetForceTorqueCollisionBehavior.srv similarity index 100% rename from franka_control/srv/SetForceTorqueCollisionBehavior.srv rename to franka_msgs/srv/SetForceTorqueCollisionBehavior.srv diff --git a/franka_control/srv/SetFullCollisionBehavior.srv b/franka_msgs/srv/SetFullCollisionBehavior.srv similarity index 100% rename from franka_control/srv/SetFullCollisionBehavior.srv rename to franka_msgs/srv/SetFullCollisionBehavior.srv diff --git a/franka_control/srv/SetJointImpedance.srv b/franka_msgs/srv/SetJointImpedance.srv similarity index 100% rename from franka_control/srv/SetJointImpedance.srv rename to franka_msgs/srv/SetJointImpedance.srv diff --git a/franka_control/srv/SetKFrame.srv b/franka_msgs/srv/SetKFrame.srv similarity index 100% rename from franka_control/srv/SetKFrame.srv rename to franka_msgs/srv/SetKFrame.srv diff --git a/franka_control/srv/SetLoad.srv b/franka_msgs/srv/SetLoad.srv similarity index 100% rename from franka_control/srv/SetLoad.srv rename to franka_msgs/srv/SetLoad.srv diff --git a/franka_visualization/CMakeLists.txt b/franka_visualization/CMakeLists.txt index 65322ddd8362228a9b76cdb73ac829d521bcccaa..3354d32e3b1730f67463d546cb93410a53fe3954 100644 --- a/franka_visualization/CMakeLists.txt +++ b/franka_visualization/CMakeLists.txt @@ -9,7 +9,7 @@ find_package(catkin REQUIRED COMPONENTS roscpp ) -find_package(Franka 0.5.0 REQUIRED) +find_package(Franka 0.7.0 REQUIRED) catkin_package(CATKIN_DEPENDS sensor_msgs roscpp) diff --git a/scripts/fail-on-output.sh b/scripts/fail-on-output.sh deleted file mode 100755 index ea21216d60af5af4527f6b1d1c0d155bf8faaa40..0000000000000000000000000000000000000000 --- a/scripts/fail-on-output.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -output="$($@)" -if [[ "$output" ]]; then - echo "$output" - exit 1 -fi diff --git a/scripts/format-check.sh b/scripts/format-check.sh deleted file mode 100755 index 916855f9578dbe9e46a4f8ea264df36a0f5d0bcf..0000000000000000000000000000000000000000 --- a/scripts/format-check.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# grep the result of clang-format with -output-replacements-xml flag -# if any "replacement" tags are found, then code is incorrectly formatted -echo $($@) | grep '<replacement ' > /dev/null -if [ $? -ne 1 ]; then - echo "Files have to be formatted with clang-format" - exit 1; -fi