高级任务:使用 ROS2 Humble 创建飞行无人机的模拟。
当前任务:我正在尝试通过主题订阅者通信和直接函数调用相结合,通过高级飞行控制器来改变电机速度(节点主题图如下所示):
只有标有红色的部分才是重要的。
Algorytm:发布命令“cmd_vel”,该命令将由 /flight_controller Velocity_subscriber 使用 -> Velocity_subscriber 绑定到 control_motors 函数,该函数将直接改变电机速度
flight_controller.hpp
#ifndef FLIGHT_CONTROLLER_HPP_
#define FLIGHT_CONTROLLER_HPP_
#include <rclcpp/rclcpp.hpp>
#include "motor_controller.hpp"
#include <geometry_msgs/msg/Twist.hpp>
class FlightController : public rclcpp::Node
{
public:
FlightController();
void update();
void control_motors(const geometry_msgs::msg::Twist::SharedPtr cmd_vel);
private:
Motor motor_fl_;
Motor motor_fr_;
Motor motor_bl_;
Motor motor_br_;
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr velocity_subscription_;
};
#endif // FLIGHT_CONTROLLER_HPP_
飞行控制器.cpp
#include "hrssd/flight_controller.hpp"
FlightController::FlightController()
: Node("flight_controller"),
motor_fl_("fl", "motors"),
motor_fr_("fr", "motors"),
motor_bl_("bl", "motors"),
motor_br_("br", "motors")
{
velocity_subscription_ = this->create_subscription<geometry_msgs::msg::Twist>(
"cmd_vel", 10,
std::bind(&FlightController::control_motors, this, std::placeholders::_1)); // <- HERE command that cousing problems
}
void FlightController::update()
{
// Implementation for updating flight status or performing periodic checks
}
void FlightController::control_motors(const geometry_msgs::msg::Twist::SharedPtr cmd_vel)
{
RCLCPP_INFO(this->get_logger(), "Flight Controller hhh");
// Here you would convert the Twist message into individual motor speeds.
// This is a simple proportional placeholder for demonstration purposes.
double linear_x = cmd_vel->linear.x;
double angular_z = cmd_vel->angular.z;
// Assuming that a positive angular_z represents a clockwise rotation
// and linear_x controls the forward/backward speed.
double speed_fl = linear_x - angular_z;
double speed_fr = linear_x + angular_z;
double speed_bl = linear_x - angular_z;
double speed_br = linear_x + angular_z;
motor_fl_.set_speed(speed_fl);
motor_fr_.set_speed(speed_fr);
motor_bl_.set_speed(speed_bl);
motor_br_.set_speed(speed_br);
}
电机_控制器.hpp
#ifndef MOTOR_HPP_
#define MOTOR_HPP_
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/Float64.hpp>
class Motor : public rclcpp::Node
{
public:
Motor(const std::string & name, const std::string & namespace_); // namespace_ currently not used
void set_speed(double speed);
double get_speed() const;
private:
void speed_callback(const std_msgs::msg::Float64::SharedPtr msg);
rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr speed_publisher_;
rclcpp::Subscription<std_msgs::msg::Float64>::SharedPtr speed_subscription_;
double current_speed_;
};
#endif // MOTOR_HPP_
电机_控制器.cpp
#include "hrssd/motor_controller.hpp"
Motor::Motor(const std::string & name, const std::string & namespace_)
: Node(name), current_speed_(0.0)
{
// Initialize the publisher for motor speed
std::string topic_name = name;
speed_publisher_ = this->create_publisher<std_msgs::msg::Float64>(topic_name, 10);
}
void Motor::set_speed(double speed)
{
// Update the current speed
current_speed_ = speed;
RCLCPP_INFO(this->get_logger(), "set_speed_motor: %f", speed);
// Publish the new speed
auto message = std_msgs::msg::Float64();
message.data = current_speed_;
speed_publisher_->publish(message);
}
double Motor::get_speed() const
{
// Return the current speed
return current_speed_;
}
void Motor::speed_callback(const std_msgs::msg::Float64::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "speed_callback_motor: %f", msg->data);
// Update the motor's speed based on the received command
set_speed(msg->data);
}
我正在发布以下命令,期望将调用 control_motors 函数,但没有收到任何反馈:
ros2 topic pub --rate 1 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.0, y: 0.0, z: 100.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
我确信的事情:
消息已发布,我通过终端中的命令检查了它:
输入: ros2主题 echo /cmd_vel
输出: 线性: x:0.0 y:0.0 坐标:100.0 角度: x:0.0 y:0.0 z:0.0
我的包内任何类型的主题订阅者通信都不起作用,尽管它们已初始化:我尝试直接向电机发布消息(绕过flight_controller),这是同样的故事,但在包外ros主题工作正常,这使得我认为“std::bind”不能正常工作。
如果您有任何线索如何解决/改进/使相同的任务更容易,请联系,谢谢!
这里有很多东西需要解开,所以我会尽量简短。
当节点收到某个主题的消息时,它不会自动调用回调函数。您必须创建一个执行器,它将在传入消息和事件上调用订阅、计时器、服务服务器、操作服务器等的回调。
有关执行器的更多信息,请参阅执行器文档。
由于 FlightController 和 Motor 类都继承自 rclcpp::Node,所以 FlightController 和 Motor 的所有实例化都将是不同的节点。所有这些节点都需要由执行器旋转,以便调用回调函数。旋转飞控不会自动旋转电机节点。
电机对象完全封装在 FlightController 类中,这使得无法从 FlightController 外部旋转这些电机。
这个问题有几种解决方案
如果你
class FlightController : public rclcpp::Node
{
public:
FlightController(std::shared_ptr<Motor> motor_fl,
std::shared_ptr<Motor> motor_fr,
std::shared_ptr<Motor> motor_bl,
std::shared_ptr<Motor> motor_br)
: motor_fl_{motor_fl}
, motor_fr_{motor_fr}
, motor_bl_{motor_bl}
, motor_br_{motor_br}
{
// do other constructor stuff here.
}
private:
std::shared_ptr<Motor> motor_fl_;
std::shared_ptr<Motor> motor_fr_;
std::shared_ptr<Motor> motor_bl_;
std::shared_ptr<Motor> motor_br_;
};
然后在主循环中将所有节点添加到单个执行器并旋转执行器。
int main(int argc, char const* argv[])
{
rclcpp::init(argc, argv);
auto motor_fl = std::make_shared<Motor>(/* constructor args for motor */);
auto motor_fr = std::make_shared<Motor>(/* constructor args for motor */);
auto motor_bl = std::make_shared<Motor>(/* constructor args for motor */);
auto motor_br = std::make_shared<Motor>(/* constructor args for motor */);
auto flight_controller = std::make_shared<FlightController>(motor_fl, motor_fr, motor_bl, motor_br);
rclcpp::executors::StaticSingleThreadedExecutor executor;
executor.add_node(motor_fl);
executor.add_node(motor_fr);
executor.add_node(motor_br);
executor.add_node(motor_bl);
executor.add_node(flight_controller);
executor.spin();
rclcpp::shutdown();
}
你没有义务继承 rclcpp::Node。您可以轻松地将节点作为构造函数的参数
class Motor
{
public:
Motor(std::shared_ptr<rclcpp::Node> node) : node_{node}
{
speed_subscription_ = node_->create_subscribtion</*message type*/>(...);
}
private:
void speed_callback(const std_msgs::msg::Float64::SharedPtr msg);
std::shared_ptr<rclcpp::Node> node_;
rclcpp::Subscription<std_msgs::msg::Float64>::SharedPtr speed_subscription_;
};
class FlightController
{
public:
FlightController(std::shared_ptr<rclcpp::Node> node)
: motor_fl_{node}
, motor_fr_{node}
, motor_bl_{node}
, motor_br_{node}
, node_{node}
{
// do other constructor stuff here.
}
private:
std::shared_ptr<Motor> motor_fl_;
std::shared_ptr<Motor> motor_fr_;
std::shared_ptr<Motor> motor_bl_;
std::shared_ptr<Motor> motor_br_;
std::shared_ptr<rclcpp::Node> node_;
};
然后在主循环中,您可以创建一个 rclcpp::Node 对象并将其传递给多个类,如下所示
int main(int argc, char const* argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("node name");
auto flight_controller = std::make_shared<FlightController>(node);
rclcpp::spin(node)
rclcpp::shutdown();
}
由于您的 FlightController 有一个
update
方法,我假设您从主循环中定期调用此方法。您可以扩展 update
方法以在最后旋转电机节点。
void FlightController::update()
{
// Implementation for updating flight status or performing periodic checks
rclcpp::spin_some(motor_fl_);
rclcpp::spin_some(motor_fr_);
rclcpp::spin_some(motor_bl_);
rclcpp::spin_some(motor_br_);
}
您可以将电机控制功能提取到单独的可执行文件中。使用启动文件,您可以使用不同的参数轻松启动此可执行文件 4 次。
然后您可以创建飞行控制器可执行文件。
我建议结合使用选项 1 和 2,因为选项 3 和 4 会产生大量计算开销,并且会减慢程序速度。