前言
本篇文章属于ROS2humble的学习笔记,来源于B站鱼香ROSup主。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。
《ROS 2机器人开发从入门到实践》课程介绍_哔哩哔哩_bilibili
一、Python节点的订阅和发布
1.下载小说,并通过话题间隔5s发送一行
(1)创建工作空间
在终端中输入:
mkdir -p topic_ws/src
(2)在src目录下创建软件包
处于src目录下,在终端中输入:
ros2 pkg create demo_python_topic --build-type ament_python --dependencies rclpy example_interfaces --license Apache-2.0
回到topic_ws目录下,再输入:
colcon build 构建文件
(3)在有__init__.py文件的目录下创建节点文件novel_pub_node.py,节点代码部分如下
import rclpy
from rclpy.node import Node
import requests
from example_interfaces.msg import String
from queue import Queue
class NovelNode(Node):
def __init__(self, node_name):
super().__init__(node_name)
self.get_logger().info(f'{node_name},启动!')
self.novel_queue = Queue() #创建队列
self.novel_publisher = self.create_publisher(String, 'novel', 100)
self.create_timer(5, self.timer_callback)
def timer_callback(self):
if self.novel_queue.qsize()>0:
line = self.novel_queue.get()
msg = String()
msg.data = line
self.novel_publisher.publish(msg)
self.get_logger().info(f'发布了:{msg}')
def download(self, url):
responese = requests.get(url)
responese.encoding = 'utf-8'
text = responese.text
self.get_logger().info(f'下载{url},{len(text)}')
for line in text.splitlines():
self.novel_queue.put(line)
def main():
rclpy.init()
node = NovelNode('novel_pub')
node.download('https://fanqienovel.com/reader/7173216089122439711?enter_from=page')
rclpy.spin(node)
rclpy.shutdown()
(4)执行代码
先找到
entry_points={
'console_scripts': [
],
},
改为
entry_points={
'console_scripts': [
'novel_pub_node=demo_python_topic.novel_pub_node:main'
],
},
等号左边是可执行文件的名字,等号右边是软件包名和节点名
然后回到 topic_ws目录下,再输入:
colcon build 构建文件
再在终端中输入source install/setup.bash
修改一下环境变量
再运行即可:ros2 run demo_python_topic novel_pub_node
2.订阅小说并合成语音
import espeakng
import rclpy
from rclpy.node import Node
from example_interfaces.msg import String
from queue import Queue
import threading
import time
class NovelSubNode(Node):
def __init__(self, node_name):
super().__init__(node_name)
self.get_logger().info(f'{node_name},启动!')
self.novel_queue = Queue()
self.create_subscription(String, 'novel', self.novel_callback, 10)
self.speech_thread_ = threading.Thread(target=self.speaker_thread)
self.speech_thread_.start()
def novel_callback(self, msg):
self.novel_queue.put(msg.data)
pass
def speaker_thread(self):
speaker = espeakng.Speaker()
speaker.voice = 'zh'
while rclpy.ok(): #检测ROS当前上下文是否ok
if self.novel_queue.qsize()>0:
text = self.novel_queue.get()
self.get_logger().info(f'朗读:{text}')
speaker.say(text) #说
speaker.wait() #等说完
else:
#让当前线程休眠1s
time.sleep(1)
def main():
rclpy.init()
node = NovelSubNode('novel_sub')
rclpy.spin(node)
rclpy.shutdown()
espeakng为朗读引入的库
注意当前线程休眠1s的操作很关键,这能降低CPU功耗
二、C++节点的订阅和发布
1.发布速度控制海龟画圆
(1)在工作空间的src目录下创建软件包
处于src目录下,在终端中输入:
ros2 pkg create demo_cpp_topic --build-type ament_cmake --dependencies rclcpp geometry_msgs turtlesim --license Apache-2.0
(2)在软件包下的src目录下创建节点文件turtle_circle.cpp
出现该报错要配置includePath
配置成这样即可
节点代码如下:
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include <chrono>
using namespace std::chrono_literals;
class TurtleCircleNode: public rclcpp::Node
{
private:
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;//发布者的共享指针
public:
explicit TurtleCircleNode(const std::string& node_name):Node(node_name)
{
publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10);
timer_ = this->create_wall_timer(1000ms,std::bind(&TurtleCircleNode::timer_callback,this));
}
void timer_callback()
{
auto msg = geometry_msgs::msg::Twist();
msg.linear.x = 1.0;
msg.angular.z = 0.5;
publisher_->publish(msg);
}
};
int main(int argc,char *argv[])
{
rclcpp::init(argc,argv);
auto node = std::make_shared<TurtleCircleNode>("turtle_circle");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
(4)执行代码
首先去CMakeLists文件
add_executable(turtle_circle src/turtle_circle.cpp) #添加可执行文件
ament_target_dependencies(turtle_circle rclcpp geometry_msgs) #添加依赖
install(TARGETS turtle_circle
DESTINATION lib/${PROJECT_NAME}
) #拷贝文件到人ros2 run的文件目录上,便于ros2 run启动
然后回到topic_ws工作空间下colcon build 编译文件
生成完后输入source install/setup.bash配置环境变量
ros2 run demo_cpp_topic turtle_circle 即可运行
2.订阅pose实现闭环控制
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include <chrono>
#include "turtlesim/msg/pose.hpp"
using namespace std::chrono_literals;
class TurtleControlNode: public rclcpp::Node
{
private:
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;//发布者的共享指针
rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr subscriber_;//订阅者的共享指针
double target_x_{1.0};
double target_y_{1.0};
double k{0.5};
double max_speed_{3.0};
public:
explicit TurtleControlNode(const std::string& node_name):Node(node_name)
{
publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10);
subscriber_ = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose",10,std::bind(&TurtleControlNode::sub_callback,this,std::placeholders::_1));
// timer_ = this->create_wall_timer(1500ms,std::bind(&TurtleControlNode::timer_callback,this));
}
void sub_callback(const turtlesim::msg::Pose::SharedPtr pose)
{
//1. 获取当前位置
auto current_x = pose->x;
auto current_y = pose->y;
RCLCPP_INFO(get_logger(),"当前:x=%f,y=%f",current_x,current_y);
//2. 计算当前海龟位置跟目标位置之间的距离差和角度差
auto distance = std::sqrt(
(target_x_-current_x)*(target_x_-current_x)+
(target_y_-current_y)*(target_y_-current_y)
);
auto angle = std::atan2((target_y_-current_y),(target_x_-current_x))-pose->theta;
//3.控制策略
auto msg = geometry_msgs::msg::Twist();
if (distance>0.1)
{
if (fabs(angle)>0.2)
{
msg.angular.z = k*angle;
}
else
{
msg.linear.x = k*distance;
}
}
//4.限制速度
if (msg.linear.x > max_speed_)
{
msg.linear.x = max_speed_;
}
publisher_->publish(msg);
}
};
int main(int argc,char *argv[])
{
rclcpp::init(argc,argv);
auto node = std::make_shared<TurtleControlNode>("turtle_control");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
三、话题通信最佳实践(获取系统信息并显示)
1.自定义消息类型
在src目录下创建status_interfaces功能包,在功能包目录下,创建msg文件夹,在msg文件夹下创建SystemStatus.msg文件
builtin_interfaces/Time stamp # 记录时间戳 string host_name #主机名字 float32 cpu_percent #CPU使用率 float32 memory_percent #内存使用率 float32 memory_total #内存总大小 float32 memory_available #内存剩余量 float64 net_sent #网络数据发送总量 float64 net_rece #网络数据接受总量
在SystemStatus.msg文件中编写如上代码
在CMakeLists文件中添加cmake函数
# find dependencies find_package(ament_cmake REQUIRED) find_package(builtin_interfaces REQUIRED) find_package(rosidl_default_generators REQUIRED) #cmake函数,来自依赖rosidl_default_generators 用于将msg消息接口定义文件转换成库或者头文件夹 rosidl_generate_interfaces(${PROJECT_NAME} "msg/SystemStatus.msg" DEPENDENCIES builtin_interfaces )
再在package.xml文件中添加声明,声明该功能包是包含消息接口的功能包
<member_of_group>rosidl_interface_packages</member_of_group>
2.发布者节点代码
import rclpy
from status_interfaces.msg import SystemStatus
from rclpy.node import Node
import psutil
import platform
class SysStatusPub(Node):
def __init__(self,node_name):
super().__init__(node_name)
self.status_publisher_ = self.create_publisher(
SystemStatus,'sys_status',10
)
self.timer_ = self.create_timer(1.0,self.timer_callback)
def timer_callback(self):
cpu_percent = psutil.cpu_percent()
memory_info = psutil.virtual_memory()
net_io_counters = psutil.net_io_counters()
msg = SystemStatus()
msg.stamp = self.get_clock().now().to_msg()
msg.host_name = platform.node()
msg.cpu_percent = cpu_percent
msg.memory_percent = memory_info.percent
msg.memory_total = memory_info.total / 1024 / 1024
msg.memory_available = memory_info.available / 1024 / 1024
msg.net_sent = net_io_counters.bytes_sent / 1024 / 1024
msg.net_recv = net_io_counters.bytes_recv / 1024 / 1024
self.get_logger().info(f'发布:{str(msg)}')
self.status_publisher_.publish(msg)
def main():
rclpy.init()
node = SysStatusPub('sys_status_pub')
rclpy.spin(node)
rclpy.shutdown()
3.订阅者节点代码
#include <QApplication>
#include <QLabel>
#include <QString>
#include <rclcpp/rclcpp.hpp>
#include "status_interfaces/msg/system_status.hpp"
using SystemStatus = status_interfaces::msg::SystemStatus;
class SysStatusDisplay : public rclcpp::Node
{
private:
rclcpp::Subscription<SystemStatus>::SharedPtr subscriber_;
QLabel *label_;
public:
SysStatusDisplay() : Node("sys_status_display")
{
label_ = new QLabel();
subscriber_ = this->create_subscription<SystemStatus>("sys_status", 10, [&]
(const SystemStatus::SharedPtr msg) -> void
{
label_->setText(get_qstr_from_msg(msg));
});
label_->setText(get_qstr_from_msg(std::make_shared<SystemStatus>()));
label_->show();
}
QString get_qstr_from_msg(const SystemStatus::SharedPtr msg)
{
std::stringstream show_str;
show_str << "=============状态可视化显示工具=============\n"
<< "数 据 时 间:\t" << msg->stamp.sec << "\ts\n"
<< "主 机 名 字\t" << msg->host_name << "\t\n"
<< "CPU 使用率:\t" << msg->cpu_percent << "\t%\n"
<< "内存使用率:\t" << msg->memory_percent << "\t%\n"
<< "内存总大小:\t" << msg->memory_total << "\tMB\n"
<< "剩余有效内存:\t" << msg->memory_available << "\tMB\n"
<< "网络发送量:\t" << msg->net_sent << "\tMB\n"
<< "网络接受量:\t" << msg->net_recv << "\tMB\n"
<< "=========================================";
return QString::fromStdString(show_str.str());
}
};
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
QApplication app(argc, argv);
auto node = std::make_shared<SysStatusDisplay>();
std::thread spin_thread([&]() -> void
{
rclcpp::spin(node);
});
spin_thread.detach();
app.exec();
return 0;
}
这里由于rclcpp::spin()和app.exec()都会阻塞代码进程,所以要多开一个线程
四、Git入门