CMakeList.txt 文件中的基本配置不再赘述
代码模板
c++ 模板
1 2 3 4 5 6 7 8 9 10
| #include "ros/ros.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,""); ros::NodeHandle nh;
return 0; }
|
python 模板
1 2 3 4 5 6 7
|
import rospy
if __name__ == "__main__": rospy.init_node("")
|
ROS通信机制
先创建一个启动乌龟界面的launch文件
1 2 3 4 5 6
| <launch> <node pkg="turtlesim" type="turtlesim_node" name="turtle1" output = "screen"/> <node pkg="turtlesim" type="turtle_teleop_key" name="turtle_key" output = "screen" /> </launch>
|
实操1 话题发布
需求描述:编码实现乌龟运动控制,让小乌龟做圆周运动
实现分析:
- 乌龟运动控制实现,关键节点有两个,一个是乌龟运动显示节点 turtlesim_node,另一个是控制节点,二者是订阅发布模式实现通信的,乌龟运动显示节点直接调用即可,运动控制节点之前是使用的 turtle_teleop_key 通过键盘控制,现在需要自定义控制节点
- 控制节点自实现时,首先需要了解控制节点与显示节点通信使用的话题与消息,可以使用ros命令结合计算图来获取
- 了解了话题与消息之后,通过 C++ 或 Python 编写运动控制节点,通过指定的话题,按照一定的逻辑发布消息即可
实现流程:
- 通过计算图结合ros命令获取话题与消息信息
- 编码实现运动控制节点
- 启动 roscore、turtlesim_node 以及自定义的控制节点,查看运行结果
话题与消息获取
先启动键盘控制乌龟运动案例
话题获取
通过 rostopic 列出话题
或者通过计算图查看话题,启动计算图
-> /turtle1/cmd_vel 传递运动指令
消息获取
获取消息类型:
1 2
| rostopic type /turtle1/cmd_vel // rostopic info /turtle1/cmd_vel
|
info 命令下也显示type
type: geometry_msgs/Twist
获取消息格式
1
| rosmsg info geometry_msgs/Twist
|
输出:
1 2 3 4 5 6 7 8
| geometry_msgs/Vector3 linear float64 x float64 y float64 z geometry_msgs/Vector3 angular float64 x float64 y float64 z
|
inear(线速度) :xyz分别对应在x、y和z方向上的速度(单位是 m/s)
angular(角速度):xyz分别对应x轴上的翻滚roll、y轴上pitch和z轴上偏航yaw的速度(单位是rad/s)
弧度:单位弧度定义为圆弧长度等于半径时的圆心角
在ros中顺时针弧度为 - ,逆时针为 +,值的范围在[-π,π]
于是可以通过直接发布指令做到简单实现
1
| rostopic pub -r 10 /turtle1/cmd_vel [TAB补齐]
|
实现发布节点
创建功能包需要依赖的功能包: roscpp rospy std_msgs geometry_msgs(封装坐标)
c++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
#include "ros/ros.h" #include "geometry_msgs/Twist.h"
int main(int argc, char *argv[]) { ros::init(argc,argv,"circle_control"); ros::NodeHandle nh; ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",1000); geometry_msgs::Twist msg;
msg.linear.x = 1.0; msg.linear.y = 1.0; msg.linear.z = 0.0; msg.angular.x = 0.0; msg.angular.y = 0.0; msg.angular.z = 1.0;
ros::Rate rate(10); while(ros::ok()) { pub.publish(msg); rate.sleep(); ros::spinOnce(); } return 0;
}
|
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
import rospy from geometry_msgs.msg import Twist
if __name__ == "__main__":
rospy.init_node("circle_control_p") pub = rospy.Publisher("/turtle1/cmd_vel",Twist,queue_size=1000) rate = rospy.Rate(10) msg = Twist() // 注意这里要()
msg.linear.x = 1.0 msg.linear.y = 0.0 msg.linear.z = 0.0
msg.angular.x = 0.0 msg.angular.y = 0.0 msg.angular.z = 1.0
while not rospy.is_shutdown(): pub.publish(msg) rate.sleep
|
实操2 话题订阅
需求描述:turtlesim中的乌龟显示节点,会发布当前乌龟的位姿(窗体中乌龟的坐标以及朝向),要求控制乌龟运动,并时时打印当前乌龟的位姿
实现分析:
- 首先,需要启动乌龟显示以及运动控制节点并控制乌龟运动
- 要通过ROS命令,来获取乌龟位姿发布的话题以及消息
- 编写订阅节点,订阅并打印乌龟的位姿
实现流程:
- 通过ros命令获取话题与消息信息
- 编码实现位姿获取节点
- 启动 roscore、turtlesim_node 、控制节点以及位姿订阅节点,控制乌龟运动并输出乌龟的位姿
话题与消息获取
先启动键盘控制乌龟运动案例
话题获取
-> /turtle1/pose 存储乌龟位置
获取消息类型
1
| rostopic info /turtle1/pose
|
-> type: turtlesim/Pose
获取消息格式
1
| rosmsg info turtlesim/Pose
|
响应结果:
1 2 3 4 5
| float32 x float32 y float32 theta float32 linear_velocity float32 angular_velocity
|
实现订阅节点
需要增添功能包turtlesim
在package.xml中增添
1 2
| <build_depend>turtlesim</build_depend> <exec_depend>sturtlesim</exec_depend>
|
在CMakeLsit.txt中增添
1 2 3 4 5 6 7
| find_package(catkin REQUIRED COMPONENTS geometry_msgs roscpp rospy std_msgs turtlesim //增添包 )
|
c++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#include "ros/ros.h" #include "turtlesim/Pose.h"
void doPose(const turtlesim::Pose::ConstPtr &pose){ ROS_INFO("乌龟的位姿信息: \n坐标(%.2f,%.2f)\n朝向(%.2f)\n线速度:%.2f\n角速度:%.2f", pose->x,pose->y,pose->theta,pose->linear_velocity,pose->angular_velocity); }
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"sub_pose"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("/turtle1/pose",1000,doPose); ros::spin(); return 0; }
|
python
1 2 3 4 5 6 7 8 9 10 11 12 13
|
import rospy from turtlesim.msg import Pose
def doPose(pose): rospy.loginfo("乌龟的位姿信息: \n坐标(%.2f,%.2f)\n朝向(%.2f)\n线速度:%.2f\n角速度:%.2f", pose.x,pose.y,pose.theta,pose.linear_velocity,pose.angular_velocity)
if __name__ == "__main__": rospy.init_node("sub_pose_p") sub = rospy.Subscriber("/turtle1/pose",Pose,doPose,queue_size=1000) rospy.spin()
|
代码中容易错误的地方:话题名是/turtle1/pose ,注意这个1
实操3 服务调用
需求描述:编码实现向 turtlesim 发送请求,在乌龟显示节点的窗体指定位置生成一乌龟,这是一个服务请求操作
实现分析:
- 首先,需要启动乌龟显示节点
- 要通过ROS命令,来获取乌龟生成服务的服务名称以及服务消息类型
- 编写服务请求节点,生成新的乌龟
实现流程:
- 通过ros命令获取服务与服务消息信息
- 编码实现服务请求节点
- 启动 roscore、turtlesim_node 、乌龟生成节点,生成新的乌龟
服务名称与服务消息获取
获取话题
-> /spawn
获取服务消息类型
type: turtlesim/Spawn
Args: x y theta name
获取服务消息格式
1
| rossrv info turtlesim/Spawn
|
响应结果:
1 2 3 4 5 6
| float32 x float32 y float32 theta string name --- string name
|
也可以简单实现增添乌龟
1
| rosservice call /spawn [TAB补齐]
|
c++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
#include "ros/ros.h" #include "turtlesim/Spawn.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"service_call"); ros::NodeHandle nh; ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn"); turtlesim::Spawn spawn; spawn.request.x = 1.0; spawn.request.y = 4.0; spawn.request.theta = 1.57; spawn.request.name = "turtlec";
client.waitForExistence(); bool flag = client.call(spawn); if (flag) { ROS_INFO("新的乌龟生成,名字:%s",spawn.response.name.c_str()); } else { ROS_INFO("乌龟生成失败!!!"); } return 0; }
|
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
import rospy from turtlesim.srv import Spawn,SpawnRequest,SpawnResponse
if __name__ == "__main__": rospy.init_node("service_call_p") client = rospy.ServiceProxy("/spawn",Spawn) request = SpawnRequest() request.x = 4.0 request.y = 1.0 request.theta = 1.57 request.name = "turtlep" client.wait_for_service() try: response = client.call(request) rospy.loginfo("乌龟创建成功!,叫:%s",response.name) except: rospy.loginfo("服务调用失败")
|
可能会出现因为重复命名的异常
实操4 参数设置
需求描述: 修改turtlesim乌龟显示节点窗体的背景色,已知背景色是通过参数服务器的方式以 rgb 方式设置的
实现分析:
- 首先,需要启动乌龟显示节点[注意这里不启动launch文件]
- 要通过ROS命令,来获取参数服务器中设置背景色的参数
- 编写参数设置节点,修改参数服务器中的参数值
实现流程:
- 通过ros命令获取参数
- 编码实现服参数设置节点
- 启动 roscore、turtlesim_node 与参数设置节点,查看运行结果
参数名获取
获取参数列表:
响应结果:
1 2 3 4 5 6
| /turtle1/background_b /turtle1/background_g /turtle1/background_r /turtlesim/background_b /turtlesim/background_g /turtlesim/background_r
|
方式1:命令行实现
1 2 3
| rosparam set /turtlesim/background_r 自定义数值 rosparam set /turtlesim/background_g 自定义数值 rosparam set /turtlesim/background_b 自定义数值
|
修改相关参数后,重启 turtlesim_node 节点,背景色就会发生改变了
方式2:代码
c++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "ros/ros.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"color"); ros::NodeHandle nh; nh.setParam("/turtlesim/background_r",0); nh.setParam("/turtlesim/background_g",0); nh.setParam("/turtlesim/background_b",0); return 0; }
|
python
1 2 3 4 5 6 7 8 9
|
import rospy
if __name__ == "__main__": rospy.init_node("color_p") rospy.set_param("/turtlesim/background_r",255) rospy.set_param("/turtlesim/background_g",255) rospy.set_param("/turtlesim/background_b",255)
|
TF坐标变换
需求描述:程序启动之初产生两只乌龟,中间的乌龟(A) 和 左下乌龟(B),B 会自动运行至A的位置,并且键盘控制时,只是控制 A 的运动,但是 B 可以跟随 A 运行
实现分析:
乌龟跟随实现的核心,是乌龟A和B都要发布相对世界坐标系的坐标信息,然后,订阅到该信息需要转换获取A相对于B坐标系的信息,最后,再生成速度信息,并控制B运动。
- 启动乌龟显示节点
- 在乌龟显示窗体中生成一只新的乌龟(需要使用服务)
- 编写两只乌龟发布坐标信息的节点
- 编写订阅节点订阅坐标信息并生成新的相对关系生成速度信息
实现流程:
- 新建功能包,添加依赖
- 编写服务客户端,用于生成一只新的乌龟
- 编写发布方,发布两只乌龟的坐标信息
- 编写订阅方,订阅两只乌龟信息,生成速度信息并发布
- 运行
创建第二只乌龟需要使用rosservice,话题使用的是 spawn
位置信息通过话题 /乌龟名称/pose 来获取的
创建功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim
服务客户端(生成乌龟)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include "ros/ros.h" #include "turtlesim/Spawn.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"turtle_new"); ros::NodeHandle nh; ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn"); turtlesim::Spawn spawn; spawn.request.x = 1; spawn.request.y = 3; spawn.request.theta = 1.57; spawn.request.name = "turtle2"; client.waitForExistence(); bool flag = client.call(spawn); if(flag) { ROS_INFO("新的乌龟生成,名称:%s",spawn.response.name.c_str()); }else { ROS_INFO("乌龟生成失败!!!"); } return 0; }
|
发布方(发布两只乌龟的坐标信息)
- 该节点需要启动两次
- 每次启动时都需要传入乌龟节点名称(第一次是 turtle1 第二次是 turtle2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include "ros/ros.h" #include "turtlesim/Pose.h" #include "tf2_ros/transform_broadcaster.h" #include "geometry_msgs/TransformStamped.h" #include "tf2/LinearMath/Quaternion.h"
std::string turtle_name;
void doPose(const turtlesim::Pose::ConstPtr& pose){ static tf2_ros::TransformBroadcaster pub; geometry_msgs::TransformStamped tfs; tfs.header.frame_id = "world"; tfs.header.stamp = ros::Time::now(); tfs.child_frame_id = turtle_name; tfs.transform.translation.x = pose->x; tfs.transform.translation.y = pose->y; tfs.transform.translation.z = 0; tf2::Quaternion qtn; qtn.setRPY(0,0,pose->theta); tfs.transform.rotation.x = qtn.getX(); tfs.transform.rotation.y = qtn.getY(); tfs.transform.rotation.z = qtn.getZ(); tfs.transform.rotation.w = qtn.getW(); pub.sendTransform(tfs); }
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"dynamic_pub"); if(argc != 2) { ROS_ERROR("请传入一个参数"); return 1; }else { turtle_name = argv[1]; ROS_INFO("乌龟 %s 坐标发送启动",turtle_name.c_str()); }
ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe(turtle_name + "/pose",100,doPose); ros::spin(); return 0; }
|
订阅方(解析坐标信息并生成速度信息)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include "ros/ros.h" #include "tf2_ros/transform_listener.h" #include "tf2_ros/buffer.h" #include "geometry_msgs/PointStamped.h" #include "tf2_geometry_msgs/tf2_geometry_msgs.h" #include "geometry_msgs/TransformStamped.h" #include "geometry_msgs/Twist.h"
int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"control"); ros::NodeHandle nh; tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer); ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",100); ros::Rate rate(1); while (ros::ok()){ try { geometry_msgs::TransformStamped tfs = buffer.lookupTransform("turtle2","turtle1",ros::Time(0)); geometry_msgs::Twist twist; twist.linear.x = 0.5 * sqrt(pow(tfs.transform.translation.x,2)+pow(tfs.transform.translation.y,2)); twist.angular.z = 0.5 * atan2(tfs.transform.translation.y,tfs.transform.translation.x); pub.publish(twist); } catch(const std::exception& e) { ROS_INFO("错误提示:%s",e.what()); } rate.sleep(); ros::spinOnce(); }
return 0; }
|
运行
使用launch文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| <launch> <node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen" /> <node pkg="turtlesim" type="turtle_teleop_key" name="key" output="screen" /> <node pkg="tf04_test" type="new_turtle" name="turtle2" output="screen"/> <node pkg="tf04_test" type="dynamic_pub" name="pub1" args="turtle1" output="screen"/> <node pkg="tf04_test" type="dynamic_pub" name="pub2" args="turtle2" output="screen"/>
<node pkg="tf04_test" type="control" name="control" output="screen"/> </launch>
|
机器人系统仿真
Xacro
需求描述:在前面小车底盘基础之上,添加摄像头和雷达传感器
实现分析:
机器人模型由多部件组成,可以将不同组件设置进单独文件,最终通过文件包含实现组件的拼装。
实现流程:
- 首先编写摄像头和雷达的 xacro 文件
- 然后再编写一个组合文件,组合底盘、摄像头与雷达
- 最后,通过 launch 文件启动 Rviz 并显示模型
**摄像头Xacro **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="camera">
<xacro:property name="camera_length" value="0.02" /> <xacro:property name="camera_width" value="0.02" /> <xacro:property name="camera_height" value="0.02" /> <xacro:property name="camera_x" value="0.08" /> <xacro:property name="camera_y" value="0.0" /> <xacro:property name="camera_z" value="${base_length / 2 + camera_height / 2}" /> <link name="camera"> <visual> <geometry> <box size="${camera_length} ${camera_width} ${camera_height}" /> </geometry> <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" /> <material name="black" /> </visual> </link>
<joint name="camera2base_link" type="fixed"> <parent link="base_link" /> <child link="camera" /> <origin xyz="${camera_x} ${camera_y} ${camera_z}" /> </joint> </robot>
|
雷达Xacro
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="laser"> <xacro:property name="support_length" value="0.15" /> <xacro:property name="support_radius" value="0.01" /> <xacro:property name="support_x" value="0.0" /> <xacro:property name="support_y" value="0.0" /> <xacro:property name="support_z" value="${base_length / 2 + support_length / 2}" /> <link name="support"> <visual> <geometry> <cylinder radius="${support_radius}" length="${support_length}" /> </geometry> <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" /> <material name="red"> <color rgba="0.8 0.2 0.0 0.8" /> </material> </visual> </link>
<joint name="support2base_link" type="fixed"> <parent link="base_link" /> <child link="support" /> <origin xyz="${support_x} ${support_y} ${support_z}" /> </joint>
<xacro:property name="laser_length" value="0.05" /> <xacro:property name="laser_radius" value="0.03" /> <xacro:property name="laser_x" value="0.0" /> <xacro:property name="laser_y" value="0.0" /> <xacro:property name="laser_z" value="${support_length / 2 + laser_length / 2}" /> <link name="laser"> <visual> <geometry> <cylinder radius="${laser_radius}" length="${laser_length}" /> </geometry> <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" /> <material name="black" /> </visual> </link>
<joint name="laser2support" type="fixed"> <parent link="support" /> <child link="laser" /> <origin xyz="${laser_x} ${laser_y} ${laser_z}" /> </joint> </robot>
|
组合底盘摄像头与雷达
1 2 3 4 5 6
| <robot name="my_car_camera" xmlns:xacro="http://wiki.ros.org/xacro"> <xacro:include filename="base.urdf.xacro"/> <xacro:include filename="camera.urdf.xacro"/> <xacro:include filename="laser.urdf.xacro"/> </robot>
|
launch 文件
1 2 3 4 5 6 7 8 9 10
| <launch> <param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/combination.urdf.xacro"/> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_car.rviz" /> <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher"/> <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher"/> <node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" /> </launch>
|