二自由度机械臂软件系统(二)ros2_control控制算法层

资料:
差速ros_control:https://zhuanlan.zhihu.com/p/682574842

1、ros2_control代码

  • 这个控制器是指的控制算法层面的东西,一般来说有两种实现方式,一种是用moveit自己默认指定的,里面会实现插值和控制算法,另一种就是自己实现了,这里是根据差速demo自己修改的。
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <algorithm>

#include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "rclcpp/logging.hpp"

#include "tf2/LinearMath/Quaternion.h"
#include "motor_test_controller.hpp"

namespace
{
constexpr auto DEFAULT_TRANSFORM_TOPIC = "/tf";
constexpr auto DEFAULT_COMMAND_TOPIC = "/test_controller/cmd_vel";
constexpr auto DEFAULT_ODOMETRY_TOPIC = "/odom";
constexpr auto odom_frame_id = "odom";
constexpr auto base_frame_id = "base_link";
}  // namespace

namespace motor_test_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;

MotorTestController::MotorTestController()
: controller_interface::ControllerInterface() {}

controller_interface::CallbackReturn MotorTestController::on_init()
{
  RCLCPP_INFO(get_node()->get_logger(), "Loading controller...");
  try {
    // Create the parameter listener and get the parameters
    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 MotorTestController::command_interface_configuration() const
{
  std::vector<std::string> conf_names;
  for (const auto & joint_name : params_.joint1_names) {
    conf_names.push_back(joint_name + "/" + HW_IF_POSITION);
  }
  for (const auto & joint_name : params_.joint2_names) {
    conf_names.push_back(joint_name + "/" + HW_IF_POSITION);
  }
  return {interface_configuration_type::INDIVIDUAL, conf_names};
}

InterfaceConfiguration MotorTestController::state_interface_configuration() const
{
  std::vector<std::string> conf_names;
  for (const auto & joint_name : params_.joint1_names) {
    conf_names.push_back(joint_name + "/" + HW_IF_POSITION);
  }
  for (const auto & joint_name : params_.joint2_names) {
    conf_names.push_back(joint_name + "/" + HW_IF_POSITION);
  }
  return {interface_configuration_type::INDIVIDUAL, conf_names};
}

controller_interface::CallbackReturn MotorTestController::on_configure(
  const rclcpp_lifecycle::State &)
{
  auto logger = get_node()->get_logger();
  RCLCPP_INFO(logger, "Configuring controller...");

  // update parameters if they have changed
  if (param_listener_->is_old(params_)) {
    params_ = param_listener_->get_params();
    RCLCPP_INFO(logger, "Parameters were updated");
  }

  if (params_.joint1_names.size() != params_.joint2_names.size())
  {
    RCLCPP_ERROR(
      logger, "The number of left wheels [%zu] and the number of right wheels [%zu] are different",
      params_.joint1_names.size(), params_.joint2_names.size());
    return controller_interface::CallbackReturn::ERROR;
  }


  if (params_.joint1_names.empty()) {
    RCLCPP_ERROR(logger, "Left wheel names parameters are empty!");
    return controller_interface::CallbackReturn::ERROR;
  }

  if (params_.joint2_names.empty()) {
    RCLCPP_ERROR(logger, "Right wheel names parameters are empty!");
    return controller_interface::CallbackReturn::ERROR;
  }

  if (!reset()) {
    return controller_interface::CallbackReturn::ERROR;
  }
  const Twist empty_twist;
  received_velocity_msg_ptr_.set(std::make_shared<Twist>(empty_twist));
  // Fill last two commands with default constructed commands
  previous_commands_.emplace(empty_twist);
  previous_commands_.emplace(empty_twist);

  // initialize command subscriber   从topic中订阅数据
  velocity_command_subscriber_ =
    get_node()->create_subscription<sensor_msgs::msg::JointState>(
    DEFAULT_COMMAND_TOPIC, rclcpp::SystemDefaultsQoS(),
    [this](const std::shared_ptr<sensor_msgs::msg::JointState> msg) -> void
    {
      if (!subscriber_is_active_) {
        RCLCPP_WARN(
          get_node()->get_logger(), "Can't accept new commands. subscriber is inactive");
        return;
      }
      received_velocity_msg_ptr_.set(std::move(msg));

    });


  // initialize odometry publisher and messasge,  定义里程计发布者

  RCLCPP_INFO(logger, "Configure over...");

  return controller_interface::CallbackReturn::SUCCESS;
}

controller_interface::CallbackReturn MotorTestController::on_activate(
  const rclcpp_lifecycle::State &)
{
  const auto left_result =
    configure_side(
    "joint1", params_.joint1_names,
    registered_joint1_handles_);
  const auto right_result =
    configure_side(
    "joint2", params_.joint2_names,
    registered_joint2_handles_);

  if (
    left_result == controller_interface::CallbackReturn::ERROR ||
    right_result == controller_interface::CallbackReturn::ERROR)
  {
    return controller_interface::CallbackReturn::ERROR;
  }

  if (params_.joint1_names.empty() ||
    params_.joint2_names.empty())
  {
    RCLCPP_ERROR(
      get_node()->get_logger(),
      "wheel interfaces are non existent");
    return controller_interface::CallbackReturn::ERROR;
  }

  is_halted = false;
  subscriber_is_active_ = true;


  RCLCPP_INFO(get_node()->get_logger(), "Subscriber and publisher are now active.");
  return controller_interface::CallbackReturn::SUCCESS;
}

controller_interface::CallbackReturn MotorTestController::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 MotorTestController::on_error(
  const rclcpp_lifecycle::State &)
{
  if (!reset()) {
    return controller_interface::CallbackReturn::ERROR;
  }
  return controller_interface::CallbackReturn::SUCCESS;
}

controller_interface::CallbackReturn MotorTestController::on_deactivate(
  const rclcpp_lifecycle::State &)
{
  subscriber_is_active_ = false;
  if (!is_halted) {
    halt();
    is_halted = true;
  }
  return controller_interface::CallbackReturn::SUCCESS;
}

controller_interface::CallbackReturn MotorTestController::on_shutdown(
  const rclcpp_lifecycle::State &)
{
  return controller_interface::CallbackReturn::SUCCESS;
}


controller_interface::return_type MotorTestController::update(
  const rclcpp::Time & time, const rclcpp::Duration & period)
{
  auto logger = get_node()->get_logger();
  //if controller unload
  if (get_state().id() == State::PRIMARY_STATE_INACTIVE) {
    if (!is_halted) {
      halt();
      is_halted = true;
    }
    return controller_interface::return_type::OK;
  }

  //update param
  this->params_ = param_listener_->get_params();


  //update cmd
  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;
  }


  // command may be limited
  Twist command = *last_command_msg;
  // Unit m/s
  if(command.position.size() == 0){
    joint1_command = 0;//std::clamp(command.position[0], -10.0,10.0);
    joint2_command = 1;//std::clamp(command.position[1], -10.0,10.0);
  }else{
      joint1_command = std::clamp(command.position[0], -10.0,10.0);
      joint2_command = std::clamp(command.position[1], -10.0,10.0);
  }
        RCLCPP_WARN(logger, "data is %d",command.position.size());
        // std::cout << "aaaa" << std::endl;

    //获取转速反馈,如果每边有多个车轮,计算平均值
    double left_feedback_mean = 0.0;
    double right_feedback_mean = 0.0;
    const double joint1_feedback = registered_joint1_handles_[0].state.get().get_value();
    const double joint2_feedback =registered_joint2_handles_[0].state.get().get_value();

    left_feedback_mean = joint1_feedback;
    right_feedback_mean = joint2_feedback;
  
  // Set wheels velocities:
    registered_joint1_handles_[0].command.get().set_value(joint1_command);
    registered_joint2_handles_[0].command.get().set_value(joint2_command);
  

  return controller_interface::return_type::OK;
}

controller_interface::CallbackReturn MotorTestController::configure_side(
  const std::string & joint_kind,
  const std::vector<std::string> & joint_names,
  std::vector<JointHandle> & registered_handles)
{
  auto logger = get_node()->get_logger();

  if (joint_names.empty()) {
    RCLCPP_ERROR(logger, "No '%s' motor names specified", joint_kind.c_str());
    return controller_interface::CallbackReturn::ERROR;
  }
  // register handles  注册句柄
  registered_handles.reserve(joint_names.size());  //储备大小
  for (const auto & joint_name : joint_names) {
    //查找指定状态句柄
    const auto state_handle = std::find_if(
      state_interfaces_.cbegin(), state_interfaces_.cend(),
      [&joint_name](const auto & interface)
      {
        return interface.get_prefix_name() == joint_name &&
        interface.get_interface_name() == HW_IF_POSITION;
      });
    if (state_handle == state_interfaces_.cend()) {  //如果没有找到
      RCLCPP_ERROR(logger, "Unable to obtain motor state handle for %s", joint_name.c_str());
      return controller_interface::CallbackReturn::ERROR;
    }

    //查找指定命令句柄
    const auto command_handle = std::find_if(
      command_interfaces_.begin(), command_interfaces_.end(),
      [&joint_name](const auto & interface)
      {
        return interface.get_prefix_name() == joint_name &&
        interface.get_interface_name() == HW_IF_POSITION;
      });

    if (command_handle == command_interfaces_.end()) {
      RCLCPP_ERROR(logger, "Unable to obtain motor command handle for %s", joint_name.c_str());
      return controller_interface::CallbackReturn::ERROR;
    }

    //添加到容器中
    registered_handles.emplace_back(
      JointHandle{std::ref(*state_handle), std::ref(
          *command_handle)});
  }

  return controller_interface::CallbackReturn::SUCCESS;
}

bool MotorTestController::reset()
{
  // release the old queue
  std::queue<Twist> empty;
  std::swap(previous_commands_, empty);

  registered_joint1_handles_.clear();
  registered_joint2_handles_.clear();

  subscriber_is_active_ = false;
  velocity_command_subscriber_.reset();
  received_velocity_msg_ptr_.set(nullptr);

  is_halted = false;
  return true;
}

void MotorTestController::halt()
{
  //make wheels stop
  const auto halt_wheels = [](auto & wheel_handles)
    {
      for (const auto & wheel_handle : wheel_handles) {
        wheel_handle.command.get().set_value(0.0);
      }
    };

  halt_wheels(registered_joint1_handles_);
  halt_wheels(registered_joint2_handles_);
}

} // namespace diff_test_controller

