* Author: Bence Magyar, Enrique Fernández, Manuel Meraz
*/
#include <memory>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include "omni_wheel_controller/omni_wheel_controller.hpp"
#include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "rclcpp/logging.hpp"
#include "tf2/LinearMath/Quaternion.h"
namespace
{
constexpr auto DEFAULT_COMMAND_TOPIC = "~/cmd_vel";
constexpr auto DEFAULT_COMMAND_UNSTAMPED_TOPIC = "~/cmd_vel_unstamped";
constexpr auto DEFAULT_COMMAND_OUT_TOPIC = "~/cmd_vel_out";
constexpr auto DEFAULT_ODOMETRY_TOPIC = "~/odom";
constexpr auto DEFAULT_TRANSFORM_TOPIC = "/tf";
}
namespace omni_wheel_controller
{
using namespace std::chrono_literals;
using controller_interface::interface_configuration_type;
using controller_interface::InterfaceConfiguration;
using hardware_interface::HW_IF_POSITION;
using hardware_interface::HW_IF_VELOCITY;
using lifecycle_msgs::msg::State;
OmniWheelController::OmniWheelController() : controller_interface::ControllerInterface() {}
const char * OmniWheelController::feedback_type() const
{
return HW_IF_POSITION;
}
controller_interface::CallbackReturn OmniWheelController::on_init()
{
try
{
param_listener_ = std::make_shared<ParamListener>(get_node());
params_ = param_listener_->get_params();
}
catch (const std::exception & e)
{
fprintf(stderr, "Exception thrown during init stage with message: %s \n", e.what());
return controller_interface::CallbackReturn::ERROR;
}
return controller_interface::CallbackReturn::SUCCESS;
}
InterfaceConfiguration OmniWheelController::command_interface_configuration() const
{
std::vector<std::string> conf_names;
for (const auto & joint_name : params_.omni_wheel_names)
{
conf_names.push_back(joint_name + "/" + HW_IF_VELOCITY);
}
return {interface_configuration_type::INDIVIDUAL, conf_names};
}
InterfaceConfiguration OmniWheelController::state_interface_configuration() const
{
std::vector<std::string> conf_names;
for (const auto & joint_name : params_.omni_wheel_names)
{
conf_names.push_back(joint_name + "/" + feedback_type());
}
return {interface_configuration_type::INDIVIDUAL, conf_names};
}
controller_interface::return_type OmniWheelController::update(
const rclcpp::Time & time, const rclcpp::Duration & period)
{
auto logger = get_node()->get_logger();
if (get_state().id() == State::PRIMARY_STATE_INACTIVE)
{
if (!is_halted)
{
halt();
is_halted = true;
}
return controller_interface::return_type::OK;
}
std::shared_ptr<Twist> last_command_msg;
received_velocity_msg_ptr_.get(last_command_msg);
if (last_command_msg == nullptr)
{
RCLCPP_WARN(logger, "Velocity message received was a nullptr.");
return controller_interface::return_type::ERROR;
}
const auto age_of_last_command = time - last_command_msg->header.stamp;
if (age_of_last_command > cmd_vel_timeout_)
{
last_command_msg->twist.linear.x = 0.0;
last_command_msg->twist.linear.y = 0.0;
last_command_msg->twist.angular.z = 0.0;
}
Twist command = *last_command_msg;
double & linear_x_command = command.twist.linear.x;
double & linear_y_command = command.twist.linear.y;
double & angular_command = command.twist.angular.z;
previous_update_timestamp_ = time;
if (params_.open_loop)
{
odometry_.updateOpenLoop(linear_x_command, linear_y_command, angular_command, time);
}
else
{
std::vector<double> wheel_feedback;
for (size_t index = 0; index < static_cast<size_t>(params_.omni_wheel_names.size()); ++index)
{
wheel_feedback.push_back(registered_omni_wheel_handles_[index].feedback.get().get_value());
if (std::isnan(wheel_feedback[index]))
{
RCLCPP_ERROR(
logger, "omni wheel %s is invalid for index [%zu]", feedback_type(),
index);
return controller_interface::return_type::ERROR;
}
}
odometry_.update(wheel_feedback, time);
}
tf2::Quaternion orientation;
orientation.setRPY(0.0, 0.0, odometry_.getHeading());
bool should_publish = false;
try
{
if (previous_publish_timestamp_ + publish_period_ < time)
{
previous_publish_timestamp_ += publish_period_;
should_publish = true;
}
}
catch (const std::runtime_error &)
{
previous_publish_timestamp_ = time;
should_publish = true;
}
if (should_publish)
{
if (realtime_odometry_publisher_->trylock())
{
auto & odometry_message = realtime_odometry_publisher_->msg_;
odometry_message.header.stamp = time;
odometry_message.pose.pose.position.x = odometry_.getX();
odometry_message.pose.pose.position.y = odometry_.getY();
odometry_message.pose.pose.orientation.x = orientation.x();
odometry_message.pose.pose.orientation.y = orientation.y();
odometry_message.pose.pose.orientation.z = orientation.z();
odometry_message.pose.pose.orientation.w = orientation.w();
odometry_message.twist.twist.linear.x = odometry_.getLinearX();
odometry_message.twist.twist.linear.y = odometry_.getLinearY();
odometry_message.twist.twist.angular.z = odometry_.getAngular();
realtime_odometry_publisher_->unlockAndPublish();
}
if (params_.enable_odom_tf && realtime_odometry_transform_publisher_->trylock())
{
auto & transform = realtime_odometry_transform_publisher_->msg_.transforms.front();
transform.header.stamp = time;
transform.transform.translation.x = odometry_.getX();
transform.transform.translation.y = odometry_.getY();
transform.transform.rotation.x = orientation.x();
transform.transform.rotation.y = orientation.y();
transform.transform.rotation.z = orientation.z();
transform.transform.rotation.w = orientation.w();
realtime_odometry_transform_publisher_->unlockAndPublish();
}
}
auto & last_command = previous_commands_.back().twist;
auto & second_to_last_command = previous_commands_.front().twist;
limiter_linear_x_.limit(
linear_x_command, last_command.linear.x, second_to_last_command.linear.x, period.seconds());
limiter_linear_y_.limit(
linear_y_command, last_command.linear.y, second_to_last_command.linear.y, period.seconds());
limiter_angular_.limit(
angular_command, last_command.angular.z, second_to_last_command.angular.z, period.seconds());
previous_commands_.pop();
previous_commands_.emplace(command);
if (publish_limited_velocity_ && realtime_limited_velocity_publisher_->trylock())
{
auto & limited_velocity_command = realtime_limited_velocity_publisher_->msg_;
limited_velocity_command.header.stamp = time;
limited_velocity_command.twist = command.twist;
realtime_limited_velocity_publisher_->unlockAndPublish();
}
Eigen::VectorXd robot_movement_vector(3);
robot_movement_vector(0) = command.twist.linear.x;
robot_movement_vector(1) = command.twist.linear.y;
robot_movement_vector(2) = command.twist.angular.z;
Eigen::VectorXd wheel_movement_vector = odometry_.getMotionMatrix() * robot_movement_vector;
for (size_t index = 0; index < params_.omni_wheel_names.size(); ++index)
{
registered_omni_wheel_handles_[index].velocity.get().set_value(wheel_movement_vector(index));
}
return controller_interface::return_type::OK;
}
controller_interface::CallbackReturn OmniWheelController::on_configure(
const rclcpp_lifecycle::State &)
{
auto logger = get_node()->get_logger();
if (param_listener_->is_old(params_))
{
params_ = param_listener_->get_params();
RCLCPP_INFO(logger, "Parameters were updated");
}
if (params_.omni_wheel_names.empty())
{
RCLCPP_ERROR(logger, "Wheel names parameters are empty!");
return controller_interface::CallbackReturn::ERROR;
}
odometry_.setWheelParams(params_.omni_wheel_distance, params_.wheel_radius, params_.omni_wheel_angle);
odometry_.setVelocityRollingWindowSize(params_.velocity_rolling_window_size);
cmd_vel_timeout_ = std::chrono::milliseconds{static_cast<int>(params_.cmd_vel_timeout * 1000.0)};
publish_limited_velocity_ = params_.publish_limited_velocity;
use_stamped_vel_ = params_.use_stamped_vel;
limiter_linear_x_ = SpeedLimiter(
params_.linear.x.has_velocity_limits, params_.linear.x.has_acceleration_limits,
params_.linear.x.has_jerk_limits, params_.linear.x.min_velocity, params_.linear.x.max_velocity,
params_.linear.x.min_acceleration, params_.linear.x.max_acceleration, params_.linear.x.min_jerk,
params_.linear.x.max_jerk);
limiter_linear_y_ = SpeedLimiter(
params_.linear.x.has_velocity_limits, params_.linear.x.has_acceleration_limits,
params_.linear.x.has_jerk_limits, params_.linear.x.min_velocity, params_.linear.x.max_velocity,
params_.linear.x.min_acceleration, params_.linear.x.max_acceleration, params_.linear.x.min_jerk,
params_.linear.x.max_jerk);
limiter_angular_ = SpeedLimiter(
params_.angular.z.has_velocity_limits, params_.angular.z.has_acceleration_limits,
params_.angular.z.has_jerk_limits, params_.angular.z.min_velocity,
params_.angular.z.max_velocity, params_.angular.z.min_acceleration,
params_.angular.z.max_acceleration, params_.angular.z.min_jerk, params_.angular.z.max_jerk);
if (!reset())
{
return controller_interface::CallbackReturn::ERROR;
}
if (publish_limited_velocity_)
{
limited_velocity_publisher_ =
get_node()->create_publisher<Twist>(DEFAULT_COMMAND_OUT_TOPIC, rclcpp::SystemDefaultsQoS());
realtime_limited_velocity_publisher_ =
std::make_shared<realtime_tools::RealtimePublisher<Twist>>(limited_velocity_publisher_);
}
const Twist empty_twist;
received_velocity_msg_ptr_.set(std::make_shared<Twist>(empty_twist));
previous_commands_.emplace(empty_twist);
previous_commands_.emplace(empty_twist);
if (use_stamped_vel_)
{
velocity_command_subscriber_ = get_node()->create_subscription<Twist>(
DEFAULT_COMMAND_TOPIC, rclcpp::SystemDefaultsQoS(),
[this](const std::shared_ptr<Twist> msg) -> void
{
if (!subscriber_is_active_)
{
RCLCPP_WARN(
get_node()->get_logger(), "Can't accept new commands. subscriber is inactive");
return;
}
if ((msg->header.stamp.sec == 0) && (msg->header.stamp.nanosec == 0))
{
RCLCPP_WARN_ONCE(
get_node()->get_logger(),
"Received TwistStamped with zero timestamp, setting it to current "
"time, this message will only be shown once");
msg->header.stamp = get_node()->get_clock()->now();
}
received_velocity_msg_ptr_.set(std::move(msg));
});
}
else
{
velocity_command_unstamped_subscriber_ =
get_node()->create_subscription<geometry_msgs::msg::Twist>(
DEFAULT_COMMAND_UNSTAMPED_TOPIC, rclcpp::SystemDefaultsQoS(),
[this](const std::shared_ptr<geometry_msgs::msg::Twist> msg) -> void
{
if (!subscriber_is_active_)
{
RCLCPP_WARN(
get_node()->get_logger(), "Can't accept new commands. subscriber is inactive");
return;
}
std::shared_ptr<Twist> twist_stamped;
received_velocity_msg_ptr_.get(twist_stamped);
twist_stamped->twist = *msg;
twist_stamped->header.stamp = get_node()->get_clock()->now();
});
}
odometry_publisher_ = get_node()->create_publisher<nav_msgs::msg::Odometry>(
DEFAULT_ODOMETRY_TOPIC, rclcpp::SystemDefaultsQoS());
realtime_odometry_publisher_ =
std::make_shared<realtime_tools::RealtimePublisher<nav_msgs::msg::Odometry>>(
odometry_publisher_);
std::string tf_prefix = "";
if (params_.tf_frame_prefix_enable)
{
if (params_.tf_frame_prefix != "")
{
tf_prefix = params_.tf_frame_prefix;
}
else
{
tf_prefix = std::string(get_node()->get_namespace());
}
if (tf_prefix == "/")
{
tf_prefix = "";
}
else
{
tf_prefix = tf_prefix + "/";
}
}
const auto odom_frame_id = tf_prefix + params_.odom_frame_id;
const auto base_frame_id = tf_prefix + params_.base_frame_id;
auto & odometry_message = realtime_odometry_publisher_->msg_;
odometry_message.header.frame_id = odom_frame_id;
odometry_message.child_frame_id = base_frame_id;
publish_rate_ = params_.publish_rate;
publish_period_ = rclcpp::Duration::from_seconds(1.0 / publish_rate_);
odometry_message.twist =
geometry_msgs::msg::TwistWithCovariance(rosidl_runtime_cpp::MessageInitialization::ALL);
constexpr size_t NUM_DIMENSIONS = 6;
for (size_t index = 0; index < 6; ++index)
{
const size_t diagonal_index = NUM_DIMENSIONS * index + index;
odometry_message.pose.covariance[diagonal_index] = params_.pose_covariance_diagonal[index];
odometry_message.twist.covariance[diagonal_index] = params_.twist_covariance_diagonal[index];
}
odometry_transform_publisher_ = get_node()->create_publisher<tf2_msgs::msg::TFMessage>(
DEFAULT_TRANSFORM_TOPIC, rclcpp::SystemDefaultsQoS());
realtime_odometry_transform_publisher_ =
std::make_shared<realtime_tools::RealtimePublisher<tf2_msgs::msg::TFMessage>>(
odometry_transform_publisher_);
auto & odometry_transform_message = realtime_odometry_transform_publisher_->msg_;
odometry_transform_message.transforms.resize(1);
odometry_transform_message.transforms.front().header.frame_id = odom_frame_id;
odometry_transform_message.transforms.front().child_frame_id = base_frame_id;
previous_update_timestamp_ = get_node()->get_clock()->now();
return controller_interface::CallbackReturn::SUCCESS;
}
controller_interface::CallbackReturn OmniWheelController::on_activate(
const rclcpp_lifecycle::State &)
{
const auto wheel_config_result =
configure_handles(params_.omni_wheel_names, registered_omni_wheel_handles_);
if (
wheel_config_result == controller_interface::CallbackReturn::ERROR
)
{
return controller_interface::CallbackReturn::ERROR;
}
if (registered_omni_wheel_handles_.empty())
{
RCLCPP_ERROR(
get_node()->get_logger(),
"Omni wheel interfaces are non existent");
return controller_interface::CallbackReturn::ERROR;
}
is_halted = false;
subscriber_is_active_ = true;
RCLCPP_DEBUG(get_node()->get_logger(), "Subscriber and publisher are now active.");
return controller_interface::CallbackReturn::SUCCESS;
}
controller_interface::CallbackReturn OmniWheelController::on_deactivate(
const rclcpp_lifecycle::State &)
{
subscriber_is_active_ = false;
if (!is_halted)
{
halt();
is_halted = true;
}
registered_omni_wheel_handles_.clear();
return controller_interface::CallbackReturn::SUCCESS;
}
controller_interface::CallbackReturn OmniWheelController::on_cleanup(
const rclcpp_lifecycle::State &)
{
if (!reset())
{
return controller_interface::CallbackReturn::ERROR;
}
received_velocity_msg_ptr_.set(std::make_shared<Twist>());
return controller_interface::CallbackReturn::SUCCESS;
}
controller_interface::CallbackReturn OmniWheelController::on_error(const rclcpp_lifecycle::State &)
{
if (!reset())
{
return controller_interface::CallbackReturn::ERROR;
}
return controller_interface::CallbackReturn::SUCCESS;
}
bool OmniWheelController::reset()
{
odometry_.resetOdometry();
std::queue<Twist> empty;
std::swap(previous_commands_, empty);
registered_omni_wheel_handles_.clear();
subscriber_is_active_ = false;
velocity_command_subscriber_.reset();
velocity_command_unstamped_subscriber_.reset();
received_velocity_msg_ptr_.set(nullptr);
is_halted = false;
return true;
}
controller_interface::CallbackReturn OmniWheelController::on_shutdown(
const rclcpp_lifecycle::State &)
{
return controller_interface::CallbackReturn::SUCCESS;
}
void OmniWheelController::halt()
{
for (const auto & wheel_handle : registered_omni_wheel_handles_)
{
wheel_handle.velocity.get().set_value(0.0);
}
}
controller_interface::CallbackReturn OmniWheelController::configure_handles(
const std::vector<std::string> & wheel_names,
std::vector<WheelHandle> & registered_handles)
{
auto logger = get_node()->get_logger();
if (wheel_names.empty())
{
RCLCPP_ERROR(logger, "No omni wheel names specified");
return controller_interface::CallbackReturn::ERROR;
}
registered_handles.reserve(wheel_names.size());
for (const auto & wheel_name : wheel_names)
{
const auto interface_name = feedback_type();
const auto state_handle = std::find_if(
state_interfaces_.cbegin(), state_interfaces_.cend(),
[&wheel_name, &interface_name](const auto & interface)
{
return interface.get_prefix_name() == wheel_name &&
interface.get_interface_name() == interface_name;
});
if (state_handle == state_interfaces_.cend())
{
RCLCPP_ERROR(logger, "Unable to obtain joint state handle for %s", wheel_name.c_str());
return controller_interface::CallbackReturn::ERROR;
}
const auto command_handle = std::find_if(
command_interfaces_.begin(), command_interfaces_.end(),
[&wheel_name](const auto & interface)
{
return interface.get_prefix_name() == wheel_name &&
interface.get_interface_name() == HW_IF_VELOCITY;
});
if (command_handle == command_interfaces_.end())
{
RCLCPP_ERROR(logger, "Unable to obtain joint command handle for %s", wheel_name.c_str());
return controller_interface::CallbackReturn::ERROR;
}
registered_handles.emplace_back(
WheelHandle{std::ref(*state_handle), std::ref(*command_handle)});
}
return controller_interface::CallbackReturn::SUCCESS;
}
}
#include "class_loader/register_macro.hpp"
CLASS_LOADER_REGISTER_CLASS(
omni_wheel_controller::OmniWheelController, controller_interface::ControllerInterface)