Enhance IIWA Robot Configuration and Launch Files

- Updated iiwa7 URDF Xacro to include command and state interfaces for position and effort with defined limits for all joints.
- Modified setting_loader.py to include new fields for command_mode and description in the RobotCfg dataclass and settings loading process.
- Created iiwa.launch.py to manage the launch of the IIWA robot, integrating MoveIt configurations and RViz support.
- Added iiwa_controllers.launch.py to set up the controller manager and spawner for the IIWA robot.
- Introduced iiwa_hardware_interface_plugin.xml to define the hardware interface for the KUKA IIWA 7 robot.
- Added iiwa7_fri.urdf.xacro to support FRI (Fast Robot Interface) with appropriate command and state interfaces for each joint.
This commit is contained in:
Даниил Грабарь
2026-04-06 09:40:04 +03:00
parent 032935dec9
commit 1dc17d5949
17 changed files with 1278 additions and 525 deletions
+176
View File
@@ -0,0 +1,176 @@
from launch import LaunchDescription
from launch.actions import (
DeclareLaunchArgument,
EmitEvent,
IncludeLaunchDescription,
OpaqueFunction,
RegisterEventHandler,
)
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.events import Shutdown
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from moveit_configs_utils import MoveItConfigsBuilder
from iiwa_utils import converter, setting_loader
import yaml
import tempfile
def wrap_for_ros2_params(yaml_path: str, namespace: str) -> str:
with open(yaml_path, "r") as f:
data = yaml.safe_load(f)
wrapped = {namespace: {"ros__parameters": data}}
tmp = tempfile.NamedTemporaryFile(
mode="w", suffix=".yaml", delete=False
)
yaml.dump(wrapped, tmp, default_flow_style=False)
tmp.close()
return tmp.name
def _runtime_setup(context, *args, **kwargs):
settings = setting_loader.build_settings(
settings_path=LaunchConfiguration("setting").perform(context), check_files=True
)
xacro_args = {
"initial_positions_file": settings.controller.moveit.initial_positions,
"robot_ip": settings.robot.ip,
"fri_port": str(settings.robot.port),
"simulate": "false",
"command_mode": settings.robot.command_mode,
}
robot_description = converter.load_robot_description(
model_path=settings.robot.description,
robot_name=settings.robot.name,
xacro_args=xacro_args,
)
rsp_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
name="robot_state_publisher",
output="screen",
parameters=[{"robot_description": robot_description,
"use_sim_time": False}],
)
controllers_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
PathJoinSubstitution(
[
FindPackageShare("iiwa_bringup"),
"launch",
"supported",
"iiwa_controllers.launch.py"
]
)
),
launch_arguments={
"robot_name": str(settings.robot.name),
"description": str(settings.robot.description),
"initial_positions_file": str(settings.controller.moveit.initial_positions),
"controller_path": str(settings.controller.controller_path)
}.items()
)
# Moveit
moveit_configs = (
MoveItConfigsBuilder("iiwa7", package_name="iiwa_config")
.robot_description(
file_path=settings.robot.description,
mappings={
"initial_positions_file": settings.controller.moveit.initial_positions
},
)
.robot_description_semantic(file_path=settings.controller.moveit.srdf)
.robot_description_kinematics(file_path=settings.controller.moveit.kinematics)
.joint_limits(file_path=settings.controller.moveit.joint_limits)
.pilz_cartesian_limits(file_path=settings.controller.moveit.pilz_limits)
.trajectory_execution(file_path=settings.controller.moveit.moveit_controllers)
.moveit_cpp(file_path=settings.controller.moveit.moveit_cpp)
.to_moveit_configs()
)
move_group = Node(
package="moveit_ros_move_group",
executable="move_group",
output="screen",
parameters=[
moveit_configs.to_dict(),
{"robot_description": robot_description},
],
)
joint_limits_ros2 = wrap_for_ros2_params(
settings.controller.moveit.joint_limits,
"robot_description_planning"
)
kinematics_ros2 = wrap_for_ros2_params(
settings.controller.moveit.kinematics,
"robot_description_kinematics"
)
rviz_launch = Node(
condition=IfCondition(LaunchConfiguration("rviz")),
package="rviz2",
executable="rviz2",
name="rviz2",
arguments=["-d", settings.digital_twin.rviz.config],
output="log",
parameters=[
moveit_configs.robot_description,
moveit_configs.robot_description_semantic,
# moveit_configs.robot_description_kinematics,
moveit_configs.planning_pipelines,
# moveit_configs.joint_limits,
joint_limits_ros2,
kinematics_ros2,
],
)
shutdown_on_rviz_exit = RegisterEventHandler(
OnProcessExit(target_action=rviz_launch, on_exit=[EmitEvent(event=Shutdown())])
)
return [
rsp_node,
controllers_launch,
move_group,
rviz_launch,
shutdown_on_rviz_exit
]
def generate_launch_description():
declare_rviz = DeclareLaunchArgument(
name="rviz",
default_value="0",
description="If true|1|yes then launch RViz/MoveIt branch (instead of controllers branch)",
)
declacre_setting = DeclareLaunchArgument(
name="setting",
default_value=PathJoinSubstitution(
[FindPackageShare("iiwa_config"), "config", "setting.yaml"]
),
description="Absolute path to settings file",
)
runtime_setup = OpaqueFunction(function=_runtime_setup)
return LaunchDescription(
[
declare_rviz,
declacre_setting,
runtime_setup,
]
)
@@ -0,0 +1,66 @@
from launch.substitutions import LaunchConfiguration
from launch.actions import RegisterEventHandler
from launch.event_handlers import OnProcessExit
from launch.actions import OpaqueFunction
from launch import LaunchDescription
from launch_ros.actions import Node
from iiwa_utils import converter
def _setup_controllers(context, *args, **kwargs):
robot_name = LaunchConfiguration("robot_name").perform(context)
description = LaunchConfiguration("description").perform(context)
initial_positions_file = LaunchConfiguration("initial_positions_file").perform(
context
)
controller_path = LaunchConfiguration("controller_path").perform(context)
robot_description = converter.load_robot_description(
model_path=description,
robot_name=robot_name,
xacro_args={"initial_positions_file": initial_positions_file},
)
ros2_control_node = Node(
package="controller_manager",
executable="ros2_control_node",
output="screen",
parameters=[
{"robot_description": robot_description},
controller_path,
],
)
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
output="screen",
)
arm_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["iiwa_arm_controller", "--controller-manager", "/controller_manager"],
output="screen",
)
arm_controller_after_jsb = RegisterEventHandler(
OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[arm_controller_spawner],
)
)
return [
ros2_control_node,
joint_state_broadcaster_spawner,
arm_controller_after_jsb,
]
def generate_launch_description():
return LaunchDescription([OpaqueFunction(function=_setup_controllers)])
@@ -41,6 +41,15 @@ def _setup_controllers(context, *args, **kwargs):
parameters=[{"use_sim_time": False}], parameters=[{"use_sim_time": False}],
) )
torque_controller_spawner = Node(
package="controller_manager",
executable="spawner",
output="screen",
arguments=["forward_torque_controller",
"--inactive"] + tmo,
parameters=[{"use_sim_time": False}]
)
spawner_urdf = URDFSpawner( spawner_urdf = URDFSpawner(
name=robot_name, name=robot_name,
robot_description=robot_description, robot_description=robot_description,
@@ -48,7 +57,7 @@ def _setup_controllers(context, *args, **kwargs):
rotation=rotation, rotation=rotation,
) )
return [jsb, jtc, spawner_urdf] return [jsb, jtc, torque_controller_spawner, spawner_urdf]
def generate_launch_description(): def generate_launch_description():
+11 -3
View File
@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--This does not replace URDF, and is not an extension of URDF.
This is a format for representing semantic information about the robot structure.
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
-->
<robot name="iiwa7"> <robot name="iiwa7">
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc--> <!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included--> <!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
@@ -6,6 +10,7 @@
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group--> <!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names--> <!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
<group name="iiwa_arm"> <group name="iiwa_arm">
<joint name="world_base_joint"/>
<joint name="joint1"/> <joint name="joint1"/>
<joint name="joint2"/> <joint name="joint2"/>
<joint name="joint3"/> <joint name="joint3"/>
@@ -14,7 +19,9 @@
<joint name="joint6"/> <joint name="joint6"/>
<joint name="joint7"/> <joint name="joint7"/>
</group> </group>
<group name="hand">
<link name="link_ee"/>
</group>
<!--GROUP STATES: Purpose: Define a named state for a particular group, in terms of joint values. This is useful to define states like 'folded arms'--> <!--GROUP STATES: Purpose: Define a named state for a particular group, in terms of joint values. This is useful to define states like 'folded arms'-->
<group_state name="home" group="iiwa_arm"> <group_state name="home" group="iiwa_arm">
<joint name="joint1" value="0"/> <joint name="joint1" value="0"/>
@@ -25,7 +32,6 @@
<joint name="joint6" value="0"/> <joint name="joint6" value="0"/>
<joint name="joint7" value="0"/> <joint name="joint7" value="0"/>
</group_state> </group_state>
<group_state name="work" group="iiwa_arm"> <group_state name="work" group="iiwa_arm">
<joint name="joint1" value="0"/> <joint name="joint1" value="0"/>
<joint name="joint2" value="0"/> <joint name="joint2" value="0"/>
@@ -35,12 +41,14 @@
<joint name="joint6" value="1.57"/> <joint name="joint6" value="1.57"/>
<joint name="joint7" value="0"/> <joint name="joint7" value="0"/>
</group_state> </group_state>
<!--END EFFECTOR: Purpose: Represent information about an end effector.-->
<end_effector name="hand" parent_link="link7" group="hand"/>
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. --> <!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
<disable_collisions link1="base_link" link2="link1" reason="Adjacent"/> <disable_collisions link1="base_link" link2="link1" reason="Adjacent"/>
<disable_collisions link1="base_link" link2="link2" reason="Never"/> <disable_collisions link1="base_link" link2="link2" reason="Never"/>
<disable_collisions link1="base_link" link2="link3" reason="Never"/> <disable_collisions link1="base_link" link2="link3" reason="Never"/>
<disable_collisions link1="base_link" link2="link4" reason="Never"/> <disable_collisions link1="base_link" link2="link4" reason="Never"/>
<disable_collisions link1="base_link" link2="link7" reason="Never"/>
<disable_collisions link1="link1" link2="link2" reason="Adjacent"/> <disable_collisions link1="link1" link2="link2" reason="Adjacent"/>
<disable_collisions link1="link1" link2="link3" reason="Never"/> <disable_collisions link1="link1" link2="link3" reason="Never"/>
<disable_collisions link1="link1" link2="link4" reason="Never"/> <disable_collisions link1="link1" link2="link4" reason="Never"/>
@@ -1,6 +1,6 @@
controller_manager: controller_manager:
ros__parameters: ros__parameters:
update_rate: 100 update_rate: 200
joint_state_broadcaster: joint_state_broadcaster:
type: "joint_state_broadcaster/JointStateBroadcaster" type: "joint_state_broadcaster/JointStateBroadcaster"
@@ -8,6 +8,10 @@ controller_manager:
iiwa_arm_controller: iiwa_arm_controller:
type: "joint_trajectory_controller/JointTrajectoryController" type: "joint_trajectory_controller/JointTrajectoryController"
forward_torque_controller:
type: "forward_command_controller/ForwardCommandController"
# Основной контроллер - плавное движение по траектории
iiwa_arm_controller: iiwa_arm_controller:
ros__parameters: ros__parameters:
joints: joints:
@@ -24,7 +28,44 @@ iiwa_arm_controller:
state_interfaces: state_interfaces:
- position - position
- velocity
allow_partial_joints_goal: false # Интерполяция между точками траектории
interpolate_from_desired_state: true interpolate_from_desired_state: true
# Разрешить неполные goals
allow_partial_joints_goal: false
# Разрешить ненулевую скорость в конечной точке траектории
# true = плавные составные движения
# false = полная остановка в каждой точке (безопаснее)
allow_nonzero_velocity_at_trajectory_end: true allow_nonzero_velocity_at_trajectory_end: true
state_publish_rate: 100.0 # Гц публикации /joint_states
action_monitor_rate: 20.0 # Гц мониторинга action goal
# Допуски — насколько точно робот должен попасть в цель
# constraints:
# stopped_velocity_tolerance: 0.01 # рад/с — считаем остановившимся
# goal_time: 1.0 # сек — доп. время на достижение цели
# joint1: { trajectory: 0, goal: 0.01 }
# joint2: { trajectory: 0, goal: 0.01 }
# joint3: { trajectory: 0, goal: 0.01 }
# joint4: { trajectory: 0, goal: 0.01 }
# joint5: { trajectory: 0, goal: 0.01 }
# joint6: { trajectory: 0, goal: 0.01 }
# joint7: { trajectory: 0, goal: 0.01 }
# Torque контроллер - прямое управление моментом
# Активировать только при command_mode:=torque в launch файле
forward_torque_controller:
ros__parameters:
joints:
- joint1
- joint2
- joint3
- joint4
- joint5
- joint6
- joint7
interface_name: effort
+2 -2
View File
@@ -51,7 +51,7 @@ Visualization Manager:
Class: moveit_rviz_plugin/MotionPlanning Class: moveit_rviz_plugin/MotionPlanning
Enabled: true Enabled: true
Move Group Namespace: "" Move Group Namespace: ""
MoveIt_Allow_Approximate_IK: false MoveIt_Allow_Approximate_IK: true
MoveIt_Allow_External_Program: false MoveIt_Allow_External_Program: false
MoveIt_Allow_Replanning: false MoveIt_Allow_Replanning: false
MoveIt_Allow_Sensor_Positioning: false MoveIt_Allow_Sensor_Positioning: false
@@ -147,7 +147,7 @@ Visualization Manager:
Colliding Link Color: 255; 0; 0 Colliding Link Color: 255; 0; 0
Goal State Alpha: 1 Goal State Alpha: 1
Goal State Color: 250; 128; 0 Goal State Color: 250; 128; 0
Interactive Marker Size: 0 Interactive Marker Size: 0.3
Joint Violation Color: 255; 0; 255 Joint Violation Color: 255; 0; 255
Planning Group: iiwa_arm Planning Group: iiwa_arm
Query Goal State: true Query Goal State: true
+5 -2
View File
@@ -1,7 +1,10 @@
robot: robot:
name: "iiwa7" name: "iiwa7"
ip: "192.168.21.144" ip: "192.170.10.2"
port: 3000 port: 30200
command_mode: "position" # torque, position
description: pkg://iiwa_description/urdf/iiwa7_fri.urdf.xacro
digital_twin: digital_twin:
webots: webots:
+52 -66
View File
@@ -1,104 +1,90 @@
cmake_minimum_required(VERSION 3.8) cmake_minimum_required(VERSION 3.8)
project(iiwa_controller) project(iiwa_controller)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic) add_compile_options(-Wall -Wextra -Wpedantic)
endif() endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ament_cmake REQUIRED) find_package(ament_cmake REQUIRED)
find_package(hardware_interface REQUIRED) find_package(hardware_interface REQUIRED)
find_package(pluginlib REQUIRED) find_package(pluginlib REQUIRED)
find_package(rclcpp REQUIRED) find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED) find_package(rclcpp_lifecycle REQUIRED)
find_package(Eigen3 REQUIRED)
# FRI headers / sources # FRI SDK
set(FRI_HEADER set(FRI_SDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/libFRI)
external/libFRI/include
external/libFRI/src/protobuf_gen
external/libFRI/src/nanopb-0.2.8 file(GLOB_RECURSE FRI_SOURCES
external/libFRI/src/protobuf "${FRI_SDK_DIR}/src/base/*.cpp"
external/libFRI/src/connection "${FRI_SDK_DIR}/src/client_lbr/*.cpp"
external/libFRI/src/client_lbr "${FRI_SDK_DIR}/src/client_trafo/*.cpp"
external/libFRI/src/base "${FRI_SDK_DIR}/src/connection/*.cpp"
"${FRI_SDK_DIR}/src/nanopb-0.2.8/*.c"
"${FRI_SDK_DIR}/src/protobuf/*.c"
"${FRI_SDK_DIR}/src/protobuf/*.cpp"
"${FRI_SDK_DIR}/src/protobuf_gen/*.c"
) )
set(FRI_SRC add_library(fri_client_sdk STATIC ${FRI_SOURCES})
external/libFRI/src/base/friClientApplication.cpp
external/libFRI/src/client_lbr/friLBRClient.cpp target_include_directories(fri_client_sdk PUBLIC
external/libFRI/src/client_lbr/friLBRCommand.cpp ${FRI_SDK_DIR}/include
external/libFRI/src/client_lbr/friLBRState.cpp ${FRI_SDK_DIR}/src/nanopb-0.2.8
external/libFRI/src/connection/friUdpConnection.cpp ${FRI_SDK_DIR}/src/protobuf
external/libFRI/src/protobuf/friCommandMessageEncoder.cpp ${FRI_SDK_DIR}/src/protobuf_gen
external/libFRI/src/protobuf/friMonitoringMessageDecoder.cpp ${FRI_SDK_DIR}/src/base
external/libFRI/src/protobuf/pb_frimessages_callbacks.c ${FRI_SDK_DIR}/src/client_lbr
external/libFRI/src/protobuf_gen/FRIMessages.pb.c ${FRI_SDK_DIR}/src/connection
external/libFRI/src/nanopb-0.2.8/pb_decode.c
external/libFRI/src/nanopb-0.2.8/pb_encode.c
external/libFRI/src/client_trafo/friTransformationClient.cpp
) )
add_library(${PROJECT_NAME} target_compile_definitions(fri_client_sdk PUBLIC PB_FIELD_16BIT)
SHARED target_compile_options(fri_client_sdk PRIVATE -fpermissive -w)
src/IIWAHardwareInterface.cpp set_target_properties(fri_client_sdk PROPERTIES POSITION_INDEPENDENT_CODE ON)
${FRI_SRC} target_link_libraries(fri_client_sdk PUBLIC pthread)
# Плагин hardware interface
add_library(${PROJECT_NAME} SHARED
src/FRIClient.cpp src/FRIClient.cpp
src/IIWAHardwareInterface.cpp
) )
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME} PUBLIC
PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
include $<INSTALL_INTERFACE:include>
${FRI_HEADER}
) )
target_compile_definitions(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} PRIVATE
PRIVATE fri_client_sdk
PB_FIELD_16BIT
HAVE_SOCKLEN_T
PB_FIELD_16BIT
PB_NO_ERRMSG
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
hardware_interface::hardware_interface hardware_interface::hardware_interface
pluginlib::pluginlib pluginlib::pluginlib
rclcpp::rclcpp rclcpp::rclcpp
rclcpp_lifecycle::rclcpp_lifecycle rclcpp_lifecycle::rclcpp_lifecycle
Eigen3::Eigen
) )
# Export plugin description for pluginlib pluginlib_export_plugin_description_file(
pluginlib_export_plugin_description_file(hardware_interface iiwa_controller_plugin.xml) hardware_interface
iiwa_hardware_interface_plugin.xml
)
# Installation # Установка — только библиотека и заголовки, без config/launch/urdf
install(TARGETS ${PROJECT_NAME} install(TARGETS ${PROJECT_NAME}
DESTINATION lib EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
) )
install( install(DIRECTORY include/
DIRECTORY include/
DESTINATION include DESTINATION include
) )
ament_export_include_directories( ament_export_include_directories(include)
include ament_export_libraries(${PROJECT_NAME})
) ament_export_targets(export_${PROJECT_NAME})
ament_export_libraries(
${PROJECT_NAME}
)
ament_export_dependencies( ament_export_dependencies(
hardware_interface hardware_interface pluginlib rclcpp rclcpp_lifecycle)
pluginlib
rclcpp
rclcpp_lifecycle
Eigen3
)
ament_package() ament_package()
@@ -1,6 +0,0 @@
<library path="iiwa_controller">
<class name="iiwa_controller/IIWAHardwareInterface"
type="iiwa_controller::IIWAHardwareInterface"
base_class_type="hardware_interface::SystemInterface"/>
</library>
@@ -0,0 +1,11 @@
<library path="iiwa_controller">
<class
name="iiwa_controller/IIWAHardwareInterface"
type="iiwa_controller::IIWAHardwareInterface"
base_class_type="hardware_interface::SystemInterface">
<description>
ROS2 hardware interface для KUKA iiwa 7 через FRI (Fast Robot Interface).
Поддерживает режимы управления: position, torque.
</description>
</class>
</library>
@@ -1,29 +1,91 @@
// ============================================================
// FRIClient.h
// Низкоуровневый клиент FRI (Fast Robot Interface).
// Наследуется от KUKA::FRI::LBRClient и реализует три
// callback-метода, которые вызывает ClientApplication::step():
// - monitor() - только чтение состояния
// - waitForCommand() - переходный режим, эхо позиции
// - command() - управление
// ============================================================
#pragma once #pragma once
#include <friLBRClient.h>
#include <vector>
#include <array> #include <array>
#include <cstring> #include <mutex>
#include <atomic>
#include "friLBRClient.h"
#include "friClientApplication.h"
#include "friUdpConnection.h"
namespace iiwa_controller {
/// Режим управления роботом через FRI
enum class CommandMode {
POSITION, // Управление по позиции суставов [рад]
TORQUE // Управление по моментум суставов [Нм]
};
class FRIClient : public KUKA::FRI::LBRClient { class FRIClient : public KUKA::FRI::LBRClient {
public: public:
FRIClient(); // Константы
static constexpr size_t N_JOINTS = 7; // Число суставов
// Конструктор, деструктор
explicit FRIClient(CommandMode mode = CommandMode::POSITION);
~FRIClient() override = default;
// Callbacks, которые вызывает ClientApplication::step()
// Вызывается в состоянии MONITORING
void monitor() override; void monitor() override;
// Вызывается в COMMANDING_WAIT: робот ждёт команд.
void waitForCommand() override; void waitForCommand() override;
// Вызывается в COMMANDING_ACTIVE: основной цикл управления
void command() override; void command() override;
// Уведомление о смене состояния FRI сессии
void onStateChange(KUKA::FRI::ESessionState oldState, void onStateChange(KUKA::FRI::ESessionState oldState,
KUKA::FRI::ESessionState newState) override; KUKA::FRI::ESessionState newState) override;
std::array<double, 7> getMeasuredJointPositions() const; // Thread-safe API для ros2_control (вызывается из read/write)
std::array<double, 7> getMeasuredTorque() const; // Записать целевую позицию из ros2_control (рад)
void setTargetJointPositions(const std::array<double, N_JOINTS>& q);
void setTargetJointPositions(const std::array<double, 7> target_pos); /// Записать целевой момент (Нм); используется только в режиме TORQUE
void setTargetJointTorques(const std::array<double, N_JOINTS>& tau);
/// Получить последнюю измеренную позицию суставов (рад)
std::array<double, N_JOINTS> getMeasuredJointPositions() const;
/// Получить последний измеренный момент (Нм)
std::array<double, N_JOINTS> getMeasuredTorque() const;
/// Проверить, активен ли FRI в режиме COMMANDING_ACTIVE
bool isCommandingActive() const;
/// Получить текущее состояние сессии FRI
KUKA::FRI::ESessionState getSessionState() const;
private: private:
std::array<double, 7> measuredJointPositions_; // Режим управления
std::array<double, 7> measuredTorque_; CommandMode cmd_mode_;
std::array<double, 7> targetJointPositions_;
// Состояние FRI сессии
std::atomic<KUKA::FRI::ESessionState> session_state_{
KUKA::FRI::IDLE};
// Данные, защищённые мьютексом
mutable std::mutex data_mutex_;
std::array<double, N_JOINTS> target_pos_{}; // Целевая позиция [рад]
std::array<double, N_JOINTS> target_tau_{}; // Целевой момент [Нм]
std::array<double, N_JOINTS> measured_pos_{}; // Измеренная позиция
std::array<double, N_JOINTS> measured_tau_{}; // Измеренный момент
// Вспомогательные методы
/// Безопасно скопировать измеренную позицию из robotState() в measured_pos_
void updateMeasuredState();
}; };
}
@@ -1,57 +1,110 @@
#ifndef IIWA_HARDWARE_INTERFACE_HPP // ============================================================
#define IIWA_HARDWARE_INTERFACE_HPP // IIWAHardwareInterface.hpp
// ROS2 hardware_interface::SystemInterface для KUKA iiwa 7.
//
// on_init() — читаем параметры из URDF/XACRO
// on_configure() — (опционально)
// on_activate() — устанавливаем FRI соединение
// on_deactivate() — разрываем FRI соединение
// read() — копируем данные FRI → интерфейсы состояния
// write() — копируем команды интерфейсов → FRI
// ============================================================
#pragma once
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <thread>
#include <atomic>
// ROS2 hardware_interface
#include "hardware_interface/handle.hpp" #include "hardware_interface/handle.hpp"
#include "hardware_interface/hardware_info.hpp" #include "hardware_interface/hardware_info.hpp"
#include "hardware_interface/system_interface.hpp" #include "hardware_interface/system_interface.hpp"
#include "hardware_interface/types/hardware_interface_return_values.hpp" #include "hardware_interface/types/hardware_interface_return_values.hpp"
#include "hardware_interface/types/hardware_interface_type_values.hpp" #include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "rclcpp_lifecycle/state.hpp"
#include "rclcpp/macros.hpp" #include "rclcpp/macros.hpp"
#include "FRIClient.h" #include "rclcpp_lifecycle/state.hpp"
#include "friUdpConnection.h"
#include "friClientApplication.h"
using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn; // Наш FRI клиент
using namespace KUKA::FRI; #include "iiwa_controller/FRIClient.h"
namespace iiwa_controller namespace iiwa_controller
{ {
class IIWAHardwareInterface : public hardware_interface::SystemInterface { class IIWAHardwareInterface : public hardware_interface::SystemInterface
{
public: public:
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override; // Макрос ROS2 для shared_ptr / weak_ptr
std::vector<hardware_interface::StateInterface> export_state_interfaces() override; RCLCPP_SHARED_PTR_DEFINITIONS(IIWAHardwareInterface)
std::vector<hardware_interface::CommandInterface> export_command_interfaces() override;
CallbackReturn on_activate(const rclcpp_lifecycle::State & previous_state) override;
CallbackReturn on_deactivate(const rclcpp_lifecycle::State & previous_state) override;
hardware_interface::return_type read(const rclcpp::Time & time, const rclcpp::Duration & period) override;
hardware_interface::return_type write(const rclcpp::Time & time, const rclcpp::Duration & period) override;
private:
// TODO: append robotClient FRI
std::unique_ptr<FRIClient> fri_client_;
std::unique_ptr<ClientApplication> app_;
std::unique_ptr<UdpConnection> connection_;
bool simulate_; // Lifecycle callbacks (порядок вызова гарантирован ROS2)
std::string hw_command_mode_; /// Инициализация: читаем параметры из <hardware><param> в URDF
std::vector<double> hw_commands_; CallbackReturn on_init(
std::vector<double> hw_states_position_; const hardware_interface::HardwareInfo& info) override;
std::vector<double> hw_states_velocity_;
std::vector<double> hw_states_effort_; /// Экспорт интерфейсов состояния: position, velocity, effort
std::vector<double> internal_command_position; std::vector<hardware_interface::StateInterface>
std::vector<double> prev_measured_pos_; export_state_interfaces() override;
bool safety_override_active_ = true;
/// Экспорт командных интерфейсов: position (и/или effort)
std::vector<hardware_interface::CommandInterface>
export_command_interfaces() override;
/// Активация: открываем UDP соединение с роботом
CallbackReturn on_activate(
const rclcpp_lifecycle::State& previous_state) override;
/// Деактивация: закрываем соединение, сбрасываем команды
CallbackReturn on_deactivate(
const rclcpp_lifecycle::State& previous_state) override;
/// Чтение данных с робота (вызывается перед каждым шагом контроллера)
hardware_interface::return_type read(
const rclcpp::Time& time,
const rclcpp::Duration& period) override;
/// Запись команд на робот (вызывается после каждого шага контроллера)
hardware_interface::return_type write(
const rclcpp::Time& time,
const rclcpp::Duration& period) override;
private:
// Параметры из URDF <hardware><param>
std::string robot_ip_; //IP адрес контроллера KUKA
int fri_port_{30200}; // UDP порт FRI (по умолчанию 30200)
bool simulate_{false}; // Режим симуляции (без реального робота)
std::string cmd_mode_str_{"position"}; // "position" или "torque"
// FRI объекты
std::unique_ptr<FRIClient> fri_client_;
std::unique_ptr<KUKA::FRI::UdpConnection> connection_;
std::unique_ptr<KUKA::FRI::ClientApplication> app_;
// FRI выполняется в отдельном фоновом потоке,
// чтобы не блокировать ros2_control loop.
std::thread fri_thread_;
std::atomic<bool> fri_running_{false};
/// Функция фонового потока: крутит app_->step() в цикле
void friThreadFunc();
// Данные интерфейсов ros2_control
// (ros2_control обращается к ним через указатели из export_*)
static constexpr size_t N_JOINTS = FRIClient::N_JOINTS;
std::vector<double> hw_pos_; // Измеренные позиции [рад]
std::vector<double> hw_vel_; // Расчётные скорости [рад/с]
std::vector<double> hw_eff_; // Измеренные моменты [Нм]
std::vector<double> cmd_pos_; // Команда позиции [рад]
std::vector<double> cmd_eff_; // Команда момента [Нм]
std::vector<double> prev_pos_; // Предыдущая позиция для расчёта velocity
// Вспомогательный метод
/// Создаёт объект CommandMode из строки параметра
CommandMode parseCommandMode(const std::string& mode_str) const;
}; };
} // namespace iiwa_controller
}
#endif
+137 -60
View File
@@ -1,79 +1,156 @@
// ============================================================
// FRIClient.cpp
//
// Ключевые решения:
// 1. В waitForCommand() мы «инициализируем» target_pos_ текущей
// позицией робота, чтобы при переходе в COMMANDING_ACTIVE
// не было рывка.
// 2. В command() данные читаются/пишутся под мьютексом —
// ros2_control::write() работает в другом потоке.
// 3. Момент в режиме TORQUE суммируется с gravity compensation
// робота (setJointPosition — feedforward, addJointTorque — delta).
// ============================================================
#include "iiwa_controller/FRIClient.h" #include "iiwa_controller/FRIClient.h"
#include "rclcpp/rclcpp.hpp"
using namespace KUKA::FRI; #include <cstring> // std::memcpy
#include <rclcpp/rclcpp.hpp>
inline const char* to_string(KUKA::FRI::ESessionState s) namespace iiwa_controller
{ {
using namespace KUKA::FRI;
switch (s) // Вспомогательная функция
{ static const char* friStateName(KUKA::FRI::ESessionState s) {
case IDLE: return "IDLE"; switch (s) {
case MONITORING_WAIT: return "MONITORING_WAIT"; case KUKA::FRI::IDLE: return "IDLE";
case MONITORING_READY: return "MONITORING_READY"; case KUKA::FRI::MONITORING_WAIT: return "MONITORING_WAIT";
case COMMANDING_WAIT: return "COMMANDING_WAIT"; case KUKA::FRI::MONITORING_READY: return "MONITORING_READY";
case COMMANDING_ACTIVE: return "COMMANDING_ACTIVE"; case KUKA::FRI::COMMANDING_WAIT: return "COMMANDING_WAIT";
case KUKA::FRI::COMMANDING_ACTIVE: return "COMMANDING_ACTIVE";
default: return "UNKNOWN"; default: return "UNKNOWN";
} }
} }
FRIClient::FRIClient() { // Конструктор
targetJointPositions_.fill(0.0); FRIClient::FRIClient(CommandMode mode): cmd_mode_(mode) {
measuredJointPositions_.fill(0.0); target_pos_.fill(0.0);
measuredTorque_.fill(0.0); target_tau_.fill(0.0);
}; measured_pos_.fill(0.0);
measured_tau_.fill(0.0);
void FRIClient::monitor()
{
std::memcpy(measuredJointPositions_.data(),
robotState().getMeasuredJointPosition(),
7 * sizeof(double));
std::memcpy(measuredTorque_.data(),
robotState().getMeasuredTorque(),
7 * sizeof(double));
} }
void FRIClient::setTargetJointPositions(const std::array<double, 7> target_pos) { // Вспомогательный приватный метод: обновить measured_pos_ и _tau_
targetJointPositions_ = target_pos; // !!!вызывать только под data_mutex_!!!
void FRIClient::updateMeasuredState() {
// getMeasuredJointPosition() возвращает указатель на массив double[7]
const double* pos_ptr = robotState().getMeasuredJointPosition();
const double* tau_ptr = robotState().getMeasuredTorque();
std::memcpy(measured_pos_.data(), pos_ptr, N_JOINTS * sizeof(double));
std::memcpy(measured_tau_.data(), tau_ptr, N_JOINTS * sizeof(double));
} }
std::array<double, 7> FRIClient::getMeasuredJointPositions() const { // monitor() — MONITORING_WAIT / MONITORING_READY
return measuredJointPositions_; // Только читаем состояние, команды не отправляем
void FRIClient::monitor() {
std::lock_guard<std::mutex> lock(data_mutex_);
updateMeasuredState();
} }
std::array<double, 7> FRIClient::getMeasuredTorque() const { // waitForCommand() — COMMANDING_WAIT
return measuredTorque_; // FRI требует, чтобы в этом состоянии мы всё равно отправляли
} // команду. Отправляем «эхо» текущей позиции — робот не двигается.
// Заодно инициализируем target_pos_ измеренной позицией, чтобы
void FRIClient::onStateChange(ESessionState oldState, ESessionState newState) { // при входе в COMMANDING_ACTIVE не было скачка.
RCLCPP_INFO_STREAM( void FRIClient::waitForCommand() {
rclcpp::get_logger("FRIClient"), std::lock_guard<std::mutex> lock(data_mutex_);
"[FRI Client] FRI state: " << to_string(oldState) << " --> " << to_string(newState)); updateMeasuredState();
}
// Инициализируем целевую позицию текущей —
// ros2_control перезапишет её в следующем цикле write()
void FRIClient::waitForCommand() target_pos_ = measured_pos_;
{
std::memcpy(targetJointPositions_.data(), // Отправляем эхо позиции
robotState().getMeasuredJointPosition(), robotCommand().setJointPosition(target_pos_.data());
7 * sizeof(double));
std::memcpy(measuredJointPositions_.data(),
robotState().getMeasuredJointPosition(),
7 * sizeof(double));
std::memcpy(measuredTorque_.data(),
robotState().getMeasuredTorque(),
7 * sizeof(double));
robotCommand().setJointPosition(targetJointPositions_.data());
} }
// command() — COMMANDING_ACTIVE
// Основной цикл управления. Вызывается каждые send_period мс.
void FRIClient::command() { void FRIClient::command() {
std::memcpy(measuredJointPositions_.data(), std::lock_guard<std::mutex> lock(data_mutex_);
robotState().getMeasuredJointPosition(), updateMeasuredState();
7 * sizeof(double));
if (cmd_mode_ == CommandMode::POSITION) {
// Режим управления позицией
// Просто отправляем целевую позицию, записанную из write()
robotCommand().setJointPosition(target_pos_.data());
}
// CommandMode::TORQUE
else {
// Режим управления моментом
// FRI требует одновременно задавать позицию
// и дополнительный момент.
// target_pos_ используется как feedforward (без движения),
// target_tau_ желаемый дополнительный момент поверх
// внутреннего регулятора KUKA.
robotCommand().setJointPosition(target_pos_.data());
robotCommand().setTorque(target_tau_.data());
}
}
// onStateChange() — уведомление о смене состояния FRI
void FRIClient::onStateChange(KUKA::FRI::ESessionState oldState,
KUKA::FRI::ESessionState newState) {
session_state_.store(newState, std::memory_order_relaxed);
RCLCPP_INFO(
rclcpp::get_logger("FRIClient"),
"[FRI] Состояние: %s → %s",
friStateName(oldState),
friStateName(newState));
// При потере сессии очищаем целевые команды для безопасности
if (newState == KUKA::FRI::IDLE ||
newState == KUKA::FRI::MONITORING_WAIT) {
std::lock_guard<std::mutex> lock(data_mutex_);
target_tau_.fill(0.0);
// target_pos_ оставляем — при переподключении нужно знать
// последнюю «безопасную» позицию
RCLCPP_WARN(rclcpp::get_logger("FRIClient"),
"[FRI] Команды сброшены (сессия неактивна)");
}
}
// Thread-safe setters/getters (вызываются из ros2_control)
void FRIClient::setTargetJointPositions(
const std::array<double, N_JOINTS>& q) {
std::lock_guard<std::mutex> lock(data_mutex_);
target_pos_ = q;
}
void FRIClient::setTargetJointTorques(
const std::array<double, N_JOINTS>& tau) {
std::lock_guard<std::mutex> lock(data_mutex_);
target_tau_ = tau;
}
std::array<double, FRIClient::N_JOINTS>
FRIClient::getMeasuredJointPositions() const {
std::lock_guard<std::mutex> lock(data_mutex_);
return measured_pos_;
}
std::array<double, FRIClient::N_JOINTS>
FRIClient::getMeasuredTorque() const {
std::lock_guard<std::mutex> lock(data_mutex_);
return measured_tau_;
}
bool FRIClient::isCommandingActive() const {
return session_state_.load(std::memory_order_relaxed) ==
KUKA::FRI::COMMANDING_ACTIVE;
}
KUKA::FRI::ESessionState FRIClient::getSessionState() const {
return session_state_.load(std::memory_order_relaxed);
}
robotCommand().setJointPosition(targetJointPositions_.data());
} }
+311 -264
View File
@@ -1,318 +1,365 @@
#include "iiwa_controller/IIWAHardwareInterface.hpp" // ============================================================
#include "rclcpp/rclcpp.hpp" // IIWAHardwareInterface.cpp
using namespace KUKA::FRI;
namespace iiwa_controller {
template<typename T>
constexpr const T& clamp(const T& v, const T& lo, const T& hi)
{
return (v < lo) ? lo : (hi < v) ? hi : v;
}
CallbackReturn IIWAHardwareInterface::on_init(const hardware_interface::HardwareInfo & info) {
if (hardware_interface::SystemInterface::on_init(info) != CallbackReturn::SUCCESS)
return CallbackReturn::ERROR;
simulate_ = false;
hw_states_position_.resize(info_.joints.size(), 0.0);
hw_states_velocity_.resize(info_.joints.size(), 0.0);
hw_states_effort_.resize(info_.joints.size(), 0.0);
hw_commands_.resize(info_.joints.size(), 0.0);
prev_measured_pos_.resize(info_.joints.size(), 0.0);
internal_command_position.resize(info_.joints.size(), 0.0);
// пробегаемся по всем интерфейсам и смотрим какой режим управления установлен
for (const hardware_interface::ComponentInfo & joint : info_.joints) {
// проверка, на то что все суставы используют один и тот же тип управления
if (joint.command_interfaces.size() != 1) {
RCLCPP_FATAL(
rclcpp::get_logger("IiwaFRIHardwareInterface"),
"Joint '%s' has %li command interfaces found. 1 expected.", joint.name.c_str(),
joint.command_interfaces.size());
return CallbackReturn::ERROR;
}
// что у каждого сустава ровно 3 интерфейса состояния:
if (hw_command_mode_.empty()) {
hw_command_mode_ = joint.command_interfaces[0].name;
if (hw_command_mode_ != hardware_interface::HW_IF_POSITION &&
hw_command_mode_ != hardware_interface::HW_IF_VELOCITY &&
hw_command_mode_ != hardware_interface::HW_IF_EFFORT)
{
RCLCPP_FATAL(
rclcpp::get_logger("IiwaFRIHardwareInterface"),
"Joint '%s' have %s unknown command interfaces.", joint.name.c_str(),
joint.command_interfaces[0].name.c_str());
return CallbackReturn::ERROR;
}
}
// //
if (hw_command_mode_ != joint.command_interfaces[0].name) { // 1. FRI работает в ОТДЕЛЬНОМ потоке (friThreadFunc), который
RCLCPP_FATAL( // непрерывно вызывает app_->step(). Это обязательно, т.к.
rclcpp::get_logger("IiwaFRIHardwareInterface"), // FRI имеет жёсткие требования по таймингу (jitter < 1мс),
"Joint '%s' has %s command interfaces. Expected %s.", joint.name.c_str(), // а ros2_control loop может иметь джиттер.
joint.command_interfaces[0].name.c_str(), hw_command_mode_.c_str()); //
// 2. Синхронизация между ros2_control (read/write) и FRI
// потоком выполнена внутри FRIClient через мьютекс.
// read() и write() просто вызывают thread-safe геттеры/
// сеттеры FRIClient — они никогда не блокируют FRI поток
// надолго.
//
// 3. В режиме симуляции (simulate: true в URDF params) FRI
// не используется — команды просто эхируются как состояние.
// Удобно для разработки без реального робота.
//
// 4. Безопасность: если FRI сессия не в COMMANDING_ACTIVE,
// write() пропускает отправку команды (FRIClient сам
// удерживает последнюю безопасную позицию).
// ============================================================
#include "iiwa_controller/IIWAHardwareInterface.hpp"
#include <chrono>
#include <thread>
#include <stdexcept>
#include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "rclcpp/rclcpp.hpp"
#include "pluginlib/class_list_macros.hpp"
// Регистрируем плагин для pluginlib
PLUGINLIB_EXPORT_CLASS(
iiwa_controller::IIWAHardwareInterface,
hardware_interface::SystemInterface)
namespace iiwa_controller
{
// Псевдоним для удобства
using CallbackReturn =
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
// Вспомогательная функция: получить параметр из HardwareInfo
// или вернуть значение по умолчанию
static std::string getParam(
const hardware_interface::HardwareInfo& info,
const std::string& name,
const std::string& default_val = "")
{
auto it = info.hardware_parameters.find(name);
return (it != info.hardware_parameters.end()) ? it->second : default_val;
}
// on_init()
// Читаем параметры из секции <hardware><param> URDF/XACRO.
// Пример в URDF:
// <param name="robot_ip">192.168.1.1</param>
// <param name="fri_port">30200</param>
// <param name="simulate">false</param>
// <param name="command_mode">position</param>
CallbackReturn IIWAHardwareInterface::on_init(
const hardware_interface::HardwareInfo& info)
{
// Базовый on_init выполняет проверку URDF структуры
if (hardware_interface::SystemInterface::on_init(info) !=
CallbackReturn::SUCCESS)
{
return CallbackReturn::ERROR; return CallbackReturn::ERROR;
} }
if (joint.state_interfaces.size() != 3) { // Читаем параметры
RCLCPP_FATAL( // TODO: Изменить IP
rclcpp::get_logger("IiwaFRIHardwareInterface"), robot_ip_ = getParam(info, "robot_ip", "192.168.1.1");
"Joint '%s' has %li state interface. 3 expected.", joint.name.c_str(), fri_port_ = std::stoi(getParam(info, "fri_port", "30200"));
joint.state_interfaces.size()); simulate_ = (getParam(info, "simulate", "false") == "true");
cmd_mode_str_ = getParam(info, "command_mode", "position");
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"Параметры: ip=%s port=%d simulate=%s mode=%s",
robot_ip_.c_str(), fri_port_,
simulate_ ? "true" : "false",
cmd_mode_str_.c_str());
// Проверяем число суставов в URDF
if (info.joints.size() != N_JOINTS)
{
RCLCPP_FATAL(rclcpp::get_logger("IIWAHardwareInterface"),
"URDF содержит %zu суставов, ожидается %zu",
info.joints.size(), N_JOINTS);
return CallbackReturn::ERROR; return CallbackReturn::ERROR;
} }
if (joint.state_interfaces[0].name != hardware_interface::HW_IF_POSITION) { // Инициализируем векторы данных
RCLCPP_FATAL( hw_pos_.assign(N_JOINTS, 0.0);
rclcpp::get_logger("IiwaFRIHardwareInterface"), hw_vel_.assign(N_JOINTS, 0.0);
"Joint '%s' have %s state interface. '%s' expected.", joint.name.c_str(), hw_eff_.assign(N_JOINTS, 0.0);
joint.state_interfaces[0].name.c_str(), hardware_interface::HW_IF_POSITION); cmd_pos_.assign(N_JOINTS, 0.0);
cmd_eff_.assign(N_JOINTS, 0.0);
prev_pos_.assign(N_JOINTS, 0.0);
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"on_init() завершён успешно");
return CallbackReturn::SUCCESS;
}
// export_state_interfaces()
// Регистрируем интерфейсы состояния:
// joint_N/position, joint_N/velocity, joint_N/effort
// ros2_control controller_manager читает эти данные
std::vector<hardware_interface::StateInterface>
IIWAHardwareInterface::export_state_interfaces()
{
std::vector<hardware_interface::StateInterface> interfaces;
interfaces.reserve(N_JOINTS * 3);
for (size_t i = 0; i < N_JOINTS; ++i)
{
const std::string& joint_name = info_.joints[i].name;
// Позиция сустава [рад]
interfaces.emplace_back(joint_name,
hardware_interface::HW_IF_POSITION, &hw_pos_[i]);
// Скорость сустава [рад/с] — вычисляется численно в read()
interfaces.emplace_back(joint_name,
hardware_interface::HW_IF_VELOCITY, &hw_vel_[i]);
// Момент сустава [Нм]
interfaces.emplace_back(joint_name,
hardware_interface::HW_IF_EFFORT, &hw_eff_[i]);
}
return interfaces;
}
// export_command_interfaces()
// Регистрируем командные интерфейсы:
// joint_N/position — для position контроллера
// joint_N/effort — для effort/impedance контроллера
std::vector<hardware_interface::CommandInterface>
IIWAHardwareInterface::export_command_interfaces()
{
std::vector<hardware_interface::CommandInterface> interfaces;
interfaces.reserve(N_JOINTS * 2);
for (size_t i = 0; i < N_JOINTS; ++i)
{
const std::string& joint_name = info_.joints[i].name;
// Командная позиция [рад]
interfaces.emplace_back(joint_name,
hardware_interface::HW_IF_POSITION, &cmd_pos_[i]);
// Командный момент [Нм]
interfaces.emplace_back(joint_name,
hardware_interface::HW_IF_EFFORT, &cmd_eff_[i]);
}
return interfaces;
}
// parseCommandMode() — вспомогательный метод
CommandMode IIWAHardwareInterface::parseCommandMode(
const std::string& mode_str) const
{
if (mode_str == "torque") return CommandMode::TORQUE;
return CommandMode::POSITION;
}
// on_activate()
// Создаём FRI объекты и запускаем фоновый поток.
CallbackReturn IIWAHardwareInterface::on_activate(
const rclcpp_lifecycle::State& /*previous_state*/)
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"Активация hardware interface...");
if (!simulate_)
{
// ---- Создаём FRI клиент с нужным режимом управления ----
CommandMode mode = parseCommandMode(cmd_mode_str_);
fri_client_ = std::make_unique<FRIClient>(mode);
connection_ = std::make_unique<KUKA::FRI::UdpConnection>();
app_ = std::make_unique<KUKA::FRI::ClientApplication>(
*connection_, *fri_client_);
// Открываем UDP соединение
// connect(port, remoteHost):
// port — локальный UDP порт (тот же, что задан в FRIConfiguration на роботе)
// remoteHost — nullptr означает «принять от любого хоста»
// (робот сам начинает посылать пакеты)
if (!app_->connect(fri_port_, nullptr))
{
RCLCPP_FATAL(rclcpp::get_logger("IIWAHardwareInterface"),
"Не удалось открыть FRI UDP порт %d", fri_port_);
return CallbackReturn::ERROR; return CallbackReturn::ERROR;
} }
if (joint.state_interfaces[1].name != hardware_interface::HW_IF_VELOCITY) { RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
RCLCPP_FATAL( "FRI UDP порт %d открыт. Ждём пакеты от робота...",
rclcpp::get_logger("IiwaFRIHardwareInterface"), fri_port_);
"Joint '%s' have %s state interface. '%s' expected.", joint.name.c_str(),
joint.state_interfaces[0].name.c_str(), hardware_interface::HW_IF_VELOCITY);
return CallbackReturn::ERROR;
}
if (joint.state_interfaces[2].name != hardware_interface::HW_IF_EFFORT) { // Запускаем FRI в фоновом потоке
RCLCPP_FATAL( fri_running_.store(true, std::memory_order_relaxed);
rclcpp::get_logger("IiwaFRIHardwareInterface"), fri_thread_ = std::thread(&IIWAHardwareInterface::friThreadFunc, this);
"Joint '%s' have %s state interface. '%s' expected.", joint.name.c_str(),
joint.state_interfaces[0].name.c_str(), hardware_interface::HW_IF_EFFORT);
return CallbackReturn::ERROR;
}
// Даём роботу 5 секунд на установку сессии
std::this_thread::sleep_for(std::chrono::seconds(5));
// Проверяем, что FRI хотя бы в состоянии MONITORING
auto state = fri_client_->getSessionState();
if (state == KUKA::FRI::IDLE)
{
RCLCPP_ERROR(rclcpp::get_logger("IIWAHardwareInterface"),
"FRI сессия не установилась. "
"Запущено ли AAServerFri на роботе?");
// Не возвращаем ERROR — даём ещё шанс (робот может быть занят)
}
else
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"FRI сессия установлена!");
}
}
else
{
RCLCPP_WARN(rclcpp::get_logger("IIWAHardwareInterface"),
"РЕЖИМ СИМУЛЯЦИИ: FRI не используется");
} }
return CallbackReturn::SUCCESS; return CallbackReturn::SUCCESS;
} }
std::vector<hardware_interface::StateInterface> IIWAHardwareInterface::export_state_interfaces() { // friThreadFunc()
std::vector<hardware_interface::StateInterface> state_interfaces; // Фоновый поток: крутим app_->step() с максимальной скоростью.
for (uint i = 0; i < info_.joints.size(); i++) { // app_->step() блокируется до получения UDP пакета от робота,
state_interfaces.emplace_back( // поэтому этот поток НЕ занимает 100% CPU зря.
hardware_interface::StateInterface( void IIWAHardwareInterface::friThreadFunc()
info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_states_position_[i])); {
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"FRI поток запущен");
while (fri_running_.load(std::memory_order_relaxed))
{
// step() = получить пакет + вызвать callback + отправить ответ
// Возвращает false если соединение потеряно
bool ok = app_->step();
if (!ok)
{
RCLCPP_WARN_THROTTLE(
rclcpp::get_logger("IIWAHardwareInterface"),
*rclcpp::Clock::make_shared(),
2000, // не чаще раза в 2 сек
"FRI app->step() вернул false (соединение потеряно?)");
}
} }
for (uint i = 0; i < info_.joints.size(); i++) { RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
state_interfaces.emplace_back( "FRI поток завершён");
hardware_interface::StateInterface(
info_.joints[i].name, hardware_interface::HW_IF_VELOCITY, &hw_states_velocity_[i]));
} }
for (uint i = 0; i < info_.joints.size(); i++) { // on_deactivate()
state_interfaces.emplace_back( // Останавливаем FRI поток и закрываем соединение.
hardware_interface::StateInterface( CallbackReturn IIWAHardwareInterface::on_deactivate(
info_.joints[i].name, hardware_interface::HW_IF_EFFORT, &hw_states_effort_[i])); const rclcpp_lifecycle::State& /*previous_state*/)
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
"Деактивация hardware interface...");
if (!simulate_)
{
// Сигнализируем потоку остановиться
fri_running_.store(false, std::memory_order_relaxed);
// Ждём завершения потока
if (fri_thread_.joinable()) {
fri_thread_.join();
} }
return state_interfaces; // Закрываем UDP соединение
} if (app_) {
std::vector<hardware_interface::CommandInterface> IIWAHardwareInterface::export_command_interfaces() {
std::vector<hardware_interface::CommandInterface> command_interfaces;
for (uint i = 0; i < info_.joints.size(); i++) {
if (hw_command_mode_ == hardware_interface::HW_IF_POSITION) {
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_commands_[i]));
} else if (hw_command_mode_ == hardware_interface::HW_IF_VELOCITY) {
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.joints[i].name, hardware_interface::HW_IF_VELOCITY, &hw_commands_[i]));
} else if (hw_command_mode_ == hardware_interface::HW_IF_EFFORT) {
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.joints[i].name, hardware_interface::HW_IF_EFFORT, &hw_commands_[i]));
}
}
return command_interfaces;
}
CallbackReturn IIWAHardwareInterface::on_activate(const rclcpp_lifecycle::State& ) {
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "Starting ...please wait...");
auto it = info_.hardware_parameters.find("simulate");
if (it != info_.hardware_parameters.end()) {
std::string sim_str = it->second;
std::transform(sim_str.begin(), sim_str.end(), sim_str.begin(), ::tolower);
simulate_ = (sim_str == "true");
}
if (!simulate_) {
std::string ip = info_.hardware_parameters.at("robot_ip");
int port = std::stoi(info_.hardware_parameters.at("robot_port"));
fri_client_ = std::make_unique<FRIClient>();
connection_ = std::make_unique<UdpConnection>();
app_ = std::make_unique<ClientApplication>(*connection_, *fri_client_);
app_->connect(port, ip.c_str());
rclcpp::Time now = rclcpp::Clock().now();
rclcpp::Duration period = rclcpp::Duration::from_seconds(0.01);
this->read(now, period);
safety_override_active_ = true;
hw_commands_ = hw_states_position_;
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "Connecting FRI to port= %i and ip= %s", port, ip.c_str());
}
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "System Successfully started!");
return CallbackReturn::SUCCESS;
}
CallbackReturn IIWAHardwareInterface::on_deactivate(const rclcpp_lifecycle::State& ) {
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "Stopping ...please wait...");
if (!simulate_) {
app_->disconnect(); app_->disconnect();
} }
std::fill(hw_commands_.begin(), hw_commands_.end(), 0.0); RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"),
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "hw_commands_ reset to zero."); "FRI отключён");
}
// Сбрасываем команды в ноль для безопасности
RCLCPP_INFO(rclcpp::get_logger("IiwaFRIHardwareInterface"), "System successfully stopped!"); std::fill(cmd_pos_.begin(), cmd_pos_.end(), 0.0);
std::fill(cmd_eff_.begin(), cmd_eff_.end(), 0.0);
return CallbackReturn::SUCCESS; return CallbackReturn::SUCCESS;
} }
// read()
hardware_interface::return_type IIWAHardwareInterface::read(const rclcpp::Time&, const rclcpp::Duration& period) { // Копируем данные из FRIClient → буферы ros2_control.
if (!simulate_) { // Вызывается перед каждым шагом контроллера (~1кГц или по URDF).
hardware_interface::return_type IIWAHardwareInterface::read(
if (!app_ || !app_->step()) // session порвалась? const rclcpp::Time& /*time*/,
{ const rclcpp::Duration& period)
RCLCPP_ERROR(rclcpp::get_logger("IIWAHardwareInterface"),
"FRI session lost");
return hardware_interface::return_type::ERROR;
}
/* ---------- 2. Считываем измеренные данные ---------- */
const auto pos_meas = fri_client_->getMeasuredJointPositions();
const auto tau_meas = fri_client_->getMeasuredTorque();
/* ---------- 3. Копируем в ros2_control ---------- */
for (size_t i = 0; i < hw_states_position_.size(); ++i)
{
hw_states_position_[i] = pos_meas[i];
// простая численная производная = (dq) / dt
hw_states_velocity_[i] =
(pos_meas[i] - prev_measured_pos_[i]) / period.seconds();
hw_states_effort_[i] = tau_meas[i];
prev_measured_pos_[i] = pos_meas[i];
}
return hardware_interface::return_type::OK;
}
for (size_t i = 0; i < hw_states_position_.size(); ++i) {
hw_states_position_[i] = hw_commands_[i];
hw_states_velocity_[i] = 0.0;
hw_states_effort_[i] = 0.0;
}
return hardware_interface::return_type::OK;
}
hardware_interface::return_type IIWAHardwareInterface::write(const rclcpp::Time&, const rclcpp::Duration&)
{ {
if (simulate_) if (simulate_)
{ {
RCLCPP_DEBUG( for (size_t i = 0; i < N_JOINTS; ++i)
rclcpp::get_logger("IIWAHardwareInterface"), {
"Simulated write to robot (echo commands)"); hw_vel_[i] = (cmd_pos_[i] - hw_pos_[i]) / period.seconds();
hw_pos_[i] = cmd_pos_[i];
hw_eff_[i] = cmd_eff_[i];
}
return hardware_interface::return_type::OK; return hardware_interface::return_type::OK;
} }
// ---------- 1. Подготовка массивов команд ---------- // Реальный робот
std::array<double, 7> cmd_position{}; // Получаем данные из FRIClient (thread-safe геттеры)
std::array<double, 7> cmd_torque{}; const auto pos = fri_client_->getMeasuredJointPositions();
const auto tau = fri_client_->getMeasuredTorque();
for (size_t i = 0; i < hw_commands_.size(); ++i) for (size_t i = 0; i < N_JOINTS; ++i)
{ {
if (hw_command_mode_ == hardware_interface::HW_IF_POSITION) // Числовая производная скорости: v = (q_new - q_old) / dt
cmd_position[i] = hw_commands_[i]; // Точнее было бы использовать фильтр, но для начала достаточно
else if (hw_command_mode_ == hardware_interface::HW_IF_EFFORT) double dt = period.seconds();
cmd_torque[i] = hw_commands_[i]; hw_vel_[i] = (dt > 1e-9)
? (pos[i] - prev_pos_[i]) / dt
: 0.0;
hw_pos_[i] = pos[i];
hw_eff_[i] = tau[i];
prev_pos_[i] = pos[i];
} }
// ---------- 2. Проверка на "нулевые" команды ----------
double sum = std::accumulate(
hw_commands_.begin(), hw_commands_.end(), 0.0,
[](double a, double b) { return a + std::abs(b); });
if (sum > 1e-3 && safety_override_active_)
{
RCLCPP_WARN_ONCE(
rclcpp::get_logger("IIWAHardwareInterface"),
"Command ignored: hw_commands_ are effectively zero (likely startup or stale)");
return hardware_interface::return_type::OK; return hardware_interface::return_type::OK;
} }
safety_override_active_ = false; // write()
// Копируем команды из буферов ros2_control → FRIClient.
// ---------- 3. Защита по лимитам углов ---------- // Вызывается после каждого шага контроллера.
const double joint_limits[7][2] = { hardware_interface::return_type IIWAHardwareInterface::write(
{-2.95, 2.95}, {-2.03, 2.03}, {-2.95, 2.95}, const rclcpp::Time& /*time*/,
{-2.03, 2.03}, {-2.95, 2.95}, {-2.03, 2.03}, {-3.0, 3.0}}; const rclcpp::Duration& /*period*/)
if (hw_command_mode_ == hardware_interface::HW_IF_POSITION)
{ {
for (size_t i = 0; i < 7; ++i) if (simulate_) {
{ return hardware_interface::return_type::OK;
cmd_position[i] = clamp(cmd_position[i], joint_limits[i][0], joint_limits[i][1]);
}
} }
// ---------- 4. Отправка команды в FRI-клиент ---------- // Упаковываем векторы ros2_control в std::array для FRIClient
if (hw_command_mode_ == hardware_interface::HW_IF_POSITION) std::array<double, N_JOINTS> pos_arr, tau_arr;
for (size_t i = 0; i < N_JOINTS; ++i)
{ {
fri_client_->setTargetJointPositions(cmd_position); pos_arr[i] = cmd_pos_[i];
} tau_arr[i] = cmd_eff_[i];
else if (hw_command_mode_ == hardware_interface::HW_IF_EFFORT)
{
// TODO: реализовать setTargetTorque при необходимости
}
else if (hw_command_mode_ == hardware_interface::HW_IF_VELOCITY)
{
// Velocity mode не реализован в FRI
} }
RCLCPP_DEBUG( // Передаём в FRIClient (thread-safe сеттеры)
rclcpp::get_logger("IIWAHardwareInterface"), // FRIClient применит их в следующем вызове command()
"Command sent to FRI"); fri_client_->setTargetJointPositions(pos_arr);
fri_client_->setTargetJointTorques(tau_arr);
return hardware_interface::return_type::OK; return hardware_interface::return_type::OK;
} }
} }
#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(iiwa_controller::IIWAHardwareInterface, hardware_interface::SystemInterface)
+76 -8
View File
@@ -20,50 +20,118 @@
<ros2_control name="iiwaWebotsControl" type="system"> <ros2_control name="iiwaWebotsControl" type="system">
<hardware> <hardware>
<plugin>webots_ros2_control::Ros2ControlSystem</plugin> <plugin>webots_ros2_control::Ros2ControlSystem</plugin>
<!-- <plugin>mock_components/GenericSystem</plugin> -->
</hardware> </hardware>
<joint name="joint1"> <joint name="joint1">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint1']}</param> <param name="initial_value">${initial_positions['joint1']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint2"> <joint name="joint2">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint2']}</param> <param name="initial_value">${initial_positions['joint2']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint3"> <joint name="joint3">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint3']}</param> <param name="initial_value">${initial_positions['joint3']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint4"> <joint name="joint4">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint4']}</param> <param name="initial_value">${initial_positions['joint4']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint5"> <joint name="joint5">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint5']}</param> <param name="initial_value">${initial_positions['joint5']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint6"> <joint name="joint6">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint6']}</param> <param name="initial_value">${initial_positions['joint6']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
<joint name="joint7"> <joint name="joint7">
<command_interface name="position"/> <command_interface name="position">
<param name="min">-3.05</param>
<param name="max"> 3.05</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position"> <state_interface name="position">
<param name="initial_value">${initial_positions['joint7']}</param> <param name="initial_value">${initial_positions['joint7']}</param>
</state_interface> </state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint> </joint>
</ros2_control> </ros2_control>
@@ -0,0 +1,147 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="iiwa7">
<xacro:arg name="initial_positions_file"
default="$(find iiwa_config)/config/moveit/initial_positions.yaml"/>
<xacro:arg name="robot_ip" default="192.170.10.2"/>
<xacro:arg name="fri_port" default="30200"/>
<xacro:arg name="simulate" default="false"/>
<xacro:arg name="command_mode" default="position"/>
<xacro:property name="initial_positions"
value="${xacro.load_yaml('$(arg initial_positions_file)')['initial_positions']}"/>
<xacro:include filename="$(find iiwa_description)/urdf/params.xacro"/>
<xacro:include filename="$(find iiwa_description)/urdf/macros.xacro"/>
<xacro:include filename="$(find iiwa_description)/urdf/links.xacro"/>
<xacro:include filename="$(find iiwa_description)/urdf/joints.xacro"/>
<ros2_control name="iiwaFRIControl" type="system">
<hardware>
<plugin>iiwa_controller/IIWAHardwareInterface</plugin>
<param name="robot_ip">$(arg robot_ip)</param>
<param name="fri_port">$(arg fri_port)</param>
<param name="simulate">$(arg simulate)</param>
<param name="command_mode">$(arg command_mode)</param>
</hardware>
<joint name="joint1">
<command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint1']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint2">
<command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint2']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint3">
<command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint3']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint4">
<command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint4']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint5">
<command_interface name="position">
<param name="min">-2.97</param>
<param name="max"> 2.97</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint5']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint6">
<command_interface name="position">
<param name="min">-2.09</param>
<param name="max"> 2.09</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint6']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<joint name="joint7">
<command_interface name="position">
<param name="min">-3.05</param>
<param name="max"> 3.05</param>
</command_interface>
<command_interface name="effort">
<param name="min">-320</param>
<param name="max"> 320</param>
</command_interface>
<state_interface name="position">
<param name="initial_value">${initial_positions['joint7']}</param>
</state_interface>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
</ros2_control>
</robot>
@@ -13,6 +13,8 @@ class RobotCfg:
name: str name: str
ip: str ip: str
port: int port: int
command_mode: str
description: str
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -153,6 +155,8 @@ def build_settings(settings_path: str, check_files: bool = True) -> Settings:
name=str(require(robot_raw, "name")), name=str(require(robot_raw, "name")),
ip=str(require(robot_raw, "ip")), ip=str(require(robot_raw, "ip")),
port=int(require(robot_raw, "port")), port=int(require(robot_raw, "port")),
command_mode=str(require(robot_raw, "command_mode")),
description=resolve_path(str(require(robot_raw, "description")), settings_dir),
) )
# digital_twin # digital_twin
@@ -205,6 +209,7 @@ def build_settings(settings_path: str, check_files: bool = True) -> Settings:
s = Settings(robot=robot, digital_twin=digital_twin, controller=controller) s = Settings(robot=robot, digital_twin=digital_twin, controller=controller)
if check_files: if check_files:
assert_file(s.robot.description, "robot.description")
assert_file(s.digital_twin.webots.world, "digital_twin.webots.world") assert_file(s.digital_twin.webots.world, "digital_twin.webots.world")
assert_file(s.digital_twin.rviz.config, "digital_twin.rviz.config") assert_file(s.digital_twin.rviz.config, "digital_twin.rviz.config")
assert_file(s.digital_twin.description, "digital_twin.description") assert_file(s.digital_twin.description, "digital_twin.description")