#include "class_loader/register_macro.hpp"

CLASS_LOADER_REGISTER_CLASS(
  motor_test_controller::MotorTestController, controller_interface::ControllerInterface)

2、连接action

  • 这个代码写完后,实际上无法实现效果,原因在于moveit规划的结果没有和ros_control这边的代码连接起来,我这里moveit使用的是rviz插件的形式(共有c++、python、rviz插件三种形式),是用一个action和其他部分连接的。
  • 我上面ros2_control的代码是通过订阅和发布topic实现的,所以要有一个程序作为一个转接点,连接action然后操作topic。
  • 当然,这种方式其实是不合适的,多此一举了,像官方库一样直接在ros2_control中实现最好。这里还是把这部分代码给出,是用python写的。
#!/usr/bin/env python

import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from control_msgs.action import FollowJointTrajectory
from std_msgs.msg import Float32MultiArray
from sensor_msgs.msg import JointState 
import time

#这个地方是连接movit和ros_control连接的关键。
# movit的配置文件中指定了一个服务器,也就是这里的action服务器,叫做“follow_joint_trajectory”。movit会向这个服务器一次性放出所有的数据。
# 在拿到数据后,在回调函数中根据时间一次发送到指定的cmd_vel中,控制器的节点会读取这个topic的数据,至此已经进入ros_control的部分了。
# 似乎不使用自定义控制器的情况下,并不需要设置这个过程,movit官方的控制器是如何实现这个效果呢?
class TrajectoryServer(Node):

    def __init__(self):
        super().__init__('trajectory_server')
        self._action_server = ActionServer(
            self,
            FollowJointTrajectory,
            'test_controller/follow_joint_trajectory',
            self.execute_callback)

        self.publisher_ = self.create_publisher(JointState, '/test_controller/cmd_vel', 10)

    def execute_callback(self, goal_handle):
        print("22222")
        self.get_logger().info('Executing goal...')

        # 解析目标关节名称和位置
        goal = goal_handle.request
        joint_names = goal.trajectory.joint_names
        points = goal.trajectory.points

        if not joint_names or not points:
            self.get_logger().error('Invalid goal')
            goal_handle.abort()
            return FollowJointTrajectory.Result()
            
        print("======================")
        print(goal)
    # 发布所有轨迹点的关节位置
        for point in points:
            joint_positions = JointState()
            joint_positions.header.stamp = self.get_clock().now().to_msg()
            joint_positions.name = joint_names
            joint_positions.position = point.positions

            # 发布关节状态
            self.publisher_.publish(joint_positions)
            self.get_logger().info(f'Published joint positions: {joint_positions.position}')

            # 模拟执行时间
            time.sleep(1)

#            rclpy.sleep(point.time_from_start.sec + point.time_from_start.nanosec / 1e9)

        self.get_logger().info('Goal successfully executed!')
        goal_handle.succeed()

        result = FollowJointTrajectory.Result()
        return result


def main(args=None):
    print("start1")
    rclpy.init(args=args)
    print("start2")
    trajectory_server = TrajectoryServer()
    print("start3")
    rclpy.spin(trajectory_server)
    print("start4")    
if __name__ == '__main__':
    main()

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值