Refactor iiwa_controller_v2: Remove obsolete files and update URDF parameters

- Deleted `system_interface_type_values.hpp`, `package.xml`, `fri_client.cpp`, and `system_interface.cpp` as part of the cleanup process.
- Updated `iiwa7.urdf.xacro` to remove deprecated parameters and adjust command interface settings.
- Modified joint definitions in `joints.xacro` to include new friction and soft limit parameters.
- Enhanced `macros.xacro` to support additional joint properties for friction and safety control.
- Adjusted mass properties in `params.xacro` to reflect accurate values for the robot's components.
This commit is contained in:
Даниил Грабарь
2026-05-18 11:10:11 +10:00
parent efaec9b442
commit ace4d02b8a
18 changed files with 240 additions and 1295 deletions
@@ -2,6 +2,7 @@
#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <string>
#include <thread>
@@ -34,13 +35,16 @@ public:
CallbackReturn on_init(
const hardware_interface::HardwareComponentInterfaceParams & params) override;
// external_torque не объявлен в URDF, поэтому регистрируем его здесь как unlisted.
// Стандартные интерфейсы (position, velocity, effort) базовый класс берёт из URDF сам.
// external_torque не объявлен в URDF регистрируем вручную как unlisted.
std::vector<hardware_interface::InterfaceDescription>
export_unlisted_state_interface_descriptions() override;
// Полный lifecycle: configure открывает сокет, activate запускает поток,
// deactivate останавливает поток, cleanup освобождает FRI-объекты.
CallbackReturn on_configure(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_cleanup(const rclcpp_lifecycle::State & previous_state) override;
hardware_interface::return_type read(
const rclcpp::Time & time, const rclcpp::Duration & period) override;
@@ -56,17 +60,15 @@ private:
int fri_port_{30200};
bool simulate_{false};
std::string cmd_mode_str_{"position"};
double joint_position_tau_{0.04}; // постоянная времени фильтра позиций [с]
double joint_position_tau_{0.04};
// Объекты FRI SDK
std::unique_ptr<FRIClient> fri_client_;
std::unique_ptr<KUKA::FRI::UdpConnection> connection_;
std::unique_ptr<KUKA::FRI::ClientApplication> app_;
// FRI работает в отдельном потоке: step() блокируется в recvfrom() и не жрёт CPU.
// FRI работает в отдельном потоке: step() блокируется в recvfrom().
// read() лишь читает готовый снимок — без блокировки RT-потока.
// Соотношение update_rate:FRI_rate = 2:1 → JTC работает вдвое быстрее FRI,
// как при fri_cycle_ms=10. Это естественно «усредняет» команды и убирает дребезг.
std::thread fri_thread_;
std::atomic<bool> fri_running_{false};
void friThreadFunc();
@@ -81,13 +83,17 @@ private:
std::array<hardware_interface::CommandInterface::SharedPtr, N_JOINTS> h_cmd_pos_;
std::array<hardware_interface::CommandInterface::SharedPtr, N_JOINTS> h_cmd_eff_;
// Предыдущие позиции и скорость (обновляются только при свежем FRI-пакете)
// Вычисление скорости конечными разностями
std::array<double, N_JOINTS> prev_pos_{};
std::array<double, N_JOINTS> vel_filtered_{};
std::array<double, N_JOINTS> velocity_{};
unsigned int last_ts_sec_{0};
unsigned int last_ts_nsec_{0};
bool velocity_initialized_{false};
void compute_velocity_(const IIWAStateSnapshot & snap);
// Отслеживание сессии FRI для обнаружения потери управления
KUKA::FRI::ESessionState previous_session_state_{KUKA::FRI::IDLE};
// Отдельный объект часов для RCLCPP_*_THROTTLE — не создаём временный в FRI-потоке
rclcpp::Clock throttle_clock_{RCL_STEADY_TIME};
CommandMode parseCommandMode(const std::string & mode_str) const;
+172 -114
View File
@@ -2,6 +2,7 @@
#include <chrono>
#include <cmath>
#include <cstdint>
#include <thread>
#include "hardware_interface/hardware_info.hpp"
@@ -20,6 +21,8 @@ namespace iiwa_controller
using CallbackReturn =
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
static const char * LOG = "IIWAHardwareInterface";
static std::string getParam(
const hardware_interface::HardwareInfo & info,
const std::string & name,
@@ -29,6 +32,8 @@ static std::string getParam(
return (it != info.hardware_parameters.end()) ? it->second : default_val;
}
// ── on_init ────────────────────────────────────────────────────────────────────
CallbackReturn IIWAHardwareInterface::on_init(
const hardware_interface::HardwareComponentInterfaceParams & params)
{
@@ -38,14 +43,13 @@ CallbackReturn IIWAHardwareInterface::on_init(
const auto & info = params.hardware_info;
robot_ip_ = getParam(info, "robot_ip", "192.170.10.2");
fri_port_ = std::stoi(getParam(info, "fri_port", "30200"));
simulate_ = (getParam(info, "simulate", "false") == "true");
cmd_mode_str_ = getParam(info, "command_mode", "position");
robot_ip_ = getParam(info, "robot_ip", "192.170.10.2");
fri_port_ = std::stoi(getParam(info, "fri_port", "30200"));
simulate_ = (getParam(info, "simulate", "false") == "true");
cmd_mode_str_ = getParam(info, "command_mode", "position");
joint_position_tau_ = std::stod(getParam(info, "joint_position_tau", "0.04"));
RCLCPP_INFO(
rclcpp::get_logger("IIWAHardwareInterface"),
RCLCPP_INFO(rclcpp::get_logger(LOG),
"on_init: ip=%s port=%d simulate=%s mode=%s tau=%.3f",
robot_ip_.c_str(), fri_port_,
simulate_ ? "true" : "false",
@@ -53,18 +57,18 @@ CallbackReturn IIWAHardwareInterface::on_init(
joint_position_tau_);
if (info.joints.size() != N_JOINTS) {
RCLCPP_FATAL(
rclcpp::get_logger("IIWAHardwareInterface"),
RCLCPP_FATAL(rclcpp::get_logger(LOG),
"URDF содержит %zu суставов, ожидается %zu", info.joints.size(), N_JOINTS);
return CallbackReturn::ERROR;
}
prev_pos_.fill(0.0);
velocity_.fill(0.0);
return CallbackReturn::SUCCESS;
}
// external_torque не объявлен в URDF, поэтому добавляем его вручную как unlisted.
// Стандартные интерфейсы (position, velocity, effort) базовый класс берёт из URDF сам.
// ── export_unlisted_state_interface_descriptions ────────────────────────────────
std::vector<hardware_interface::InterfaceDescription>
IIWAHardwareInterface::export_unlisted_state_interface_descriptions()
{
@@ -73,8 +77,8 @@ IIWAHardwareInterface::export_unlisted_state_interface_descriptions()
for (size_t i = 0; i < N_JOINTS; ++i) {
hardware_interface::InterfaceInfo if_info;
if_info.name = "external_torque";
if_info.data_type = "double";
if_info.name = "external_torque";
if_info.data_type = "double";
if_info.initial_value = "0.0";
descs.emplace_back(info_.joints[i].name, if_info);
}
@@ -82,29 +86,55 @@ IIWAHardwareInterface::export_unlisted_state_interface_descriptions()
return descs;
}
CommandMode IIWAHardwareInterface::parseCommandMode(const std::string & mode_str) const
// ── on_configure ───────────────────────────────────────────────────────────────
// Открывает UDP-сокет и создаёт FRI-объекты. Не запускает поток.
CallbackReturn IIWAHardwareInterface::on_configure(const rclcpp_lifecycle::State &)
{
return (mode_str == "torque") ? CommandMode::TORQUE : CommandMode::POSITION;
if (simulate_) {
RCLCPP_WARN(rclcpp::get_logger(LOG), "РЕЖИМ СИМУЛЯЦИИ: FRI не используется");
return CallbackReturn::SUCCESS;
}
fri_client_ = std::make_unique<FRIClient>(parseCommandMode(cmd_mode_str_), joint_position_tau_);
// 100 мс таймаут recvfrom — поток корректно завершится после disconnect().
connection_ = std::make_unique<KUKA::FRI::UdpConnection>(100);
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;
}
// ── on_activate ────────────────────────────────────────────────────────────────
// Получает хэндлы интерфейсов, запускает FRI-поток и ждёт установки сессии.
CallbackReturn IIWAHardwareInterface::on_activate(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "Активация...");
RCLCPP_INFO(rclcpp::get_logger(LOG), "Активация...");
for (size_t i = 0; i < N_JOINTS; ++i) {
const std::string & jn = info_.joints[i].name;
h_pos_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_POSITION);
h_vel_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_VELOCITY);
h_eff_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_EFFORT);
h_ext_[i] = get_state_interface_handle(jn + "/external_torque");
h_pos_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_POSITION);
h_vel_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_VELOCITY);
h_eff_[i] = get_state_interface_handle(jn + "/" + hardware_interface::HW_IF_EFFORT);
h_ext_[i] = get_state_interface_handle(jn + "/external_torque");
h_cmd_pos_[i] = get_command_interface_handle(jn + "/" + hardware_interface::HW_IF_POSITION);
h_cmd_eff_[i] = get_command_interface_handle(jn + "/" + hardware_interface::HW_IF_EFFORT);
if (!h_pos_[i] || !h_vel_[i] || !h_eff_[i] || !h_ext_[i] || !h_cmd_pos_[i] || !h_cmd_eff_[i]) {
RCLCPP_FATAL(
rclcpp::get_logger("IIWAHardwareInterface"),
if (!h_pos_[i] || !h_vel_[i] || !h_eff_[i] || !h_ext_[i] ||
!h_cmd_pos_[i] || !h_cmd_eff_[i])
{
RCLCPP_FATAL(rclcpp::get_logger(LOG),
"Не удалось получить хэндл интерфейса для сустава '%s'. "
"Проверьте объявление <state_interface>/<command_interface> в URDF.",
jn.c_str());
@@ -113,93 +143,55 @@ CallbackReturn IIWAHardwareInterface::on_activate(const rclcpp_lifecycle::State
}
if (simulate_) {
RCLCPP_WARN(rclcpp::get_logger("IIWAHardwareInterface"), "РЕЖИМ СИМУЛЯЦИИ: FRI не используется");
return CallbackReturn::SUCCESS;
}
fri_client_ = std::make_unique<FRIClient>(parseCommandMode(cmd_mode_str_), joint_position_tau_);
// 100 мс таймаут: если закрытие сокета не разблокирует recvfrom() мгновенно,
// поток всё равно выйдет через одну итерацию.
connection_ = std::make_unique<KUKA::FRI::UdpConnection>(100);
app_ = std::make_unique<KUKA::FRI::ClientApplication>(*connection_, *fri_client_);
if (!app_->connect(fri_port_, robot_ip_.c_str())) {
RCLCPP_FATAL(
rclcpp::get_logger("IIWAHardwareInterface"),
"Не удалось подключиться к %s:%d", robot_ip_.c_str(), fri_port_);
return CallbackReturn::ERROR;
}
RCLCPP_INFO(
rclcpp::get_logger("IIWAHardwareInterface"),
"UDP-порт %d открыт. Запустите ServerFriRos2 на роботе (%s)...",
fri_port_, robot_ip_.c_str());
fri_running_.store(true, std::memory_order_relaxed);
fri_thread_ = std::thread(&IIWAHardwareInterface::friThreadFunc, this);
// Ждём пока FRI-сессия установится, максимум 15 секунд.
constexpr int kTimeoutMs = 15000;
constexpr int kPollMs = 100;
int elapsed = 0;
while (fri_client_->getSessionState() == KUKA::FRI::IDLE && elapsed < kTimeoutMs) {
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));
elapsed += kPollMs;
}
if (fri_client_->getSessionState() == KUKA::FRI::IDLE) {
RCLCPP_ERROR(
rclcpp::get_logger("IIWAHardwareInterface"),
RCLCPP_ERROR(rclcpp::get_logger(LOG),
"FRI не подключился за %d с. Проверьте ServerFriRos2 на %s",
kTimeoutMs / 1000, robot_ip_.c_str());
} else {
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "FRI сессия установлена!");
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI сессия установлена!");
const auto snap = fri_client_->getStateSnapshot();
prev_pos_ = snap.measured_pos;
vel_filtered_.fill(0.0);
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;
}
previous_session_state_ = fri_client_->getSessionState();
return CallbackReturn::SUCCESS;
}
// Отдельный поток для FRI: step() блокируется в recvfrom() пока не придёт UDP-пакет,
// потом вызывает нужный callback и отправляет ответ роботу.
// read() лишь читает готовый снимок — без блокировки RT-потока и без нарушения периода.
void IIWAHardwareInterface::friThreadFunc()
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "FRI поток запущен");
while (fri_running_.load(std::memory_order_relaxed)) {
if (!app_->step()) {
RCLCPP_WARN_THROTTLE(
rclcpp::get_logger("IIWAHardwareInterface"),
throttle_clock_, 2000,
"FRI: step() вернул false, возможно потеряли соединение");
}
}
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "FRI поток завершён");
}
// ── on_deactivate ──────────────────────────────────────────────────────────────
// Останавливает FRI-поток. FRI-объекты остаются — их очищает on_cleanup().
CallbackReturn IIWAHardwareInterface::on_deactivate(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "Деактивация...");
RCLCPP_INFO(rclcpp::get_logger(LOG), "Деактивация...");
if (!simulate_) {
if (!simulate_ && fri_running_.load()) {
fri_running_.store(false, std::memory_order_relaxed);
// Сначала закрываем сокет, это разблокирует recvfrom() в FRI-потоке.
// Только после этого ждём завершения потока. Если сделать наоборот,
// join() зависнет навсегда потому что поток заблокирован в recvfrom().
if (app_) {
app_->disconnect();
}
if (fri_thread_.joinable()) {
fri_thread_.join();
}
RCLCPP_INFO(rclcpp::get_logger("IIWAHardwareInterface"), "FRI отключён");
// Сначала закрываем сокет это разблокирует 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) {
@@ -207,12 +199,81 @@ CallbackReturn IIWAHardwareInterface::on_deactivate(const rclcpp_lifecycle::Stat
h_cmd_pos_[i] = h_cmd_eff_[i] = nullptr;
}
velocity_initialized_ = false;
return CallbackReturn::SUCCESS;
}
// read() не блокируется — берёт последний снимок от FRI-потока через мьютекс.
// Период JTC остаётся стабильным: при update_rate=400 и fri_cycle_ms=5 соотношение 2:1,
// идентичное рабочей конфигурации fri_cycle_ms=10 + update_rate=200.
// ── on_cleanup ─────────────────────────────────────────────────────────────────
// Освобождает FRI-объекты. Вызывается после on_deactivate().
CallbackReturn IIWAHardwareInterface::on_cleanup(const rclcpp_lifecycle::State &)
{
fri_client_.reset();
connection_.reset();
app_.reset();
return CallbackReturn::SUCCESS;
}
// ── friThreadFunc ──────────────────────────────────────────────────────────────
void IIWAHardwareInterface::friThreadFunc()
{
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI поток запущен");
while (fri_running_.load(std::memory_order_relaxed)) {
if (!app_->step()) {
RCLCPP_WARN_THROTTLE(rclcpp::get_logger(LOG), throttle_clock_, 2000,
"FRI: step() вернул false, возможно потеряли соединение");
}
}
RCLCPP_INFO(rclcpp::get_logger(LOG), "FRI поток завершён");
}
// ── compute_velocity_ ──────────────────────────────────────────────────────────
// Конечные разности с int64-вычитанием для точности при больших Unix-timestamp'ах.
void IIWAHardwareInterface::compute_velocity_(const IIWAStateSnapshot & snap)
{
if (!velocity_initialized_) {
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;
return;
}
if (snap.time_stamp_sec == last_ts_sec_ && snap.time_stamp_nano_sec == last_ts_nsec_) {
return; // нового FRI-пакета ещё нет
}
const double dt =
static_cast<double>(
static_cast<int64_t>(snap.time_stamp_sec) -
static_cast<int64_t>(last_ts_sec_)) +
(static_cast<double>(snap.time_stamp_nano_sec) -
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) {
for (size_t i = 0; i < N_JOINTS; ++i) {
const double raw = (snap.measured_pos[i] - prev_pos_[i]) / dt;
const double clamped = std::clamp(raw, -kMaxVel[i], kMaxVel[i]);
velocity_[i] = (std::abs(clamped) < kVelDeadband) ? 0.0 : clamped;
}
}
prev_pos_ = snap.measured_pos;
last_ts_sec_ = snap.time_stamp_sec;
last_ts_nsec_ = snap.time_stamp_nano_sec;
}
// ── read ───────────────────────────────────────────────────────────────────────
hardware_interface::return_type IIWAHardwareInterface::read(
const rclcpp::Time &, const rclcpp::Duration &)
{
@@ -230,38 +291,22 @@ hardware_interface::return_type IIWAHardwareInterface::read(
const auto snap = fri_client_->getStateSnapshot();
// Обновляем скорость только при свежем FRI-пакете.
// measured_pos в Commanding = filtered_pos_ (open-loop), поэтому скорость — это
// производная сглаженной команды: гладкий сигнал без шума датчика и без лага.
const bool fresh = (snap.time_stamp_sec != last_ts_sec_ ||
snap.time_stamp_nano_sec != last_ts_nsec_);
if (fresh) {
const double dt =
(static_cast<double>(snap.time_stamp_sec) - static_cast<double>(last_ts_sec_)) +
(static_cast<double>(snap.time_stamp_nano_sec) - static_cast<double>(last_ts_nsec_)) * 1e-9;
// iiwa7 physical velocity limits [rad/s], used to clamp impossible spikes
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) {
for (size_t i = 0; i < N_JOINTS; ++i) {
const double raw = (snap.measured_pos[i] - prev_pos_[i]) / dt;
const double clamped = std::clamp(raw, -kMaxVel[i], kMaxVel[i]);
vel_filtered_[i] = (std::abs(clamped) < kVelDeadband) ? 0.0 : clamped;
}
}
for (size_t i = 0; i < N_JOINTS; ++i) {
prev_pos_[i] = snap.measured_pos[i];
}
last_ts_sec_ = snap.time_stamp_sec;
last_ts_nsec_ = snap.time_stamp_nano_sec;
// Обнаружение потери управления: неожиданный выход из COMMANDING_ACTIVE.
const auto current_state = fri_client_->getSessionState();
if (previous_session_state_ == KUKA::FRI::COMMANDING_ACTIVE &&
current_state != KUKA::FRI::COMMANDING_ACTIVE)
{
RCLCPP_ERROR(rclcpp::get_logger(LOG),
"Робот вышел из COMMANDING_ACTIVE! Деактивируйте и повторно активируйте контроллер.");
return hardware_interface::return_type::ERROR;
}
previous_session_state_ = current_state;
compute_velocity_(snap);
for (size_t i = 0; i < N_JOINTS; ++i) {
set_state(h_pos_[i], snap.measured_pos[i], false);
set_state(h_vel_[i], vel_filtered_[i], false);
set_state(h_vel_[i], velocity_[i], false);
set_state(h_eff_[i], snap.measured_tau[i], false);
set_state(h_ext_[i], snap.external_tau[i], false);
}
@@ -269,6 +314,8 @@ hardware_interface::return_type IIWAHardwareInterface::read(
return hardware_interface::return_type::OK;
}
// ── write ──────────────────────────────────────────────────────────────────────
hardware_interface::return_type IIWAHardwareInterface::write(
const rclcpp::Time &, const rclcpp::Duration &)
{
@@ -276,6 +323,10 @@ hardware_interface::return_type IIWAHardwareInterface::write(
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{}, tau_cmd{};
for (size_t i = 0; i < N_JOINTS; ++i) {
get_command(h_cmd_pos_[i], pos_cmd[i], false);
@@ -288,4 +339,11 @@ hardware_interface::return_type IIWAHardwareInterface::write(
return hardware_interface::return_type::OK;
}
// ── parseCommandMode ───────────────────────────────────────────────────────────
CommandMode IIWAHardwareInterface::parseCommandMode(const std::string & mode_str) const
{
return (mode_str == "torque") ? CommandMode::TORQUE : CommandMode::POSITION;
}
} // namespace iiwa_controller