Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1fc2c5ff5 |
@@ -3,9 +3,6 @@ robot:
|
|||||||
ip: "192.170.10.2"
|
ip: "192.170.10.2"
|
||||||
port: 30200
|
port: 30200
|
||||||
fri_cycle_ms: 10 # период FRI-цикла: 5 мс (200 Гц) или 10 мс (100 Гц)
|
fri_cycle_ms: 10 # период FRI-цикла: 5 мс (200 Гц) или 10 мс (100 Гц)
|
||||||
joint_position_tau: 0.04 # EMA фильтр позиций [с]: сглаживает команды перед отправкой в FRI
|
|
||||||
joint_velocity_tau: 0.01 # EMA фильтр скорости [с]: убирает выбросы конечных разностей
|
|
||||||
active_controller: "jtc" # "jtc" = MoveIt/JointTrajectoryController, "forward" = ForwardCommandController
|
|
||||||
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -143,13 +143,6 @@ _BLOCKS: List[_Block] = [
|
|||||||
_Field("fri_cycle_ms", "Цикл FRI (мс):", "10",
|
_Field("fri_cycle_ms", "Цикл FRI (мс):", "10",
|
||||||
note="5 мс = 200 Гц, 10 мс = 100 Гц",
|
note="5 мс = 200 Гц, 10 мс = 100 Гц",
|
||||||
options=["10", "5"]),
|
options=["10", "5"]),
|
||||||
_Field("active_controller", "Активный ROS-контроллер:", "jtc",
|
|
||||||
note="jtc = JointTrajectoryController (MoveIt), forward = ForwardCommandController",
|
|
||||||
options=["jtc", "forward"]),
|
|
||||||
_Field("joint_position_tau", "EMA tau фильтра положения (с):", "0.04",
|
|
||||||
note="Сглаживает команды положения перед отправкой в FRI"),
|
|
||||||
_Field("joint_velocity_tau", "EMA tau фильтра скорости (с):", "0.01",
|
|
||||||
note="Убирает выбросы из оценки скорости конечной разностью"),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ robot:
|
|||||||
ip: "192.170.10.2"
|
ip: "192.170.10.2"
|
||||||
port: 30200
|
port: 30200
|
||||||
fri_cycle_ms: 10
|
fri_cycle_ms: 10
|
||||||
joint_position_tau: 0.04
|
|
||||||
joint_velocity_tau: 0.01
|
|
||||||
active_controller: "jtc"
|
|
||||||
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -38,11 +35,10 @@ robot:
|
|||||||
| `ip` | KUKA controller IP address | **Change** to the actual controller address |
|
| `ip` | KUKA controller IP address | **Change** to the actual controller address |
|
||||||
| `port` | FRI UDP port | Default: `30200`; change only if the port conflicts |
|
| `port` | FRI UDP port | Default: `30200`; change only if the port conflicts |
|
||||||
| `fri_cycle_ms` | FRI cycle: `5` ms = 200 Hz, `10` ms = 100 Hz | Use `10` for stable operation or `5` for high-precision tasks |
|
| `fri_cycle_ms` | FRI cycle: `5` ms = 200 Hz, `10` ms = 100 Hz | Use `10` for stable operation or `5` for high-precision tasks |
|
||||||
| `joint_position_tau` | Position EMA filter [s], smoothing commands before transmission | Decrease for a faster response; increase if vibration occurs |
|
|
||||||
| `joint_velocity_tau` | Velocity EMA filter [s], removing finite-difference spikes | Tune in the same way as `joint_position_tau` |
|
|
||||||
| `active_controller` | Control mode: `jtc` (MoveIt / JointTrajectory) or `forward` (direct control) | Use `jtc` for most tasks |
|
|
||||||
| `description` | Path to the robot URDF | Do not change |
|
| `description` | Path to the robot URDF | Do not change |
|
||||||
|
|
||||||
|
Motion always uses JointTrajectoryController. Position and velocity EMA filters are not applied.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `digital_twin` section — simulator
|
## `digital_twin` section — simulator
|
||||||
@@ -70,6 +66,7 @@ digital_twin:
|
|||||||
| `webots.cameras` | List of YAML configurations for connected cameras |
|
| `webots.cameras` | List of YAML configurations for connected cameras |
|
||||||
| `rviz.config` | Path to the RViz configuration |
|
| `rviz.config` | Path to the RViz configuration |
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `tool` section — active tool
|
## `tool` section — active tool
|
||||||
@@ -169,7 +166,6 @@ The remaining parameters (`tls`, `topic_whitelist`, `min_qos_depth`, and others)
|
|||||||
| ✅ | `robot.fri_cycle_ms` | Select `10` (standard) or `5` (high frequency) |
|
| ✅ | `robot.fri_cycle_ms` | Select `10` (standard) or `5` (high frequency) |
|
||||||
| ✅ | `tool.active` | Set the active tool |
|
| ✅ | `tool.active` | Set the active tool |
|
||||||
| ✅ | `web.enabled` | Set to `false` if the web interface is not needed |
|
| ✅ | `web.enabled` | Set to `false` if the web interface is not needed |
|
||||||
| ⚠️ | `robot.active_controller` | Change only when intentionally switching the control mode |
|
|
||||||
| ⚠️ | `planning.*` | Change only when another planner or other parameters are required |
|
| ⚠️ | `planning.*` | Change only when another planner or other parameters are required |
|
||||||
| ❌ | `robot.description` | Do not change; this is the URDF path |
|
| ❌ | `robot.description` | Do not change; this is the URDF path |
|
||||||
| ❌ | `controller.moveit.*` | Do not change; these are package-internal MoveIt configuration paths |
|
| ❌ | `controller.moveit.*` | Do not change; these are package-internal MoveIt configuration paths |
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ robot:
|
|||||||
ip: "192.170.10.2"
|
ip: "192.170.10.2"
|
||||||
port: 30200
|
port: 30200
|
||||||
fri_cycle_ms: 10
|
fri_cycle_ms: 10
|
||||||
joint_position_tau: 0.04
|
|
||||||
joint_velocity_tau: 0.01
|
|
||||||
active_controller: "jtc"
|
|
||||||
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -40,11 +37,10 @@ robot:
|
|||||||
| `ip` | IP-адрес контроллера KUKA | **Изменить** на реальный IP вашего контроллера |
|
| `ip` | IP-адрес контроллера KUKA | **Изменить** на реальный IP вашего контроллера |
|
||||||
| `port` | UDP-порт FRI | По умолчанию `30200` — менять только при конфликте портов |
|
| `port` | UDP-порт FRI | По умолчанию `30200` — менять только при конфликте портов |
|
||||||
| `fri_cycle_ms` | Период цикла FRI: `5` мс = 200 Гц, `10` мс = 100 Гц | `10` для стабильной работы, `5` для высокоточных задач |
|
| `fri_cycle_ms` | Период цикла FRI: `5` мс = 200 Гц, `10` мс = 100 Гц | `10` для стабильной работы, `5` для высокоточных задач |
|
||||||
| `joint_position_tau` | EMA-фильтр позиций [с] — сглаживает команды перед отправкой | Уменьшать для более быстрой реакции, увеличивать при вибрациях |
|
|
||||||
| `joint_velocity_tau` | EMA-фильтр скорости [с] — убирает выбросы конечных разностей | Аналогично `joint_position_tau` |
|
|
||||||
| `active_controller` | Режим управления: `jtc` (MoveIt / JointTrajectory) или `forward` (прямое управление) | `jtc` для большинства задач |
|
|
||||||
| `description` | Путь к URDF-описанию робота | Не менять |
|
| `description` | Путь к URDF-описанию робота | Не менять |
|
||||||
|
|
||||||
|
Управление движением всегда выполняет JointTrajectoryController. EMA-фильтры положения и скорости не применяются.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Блок `digital_twin` — симулятор
|
## Блок `digital_twin` — симулятор
|
||||||
@@ -72,6 +68,7 @@ digital_twin:
|
|||||||
| `webots.cameras` | Список YAML-конфигов подключённых камер |
|
| `webots.cameras` | Список YAML-конфигов подключённых камер |
|
||||||
| `rviz.config` | Путь к конфигурации RViz |
|
| `rviz.config` | Путь к конфигурации RViz |
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Блок `tool` — активный инструмент
|
## Блок `tool` — активный инструмент
|
||||||
@@ -171,7 +168,6 @@ foxglove:
|
|||||||
| ✅ | `robot.fri_cycle_ms` | Выбрать `10` (стандарт) или `5` (высокая частота) |
|
| ✅ | `robot.fri_cycle_ms` | Выбрать `10` (стандарт) или `5` (высокая частота) |
|
||||||
| ✅ | `tool.active` | Указать активный инструмент |
|
| ✅ | `tool.active` | Указать активный инструмент |
|
||||||
| ✅ | `web.enabled` | Выключить (`false`), если веб-интерфейс не нужен |
|
| ✅ | `web.enabled` | Выключить (`false`), если веб-интерфейс не нужен |
|
||||||
| ⚠️ | `robot.active_controller` | Менять только при намеренном переключении режима управления |
|
|
||||||
| ⚠️ | `planning.*` | Менять при необходимости другого планировщика или параметров |
|
| ⚠️ | `planning.*` | Менять при необходимости другого планировщика или параметров |
|
||||||
| ❌ | `robot.description` | Не трогать — путь к URDF |
|
| ❌ | `robot.description` | Не трогать — путь к URDF |
|
||||||
| ❌ | `controller.moveit.*` | Не трогать — пути к конфигам MoveIt внутри пакетов |
|
| ❌ | `controller.moveit.*` | Не трогать — пути к конфигам MoveIt внутри пакетов |
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ def _runtime_setup(context, *args, **kwargs):
|
|||||||
"robot_ip": settings.robot.ip,
|
"robot_ip": settings.robot.ip,
|
||||||
"fri_port": str(settings.robot.port),
|
"fri_port": str(settings.robot.port),
|
||||||
"simulate": "false",
|
"simulate": "false",
|
||||||
"joint_position_tau": str(settings.robot.joint_position_tau),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
robot_description = converter.load_robot_description(
|
robot_description = converter.load_robot_description(
|
||||||
@@ -117,8 +116,8 @@ def _runtime_setup(context, *args, **kwargs):
|
|||||||
"rotation": str(settings.digital_twin.webots.rotation),
|
"rotation": str(settings.digital_twin.webots.rotation),
|
||||||
"controller_timer": str(settings.digital_twin.webots.controller_timer),
|
"controller_timer": str(settings.digital_twin.webots.controller_timer),
|
||||||
"fri_cycle_ms": str(settings.robot.fri_cycle_ms),
|
"fri_cycle_ms": str(settings.robot.fri_cycle_ms),
|
||||||
"joint_position_tau": str(settings.robot.joint_position_tau),
|
"robot_ip": settings.robot.ip,
|
||||||
"controller": settings.robot.active_controller,
|
"fri_port": str(settings.robot.port),
|
||||||
}
|
}
|
||||||
|
|
||||||
setup.append(IncludeLaunchDescription(
|
setup.append(IncludeLaunchDescription(
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ def _setup_controllers(context, *args, **kwargs):
|
|||||||
controller_timer = LaunchConfiguration("controller_timer").perform(context)
|
controller_timer = LaunchConfiguration("controller_timer").perform(context)
|
||||||
controller_path = LaunchConfiguration("controller_path").perform(context)
|
controller_path = LaunchConfiguration("controller_path").perform(context)
|
||||||
simulate = LaunchConfiguration("simulate").perform(context).lower() in ("true", "1", "yes")
|
simulate = LaunchConfiguration("simulate").perform(context).lower() in ("true", "1", "yes")
|
||||||
controller = LaunchConfiguration("controller").perform(context) # "jtc" | "forward"
|
|
||||||
fri_cycle_ms = int(LaunchConfiguration("fri_cycle_ms").perform(context))
|
fri_cycle_ms = int(LaunchConfiguration("fri_cycle_ms").perform(context))
|
||||||
joint_position_tau = LaunchConfiguration("joint_position_tau").perform(context)
|
if not 1 <= fri_cycle_ms <= 100:
|
||||||
|
raise ValueError("fri_cycle_ms must be within the FRI 1.16 range 1..100 ms")
|
||||||
update_rate = 1000 // fri_cycle_ms
|
update_rate = 1000 // fri_cycle_ms
|
||||||
|
|
||||||
xacro_args = {"initial_positions_file": initial_positions_file}
|
xacro_args = {"initial_positions_file": initial_positions_file}
|
||||||
@@ -29,7 +29,8 @@ def _setup_controllers(context, *args, **kwargs):
|
|||||||
if simulate:
|
if simulate:
|
||||||
xacro_args["simulate"] = "true"
|
xacro_args["simulate"] = "true"
|
||||||
else:
|
else:
|
||||||
xacro_args["joint_position_tau"] = joint_position_tau
|
xacro_args["robot_ip"] = LaunchConfiguration("robot_ip").perform(context)
|
||||||
|
xacro_args["fri_port"] = LaunchConfiguration("fri_port").perform(context)
|
||||||
|
|
||||||
robot_description = converter.load_robot_description(
|
robot_description = converter.load_robot_description(
|
||||||
model_path=description,
|
model_path=description,
|
||||||
@@ -98,33 +99,17 @@ def _setup_controllers(context, *args, **kwargs):
|
|||||||
|
|
||||||
cm = ["--controller-manager", "/controller_manager"]
|
cm = ["--controller-manager", "/controller_manager"]
|
||||||
|
|
||||||
jtc_args = ["iiwa_arm_controller"] + cm
|
|
||||||
if controller == "forward":
|
|
||||||
jtc_args += ["--inactive"]
|
|
||||||
|
|
||||||
# ForwardCommandController: активен если controller=forward, иначе --inactive
|
|
||||||
forward_args = ["forward_position_controller"] + cm
|
|
||||||
if controller != "forward":
|
|
||||||
forward_args += ["--inactive"]
|
|
||||||
|
|
||||||
jtc = Node(
|
jtc = Node(
|
||||||
package="controller_manager",
|
package="controller_manager",
|
||||||
executable="spawner",
|
executable="spawner",
|
||||||
output="screen",
|
output="screen",
|
||||||
arguments=jtc_args,
|
arguments=["iiwa_arm_controller"] + cm,
|
||||||
)
|
|
||||||
|
|
||||||
forward_controller = Node(
|
|
||||||
package="controller_manager",
|
|
||||||
executable="spawner",
|
|
||||||
output="screen",
|
|
||||||
arguments=forward_args,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
jtc_after_jsb = RegisterEventHandler(
|
jtc_after_jsb = RegisterEventHandler(
|
||||||
OnProcessExit(
|
OnProcessExit(
|
||||||
target_action=jsb,
|
target_action=jsb,
|
||||||
on_exit=[jtc, forward_controller],
|
on_exit=[jtc],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,7 +123,7 @@ def _setup_controllers(context, *args, **kwargs):
|
|||||||
def generate_launch_description():
|
def generate_launch_description():
|
||||||
return LaunchDescription([
|
return LaunchDescription([
|
||||||
DeclareLaunchArgument("fri_cycle_ms", default_value="5"),
|
DeclareLaunchArgument("fri_cycle_ms", default_value="5"),
|
||||||
DeclareLaunchArgument("joint_position_tau", default_value="0.04"),
|
DeclareLaunchArgument("robot_ip", default_value="192.170.10.2"),
|
||||||
DeclareLaunchArgument("controller", default_value="jtc"),
|
DeclareLaunchArgument("fri_port", default_value="30200"),
|
||||||
OpaqueFunction(function=_setup_controllers),
|
OpaqueFunction(function=_setup_controllers),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ controller_manager:
|
|||||||
iiwa_arm_controller:
|
iiwa_arm_controller:
|
||||||
type: "joint_trajectory_controller/JointTrajectoryController"
|
type: "joint_trajectory_controller/JointTrajectoryController"
|
||||||
|
|
||||||
forward_position_controller:
|
|
||||||
type: "forward_command_controller/ForwardCommandController"
|
|
||||||
|
|
||||||
joint_state_broadcaster:
|
joint_state_broadcaster:
|
||||||
ros__parameters:
|
ros__parameters:
|
||||||
@@ -58,15 +56,3 @@ iiwa_arm_controller:
|
|||||||
# joint5: { trajectory: 0.1, goal: 0.01 }
|
# joint5: { trajectory: 0.1, goal: 0.01 }
|
||||||
# joint6: { trajectory: 0.1, goal: 0.01 }
|
# joint6: { trajectory: 0.1, goal: 0.01 }
|
||||||
# joint7: { trajectory: 0.1, goal: 0.01 }
|
# joint7: { trajectory: 0.1, goal: 0.01 }
|
||||||
|
|
||||||
forward_position_controller:
|
|
||||||
ros__parameters:
|
|
||||||
joints:
|
|
||||||
- joint1
|
|
||||||
- joint2
|
|
||||||
- joint3
|
|
||||||
- joint4
|
|
||||||
- joint5
|
|
||||||
- joint6
|
|
||||||
- joint7
|
|
||||||
interface_name: position
|
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ robot:
|
|||||||
ip: "192.170.10.2"
|
ip: "192.170.10.2"
|
||||||
port: 30200
|
port: 30200
|
||||||
fri_cycle_ms: 10 # период FRI-цикла: 5 мс (200 Гц) или 10 мс (100 Гц)
|
fri_cycle_ms: 10 # период FRI-цикла: 5 мс (200 Гц) или 10 мс (100 Гц)
|
||||||
joint_position_tau: 0.04 # EMA фильтр позиций [с]: сглаживает команды перед отправкой в FRI
|
|
||||||
joint_velocity_tau: 0.01 # EMA фильтр скорости [с]: убирает выбросы конечных разностей
|
|
||||||
active_controller: "jtc" # "jtc" = MoveIt/JointTrajectoryController, "forward" = ForwardCommandController
|
|
||||||
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
description: pkg://iiwa_description/urdf/iiwa7.urdf.xacro
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ file(GLOB_RECURSE FRI_SOURCES
|
|||||||
|
|
||||||
add_library(fri_client_sdk STATIC ${FRI_SOURCES})
|
add_library(fri_client_sdk STATIC ${FRI_SOURCES})
|
||||||
|
|
||||||
target_include_directories(fri_client_sdk PUBLIC
|
target_include_directories(fri_client_sdk SYSTEM PUBLIC
|
||||||
${FRI_SDK_DIR}/include
|
${FRI_SDK_DIR}/include
|
||||||
${FRI_SDK_DIR}/src/nanopb-0.2.8
|
${FRI_SDK_DIR}/src/nanopb-0.2.8
|
||||||
${FRI_SDK_DIR}/src/protobuf
|
${FRI_SDK_DIR}/src/protobuf
|
||||||
@@ -93,4 +93,12 @@ ament_export_targets(export_${PROJECT_NAME})
|
|||||||
ament_export_dependencies(
|
ament_export_dependencies(
|
||||||
controller_interface hardware_interface pluginlib rclcpp rclcpp_lifecycle realtime_tools std_msgs)
|
controller_interface hardware_interface pluginlib rclcpp rclcpp_lifecycle realtime_tools std_msgs)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
find_package(ament_cmake_gtest REQUIRED)
|
||||||
|
ament_add_gtest(test_fri_client test/test_fri_client.cpp)
|
||||||
|
target_link_libraries(test_fri_client ${PROJECT_NAME} fri_client_sdk)
|
||||||
|
ament_add_gtest(test_hardware test/test_hardware.cpp)
|
||||||
|
target_link_libraries(test_hardware ${PROJECT_NAME} fri_client_sdk hardware_interface::hardware_interface)
|
||||||
|
endif()
|
||||||
|
|
||||||
ament_package()
|
ament_package()
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# iiwa_controller
|
||||||
|
|
||||||
|
KUKA LBR iiwa 7 R800 `hardware_interface::SystemInterface`, targeting the bundled
|
||||||
|
**FRI C++ SDK 1.16**, ROS 2 Jazzy, **hardware_interface 4.48.0**, C++17.
|
||||||
|
[Русский](README.md).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
The SDK 1.16 Doxygen in the `kuka-fri` skill and Jazzy hardware documentation
|
||||||
|
were read before reviewing the implementation. Bundled SDK sources,
|
||||||
|
`ServerFriRos2.java`, the URDF and the full
|
||||||
|
[KUKA Sunrise.FRI 1.16 manual](../../doc/pdf/KUKA_SunriseFRI_116_en.pdf)
|
||||||
|
were then checked. Relevant manual sections: 2.1.4, 6.2.1–6.2.5 and 6.3.3.
|
||||||
|
Monitoring mirrors commanded position; COMMANDING_WAIT mirrors IPO.
|
||||||
|
Commanding states require continuous commands. Measured, commanded and IPO
|
||||||
|
positions are distinct quantities.
|
||||||
|
|
||||||
|
The plugin uses framework-managed interfaces and the installed Jazzy headers,
|
||||||
|
as described in [Writing a Hardware Component](https://control.ros.org/jazzy/doc/ros2_control/hardware_interface/doc/writing_new_hardware_component.html).
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
- Seven joints, `joint1`…`joint7`, in SDK A1…A7 order. Command: position [rad].
|
||||||
|
States: encoder position [rad], finite-difference velocity [rad/s] calculated
|
||||||
|
with robot timestamps, measured effort and estimated external torque [Nm].
|
||||||
|
- No position or velocity EMA. Commands retain a URDF velocity step limit
|
||||||
|
(`max_velocity * FRI period`); this is a rate limit, not an EMA filter.
|
||||||
|
Non-finite/out-of-position-range commands fault. Existing robot limits are
|
||||||
|
unchanged and come from Jazzy `HardwareInfo::limits`.
|
||||||
|
- POSITION with JOINT overlay supports position, joint impedance and Cartesian
|
||||||
|
impedance control used by `ServerFriRos2`. TORQUE/WRENCH are unsupported.
|
||||||
|
Monitoring without an overlay remains supported.
|
||||||
|
- The only launched motion controller is `iiwa_arm_controller`, a
|
||||||
|
JointTrajectoryController. Action: `/iiwa_arm_controller/follow_joint_trajectory`.
|
||||||
|
Existing joint-state broadcaster QoS is retained; no new topics or TF frames.
|
||||||
|
- Hardware IP/port reach the URDF from `robot.ip`/`robot.port`; project defaults
|
||||||
|
remain `192.170.10.2:30200`. Match `robot.fri_cycle_ms` to the Sunrise period.
|
||||||
|
|
||||||
|
One thread owns SDK `step()`. Cyclic exchange uses `try_lock`, so a busy mutex
|
||||||
|
never stalls either cycle. Readers retain a complete previous snapshot with a
|
||||||
|
freshness check; writers retry next cycle. Startup allows 15 seconds for the
|
||||||
|
first UDP packet. After that, a failed `step()` is terminal. Activation requires
|
||||||
|
fresh telemetry, at least MONITORING_READY, and GOOD/EXCELLENT quality.
|
||||||
|
|
||||||
|
Missing telemetry/commands for 100 ms, non-increasing timestamps, invalid
|
||||||
|
telemetry, unexpected commanding mode, a safety stop, inactive drives or poor
|
||||||
|
commanding quality stop command generation and surface ERROR to ROS. Leaving
|
||||||
|
COMMANDING_ACTIVE requires explicit lifecycle recovery and a new Sunrise
|
||||||
|
session. The 15 s/100 ms thresholds, command guards and explicit-recovery policy
|
||||||
|
are driver decisions, not universal KUKA requirements. Thread joining precedes
|
||||||
|
socket closure. Cleanup, error, shutdown and destruction release application,
|
||||||
|
connection, then client. Stopping FRI does not replace hardware emergency stop.
|
||||||
|
|
||||||
|
Dependencies remain those in package.xml; SDK sources build as `fri_client_sdk`.
|
||||||
|
Relevant headers are `friLBRClient.h`, `friClientApplication.h`,
|
||||||
|
`friUdpConnection.h`, and `friException.h`. SDK `FRIException` is caught explicitly.
|
||||||
|
Bundled SDK files are unchanged.
|
||||||
|
|
||||||
|
## Offline validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
colcon build --base-paths src --packages-select iiwa_controller iiwa_bringup iiwa_config iiwa_description iiwa_utils --symlink-install
|
||||||
|
source install/setup.bash
|
||||||
|
colcon test --base-paths src --packages-select iiwa_controller
|
||||||
|
colcon test-result --test-result-base build/iiwa_controller --verbose
|
||||||
|
/usr/bin/python3 src/iiwa_controller/test/smoke_jtc.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests exercise real SDK callbacks with artificial data and the plugin's local
|
||||||
|
`simulate=true` path: measurements, IPO, no EMA, modes, limits, faults and
|
||||||
|
repeated lifecycle transitions. They do not establish physical FRI timing.
|
||||||
|
|
||||||
|
Before hardware use, run `cobot run local`, choose **Webots simulator**, verify
|
||||||
|
that the broadcaster and JTC are active, execute and cancel a small valid
|
||||||
|
MoveIt trajectory, and check the action result and simulated stop. Webots uses
|
||||||
|
a separate hardware plugin; it does not validate UDP/FRI. No physical robot was
|
||||||
|
run for this revision. Hardware testing requires the networking, limits,
|
||||||
|
tool/load, mode, operator supervision and stop checks in AGENTS.md.
|
||||||
|
|
||||||
|
Revision results: all five affected ROS packages built; 14 C++ cases in two
|
||||||
|
CTest checks passed. The local smoke script loaded the actual URDF with our
|
||||||
|
plugin in simulation mode, activated JSB/JTC and completed a +0.02 rad joint-1
|
||||||
|
trajectory with SUCCEEDED. On SIGINT, Controller Manager's `pal_statistics`
|
||||||
|
reported an invalid shutdown context; hardware lifecycle shutdown succeeded.
|
||||||
|
Webots and physical FRI were not run.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# iiwa_controller
|
||||||
|
|
||||||
|
Аппаратный `hardware_interface::SystemInterface` для KUKA LBR iiwa 7 R800.
|
||||||
|
Проверенная версия: встроенный **FRI C++ SDK 1.16**, ROS 2 Jazzy,
|
||||||
|
`hardware_interface` **4.48.0**, C++17. [English](README.en.md).
|
||||||
|
|
||||||
|
## Основания реализации
|
||||||
|
|
||||||
|
Перед ревизией реализации изучены Doxygen SDK 1.16 из навыка `kuka-fri`
|
||||||
|
и документация ros2_control Jazzy. Затем проверены поставляемые исходники SDK,
|
||||||
|
`ServerFriRos2.java`, URDF и полное руководство
|
||||||
|
[KUKA Sunrise.FRI 1.16](../../doc/pdf/KUKA_SunriseFRI_116_en.pdf).
|
||||||
|
|
||||||
|
Требования KUKA: разделы 2.1.4, 6.2.1–6.2.5 и 6.3.3. `monitor()` зеркалирует
|
||||||
|
commanded position, `waitForCommand()` — IPO; в обоих commanding-состояниях
|
||||||
|
нужны непрерывные команды. Измеренная позиция, commanded position и IPO — разные
|
||||||
|
величины. Позиционный интерфейс содержит 7 значений A1…A7 в радианах.
|
||||||
|
|
||||||
|
Интеграция ROS следует [Writing a Hardware Component (Jazzy)](https://control.ros.org/jazzy/doc/ros2_control/hardware_interface/doc/writing_new_hardware_component.html),
|
||||||
|
сигнатурам установленных заголовков и автоматически создаваемым интерфейсам.
|
||||||
|
|
||||||
|
## Поведение
|
||||||
|
|
||||||
|
- ROS-интерфейсы: `joint1`…`joint7`; команда `position` [рад]; состояния
|
||||||
|
`position` [рад], `velocity` [рад/с], `effort` и `external_torque` [Нм].
|
||||||
|
- `position` всегда содержит измерение энкодеров. Скорость — конечная разность
|
||||||
|
измеренных позиций по временным меткам робота. EMA-фильтров нет.
|
||||||
|
- Команды передаются без EMA. Ограничение шага `max_velocity * FRI period`
|
||||||
|
сохраняется как ограничение скорости, а не сглаживающий фильтр. Позиция вне
|
||||||
|
ограничений и NaN/Inf приводят к ошибке. Пределы берутся из URDF через Jazzy
|
||||||
|
`HardwareInfo::limits`; численные пределы робота не изменены.
|
||||||
|
- Поддерживается POSITION + JOINT overlay с position, joint impedance или
|
||||||
|
Cartesian impedance control, согласно `ServerFriRos2`. TORQUE/WRENCH
|
||||||
|
намеренно не реализованы. Мониторинг без overlay поддерживается.
|
||||||
|
- Единственный запускаемый контроллер движения — `iiwa_arm_controller`
|
||||||
|
(`joint_trajectory_controller/JointTrajectoryController`). Action:
|
||||||
|
`/iiwa_arm_controller/follow_joint_trajectory`. Joint state broadcaster
|
||||||
|
публикует состояния с существующими настройками QoS. Новых тем и TF нет.
|
||||||
|
- IP и порт передаются из `robot.ip` / `robot.port` в аппаратный URDF;
|
||||||
|
исходные значения проекта — `192.170.10.2:30200`. Период manager задаётся
|
||||||
|
`robot.fri_cycle_ms`; его нужно согласовать с выбранным периодом Sunrise.
|
||||||
|
|
||||||
|
Один рабочий поток владеет `ClientApplication::step()`. Межпоточный обмен
|
||||||
|
в цикле использует `try_lock`: при конкуренции цикл не ждёт mutex. `read()`
|
||||||
|
использует последний целый снимок с проверкой давности, `write()` повторяет
|
||||||
|
передачу в следующем цикле. До первого UDP-пакета допускается ожидание Sunrise
|
||||||
|
до 15 с; после первого пакета ошибка `step()` завершает поток. Активация требует
|
||||||
|
свежей сессии как минимум MONITORING_READY и качества GOOD/EXCELLENT.
|
||||||
|
|
||||||
|
При отсутствии пакетов/команд 100 мс, нерастущем timestamp, нечисловой
|
||||||
|
телеметрии, недопустимом режиме, safety stop, неактивных приводах или плохом
|
||||||
|
качестве в commanding приложение прекращает выдачу команд; ROS получает ERROR.
|
||||||
|
Выход из COMMANDING_ACTIVE требует явного восстановления lifecycle и новой
|
||||||
|
сессии Sunrise. Пороги 15 с и 100 мс, ограничения команд и политика явного
|
||||||
|
восстановления — решения драйвера, **не универсальные требования KUKA**.
|
||||||
|
Остановка потока предшествует закрытию сокета. Объекты уничтожаются в порядке
|
||||||
|
application → connection → client; cleanup, error, shutdown и деструктор
|
||||||
|
выполняют это одинаково. Прекращение FRI не заменяет аппаратную аварийную остановку.
|
||||||
|
|
||||||
|
Зависимости: `hardware_interface`, `pluginlib`, `rclcpp`, `rclcpp_lifecycle`,
|
||||||
|
существующие зависимости package.xml; SDK собирается как `fri_client_sdk`.
|
||||||
|
Публичные SDK заголовки: `friLBRClient.h`, `friClientApplication.h`,
|
||||||
|
`friUdpConnection.h`; исключения обрабатываются как `FRIException`.
|
||||||
|
Поставляемые файлы SDK не изменены.
|
||||||
|
|
||||||
|
## Проверка без робота
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
colcon build --base-paths src --packages-select iiwa_controller iiwa_bringup iiwa_config iiwa_description iiwa_utils --symlink-install
|
||||||
|
source install/setup.bash
|
||||||
|
colcon test --base-paths src --packages-select iiwa_controller
|
||||||
|
colcon test-result --test-result-base build/iiwa_controller --verbose
|
||||||
|
/usr/bin/python3 src/iiwa_controller/test/smoke_jtc.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Тесты используют реальный SDK с искусственными данными и локальный режим
|
||||||
|
`simulate=true` аппаратного плагина. Они проверяют измерения, IPO, отсутствие
|
||||||
|
EMA, режимы, ограничения, ошибки и повторные переходы lifecycle. Они не
|
||||||
|
доказывают временные характеристики реального FRI-соединения.
|
||||||
|
|
||||||
|
Webots smoke-test перед аппаратным запуском: `cobot run local` → **Webots
|
||||||
|
simulator**; проверить, что `joint_state_broadcaster` и `iiwa_arm_controller`
|
||||||
|
активны, выполнить небольшую допустимую траекторию MoveIt и отменить её.
|
||||||
|
Проверить результат action и остановку симулятора. Webots использует отдельный
|
||||||
|
плагин и сам по себе не проверяет UDP/FRI. Реальный робот в этой ревизии не запускался.
|
||||||
|
Для аппаратной проверки нужны оператор, свободная рабочая зона, проверенные
|
||||||
|
сеть, tool/load data, режим, ограничения и процедура останова из AGENTS.md.
|
||||||
|
|
||||||
|
Результаты ревизии: сборка пяти затронутых ROS-пакетов прошла; 14 C++-сценариев
|
||||||
|
в двух CTest-проверках прошли. `smoke_jtc.py` загрузил настоящий URDF с локальной
|
||||||
|
имитацией нашего плагина, активировал JSB/JTC и завершил траекторию +0.02 рад
|
||||||
|
сустава 1 со статусом SUCCEEDED. При SIGINT библиотека `pal_statistics`
|
||||||
|
Controller Manager вывела сообщение об остановленном контексте; lifecycle
|
||||||
|
аппаратного плагина завершился успешно. Webots и физический FRI не запускались.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "friClientApplication.h"
|
#include "friClientApplication.h"
|
||||||
@@ -10,62 +11,65 @@
|
|||||||
|
|
||||||
namespace iiwa_controller
|
namespace iiwa_controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// Снимок состояния робота захватывается атомарно за один lock в FRI-потоке
|
|
||||||
// и так же за один lock читается из read() в потоке управления.
|
|
||||||
struct IIWAStateSnapshot
|
struct IIWAStateSnapshot
|
||||||
{
|
{
|
||||||
std::array<double, 7> measured_pos{}; // в Commanding = filtered_pos_ (open-loop)
|
std::array<double, 7> measured_pos{}; // encoder position [rad], never a command
|
||||||
std::array<double, 7> measured_tau{}; // измеренные моменты [Нм]
|
std::array<double, 7> measured_tau{}; // measured torque [Nm]
|
||||||
std::array<double, 7> external_tau{}; // внешние моменты без компенсации модели [Нм]
|
std::array<double, 7> external_tau{}; // estimated external torque [Nm]
|
||||||
std::array<double, 7> ipo_pos{}; // позиция интерполятора [рад], только в Commanding
|
std::array<double, 7> ipo_pos{}; // interpolator position [rad]
|
||||||
double sample_time{0.005}; // период цикла FRI [с]
|
double sample_time{0.0};
|
||||||
|
KUKA::FRI::ESessionState session{KUKA::FRI::IDLE};
|
||||||
KUKA::FRI::EConnectionQuality quality{KUKA::FRI::POOR};
|
KUKA::FRI::EConnectionQuality quality{KUKA::FRI::POOR};
|
||||||
bool ipo_valid{false}; // в Monitor-режиме IPO недоступна
|
KUKA::FRI::ESafetyState safety{KUKA::FRI::NORMAL_OPERATION};
|
||||||
unsigned int time_stamp_sec{0}; // Unix-время пакета [с]
|
KUKA::FRI::EDriveState drives{KUKA::FRI::OFF};
|
||||||
unsigned int time_stamp_nano_sec{0}; // наносекундная часть [нс]
|
KUKA::FRI::EOperationMode operation{KUKA::FRI::TEST_MODE_1};
|
||||||
|
KUKA::FRI::EControlMode control{KUKA::FRI::NO_CONTROL};
|
||||||
|
KUKA::FRI::EClientCommandMode command_mode{KUKA::FRI::NO_COMMAND_MODE};
|
||||||
|
KUKA::FRI::EOverlayType overlay{KUKA::FRI::NO_OVERLAY};
|
||||||
|
double tracking_performance{0.0};
|
||||||
|
bool valid{false};
|
||||||
|
bool ipo_valid{false};
|
||||||
|
unsigned int time_stamp_sec{0};
|
||||||
|
unsigned int time_stamp_nano_sec{0};
|
||||||
|
std::chrono::steady_clock::time_point received_at{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRIClient : public KUKA::FRI::LBRClient
|
class FRIClient : public KUKA::FRI::LBRClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static constexpr size_t N_JOINTS = 7;
|
static constexpr size_t N_JOINTS = KUKA::FRI::LBRState::NUMBER_OF_JOINTS;
|
||||||
|
using Joints = std::array<double, N_JOINTS>;
|
||||||
// joint_position_tau — постоянная времени экспоненциального фильтра позиций [с].
|
FRIClient();
|
||||||
// Аналог joint_position_tau из lbr_fri_ros2_stack (по умолчанию 0.04 с = 40 мс).
|
|
||||||
// Сглаживает скачки команд перед отправкой роботу → убирает писк и стук суставов.
|
|
||||||
explicit FRIClient(double joint_position_tau = 0.04);
|
|
||||||
~FRIClient() override = default;
|
|
||||||
|
|
||||||
// Коллбэки FRI SDK, вызываются из friThreadFunc через ClientApplication::step()
|
|
||||||
void monitor() override;
|
void monitor() override;
|
||||||
void waitForCommand() override;
|
void waitForCommand() override;
|
||||||
void command() override;
|
void command() override;
|
||||||
void onStateChange(
|
void onStateChange(KUKA::FRI::ESessionState oldState,
|
||||||
KUKA::FRI::ESessionState oldState, KUKA::FRI::ESessionState newState) override;
|
KUKA::FRI::ESessionState newState) override;
|
||||||
|
|
||||||
// Потокобезопасное API для ros2_control, вызывается из read() и write()
|
// Configure before starting the FRI thread; limits come from the robot URDF.
|
||||||
void setTargetJointPositions(const std::array<double, N_JOINTS> & q);
|
void setLimits(const Joints & lower, const Joints & upper, const Joints & velocity);
|
||||||
IIWAStateSnapshot getStateSnapshot() const;
|
void setTargetJointPositions(const Joints & q);
|
||||||
|
IIWAStateSnapshot getStateSnapshot() const; // lifecycle/non-RT use
|
||||||
|
bool tryGetStateSnapshot(IIWAStateSnapshot & snapshot) const;
|
||||||
bool isCommandingActive() const;
|
bool isCommandingActive() const;
|
||||||
KUKA::FRI::ESessionState getSessionState() const;
|
KUKA::FRI::ESessionState getSessionState() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double joint_position_tau_;
|
|
||||||
std::atomic<KUKA::FRI::ESessionState> session_state_{KUKA::FRI::IDLE};
|
std::atomic<KUKA::FRI::ESessionState> session_state_{KUKA::FRI::IDLE};
|
||||||
|
mutable std::mutex state_mutex_;
|
||||||
mutable std::mutex data_mutex_;
|
std::mutex command_mutex_;
|
||||||
std::array<double, N_JOINTS> target_pos_{};
|
Joints target_pos_{};
|
||||||
// Сглаженная позиция, которую реально отправляем роботу.
|
Joints requested_pos_{};
|
||||||
// Инициализируется IPO-позицией в waitForCommand(), чтобы не было скачка при старте.
|
Joints sent_pos_{};
|
||||||
std::array<double, N_JOINTS> filtered_pos_{};
|
Joints lower_, upper_, max_velocity_;
|
||||||
IIWAStateSnapshot snapshot_{};
|
bool initialized_{false};
|
||||||
|
bool requested_{false};
|
||||||
// Обновить snapshot_ без поля ipo_pos (в Monitor-режиме getIpoJointPosition() недоступна)
|
std::chrono::steady_clock::time_point requested_at_{};
|
||||||
void captureMonitoringData();
|
std::chrono::steady_clock::time_point last_command_at_{};
|
||||||
// Обновить snapshot_ вместе с ipo_pos (в Commanding-режиме)
|
IIWAStateSnapshot snapshot_{}; // published under state_mutex_
|
||||||
void captureCommandingData();
|
IIWAStateSnapshot current_{}; // owned exclusively by FRI thread
|
||||||
|
void captureData(bool commanding);
|
||||||
|
void publishState();
|
||||||
|
void validateCommanding() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace iiwa_controller
|
} // namespace iiwa_controller
|
||||||
|
|||||||
@@ -8,10 +8,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// В Jazzy 4.44.0+ нельзя переопределять export_state_interfaces() и export_command_interfaces().
|
// Jazzy 4.48: framework-managed interfaces and handle API.
|
||||||
// Устаревший конструктор не регистрирует introspection-callback pal_statistics,
|
|
||||||
// из-за чего падает с segfault. Базовый класс сам создаёт интерфейсы из URDF.
|
|
||||||
// Данные читаем и пишем через handle-API: set_state() и get_command().
|
|
||||||
#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_component_interface_params.hpp"
|
#include "hardware_interface/types/hardware_component_interface_params.hpp"
|
||||||
@@ -31,6 +28,7 @@ class IIWAHardwareInterface : public hardware_interface::SystemInterface
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RCLCPP_SHARED_PTR_DEFINITIONS(IIWAHardwareInterface)
|
RCLCPP_SHARED_PTR_DEFINITIONS(IIWAHardwareInterface)
|
||||||
|
~IIWAHardwareInterface() override;
|
||||||
|
|
||||||
CallbackReturn on_init(
|
CallbackReturn on_init(
|
||||||
const hardware_interface::HardwareComponentInterfaceParams & params) override;
|
const hardware_interface::HardwareComponentInterfaceParams & params) override;
|
||||||
@@ -39,13 +37,16 @@ public:
|
|||||||
std::vector<hardware_interface::InterfaceDescription>
|
std::vector<hardware_interface::InterfaceDescription>
|
||||||
export_unlisted_state_interface_descriptions() override;
|
export_unlisted_state_interface_descriptions() override;
|
||||||
|
|
||||||
// Полный lifecycle: configure открывает сокет, activate запускает поток,
|
// Полный lifecycle: configure создаёт SDK, activate открывает сокет и запускает поток,
|
||||||
// deactivate останавливает поток, cleanup освобождает FRI-объекты.
|
// deactivate останавливает поток, cleanup освобождает FRI-объекты.
|
||||||
CallbackReturn on_configure(const rclcpp_lifecycle::State & previous_state) override;
|
CallbackReturn on_configure(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
CallbackReturn on_activate(const rclcpp_lifecycle::State & previous_state) override;
|
CallbackReturn on_activate(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
CallbackReturn on_deactivate(const rclcpp_lifecycle::State & previous_state) override;
|
CallbackReturn on_deactivate(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
CallbackReturn on_cleanup(const rclcpp_lifecycle::State & previous_state) override;
|
CallbackReturn on_cleanup(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
|
|
||||||
|
CallbackReturn on_shutdown(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
|
CallbackReturn on_error(const rclcpp_lifecycle::State & previous_state) override;
|
||||||
|
|
||||||
hardware_interface::return_type read(
|
hardware_interface::return_type read(
|
||||||
const rclcpp::Time & time, const rclcpp::Duration & period) override;
|
const rclcpp::Time & time, const rclcpp::Duration & period) override;
|
||||||
|
|
||||||
@@ -59,11 +60,6 @@ private:
|
|||||||
std::string robot_ip_;
|
std::string robot_ip_;
|
||||||
int fri_port_{30200};
|
int fri_port_{30200};
|
||||||
bool simulate_{false};
|
bool simulate_{false};
|
||||||
double joint_position_tau_{0.04};
|
|
||||||
// EMA-фильтр скорости: сглаживает одиночные выбросы конечных разностей.
|
|
||||||
// joint_velocity_tau = 0 отключает фильтр (raw finite difference).
|
|
||||||
double joint_velocity_tau_{0.01};
|
|
||||||
|
|
||||||
// Объекты FRI SDK
|
// Объекты FRI SDK
|
||||||
std::unique_ptr<FRIClient> fri_client_;
|
std::unique_ptr<FRIClient> fri_client_;
|
||||||
std::unique_ptr<KUKA::FRI::UdpConnection> connection_;
|
std::unique_ptr<KUKA::FRI::UdpConnection> connection_;
|
||||||
@@ -74,6 +70,12 @@ private:
|
|||||||
std::thread fri_thread_;
|
std::thread fri_thread_;
|
||||||
std::atomic<bool> fri_running_{false};
|
std::atomic<bool> fri_running_{false};
|
||||||
void friThreadFunc();
|
void friThreadFunc();
|
||||||
|
void stopFRI();
|
||||||
|
void releaseFRI();
|
||||||
|
std::atomic<bool> fri_fault_{false};
|
||||||
|
bool active_{false};
|
||||||
|
IIWAStateSnapshot last_snapshot_{};
|
||||||
|
std::array<double, N_JOINTS> lower_, upper_, max_velocity_;
|
||||||
|
|
||||||
// Хэндлы интерфейсов состояния, заполняются в on_activate
|
// Хэндлы интерфейсов состояния, заполняются в on_activate
|
||||||
std::array<hardware_interface::StateInterface::SharedPtr, N_JOINTS> h_pos_;
|
std::array<hardware_interface::StateInterface::SharedPtr, N_JOINTS> h_pos_;
|
||||||
@@ -83,19 +85,15 @@ private:
|
|||||||
|
|
||||||
std::array<hardware_interface::CommandInterface::SharedPtr, N_JOINTS> h_cmd_pos_;
|
std::array<hardware_interface::CommandInterface::SharedPtr, N_JOINTS> h_cmd_pos_;
|
||||||
|
|
||||||
// Вычисление скорости: конечные разности + EMA-фильтр
|
// Вычисление скорости по измеренной позиции и меткам времени FRI.
|
||||||
std::array<double, N_JOINTS> prev_pos_{};
|
std::array<double, N_JOINTS> prev_pos_{};
|
||||||
std::array<double, N_JOINTS> velocity_{}; // отфильтрованная скорость, публикуется в JTC
|
std::array<double, N_JOINTS> velocity_{}; // измеренная скорость [рад/с]
|
||||||
std::array<double, N_JOINTS> velocity_raw_{}; // сырая скорость до фильтра
|
|
||||||
unsigned int last_ts_sec_{0};
|
unsigned int last_ts_sec_{0};
|
||||||
unsigned int last_ts_nsec_{0};
|
unsigned int last_ts_nsec_{0};
|
||||||
bool velocity_initialized_{false};
|
bool velocity_initialized_{false};
|
||||||
void compute_velocity_(const IIWAStateSnapshot & snap);
|
void compute_velocity_(const IIWAStateSnapshot & snap);
|
||||||
|
|
||||||
// Отслеживание сессии FRI для обнаружения потери управления
|
|
||||||
KUKA::FRI::ESessionState previous_session_state_{KUKA::FRI::IDLE};
|
|
||||||
|
|
||||||
rclcpp::Clock throttle_clock_{RCL_STEADY_TIME};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<depend>realtime_tools</depend>
|
<depend>realtime_tools</depend>
|
||||||
<depend>std_msgs</depend>
|
<depend>std_msgs</depend>
|
||||||
|
|
||||||
|
<test_depend>ament_cmake_gtest</test_depend>
|
||||||
<test_depend>ament_lint_auto</test_depend>
|
<test_depend>ament_lint_auto</test_depend>
|
||||||
<test_depend>ament_lint_common</test_depend>
|
<test_depend>ament_lint_common</test_depend>
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +1,190 @@
|
|||||||
#include "iiwa_controller/FRIClient.h"
|
#include "iiwa_controller/FRIClient.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
#include <rclcpp/rclcpp.hpp>
|
|
||||||
|
|
||||||
namespace iiwa_controller
|
namespace iiwa_controller
|
||||||
{
|
{
|
||||||
|
using namespace KUKA::FRI;
|
||||||
|
|
||||||
static const char * friStateName(KUKA::FRI::ESessionState s)
|
FRIClient::FRIClient()
|
||||||
{
|
{
|
||||||
switch (s) {
|
lower_.fill(-std::numeric_limits<double>::infinity());
|
||||||
case KUKA::FRI::IDLE: return "IDLE";
|
upper_.fill(std::numeric_limits<double>::infinity());
|
||||||
case KUKA::FRI::MONITORING_WAIT: return "MONITORING_WAIT";
|
max_velocity_.fill(std::numeric_limits<double>::infinity());
|
||||||
case KUKA::FRI::MONITORING_READY: return "MONITORING_READY";
|
}
|
||||||
case KUKA::FRI::COMMANDING_WAIT: return "COMMANDING_WAIT";
|
|
||||||
case KUKA::FRI::COMMANDING_ACTIVE: return "COMMANDING_ACTIVE";
|
void FRIClient::setLimits(const Joints & lower, const Joints & upper, const Joints & velocity)
|
||||||
default: return "UNKNOWN";
|
{
|
||||||
|
lower_ = lower;
|
||||||
|
upper_ = upper;
|
||||||
|
max_velocity_ = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRIClient::captureData(bool commanding)
|
||||||
|
{
|
||||||
|
const auto & state = robotState();
|
||||||
|
std::copy_n(state.getMeasuredJointPosition(), N_JOINTS, current_.measured_pos.begin());
|
||||||
|
std::copy_n(state.getMeasuredTorque(), N_JOINTS, current_.measured_tau.begin());
|
||||||
|
std::copy_n(state.getExternalTorque(), N_JOINTS, current_.external_tau.begin());
|
||||||
|
current_.sample_time = state.getSampleTime();
|
||||||
|
current_.session = state.getSessionState();
|
||||||
|
current_.quality = state.getConnectionQuality();
|
||||||
|
current_.safety = state.getSafetyState();
|
||||||
|
current_.drives = state.getDriveState();
|
||||||
|
current_.operation = state.getOperationMode();
|
||||||
|
current_.control = state.getControlMode();
|
||||||
|
current_.command_mode = commanding ? state.getClientCommandMode() : NO_COMMAND_MODE;
|
||||||
|
current_.overlay = commanding ? state.getOverlayType() : NO_OVERLAY;
|
||||||
|
current_.tracking_performance = commanding ? state.getTrackingPerformance() : 0.0;
|
||||||
|
current_.ipo_valid = commanding;
|
||||||
|
if (commanding) {
|
||||||
|
std::copy_n(state.getIpoJointPosition(), N_JOINTS, current_.ipo_pos.begin());
|
||||||
|
}
|
||||||
|
const auto sec = state.getTimestampSec();
|
||||||
|
const auto nsec = state.getTimestampNanoSec();
|
||||||
|
if (current_.valid && (sec < current_.time_stamp_sec ||
|
||||||
|
(sec == current_.time_stamp_sec && nsec <= current_.time_stamp_nano_sec)))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("FRI timestamp did not advance");
|
||||||
|
}
|
||||||
|
if (nsec >= 1000000000 || !std::isfinite(current_.sample_time) || current_.sample_time <= 0.0) {
|
||||||
|
throw std::runtime_error("Invalid FRI timestamp/sample period");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
|
if (!std::isfinite(current_.measured_pos[i]) || !std::isfinite(current_.measured_tau[i]) ||
|
||||||
|
!std::isfinite(current_.external_tau[i]) ||
|
||||||
|
(commanding && !std::isfinite(current_.ipo_pos[i])))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Non-finite FRI telemetry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_.time_stamp_sec = sec;
|
||||||
|
current_.time_stamp_nano_sec = nsec;
|
||||||
|
current_.received_at = std::chrono::steady_clock::now();
|
||||||
|
current_.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRIClient::publishState()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(state_mutex_, std::try_to_lock);
|
||||||
|
if (lock.owns_lock()) {snapshot_ = current_;}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRIClient::validateCommanding() const
|
||||||
|
{
|
||||||
|
if (current_.command_mode != POSITION || current_.overlay != JOINT ||
|
||||||
|
(current_.control != POSITION_CONTROL_MODE && current_.control != JOINT_IMP_CONTROL_MODE &&
|
||||||
|
current_.control != CART_IMP_CONTROL_MODE))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Expected FRI POSITION command mode with JOINT overlay");
|
||||||
|
}
|
||||||
|
if (current_.quality < GOOD || current_.safety != NORMAL_OPERATION ||
|
||||||
|
current_.drives != ACTIVE)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("FRI commanding unavailable: quality, safety or drives");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FRIClient::FRIClient(double joint_position_tau)
|
|
||||||
: joint_position_tau_(joint_position_tau)
|
|
||||||
{
|
|
||||||
target_pos_.fill(0.0);
|
|
||||||
filtered_pos_.fill(0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вызывается только в Monitor-состояниях.
|
|
||||||
// В Monitor-режиме getIpoJointPosition() бросает FRIException, поэтому здесь не зовём.
|
|
||||||
void FRIClient::captureMonitoringData()
|
|
||||||
{
|
|
||||||
std::memcpy(
|
|
||||||
snapshot_.measured_pos.data(),
|
|
||||||
robotState().getMeasuredJointPosition(), N_JOINTS * sizeof(double));
|
|
||||||
std::memcpy(
|
|
||||||
snapshot_.measured_tau.data(),
|
|
||||||
robotState().getMeasuredTorque(), N_JOINTS * sizeof(double));
|
|
||||||
std::memcpy(
|
|
||||||
snapshot_.external_tau.data(),
|
|
||||||
robotState().getExternalTorque(), N_JOINTS * sizeof(double));
|
|
||||||
snapshot_.sample_time = robotState().getSampleTime();
|
|
||||||
snapshot_.quality = robotState().getConnectionQuality();
|
|
||||||
snapshot_.ipo_valid = false;
|
|
||||||
snapshot_.time_stamp_sec = robotState().getTimestampSec();
|
|
||||||
snapshot_.time_stamp_nano_sec = robotState().getTimestampNanoSec();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вызывается из Commanding-состояний (COMMANDING_WAIT и COMMANDING_ACTIVE).
|
|
||||||
// В отличие от Monitor, здесь getIpoJointPosition() доступна.
|
|
||||||
void FRIClient::captureCommandingData()
|
|
||||||
{
|
|
||||||
captureMonitoringData();
|
|
||||||
std::memcpy(
|
|
||||||
snapshot_.ipo_pos.data(),
|
|
||||||
robotState().getIpoJointPosition(), N_JOINTS * sizeof(double));
|
|
||||||
snapshot_.ipo_valid = true;
|
|
||||||
// Open-loop: JTC видит filtered_pos_ как «измеренную» позицию — как в lbr_fri_ros2_stack.
|
|
||||||
// Благодаря этому JTC не видит расхождения и не генерирует коррекций.
|
|
||||||
snapshot_.measured_pos = filtered_pos_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вызывается в MONITORING_WAIT и MONITORING_READY
|
|
||||||
void FRIClient::monitor()
|
void FRIClient::monitor()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
captureData(false);
|
||||||
captureMonitoringData();
|
LBRClient::monitor();
|
||||||
|
initialized_ = false;
|
||||||
|
publishState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вызывается в COMMANDING_WAIT.
|
|
||||||
// По документации FRI (п. 6.2.2) клиент должен отправлять команды в каждом цикле.
|
|
||||||
// Переход в COMMANDING_ACTIVE происходит только когда разница между commanded_position
|
|
||||||
// и IPO_position меньше 0.001 рад для всех суставов.
|
|
||||||
// Важно эхировать именно IPO-позицию, не measured. Если взять measured,
|
|
||||||
// статическое отклонение не даст выполниться этому условию.
|
|
||||||
void FRIClient::waitForCommand()
|
void FRIClient::waitForCommand()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
captureData(true);
|
||||||
captureCommandingData();
|
validateCommanding();
|
||||||
|
LBRClient::waitForCommand(); // SDK 1.16 mirrors IPO, not measured position.
|
||||||
// Инициализируем цель и фильтр IPO-позицией.
|
sent_pos_ = target_pos_ = current_.ipo_pos;
|
||||||
// Фильтр стартует с IPO — это гарантирует нулевой скачок при переходе в COMMANDING_ACTIVE.
|
initialized_ = true;
|
||||||
std::memcpy(target_pos_.data(), snapshot_.ipo_pos.data(), N_JOINTS * sizeof(double));
|
last_command_at_ = current_.received_at;
|
||||||
std::memcpy(filtered_pos_.data(), snapshot_.ipo_pos.data(), N_JOINTS * sizeof(double));
|
publishState();
|
||||||
|
|
||||||
robotCommand().setJointPosition(filtered_pos_.data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вызывается в COMMANDING_ACTIVE, основной цикл управления
|
|
||||||
void FRIClient::command()
|
void FRIClient::command()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
captureData(true);
|
||||||
|
validateCommanding();
|
||||||
// EMA-фильтр применяется ДО захвата снимка — тогда snapshot_.measured_pos = filtered_pos_
|
if (!initialized_) {
|
||||||
// будет содержать то, что реально отправлено роботу в этом цикле (не прошлом).
|
sent_pos_ = target_pos_ = current_.ipo_pos;
|
||||||
// Это соответствует lbr_fri_ros2_stack: снимок захватывается post-EMA.
|
last_command_at_ = current_.received_at;
|
||||||
const double dt = robotState().getSampleTime();
|
initialized_ = true;
|
||||||
const double alpha = (joint_position_tau_ > 0.0) ? dt / (joint_position_tau_ + dt) : 1.0;
|
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
|
||||||
filtered_pos_[i] = alpha * target_pos_[i] + (1.0 - alpha) * filtered_pos_[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
robotCommand().setJointPosition(filtered_pos_.data());
|
|
||||||
|
|
||||||
// Захватываем снимок ПОСЛЕ EMA: measured_pos = filtered_pos_ = что робот только что получил.
|
|
||||||
captureCommandingData();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ||
|
|
||||||
newState == KUKA::FRI::MONITORING_READY)
|
|
||||||
{
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(command_mutex_, std::try_to_lock);
|
||||||
|
if (lock.owns_lock() && requested_ && requested_at_ > last_command_at_) {
|
||||||
|
target_pos_ = requested_pos_;
|
||||||
|
last_command_at_ = requested_at_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Local watchdog: a stalled ros2_control loop must not leave a moving target active.
|
||||||
|
if (current_.received_at - last_command_at_ > std::chrono::milliseconds(100)) {
|
||||||
|
throw std::runtime_error("FRI command watchdog expired");
|
||||||
|
}
|
||||||
|
const double dt = current_.sample_time;
|
||||||
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
|
if (!std::isfinite(target_pos_[i]) || target_pos_[i] < lower_[i] ||
|
||||||
|
target_pos_[i] > upper_[i])
|
||||||
|
{
|
||||||
|
throw std::runtime_error("FRI position command outside configured limits");
|
||||||
|
}
|
||||||
|
const double step = target_pos_[i] - sent_pos_[i];
|
||||||
|
sent_pos_[i] += std::clamp(step, -max_velocity_[i] * dt, max_velocity_[i] * dt);
|
||||||
|
}
|
||||||
|
robotCommand().setJointPosition(sent_pos_.data());
|
||||||
|
publishState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRIClient::onStateChange(ESessionState oldState, ESessionState newState)
|
||||||
|
{
|
||||||
|
session_state_.store(newState);
|
||||||
|
if (oldState == COMMANDING_ACTIVE && newState != COMMANDING_ACTIVE) {
|
||||||
|
throw std::runtime_error("FRI left COMMANDING_ACTIVE; explicit recovery required");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FRIClient::setTargetJointPositions(const std::array<double, N_JOINTS> & q)
|
void FRIClient::setTargetJointPositions(const Joints & q)
|
||||||
{
|
{
|
||||||
// До первой команды контроллера интерфейс содержит NaN.
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
// Если отправить NaN роботу в COMMANDING_ACTIVE, получим CK_COMPOUND_RETURN_ERROR.
|
if (!std::isfinite(q[i]) || q[i] < lower_[i] || q[i] > upper_[i]) {
|
||||||
for (const auto & v : q) {
|
throw std::invalid_argument("Invalid FRI position command");
|
||||||
if (!std::isfinite(v)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
std::unique_lock<std::mutex> lock(command_mutex_, std::try_to_lock);
|
||||||
target_pos_ = q;
|
if (lock.owns_lock()) {
|
||||||
|
requested_pos_ = q;
|
||||||
|
requested_at_ = std::chrono::steady_clock::now();
|
||||||
|
requested_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IIWAStateSnapshot FRIClient::getStateSnapshot() const
|
IIWAStateSnapshot FRIClient::getStateSnapshot() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||||
return snapshot_;
|
return snapshot_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FRIClient::tryGetStateSnapshot(IIWAStateSnapshot & snapshot) const
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(state_mutex_, std::try_to_lock);
|
||||||
|
if (!lock.owns_lock()) {return false;}
|
||||||
|
snapshot = snapshot_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool FRIClient::isCommandingActive() const
|
bool FRIClient::isCommandingActive() const
|
||||||
{
|
{
|
||||||
return session_state_.load(std::memory_order_relaxed) == KUKA::FRI::COMMANDING_ACTIVE;
|
return getSessionState() == COMMANDING_ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
KUKA::FRI::ESessionState FRIClient::getSessionState() const
|
ESessionState FRIClient::getSessionState() const
|
||||||
{
|
{
|
||||||
return session_state_.load(std::memory_order_relaxed);
|
return session_state_.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace iiwa_controller
|
} // namespace iiwa_controller
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
#include "iiwa_controller/IIWAHardwareInterface.hpp"
|
#include "iiwa_controller/IIWAHardwareInterface.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "friException.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -21,6 +26,32 @@ namespace iiwa_controller
|
|||||||
using CallbackReturn =
|
using CallbackReturn =
|
||||||
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
|
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
|
||||||
|
|
||||||
|
// The SDK keeps a 100 ms receive bound after the first packet. During startup
|
||||||
|
// only, allow 15 s for the operator to start Sunrise without retrying failed SDK steps.
|
||||||
|
class StartupConnection : public KUKA::FRI::UdpConnection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit StartupConnection(const std::atomic<bool> & running)
|
||||||
|
: KUKA::FRI::UdpConnection(100), running_(running) {}
|
||||||
|
|
||||||
|
int receive(char * buffer, int size) override
|
||||||
|
{
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(15);
|
||||||
|
do {
|
||||||
|
const int received = KUKA::FRI::UdpConnection::receive(buffer, size);
|
||||||
|
if (received >= 0) {
|
||||||
|
started_ = true;
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
if (started_) {return received;}
|
||||||
|
} while (running_.load() && std::chrono::steady_clock::now() < deadline);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const std::atomic<bool> & running_;
|
||||||
|
bool started_{false};
|
||||||
|
};
|
||||||
|
|
||||||
static const char * LOG = "IIWAHardwareInterface";
|
static const char * LOG = "IIWAHardwareInterface";
|
||||||
|
|
||||||
static std::string getParam(
|
static std::string getParam(
|
||||||
@@ -43,18 +74,27 @@ CallbackReturn IIWAHardwareInterface::on_init(
|
|||||||
|
|
||||||
const auto & info = params.hardware_info;
|
const auto & info = params.hardware_info;
|
||||||
|
|
||||||
|
try {
|
||||||
robot_ip_ = getParam(info, "robot_ip", "192.170.10.2");
|
robot_ip_ = getParam(info, "robot_ip", "192.170.10.2");
|
||||||
fri_port_ = std::stoi(getParam(info, "fri_port", "30200"));
|
size_t used = 0;
|
||||||
simulate_ = (getParam(info, "simulate", "false") == "true");
|
const auto port = getParam(info, "fri_port", "30200");
|
||||||
joint_position_tau_ = std::stod(getParam(info, "joint_position_tau", "0.04"));
|
fri_port_ = std::stoi(port, &used);
|
||||||
joint_velocity_tau_ = std::stod(getParam(info, "joint_velocity_tau", "0.01"));
|
if (used != port.size() || fri_port_ < 1 || fri_port_ > 65535) {
|
||||||
|
throw std::invalid_argument("Invalid fri_port");
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG),
|
}
|
||||||
"on_init: ip=%s port=%d simulate=%s pos_tau=%.3f vel_tau=%.3f",
|
const auto simulation = getParam(info, "simulate", "false");
|
||||||
robot_ip_.c_str(), fri_port_,
|
if (simulation != "true" && simulation != "false") {
|
||||||
simulate_ ? "true" : "false",
|
throw std::invalid_argument("simulate must be true or false");
|
||||||
joint_position_tau_,
|
}
|
||||||
joint_velocity_tau_);
|
simulate_ = simulation == "true";
|
||||||
|
in_addr address{};
|
||||||
|
if (!simulate_ && inet_pton(AF_INET, robot_ip_.c_str(), &address) != 1) {
|
||||||
|
throw std::invalid_argument("robot_ip must be an IPv4 address");
|
||||||
|
}
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "Invalid hardware parameters: %s", e.what());
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
if (info.joints.size() != N_JOINTS) {
|
if (info.joints.size() != N_JOINTS) {
|
||||||
RCLCPP_FATAL(rclcpp::get_logger(LOG),
|
RCLCPP_FATAL(rclcpp::get_logger(LOG),
|
||||||
@@ -62,9 +102,41 @@ CallbackReturn IIWAHardwareInterface::on_init(
|
|||||||
return CallbackReturn::ERROR;
|
return CallbackReturn::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
|
const auto & joint = info.joints[i];
|
||||||
|
// SDK arrays are ordered A1..A7. Never silently permute axes.
|
||||||
|
if (joint.name != "joint" + std::to_string(i + 1) ||
|
||||||
|
joint.command_interfaces.size() != 1 || joint.command_interfaces[0].name != "position")
|
||||||
|
{
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "Expected joint1..joint7 with position commands");
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
for (const auto * name : {"position", "velocity", "effort"}) {
|
||||||
|
if (std::none_of(joint.state_interfaces.begin(), joint.state_interfaces.end(),
|
||||||
|
[&](const auto & interface) {return interface.name == name;}))
|
||||||
|
{
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto limits = info.limits.find(joint.name);
|
||||||
|
if (limits == info.limits.end() || !limits->second.has_position_limits ||
|
||||||
|
!limits->second.has_velocity_limits)
|
||||||
|
{
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "Missing position/velocity limits for %s", joint.name.c_str());
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
lower_[i] = limits->second.min_position;
|
||||||
|
upper_[i] = limits->second.max_position;
|
||||||
|
max_velocity_[i] = limits->second.max_velocity;
|
||||||
|
if (!std::isfinite(lower_[i]) || !std::isfinite(upper_[i]) || lower_[i] >= upper_[i] ||
|
||||||
|
!std::isfinite(max_velocity_[i]) || max_velocity_[i] <= 0.0)
|
||||||
|
{
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prev_pos_.fill(0.0);
|
prev_pos_.fill(0.0);
|
||||||
velocity_.fill(0.0);
|
velocity_.fill(0.0);
|
||||||
velocity_raw_.fill(0.0);
|
|
||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +160,7 @@ IIWAHardwareInterface::export_unlisted_state_interface_descriptions()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── on_configure ───────────────────────────────────────────────────────────────
|
// ── on_configure ───────────────────────────────────────────────────────────────
|
||||||
// Открывает UDP-сокет и создаёт FRI-объекты. Не запускает поток.
|
// Создаёт FRI-объекты. Сокет и поток запускаются при активации.
|
||||||
|
|
||||||
CallbackReturn IIWAHardwareInterface::on_configure(const rclcpp_lifecycle::State &)
|
CallbackReturn IIWAHardwareInterface::on_configure(const rclcpp_lifecycle::State &)
|
||||||
{
|
{
|
||||||
@@ -97,143 +169,148 @@ CallbackReturn IIWAHardwareInterface::on_configure(const rclcpp_lifecycle::State
|
|||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
fri_client_ = std::make_unique<FRIClient>(joint_position_tau_);
|
releaseFRI();
|
||||||
// 100 мс таймаут recvfrom — поток корректно завершится после disconnect().
|
fri_client_ = std::make_unique<FRIClient>();
|
||||||
connection_ = std::make_unique<KUKA::FRI::UdpConnection>(100);
|
fri_client_->setLimits(lower_, upper_, max_velocity_);
|
||||||
|
connection_ = std::make_unique<StartupConnection>(fri_running_);
|
||||||
app_ = std::make_unique<KUKA::FRI::ClientApplication>(*connection_, *fri_client_);
|
app_ = std::make_unique<KUKA::FRI::ClientApplication>(*connection_, *fri_client_);
|
||||||
|
|
||||||
if (!app_->connect(fri_port_, robot_ip_.c_str())) {
|
|
||||||
RCLCPP_FATAL(rclcpp::get_logger(LOG),
|
|
||||||
"Не удалось открыть UDP-сокет на порту %d (робот: %s)", fri_port_, robot_ip_.c_str());
|
|
||||||
return CallbackReturn::ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG),
|
|
||||||
"UDP-порт %d открыт. Запустите ServerFriRos2 на роботе (%s)...",
|
|
||||||
fri_port_, robot_ip_.c_str());
|
|
||||||
|
|
||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── on_activate ────────────────────────────────────────────────────────────────
|
CallbackReturn IIWAHardwareInterface::on_activate(const rclcpp_lifecycle::State & state)
|
||||||
// Получает хэндлы интерфейсов, запускает FRI-поток и ждёт установки сессии.
|
|
||||||
|
|
||||||
CallbackReturn IIWAHardwareInterface::on_activate(const rclcpp_lifecycle::State &)
|
|
||||||
{
|
{
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "Активация...");
|
stopFRI();
|
||||||
|
velocity_initialized_ = false;
|
||||||
|
last_snapshot_ = {};
|
||||||
|
try {
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
const std::string & jn = info_.joints[i].name;
|
const auto & name = info_.joints[i].name;
|
||||||
|
h_pos_[i] = get_state_interface_handle(name + "/position");
|
||||||
h_pos_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_POSITION);
|
h_vel_[i] = get_state_interface_handle(name + "/velocity");
|
||||||
h_vel_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_VELOCITY);
|
h_eff_[i] = get_state_interface_handle(name + "/effort");
|
||||||
h_eff_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_EFFORT);
|
h_ext_[i] = get_state_interface_handle(name + "/external_torque");
|
||||||
h_ext_[i] = get_state_interface_handle(jn + "/external_torque");
|
h_cmd_pos_[i] = get_command_interface_handle(name + "/position");
|
||||||
h_cmd_pos_[i] = get_command_interface_handle(jn + "/" + hardware_interface::HW_IF_POSITION);
|
}
|
||||||
|
if (!simulate_) {
|
||||||
if (!h_pos_[i] || !h_vel_[i] || !h_eff_[i] || !h_ext_[i] || !h_cmd_pos_[i])
|
// Recreate SDK sequence counters and buffers for every new session.
|
||||||
|
if (on_configure(state) != CallbackReturn::SUCCESS ||
|
||||||
|
!app_->connect(fri_port_, robot_ip_.c_str()))
|
||||||
{
|
{
|
||||||
RCLCPP_FATAL(rclcpp::get_logger(LOG),
|
releaseFRI();
|
||||||
"Не удалось получить хэндл интерфейса для сустава '%s'. "
|
return CallbackReturn::ERROR;
|
||||||
"Проверьте объявление <state_interface>/<command_interface> в URDF.",
|
}
|
||||||
jn.c_str());
|
fri_fault_.store(false);
|
||||||
|
fri_running_.store(true);
|
||||||
|
fri_thread_ = std::thread(&IIWAHardwareInterface::friThreadFunc, this);
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(15);
|
||||||
|
while (fri_running_.load() && std::chrono::steady_clock::now() < deadline) {
|
||||||
|
last_snapshot_ = fri_client_->getStateSnapshot();
|
||||||
|
if (last_snapshot_.valid && last_snapshot_.quality >= KUKA::FRI::GOOD &&
|
||||||
|
last_snapshot_.session >= KUKA::FRI::MONITORING_READY) {break;}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
if (fri_fault_.load() || !last_snapshot_.valid ||
|
||||||
|
last_snapshot_.quality < KUKA::FRI::GOOD ||
|
||||||
|
last_snapshot_.session < KUKA::FRI::MONITORING_READY ||
|
||||||
|
std::chrono::steady_clock::now() - last_snapshot_.received_at > std::chrono::milliseconds(100))
|
||||||
|
{
|
||||||
|
stopFRI();
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "FRI activation failed: no fresh ready session");
|
||||||
return CallbackReturn::ERROR;
|
return CallbackReturn::ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
|
double position = 0.0;
|
||||||
if (simulate_) {
|
if (simulate_) {
|
||||||
return CallbackReturn::SUCCESS;
|
get_state(h_pos_[i], position, true);
|
||||||
}
|
if (!std::isfinite(position)) {position = 0.0;}
|
||||||
|
|
||||||
fri_running_.store(true, std::memory_order_relaxed);
|
|
||||||
fri_thread_ = std::thread(&IIWAHardwareInterface::friThreadFunc, this);
|
|
||||||
|
|
||||||
constexpr int kTimeoutMs = 15000;
|
|
||||||
constexpr int kPollMs = 200;
|
|
||||||
for (int elapsed = 0;
|
|
||||||
fri_client_->getSessionState() == KUKA::FRI::IDLE && elapsed < kTimeoutMs;
|
|
||||||
elapsed += kPollMs)
|
|
||||||
{
|
|
||||||
RCLCPP_INFO_THROTTLE(rclcpp::get_logger(LOG), throttle_clock_, 2000,
|
|
||||||
"Ожидание FRI-сессии... (%d мс)", elapsed);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(kPollMs));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fri_client_->getSessionState() == KUKA::FRI::IDLE) {
|
|
||||||
RCLCPP_ERROR(rclcpp::get_logger(LOG),
|
|
||||||
"FRI не подключился за %d с. Проверьте ServerFriRos2 на %s",
|
|
||||||
kTimeoutMs / 1000, robot_ip_.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI сессия установлена!");
|
position = last_snapshot_.measured_pos[i];
|
||||||
const auto snap = fri_client_->getStateSnapshot();
|
|
||||||
prev_pos_ = snap.measured_pos;
|
|
||||||
last_ts_sec_ = snap.time_stamp_sec;
|
|
||||||
last_ts_nsec_ = snap.time_stamp_nano_sec;
|
|
||||||
velocity_.fill(0.0);
|
|
||||||
velocity_initialized_ = true;
|
|
||||||
}
|
}
|
||||||
|
set_state(h_pos_[i], position, true);
|
||||||
previous_session_state_ = fri_client_->getSessionState();
|
set_state(h_vel_[i], 0.0, true);
|
||||||
|
set_state(h_eff_[i], simulate_ ? 0.0 : last_snapshot_.measured_tau[i], true);
|
||||||
|
set_state(h_ext_[i], simulate_ ? 0.0 : last_snapshot_.external_tau[i], true);
|
||||||
|
set_command(h_cmd_pos_[i], position, true);
|
||||||
|
}
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
stopFRI();
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "Activation failed: %s", e.what());
|
||||||
|
return CallbackReturn::ERROR;
|
||||||
|
}
|
||||||
|
active_ = true;
|
||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── on_deactivate ──────────────────────────────────────────────────────────────
|
void IIWAHardwareInterface::stopFRI()
|
||||||
// Останавливает FRI-поток. FRI-объекты остаются — их очищает on_cleanup().
|
{
|
||||||
|
active_ = false;
|
||||||
|
fri_running_.store(false);
|
||||||
|
// Receive has a finite timeout. Never close a socket while step() uses it.
|
||||||
|
if (fri_thread_.joinable()) {fri_thread_.join();}
|
||||||
|
if (app_) {app_->disconnect();}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IIWAHardwareInterface::releaseFRI()
|
||||||
|
{
|
||||||
|
stopFRI();
|
||||||
|
// ClientApplication::~ClientApplication calls the connection by reference.
|
||||||
|
app_.reset();
|
||||||
|
connection_.reset();
|
||||||
|
fri_client_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
IIWAHardwareInterface::~IIWAHardwareInterface() {releaseFRI();}
|
||||||
|
|
||||||
CallbackReturn IIWAHardwareInterface::on_deactivate(const rclcpp_lifecycle::State &)
|
CallbackReturn IIWAHardwareInterface::on_deactivate(const rclcpp_lifecycle::State &)
|
||||||
{
|
{
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "Деактивация...");
|
stopFRI();
|
||||||
|
|
||||||
if (!simulate_ && fri_running_.load()) {
|
|
||||||
fri_running_.store(false, std::memory_order_relaxed);
|
|
||||||
// Сначала закрываем сокет — это разблокирует recvfrom() в FRI-потоке.
|
|
||||||
// Только потом join(), иначе он зависнет навсегда.
|
|
||||||
if (app_) { app_->disconnect(); }
|
|
||||||
if (fri_thread_.joinable()) { fri_thread_.join(); }
|
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI поток остановлен");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
|
||||||
h_pos_[i] = h_vel_[i] = h_eff_[i] = h_ext_[i] = nullptr;
|
|
||||||
h_cmd_pos_[i] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
velocity_initialized_ = false;
|
velocity_initialized_ = false;
|
||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── on_cleanup ─────────────────────────────────────────────────────────────────
|
|
||||||
// Освобождает FRI-объекты. Вызывается после on_deactivate().
|
|
||||||
|
|
||||||
CallbackReturn IIWAHardwareInterface::on_cleanup(const rclcpp_lifecycle::State &)
|
CallbackReturn IIWAHardwareInterface::on_cleanup(const rclcpp_lifecycle::State &)
|
||||||
{
|
{
|
||||||
fri_client_.reset();
|
releaseFRI();
|
||||||
connection_.reset();
|
|
||||||
app_.reset();
|
|
||||||
return CallbackReturn::SUCCESS;
|
return CallbackReturn::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── friThreadFunc ──────────────────────────────────────────────────────────────
|
CallbackReturn IIWAHardwareInterface::on_shutdown(const rclcpp_lifecycle::State & state)
|
||||||
|
{
|
||||||
|
return on_cleanup(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
CallbackReturn IIWAHardwareInterface::on_error(const rclcpp_lifecycle::State & state)
|
||||||
|
{
|
||||||
|
return on_cleanup(state);
|
||||||
|
}
|
||||||
|
|
||||||
void IIWAHardwareInterface::friThreadFunc()
|
void IIWAHardwareInterface::friThreadFunc()
|
||||||
{
|
{
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI поток запущен");
|
try {
|
||||||
|
while (fri_running_.load()) {
|
||||||
while (fri_running_.load(std::memory_order_relaxed)) {
|
|
||||||
if (!app_->step()) {
|
if (!app_->step()) {
|
||||||
RCLCPP_WARN_THROTTLE(rclcpp::get_logger(LOG), throttle_clock_, 2000,
|
throw std::runtime_error("FRI step failed");
|
||||||
"FRI: step() вернул false, возможно потеряли соединение");
|
}
|
||||||
|
if (fri_client_->getSessionState() == KUKA::FRI::IDLE) {
|
||||||
|
throw std::runtime_error("FRI session ended");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const KUKA::FRI::FRIException & e) {
|
||||||
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI поток завершён");
|
fri_fault_.store(true);
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "FRI SDK: %s", e.getErrorMessage());
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
fri_fault_.store(true);
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "FRI: %s", e.what());
|
||||||
|
} catch (...) {
|
||||||
|
fri_fault_.store(true);
|
||||||
|
RCLCPP_ERROR(rclcpp::get_logger(LOG), "Unknown FRI failure");
|
||||||
|
}
|
||||||
|
fri_running_.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── compute_velocity_ ──────────────────────────────────────────────────────────
|
// ── compute_velocity_ ──────────────────────────────────────────────────────────
|
||||||
// Конечные разности + EMA-фильтр.
|
// Velocity is the finite difference of measured positions at robot timestamps.
|
||||||
// int64-вычитание timestamp'ов предотвращает потерю точности при больших Unix-значениях.
|
|
||||||
// EMA-фильтр (joint_velocity_tau) убирает одиночные выбросы, которые видит JTC как
|
|
||||||
// скачки состояния и компенсирует агрессивными командами → хруст двигателей.
|
|
||||||
|
|
||||||
void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
|
void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
|
||||||
{
|
{
|
||||||
@@ -242,7 +319,6 @@ void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
|
|||||||
last_ts_sec_ = snap.time_stamp_sec;
|
last_ts_sec_ = snap.time_stamp_sec;
|
||||||
last_ts_nsec_ = snap.time_stamp_nano_sec;
|
last_ts_nsec_ = snap.time_stamp_nano_sec;
|
||||||
velocity_.fill(0.0);
|
velocity_.fill(0.0);
|
||||||
velocity_raw_.fill(0.0);
|
|
||||||
velocity_initialized_ = true;
|
velocity_initialized_ = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -258,23 +334,10 @@ void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
|
|||||||
(static_cast<double>(snap.time_stamp_nano_sec) -
|
(static_cast<double>(snap.time_stamp_nano_sec) -
|
||||||
static_cast<double>(last_ts_nsec_)) * 1e-9;
|
static_cast<double>(last_ts_nsec_)) * 1e-9;
|
||||||
|
|
||||||
static constexpr std::array<double, N_JOINTS> kMaxVel =
|
|
||||||
{1.71, 1.71, 1.75, 2.27, 2.44, 3.14, 3.14};
|
|
||||||
static constexpr double kVelDeadband = 1e-4;
|
|
||||||
|
|
||||||
if (dt > 0.0) {
|
if (dt > 0.0) {
|
||||||
// EMA alpha для фильтра скорости: tau=0 → alpha=1 (без фильтра)
|
|
||||||
const double vel_alpha = (joint_velocity_tau_ > 0.0)
|
|
||||||
? dt / (joint_velocity_tau_ + dt)
|
|
||||||
: 1.0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
const double raw = (snap.measured_pos[i] - prev_pos_[i]) / dt;
|
velocity_[i] = (snap.measured_pos[i] - prev_pos_[i]) / dt;
|
||||||
const double clamped = std::clamp(raw, -kMaxVel[i], kMaxVel[i]);
|
|
||||||
velocity_raw_[i] = (std::abs(clamped) < kVelDeadband) ? 0.0 : clamped;
|
|
||||||
|
|
||||||
// EMA: velocity_[i] = alpha * raw + (1 - alpha) * prev_filtered
|
|
||||||
velocity_[i] = vel_alpha * velocity_raw_[i] + (1.0 - vel_alpha) * velocity_[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,10 +351,13 @@ void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
|
|||||||
hardware_interface::return_type IIWAHardwareInterface::read(
|
hardware_interface::return_type IIWAHardwareInterface::read(
|
||||||
const rclcpp::Time &, const rclcpp::Duration &)
|
const rclcpp::Time &, const rclcpp::Duration &)
|
||||||
{
|
{
|
||||||
|
if (!active_) {return hardware_interface::return_type::OK;}
|
||||||
if (simulate_) {
|
if (simulate_) {
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
double pos = 0.0;
|
double pos = 0.0;
|
||||||
get_command(h_cmd_pos_[i], pos, false);
|
if (!get_command(h_cmd_pos_[i], pos, false) || !std::isfinite(pos)) {
|
||||||
|
return hardware_interface::return_type::ERROR;
|
||||||
|
}
|
||||||
set_state(h_pos_[i], pos, false);
|
set_state(h_pos_[i], pos, false);
|
||||||
set_state(h_vel_[i], 0.0, false);
|
set_state(h_vel_[i], 0.0, false);
|
||||||
set_state(h_eff_[i], 0.0, false);
|
set_state(h_eff_[i], 0.0, false);
|
||||||
@@ -300,19 +366,17 @@ hardware_interface::return_type IIWAHardwareInterface::read(
|
|||||||
return hardware_interface::return_type::OK;
|
return hardware_interface::return_type::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto snap = fri_client_->getStateSnapshot();
|
if (!fri_client_ || fri_fault_.load() || !fri_running_.load()) {
|
||||||
|
return hardware_interface::return_type::ERROR;
|
||||||
// Обнаружение потери управления: неожиданный выход из COMMANDING_ACTIVE.
|
}
|
||||||
const auto current_state = fri_client_->getSessionState();
|
fri_client_->tryGetStateSnapshot(last_snapshot_);
|
||||||
if (previous_session_state_ == KUKA::FRI::COMMANDING_ACTIVE &&
|
const auto & snap = last_snapshot_;
|
||||||
current_state != KUKA::FRI::COMMANDING_ACTIVE)
|
if (!snap.valid || std::chrono::steady_clock::now() - snap.received_at >
|
||||||
|
std::chrono::milliseconds(100))
|
||||||
{
|
{
|
||||||
RCLCPP_ERROR(rclcpp::get_logger(LOG),
|
fri_running_.store(false);
|
||||||
"Робот вышел из COMMANDING_ACTIVE! Деактивируйте и повторно активируйте контроллер.");
|
|
||||||
return hardware_interface::return_type::ERROR;
|
return hardware_interface::return_type::ERROR;
|
||||||
}
|
}
|
||||||
previous_session_state_ = current_state;
|
|
||||||
|
|
||||||
compute_velocity_(snap);
|
compute_velocity_(snap);
|
||||||
|
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
@@ -330,21 +394,23 @@ hardware_interface::return_type IIWAHardwareInterface::read(
|
|||||||
hardware_interface::return_type IIWAHardwareInterface::write(
|
hardware_interface::return_type IIWAHardwareInterface::write(
|
||||||
const rclcpp::Time &, const rclcpp::Duration &)
|
const rclcpp::Time &, const rclcpp::Duration &)
|
||||||
{
|
{
|
||||||
if (simulate_) {
|
if (!active_ || simulate_) {return hardware_interface::return_type::OK;}
|
||||||
return hardware_interface::return_type::OK;
|
if (!fri_client_ || fri_fault_.load() || !fri_running_.load()) {
|
||||||
|
return hardware_interface::return_type::ERROR;
|
||||||
}
|
}
|
||||||
|
if (!fri_client_->isCommandingActive()) {return hardware_interface::return_type::OK;}
|
||||||
if (fri_client_->getSessionState() != KUKA::FRI::COMMANDING_ACTIVE) {
|
|
||||||
return hardware_interface::return_type::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<double, N_JOINTS> pos_cmd{};
|
std::array<double, N_JOINTS> pos_cmd{};
|
||||||
for (size_t i = 0; i < N_JOINTS; ++i) {
|
for (size_t i = 0; i < N_JOINTS; ++i) {
|
||||||
get_command(h_cmd_pos_[i], pos_cmd[i], false);
|
if (!get_command(h_cmd_pos_[i], pos_cmd[i], false)) {
|
||||||
|
return hardware_interface::return_type::OK; // retry next cycle; watchdog stays armed
|
||||||
|
}
|
||||||
|
if (!std::isfinite(pos_cmd[i]) || pos_cmd[i] < lower_[i] || pos_cmd[i] > upper_[i]) {
|
||||||
|
fri_fault_.store(true);
|
||||||
|
fri_running_.store(false);
|
||||||
|
return hardware_interface::return_type::ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fri_client_->setTargetJointPositions(pos_cmd);
|
fri_client_->setTargetJointPositions(pos_cmd);
|
||||||
|
|
||||||
return hardware_interface::return_type::OK;
|
return hardware_interface::return_type::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"""Manual local JTC integration check; never opens a FRI connection."""
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
import xacro
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parents[3]
|
||||||
|
work = Path(tempfile.mkdtemp(prefix='iiwa-controller-smoke-'))
|
||||||
|
env = dict(os.environ, ROS_DOMAIN_ID='83', ROS_LOCALHOST_ONLY='1', ROS_LOG_DIR=str(work / 'logs'), ROS_HOME=str(work / 'ros'))
|
||||||
|
env['CYCLONEDDS_URI'] = '<CycloneDDS><Domain><General><Interfaces><NetworkInterface name="lo"/></Interfaces><AllowMulticast>false</AllowMulticast></General><Discovery><Peers><Peer Address="127.0.0.1"/></Peers></Discovery></Domain></CycloneDDS>'
|
||||||
|
xml = xacro.process_file(str(root / 'src/iiwa_description/urdf/iiwa7.urdf.xacro')).toxml()
|
||||||
|
assert '<param name="simulate">false</param>' in xml
|
||||||
|
# Exercise our plugin's explicit local simulation path, with no FRI socket.
|
||||||
|
xml = xml.replace('<param name="simulate">false</param>', '<param name="simulate">true</param>')
|
||||||
|
params = work / 'robot.yaml'
|
||||||
|
params.write_text(yaml.safe_dump({'/**': {'ros__parameters': {'robot_description': xml}}}))
|
||||||
|
processes = []
|
||||||
|
handles = []
|
||||||
|
try:
|
||||||
|
for package, executable, extra in [
|
||||||
|
('robot_state_publisher', 'robot_state_publisher', []),
|
||||||
|
('controller_manager', 'ros2_control_node', ['--params-file', str(root / 'src/iiwa_config/config/moveit/iiwa_controller.yaml')]),
|
||||||
|
]:
|
||||||
|
log = (work / (executable + '.log')).open('w')
|
||||||
|
handles.append(log)
|
||||||
|
processes.append(subprocess.Popen(['ros2', 'run', package, executable, '--ros-args', '--params-file', str(params)] + extra,
|
||||||
|
env=env, stdout=log, stderr=subprocess.STDOUT, start_new_session=True))
|
||||||
|
for name in ['joint_state_broadcaster', 'iiwa_arm_controller']:
|
||||||
|
result = subprocess.run(['ros2', 'run', 'controller_manager', 'spawner', name, '--controller-manager-timeout', '15'],
|
||||||
|
env=env, text=True, capture_output=True, timeout=25)
|
||||||
|
print(result.stdout, result.stderr, flush=True)
|
||||||
|
assert result.returncode == 0, name
|
||||||
|
result = subprocess.run(['ros2', 'control', 'list_controllers'], env=env, text=True, capture_output=True, timeout=10)
|
||||||
|
print(result.stdout, result.stderr, flush=True)
|
||||||
|
assert result.returncode == 0 and 'iiwa_arm_controller' in result.stdout and 'active' in result.stdout
|
||||||
|
initial = yaml.safe_load((root / 'src/iiwa_config/config/moveit/initial_positions.yaml').read_text())['initial_positions']
|
||||||
|
positions = [float(initial[f'joint{i}']) for i in range(1, 8)]
|
||||||
|
positions[0] += 0.02
|
||||||
|
goal = {'trajectory': {'joint_names': [f'joint{i}' for i in range(1, 8)],
|
||||||
|
'points': [{'positions': positions, 'time_from_start': {'sec': 2, 'nanosec': 0}}]}}
|
||||||
|
result = subprocess.run(['ros2', 'action', 'send_goal', '/iiwa_arm_controller/follow_joint_trajectory',
|
||||||
|
'control_msgs/action/FollowJointTrajectory', yaml.safe_dump(goal)],
|
||||||
|
env=env, text=True, capture_output=True, timeout=15)
|
||||||
|
print(result.stdout, result.stderr, flush=True)
|
||||||
|
assert result.returncode == 0 and 'SUCCEEDED' in result.stdout
|
||||||
|
print('SMOKE PASS: actual URDF, iiwa hardware plugin, broadcaster and JTC active; no FRI connection.', flush=True)
|
||||||
|
finally:
|
||||||
|
for process in processes:
|
||||||
|
os.killpg(process.pid, signal.SIGINT)
|
||||||
|
for process in processes:
|
||||||
|
try: process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired: os.killpg(process.pid, signal.SIGKILL)
|
||||||
|
for handle in handles: handle.close()
|
||||||
|
for log in work.glob('*.log'):
|
||||||
|
print('\n' + log.name + '\n' + log.read_text()[-4500:])
|
||||||
|
print('Logs:', work)
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
|
#include "iiwa_controller/FRIClient.h"
|
||||||
|
#include "friClientData.h"
|
||||||
|
#include "pb_frimessages_callbacks.h"
|
||||||
|
|
||||||
|
using namespace KUKA::FRI;
|
||||||
|
|
||||||
|
class TestApplication : public ClientApplication
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ClientApplication::ClientApplication;
|
||||||
|
ClientData * data() {return _data;}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClientFixture : public iiwa_controller::FRIClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClientFixture() : FRIClient(), app(connection, *this), data(app.data())
|
||||||
|
{
|
||||||
|
auto & m = data->monitoringMsg;
|
||||||
|
m.connectionInfo.sendPeriod = 10;
|
||||||
|
m.connectionInfo.quality = FRIConnectionQuality_GOOD;
|
||||||
|
m.robotInfo.safetyState = SafetyState_NORMAL_OPERATION;
|
||||||
|
m.robotInfo.operationMode = OperationMode_AUTOMATIC_MODE;
|
||||||
|
m.robotInfo.controlMode = ControlMode_POSITION_CONTROLMODE;
|
||||||
|
auto * drives = static_cast<tRepeatedIntArguments *>(m.robotInfo.driveState.arg);
|
||||||
|
std::fill_n(drives->value, 7, DriveState_ACTIVE);
|
||||||
|
m.ipoData.clientCommandMode = ClientCommandMode_POSITION;
|
||||||
|
m.ipoData.overlayType = OverlayType_JOINT;
|
||||||
|
m.ipoData.has_trackingPerformance = false;
|
||||||
|
m.monitorData.timestamp.sec = 1;
|
||||||
|
m.monitorData.timestamp.nanosec = 0;
|
||||||
|
fill(m.monitorData.measuredJointPosition, 0.2);
|
||||||
|
fill(m.monitorData.commandedJointPosition, 0.3);
|
||||||
|
fill(m.monitorData.measuredTorque, 1.0);
|
||||||
|
fill(m.monitorData.externalTorque, 0.1);
|
||||||
|
fill(m.ipoData.jointPosition, 0.4);
|
||||||
|
}
|
||||||
|
static void fill(JointValues & values, double value)
|
||||||
|
{
|
||||||
|
auto * args = static_cast<tRepeatedDoubleArguments *>(values.value.arg);
|
||||||
|
std::fill_n(args->value, 7, value);
|
||||||
|
args->size = 7;
|
||||||
|
}
|
||||||
|
void cycle(ESessionState state)
|
||||||
|
{
|
||||||
|
data->resetCommandMessage();
|
||||||
|
data->monitoringMsg.ipoData.has_jointPosition = state >= COMMANDING_WAIT;
|
||||||
|
data->monitoringMsg.connectionInfo.sessionState = static_cast<FRISessionState>(state);
|
||||||
|
onStateChange(data->lastState, state);
|
||||||
|
data->lastState = state;
|
||||||
|
data->monitoringMsg.monitorData.timestamp.nanosec += 10000000;
|
||||||
|
if (state == COMMANDING_WAIT) waitForCommand();
|
||||||
|
else if (state == COMMANDING_ACTIVE) command();
|
||||||
|
else monitor();
|
||||||
|
}
|
||||||
|
double sentPosition()
|
||||||
|
{
|
||||||
|
return static_cast<tRepeatedDoubleArguments *>(
|
||||||
|
data->commandMsg.commandData.jointPosition.value.arg)->value[0];
|
||||||
|
}
|
||||||
|
UdpConnection connection;
|
||||||
|
TestApplication app;
|
||||||
|
ClientData * data;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(FRIClient, ReportsMeasuredPositionInCommanding)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
EXPECT_DOUBLE_EQ(client.sentPosition(), 0.4);
|
||||||
|
EXPECT_DOUBLE_EQ(client.getStateSnapshot().measured_pos[0], 0.2);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
EXPECT_DOUBLE_EQ(client.getStateSnapshot().measured_pos[0], 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, PreservesSdkMonitoringReplyWithoutIpo)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(MONITORING_READY);
|
||||||
|
EXPECT_TRUE(client.data->commandMsg.commandData.has_jointPosition);
|
||||||
|
EXPECT_DOUBLE_EQ(client.sentPosition(), 0.3);
|
||||||
|
EXPECT_FALSE(client.getStateSnapshot().ipo_valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, RejectsUnsupportedCommandModeBeforeSending)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.data->monitoringMsg.ipoData.clientCommandMode = ClientCommandMode_TORQUE;
|
||||||
|
EXPECT_THROW(client.cycle(COMMANDING_WAIT), std::runtime_error);
|
||||||
|
EXPECT_FALSE(client.data->commandMsg.commandData.has_jointPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, PositionCommandsHaveNoEmaDelay)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
iiwa_controller::FRIClient::Joints q;
|
||||||
|
q.fill(0.41);
|
||||||
|
client.setTargetJointPositions(q);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
EXPECT_DOUBLE_EQ(client.sentPosition(), 0.41);
|
||||||
|
EXPECT_DOUBLE_EQ(client.getStateSnapshot().measured_pos[0], 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, RejectsInvalidCommandsAndBoundsCommandRate)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
iiwa_controller::FRIClient::Joints lower, upper, velocity, q;
|
||||||
|
lower.fill(-1.0); upper.fill(1.0); velocity.fill(1.0);
|
||||||
|
client.setLimits(lower, upper, velocity);
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
q.fill(0.8);
|
||||||
|
client.setTargetJointPositions(q);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
EXPECT_NEAR(client.sentPosition(), 0.41, 1e-12); // 1 rad/s * 10 ms
|
||||||
|
q[0] = 1.1;
|
||||||
|
EXPECT_THROW(client.setTargetJointPositions(q), std::invalid_argument);
|
||||||
|
q[0] = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
EXPECT_THROW(client.setTargetJointPositions(q), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, SafetyStopCannotSendApplicationCommand)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
client.data->monitoringMsg.robotInfo.safetyState = SafetyState_SAFETY_STOP_LEVEL_1;
|
||||||
|
EXPECT_THROW(client.cycle(COMMANDING_ACTIVE), std::runtime_error);
|
||||||
|
EXPECT_FALSE(client.data->commandMsg.commandData.has_jointPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, DuplicateTimestampIsNotFreshTelemetry)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(MONITORING_READY);
|
||||||
|
EXPECT_THROW(client.monitor(), std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, LeavingCommandingRequiresExplicitRecovery)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
EXPECT_THROW(client.cycle(MONITORING_READY), std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, PositionOverlaySupportsSunriseImpedanceModes)
|
||||||
|
{
|
||||||
|
for (auto mode : {ControlMode_JOINT_IMPEDANCE_CONTROLMODE,
|
||||||
|
ControlMode_CARTESIAN_IMPEDANCE_CONTROLMODE})
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.data->monitoringMsg.robotInfo.controlMode = mode;
|
||||||
|
EXPECT_NO_THROW(client.cycle(COMMANDING_WAIT));
|
||||||
|
EXPECT_DOUBLE_EQ(client.sentPosition(), 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FRIClient, StalledRosCommandStreamStopsBeforeSending)
|
||||||
|
{
|
||||||
|
ClientFixture client;
|
||||||
|
client.cycle(COMMANDING_WAIT);
|
||||||
|
client.cycle(COMMANDING_ACTIVE);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(110));
|
||||||
|
EXPECT_THROW(client.cycle(COMMANDING_ACTIVE), std::runtime_error);
|
||||||
|
EXPECT_FALSE(client.data->commandMsg.commandData.has_jointPosition);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "iiwa_controller/IIWAHardwareInterface.hpp"
|
||||||
|
|
||||||
|
using Hardware = iiwa_controller::IIWAHardwareInterface;
|
||||||
|
using Return = hardware_interface::CallbackReturn;
|
||||||
|
|
||||||
|
hardware_interface::HardwareComponentInterfaceParams parameters()
|
||||||
|
{
|
||||||
|
hardware_interface::HardwareComponentInterfaceParams params;
|
||||||
|
auto & info = params.hardware_info;
|
||||||
|
info.name = "iiwa_test";
|
||||||
|
info.type = "system";
|
||||||
|
info.hardware_parameters["simulate"] = "true";
|
||||||
|
for (int i = 1; i <= 7; ++i) {
|
||||||
|
hardware_interface::ComponentInfo joint;
|
||||||
|
joint.name = "joint" + std::to_string(i);
|
||||||
|
for (const auto * name : {"position", "velocity", "effort"}) {
|
||||||
|
hardware_interface::InterfaceInfo interface;
|
||||||
|
interface.name = name;
|
||||||
|
interface.data_type = "double";
|
||||||
|
interface.initial_value = "0.2";
|
||||||
|
joint.state_interfaces.push_back(interface);
|
||||||
|
}
|
||||||
|
hardware_interface::InterfaceInfo command;
|
||||||
|
command.name = "position";
|
||||||
|
command.data_type = "double";
|
||||||
|
joint.command_interfaces.push_back(command);
|
||||||
|
joint_limits::JointLimits limits;
|
||||||
|
limits.has_position_limits = true;
|
||||||
|
limits.min_position = -2.0;
|
||||||
|
limits.max_position = 2.0;
|
||||||
|
limits.has_velocity_limits = true;
|
||||||
|
limits.max_velocity = 1.0;
|
||||||
|
info.limits[joint.name] = limits;
|
||||||
|
info.joints.push_back(joint);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hardware, RejectsMalformedPortWithoutThrowing)
|
||||||
|
{
|
||||||
|
Hardware hardware;
|
||||||
|
auto params = parameters();
|
||||||
|
params.hardware_info.hardware_parameters["fri_port"] = "30200oops";
|
||||||
|
EXPECT_EQ(hardware.on_init(params), Return::ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hardware, RejectsWrongJointOrderAndMissingInterfaces)
|
||||||
|
{
|
||||||
|
auto params = parameters();
|
||||||
|
std::swap(params.hardware_info.joints[0], params.hardware_info.joints[1]);
|
||||||
|
Hardware wrong_order;
|
||||||
|
EXPECT_EQ(wrong_order.on_init(params), Return::ERROR);
|
||||||
|
params = parameters();
|
||||||
|
params.hardware_info.joints[0].state_interfaces.pop_back();
|
||||||
|
Hardware missing_interface;
|
||||||
|
EXPECT_EQ(missing_interface.on_init(params), Return::ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hardware, SimulationSeedsCommandsAndSupportsRepeatedLifecycle)
|
||||||
|
{
|
||||||
|
Hardware hardware;
|
||||||
|
ASSERT_EQ(hardware.on_init(parameters()), Return::SUCCESS);
|
||||||
|
auto states = hardware.on_export_state_interfaces();
|
||||||
|
auto commands = hardware.on_export_command_interfaces();
|
||||||
|
const rclcpp_lifecycle::State state;
|
||||||
|
ASSERT_EQ(hardware.on_configure(state), Return::SUCCESS);
|
||||||
|
for (int cycle = 0; cycle < 2; ++cycle) {
|
||||||
|
ASSERT_EQ(hardware.on_activate(state), Return::SUCCESS);
|
||||||
|
const double command = hardware.get_command<double>("joint1/position");
|
||||||
|
EXPECT_DOUBLE_EQ(command, 0.2);
|
||||||
|
EXPECT_EQ(hardware.read(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
|
||||||
|
hardware_interface::return_type::OK);
|
||||||
|
EXPECT_EQ(hardware.on_deactivate(state), Return::SUCCESS);
|
||||||
|
EXPECT_EQ(hardware.read(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
|
||||||
|
hardware_interface::return_type::OK);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(hardware.on_cleanup(state), Return::SUCCESS);
|
||||||
|
EXPECT_EQ(hardware.on_shutdown(state), Return::SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hardware, ConfiguredRealHardwareCleansUpInDependencyOrder)
|
||||||
|
{
|
||||||
|
Hardware hardware;
|
||||||
|
auto params = parameters();
|
||||||
|
params.hardware_info.hardware_parameters["simulate"] = "false";
|
||||||
|
params.hardware_info.hardware_parameters["robot_ip"] = "127.0.0.1";
|
||||||
|
ASSERT_EQ(hardware.on_init(params), Return::SUCCESS);
|
||||||
|
const rclcpp_lifecycle::State state;
|
||||||
|
ASSERT_EQ(hardware.on_configure(state), Return::SUCCESS);
|
||||||
|
EXPECT_EQ(hardware.on_cleanup(state), Return::SUCCESS);
|
||||||
|
EXPECT_EQ(hardware.on_error(state), Return::SUCCESS);
|
||||||
|
}
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
<xacro:arg name="simulate" default="false"/>
|
<xacro:arg name="simulate" default="false"/>
|
||||||
<xacro:arg name="robot_ip" default="192.170.10.2"/>
|
<xacro:arg name="robot_ip" default="192.170.10.2"/>
|
||||||
<xacro:arg name="fri_port" default="30200"/>
|
<xacro:arg name="fri_port" default="30200"/>
|
||||||
<xacro:arg name="joint_position_tau" default="0.04"/>
|
|
||||||
<xacro:arg name="joint_velocity_tau" default="0.01"/>
|
|
||||||
|
|
||||||
<xacro:property name="initial_positions"
|
<xacro:property name="initial_positions"
|
||||||
value="${xacro.load_yaml('$(arg initial_positions_file)')['initial_positions']}"/>
|
value="${xacro.load_yaml('$(arg initial_positions_file)')['initial_positions']}"/>
|
||||||
@@ -146,8 +144,6 @@
|
|||||||
<param name="robot_ip">$(arg robot_ip)</param>
|
<param name="robot_ip">$(arg robot_ip)</param>
|
||||||
<param name="fri_port">$(arg fri_port)</param>
|
<param name="fri_port">$(arg fri_port)</param>
|
||||||
<param name="simulate">false</param>
|
<param name="simulate">false</param>
|
||||||
<param name="joint_position_tau">$(arg joint_position_tau)</param>
|
|
||||||
<param name="joint_velocity_tau">$(arg joint_velocity_tau)</param>
|
|
||||||
</hardware>
|
</hardware>
|
||||||
|
|
||||||
<joint name="joint1">
|
<joint name="joint1">
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class RobotCfg:
|
|||||||
port: int
|
port: int
|
||||||
description: str
|
description: str
|
||||||
fri_cycle_ms: int
|
fri_cycle_ms: int
|
||||||
joint_position_tau: float
|
|
||||||
active_controller: str # "jtc" | "forward"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -290,8 +288,6 @@ def build_settings(settings_path: str, check_files: bool = True) -> Settings:
|
|||||||
port=int(require(robot_raw, "port")),
|
port=int(require(robot_raw, "port")),
|
||||||
description=resolve_path(str(require(robot_raw, "description")), settings_dir),
|
description=resolve_path(str(require(robot_raw, "description")), settings_dir),
|
||||||
fri_cycle_ms=int(robot_raw.get("fri_cycle_ms", 5)),
|
fri_cycle_ms=int(robot_raw.get("fri_cycle_ms", 5)),
|
||||||
joint_position_tau=float(robot_raw.get("joint_position_tau", 0.04)),
|
|
||||||
active_controller=str(robot_raw.get("active_controller", "jtc")),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# digital_twin
|
# digital_twin
|
||||||
|
|||||||
Reference in New Issue
Block a user