文章目录

  • 一、ROS基础概述
    • 1、操作系统安装
    • 2、ROS Noetic安装
    • 3、常用工具安装
      • 1、Terminator
      • 2、VScode
  • 二、核心概念
    • 1、ROS介绍
    • 2、ROS核心概念
    • 3、ROS常用命令行工具
  • 三、编程基础
    • 1、工作空间创建
    • 2、Topic话题通信
      • 1、发布者publisher编程实现
      • 2、订阅者subscriber编程实现
      • 3、自定义话题通信
        • 1、C++文件
        • 2、python文件
    • 3、Service服务通信
      • 1、客户端Client编程实现
      • 2、服务端Service编程实现
      • 3、自定义数据服务
        • 1、C++文件
        • 2、python文件
    • 4、全局参数使用与编程
      • 1、C++文件
      • 2、python文件
    • 5、launch启动文件
      • 1、基本介绍
      • 2、launch简单示例
  • 四、通信机制进阶
    • 1、常用API
      • 1、初始化
      • 2、话题和服务
      • 3、回旋函数
      • 4、时间
      • 5、其他函数
    • 2、自定义头文件和源文件
      • 1、自定义头文件
      • 2、自定义源文件
    • 3、python模块导入
  • 五、ROS运行管理
    • 1、元功能包
    • 2、ROS节点重命名
    • 3、ROS话题名称
    • 4、ROS参数名设置
    • 5、ROS分布式
      • 1.准备
      • 2.配置文件修改
      • 3.配置主机IP
      • 4.配置从机IP
  • 六、ROS常用组件
    • 1、TF坐标变换
      • 1、坐标msg消息
      • 2、静态坐标建立
        • 1、C++实现
        • 2、python实现
      • 3、动态坐标建立
        • 1、C++
        • 2、python
      • 4、多坐标点变换
      • 5、坐标位置查看
      • 6、海龟跟随实战
    • 2、rosbag
      • **命令行**
      • **代码运行**
        • 1、C++实现
      • 2.读bag
        • 2、python实现
      • 1、简单介绍与使用
      • 2、广播与监听编程实现
    • 3、可视化工具
  • 七、机器人系统仿真
    • 1、概述
    • 2、URDF集成Rviz基本流程
      • 1.创建功能包,导入依赖
      • 2.编写 URDF 文件
      • 3.在 launch 文件中集成 URDF 与 Rviz
      • 4.在 Rviz 中显示机器人模型
      • 5.优化 rviz 启动
    • 3、URDF语法学习
      • robot标签
      • link标签
      • joint
      • URDF工具
    • 4、URDF之xacro
      • 1、快速体验
      • 2、xacro语法学习
      • 3、xacro模型实现
    • 5、Rviz控制机器人运动(arbotix)
    • 6、URDF集成Gazebo
      • 1、快速体验
      • 2、gazebo相关设置
      • 3、仿真环境的搭建与使用
    • 7、URDF、Rviz和Gazebo综合使用
  • 八、机器人导航
    • 1、导航概述
    • 2、导航实现
      • 1、准备工作
      • 2、SLAM建图
    • 3、地图服务
    • 4、定位
      • 执行
    • 5、路径规划
    • 6、导航与SLAM建图
      • 1.编写launch文件
      • 2.测试
  • 九、学习资料

一、ROS基础概述

1、操作系统安装

ROS目前只能在基于Unix的平台上运行,因此我们使用Ubuntu来作为ROS的系统。这里我们安装了Ubuntu20.04ROS Noetic Ninjemys。本机和虚拟机安装详见以下文章,选其中一种即可

笔记本安装 Windows10 和 Ubuntu20.04 双系统

VMWare虚拟机安装Ubuntu20.04详细过程

2、ROS Noetic安装

官网ROS Noetic安装教程参考:https://wiki.ros.org/cn/noetic/Installation/Ubuntu

1、配置系统软件源

打开“软件更新”,进入到“Ubuntu软件“页面,允许universe、restricted、multiverse三项,源换成国内源即可。

2、添加ROS软件源

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

3、设置密钥

sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

4、安装ROS

#更新apt索引包
sudo apt update
#安装桌面完整版,若网络原因失败可多安装几次或使用手机热点
sudo apt install ros-noetic-desktop-full

5、设置环境

echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc

注意: 在 ROS 版本 noetic 中无需构建软件包的依赖关系,没有rosdep的相关安装与配置,其他版本需要安装。

6、小海龟测试

终端输入roscore可以查看安装好的ros的发行版名称为noetic和版本号;ctrl+Alt+t再次打开一个新的终端,输入rosrun turtlesim turtlesim_node,即会出现小海龟的仿真界面;再次打开新终端,输入rosrun turtlesim turtle_teleop_key,即可控制小海龟移动了。

7、ROS的卸载

sudo apt remove ros-noetic-*

8、安装构建依赖

#安装构建依赖相关工具
sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential
#初始化rosdep
sudo rosdep init
rosdep update
#若一切顺利即可ok
#--------------------------------
#出现问题后找备份 https://gitee.com/zhao-xuzuo/rosdistro
cd /usr/lib/python3/dist-packages/
find . -type f | xargs grep "raw.githubusercontent"
#修改相关文件,使用sudo gedit
./rosdistro/__init__.py
./rosdep2/gbpdistro_support.py
./rosdep2/sources_list.py
./rosdep2/rep3.py
#文件中涉及的 URL 内容,如果是:raw.githubusercontent.com/ros/rosdistro/master都替换成gitee.com/zhao-xuzuo/rosdistro/raw/master
#修改完毕,再重新执行命令
sudo rosdep init
rosdep update

3、常用工具安装

1、Terminator

sudo apt install terminator
Alt+Up                          //移动到上面的终端
Alt+Down                        //移动到下面的终端
Alt+Left                        //移动到左边的终端
Alt+Right                       //移动到右边的终端
Ctrl+Shift+O                    //水平分割终端
Ctrl+Shift+E                    //垂直分割终端
Ctrl+Shift+C                    //复制选中的内容到剪贴板
Ctrl+Shift+V                    //粘贴剪贴板的内容到此处
Ctrl+Shift+W                    //关闭当前终端
Ctrl+Shift+Q                    //退出当前窗口,当前窗口的所有终端都将被关闭
Ctrl+Shift+X                    //最大化显示当前终端

2、VScode

下载地址:https://code.visualstudio.com/docs?start=true,下载deb,双击进行安装。

#第二种方式安装
sudo dpkg -i xxxx.deb
#卸载
sudo dpkg --purge  code

VScode插件安装

C/C++
python
CMake Tools
ROS
#-------
#在当前目录打开VScode
code .

vscode 中编译 ros

快捷键 ctrl + shift + B 调用编译,选择:catkin_make:build

可以点击配置设置为默认,修改.vscode/tasks.json 文件,此后ctrl + shift + B可直接编译

{// 有关 tasks.json 格式的文档,请参见// https://go.microsoft.com/fwlink/?LinkId=733558"version": "2.0.0","tasks": [{"label": "catkin_make:debug", //代表提示的描述性信息"type": "shell",  //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行"command": "catkin_make",//这个是我们需要运行的命令"args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”"group": {"kind":"build","isDefault":true},"presentation": {"reveal": "always"//可选always或者silence,代表是否输出信息},"problemMatcher": "$msCompile"}]
}

添加功能包,选定 src 右击 —> create catkin package

PS1: 如果没有代码提示

如果VS没有智能提示,修改 .vscode/c_cpp_properties.json

设置 "cppStandard": "c++17"

PS2: main 函数的参数不可以被 const 修饰

PS3: 当ROS__INFO 终端输出有中文时,会出现乱码

#添加任意一句即可
setlocale(LC_CTYPE, "zh_CN.utf8");
setlocale(LC_ALL, "");

PS4:运行python文件可能会找不到文件或目录

因为ubuntu20.04默认使用python3,有三种解决方法

解决1: #!/usr/bin/env python3 直接使用 python3 (不推荐)

解决2: 创建一个链接符号到 python 命令:sudo ln -s /usr/bin/python3 /usr/bin/python

解决3:像C++一样进行链接编译,让程序自动寻找合适解释器

二、核心概念

1、ROS介绍

ROS (Robot Operating System, 机器人操作系统) 提供一系列程序库和工具以帮助软件开发者创建机器人应用软件。它提供了硬件抽象、设备驱动、函数库、可视化工具、消息传递和软件包管理等诸多功能。

ROS中文官网:https://wiki.ros.org/cn

2、ROS核心概念

节点与节点管理器

  • 节点(Node)—— 执行单元
    执行具体任务的进程、独立运行的可执行文件;不同节点可使用不同的编程语言,可分布式运行在不同的主机;节点在系统中的名称必须是唯一的。

  • 节点管理器 (ROS Master)—— 控制中心
    为节点提供命名和注册服务;跟踪和记录话题/服务通信,辅助节点相互查找、 建立连接;提供参数服务器,节点使用此服务器存储和检索 运行时的参数。

话题通信

  • 话题(Topic)—— 异步通信机制
    节点间用来传输数据的重要总线;使用发布(Publisher)/订阅(Subscriber)模型,数据由发布者传输到订阅者,同一个话题的订阅者或发布者可以不唯一。

  • 消息(Message)——话题数据

    话题的具体数据称为消息(Message),使用.msg文件定义;消息用来描述话题当中具体的数据类型,在ROS中,有些消息已经被预定义了,比如雷达、图像等,也可以自定义消息。

服务通信

  • 服务(Service)—— 同步通信机制
    使用客户端(Service)/服务器(Client)模型,客户端发送请求数据,服务器完成 处理后返回应答数据。使用.src文件定义。

话题与服务区别

话题 服务
同步性 异步 同步
通信模型 发布/订阅 服务器/客户端
底层协议 ROSTCP/ROSUDP ROSTCP/ROSUDP
反馈机制
缓冲区
实时性
节点关系 多对多 一对多(一个server)
适用场景 数据传输,连续高频的数据发布与接收:雷达、里程计 逻辑处理,偶尔调用或执行某一项特定功能:拍照、语音识别
通信数据 msg srv

参数

  • 参数(Parameter)—— 全局共享字典
    可通过网络访问的共享、多变量字典;节点使用此服务器来存储和检索运行时的参数;适合存储静态、非二进制的配置参数,不适合存储动态配置的数据。

文件系统

  • 功能包(Package): ROS软件中的基本单元,包含节点源码、配置文件、数据定义等。

  • 功能包清单(Package manifest):记录功能包的基本信息,包含作者信息、许可信息、依赖选项、编译标志等。

  • 元功能包(Meta Packages):组织多个用于同一目的功能包。

3、ROS常用命令行工具

http://wiki.ros.org/ROS/CommandLineTools

#分别在四个终端打开运行
#启动ROS Master
roscore
#启动海龟仿真节点
rosrun turtlesim turtlesim_node
#启动海龟控制节点
rosrun turtlesim turtle_teleop_key
#查看系统运行计算图
rqt_graph

rosnode节点命令

#查看活动节点
rosnode list
#查看某一节点具体信息
rosnode info /turtlesim

rostopic话题命令

#打印pub发布的数据
rostopic echo pub
#以10Hz的频率发布话题移动海龟,这里命令用双击tab补全后修改
#速度为m/s,角度为弧度/s
rostopic pub -r 10 /turtle1/cmd_vel geometry_msgs/Twist "linear:x: 1.0y: 0.0z: 0.0
angular:x: 0.0y: 0.0z: 1.0"

rosmsg消息命令

#查看消息
rosmsg show geometry_msgs/Twist

rosservice服务命令

#打印可用服务
rosservice list
#创建一个新海龟
rosservice call /spawn "x: 2.0
y: 2.0
theta: 0.0
name: 'turtle2'"

rosbag命令

#保存所有步骤,-O代表保存的压缩包路径
rosbag record -a -O cmd_record
#复原所有步骤
rosbag play cmd_record.bag

rossrv

#rossrv是用于显示有关ROS服务类型的信息的命令行工具,与 rosmsg 使用语法高度雷同。
rossrv show    #显示服务消息详情
rossrv info    #显示服务消息相关信息
rossrv list    #列出所有服务信息
rossrv md5    #显示 md5 加密后的服务消息
rossrv package   #显示某个包下所有服务消息
rossrv packages    #显示包含服务消息的所有包

rosparam

#rosparam包含rosparam命令行工具,用于使用YAML编码文件在参数服务器上获取和设置ROS参数。
#列出当前多有参数
rosparam list
#显示某个参数值
rosparam get param_key
#设置某个参数值
rosparam set param_key param_value
#保存参数到文件
rosparam dump file_name
#从文件读取参数
rosparam load file_name
#删除参数
rosparam delete param_key
#增
catkin_create_pkg #自定义包名 依赖包 === 创建新的ROS功能包
sudo apt install xxx #=== 安装 ROS功能包
#删
sudo apt purge xxx #==== 删除某个功能包
#查
rospack list #=== 列出所有功能包
rospack find 包名 #=== 查找某个功能包是否存在,如果存在返回安装路径
roscd 包名 #=== 进入某个功能包
rosls 包名 #=== 列出某个包下的文件
apt search xxx #=== 搜索某个功能包
#改
rosed 包名 文件名 #=== 修改功能包文件
#需要安装 vim
#比如:rosed turtlesim Color.msg
#执行
roscore

三、编程基础

步骤

  • 先创建一个工作空间;
  • 创建一个功能包;
  • 编辑源文件;
  • 编辑配置文件;
  • 编译并执行。

1、工作空间创建

工作空间(workspace)是一个存放工程开发相关文件的文件夹。

  • src:代码空间(Source Space)
  • build:编译空间(Build Space)
  • devel:开发空间(Development Space)
  • install:安装空间(Install Space)

1、创建工作空间catkin_ws

#创建目录
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
#初始化文件夹
catkin_init_workspace
#src文件中创建了一个 CMakeLists.txt 的文件,目的是告诉系统,这个是ROS的工作空间。

**2、编译工作空间 **

#需要在整个工作目录下编译
#编译完成后,会出现build 和 devel两个文件夹
cd ~/catkin_ws/
catkin_make
#生成install文件夹
catkin_make install

3、设置环境变量

echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
#查看当前环境变量
echo $ROS_PACKAGE_PATH

4、创建功能包

#std_msgs:包含常见消息类型,标准消息库
#roscpp:使用C++实现ROS各种功能
#rospy:使用python实现ROS各种功能
#test_pkg就是其中一个功能包,还可以下载其他功能包
cd ~/catkin_ws/src
catkin_create_pkg test_pkg std_msgs roscpp rospy

package.xml是功能包的描述文件,CMakeLists.txt定义编译规则,编译出的文件在devel目录下的include文件夹(头文件)和lib文件夹(可执行文件)

5、编译功能包

cd ~/catkin_ws
catkin_make

2、Topic话题通信

下载功能包

#下面的服务功能都需要依赖以下功能包
cd ~/catkin_ws/srcs
catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim

1、发布者publisher编程实现

指发布程序代码给海龟仿真器,控制海龟运动

1、编写发布者代码

发布者流程

  • 初始化ROS节点;
  • 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型;
  • 创建消息数据;
  • 按照一定频率循环发布消息。

以下两种语言二选一即可

/*** 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist* 将代码保存在功能包下的src目录下,并命名为velocity_publisher.cpp*/#include <ros/ros.h>
#include <geometry_msgs/Twist.h>int main(int argc, char **argv)
{// ROS节点初始化/*param argc 参数个数*param argv 参数列表*param name 节点名称,需要保证其唯一性,不允许包含命名空间*param options 节点启动选项,被封装进了ros::init_options*/ros::init(argc, argv, "velocity_publisher");// 创建节点句柄ros::NodeHandle n;// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);#休眠的原因是注册未完成可能已经发出消息了,导致消息丢失ros::Duration(3).sleep();// 设置循环的频率ros::Rate loop_rate(10);int count = 0;while (ros::ok()){// 初始化geometry_msgs::Twist类型的消息geometry_msgs::Twist vel_msg;vel_msg.linear.x = 0.5;vel_msg.angular.z = 0.2;// 发布消息turtle_vel_pub.publish(vel_msg);ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", vel_msg.linear.x, vel_msg.angular.z);// 按照循环频率延时loop_rate.sleep(); }return 0;
}

python文件需要确保文件拥有可执行权限,这里我把python文件都放在了scripts文件夹下,在此打开终端,运行chmod +x *.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#python程序,区分与cpp文件,将代码保存在功能包下的scripts目录下,并命名为velocity_publisher.py
# 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist
import rospy
from geometry_msgs.msg import Twist
def velocity_publisher():# ROS节点初始化rospy.init_node('velocity_publisher', anonymous=True)# 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)#休眠的原因是注册未完成可能已经发出消息了,导致消息丢失rospy.sleep(3)#设置循环的频率rate = rospy.Rate(10) while not rospy.is_shutdown():# 初始化geometry_msgs::Twist类型的消息vel_msg = Twist()vel_msg.linear.x = 0.5vel_msg.angular.z = 0.2# 发布消息turtle_vel_pub.publish(vel_msg)rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", vel_msg.linear.x, vel_msg.angular.z)# 按照循环频率延时rate.sleep()
if __name__ == '__main__':try:velocity_publisher()except rospy.ROSInterruptException:pass

2、定义编译规则

仅C++代码需要定义编译规则,python代码可直接运行

#打开功能包的CMakeLists.txt文件,在build下添加以下规则
#编译
add_executable(velocity_publisher src/velocity_publisher.cpp)
#链接
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})

3、编译

仅C++代码需要编译,python代码可直接运行

#要在工作空间下编译
cd ~/catkin_ws
catkin_make

4、运行

运行成功后海龟按照发布者的程序进行运动

#首先刷新一下环境变量,放入本地环境变量了也可以不刷新
source ~/catkin_ws/devel/setup.bash
#分别在三个终端打开,就可以看见海龟运行了
roscore
rosrun turtlesim turtlesim_node
#python运行以下命令
#若出现“python3”: 没有那个文件或目录,则需要更改py文件中的python解释器
rosrun learning_topic velocity_publisher.py
#C++运行下面命令
rosrun learning_topic velocity_publisher

2、订阅者subscriber编程实现

流程步骤与发布者相似,订阅者可以监听海龟的位置信息变化并打印在屏幕

订阅者流程

  • 初始化ROS节点;
  • 订阅需要的话题;
  • 循环等待话题消息,接收到消息后进入回调函数;
  • 在回调函数中完成消息处理。

C++订阅代码

/*** 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose*/
#include <ros/ros.h>
#include "turtlesim/Pose.h"
// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{// 将接收到的消息打印出来ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
}
int main(int argc, char **argv)
{// 初始化ROS节点ros::init(argc, argv, "pose_subscriber");// 创建节点句柄ros::NodeHandle n;// 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallbackros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);// 循环等待回调函数ros::spin();return 0;
}

python订阅代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
import rospy
from turtlesim.msg import Pose
def poseCallback(msg):rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f", msg.x, msg.y)
def pose_subscriber():# ROS节点初始化rospy.init_node('pose_subscriber', anonymous=True)# 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallbackrospy.Subscriber("/turtle1/pose", Pose, poseCallback)# 循环等待回调函数rospy.spin()
if __name__ == '__main__':pose_subscriber()

3、自定义话题通信

在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,就会显得力不从心,这时候就需要自定义消息。

ROS Master作为节点管理者,负责节点之间的通信连接,一旦发布者和消息者之间连通后,master关机后两者仍可通信,但无法更改对象

learning_topic文件夹下创建msg文件夹,在里面创建Person.msg文件(注意一定要叫msg文件夹,在CMakeLists.txt规则有写),定义话题消息

string name
uint8 sex
uint8 ageuint8 unknown=0
uint8 male=1
uint8 female=2

在功能包的package.xml文件下添加依赖

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

CMakeLists.txt文件下添加编译规则,C++和python都需要。找到对应的配置文件下添加编译选项

find_package(catkin REQUIRED COMPONENTSgeometry_msgsroscpprospystd_msgsturtlesim#添加message_generation
)
#添加,配置 msg 源文件
add_message_files(FILESPerson.msg
)
#添加,生成消息时依赖于 std_msgs
generate_messages(DEPENDENCIESstd_msgs
)
catkin_package(#添加,执行时依赖CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim message_runtime
)

最后编译生成相关依赖

cd ~/catkin_ws
#编译
catkin_make

C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)

Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/msg)

1、C++文件

C++文件放置在功能包learning_topic下的src文件夹

为了方便代码提示,可在vscode中的c_cpp_properties.json下的includePath中添加编译好的头文件目录,若存在就无须配置,我的是"/home/shawn/catkin_ws/src/learning_parameter/include/**",

发布者

/*** 该例程将发布/person_info话题,自定义消息类型learning_topic::Person*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{// ROS节点初始化ros::init(argc, argv, "person_publisher");// 创建节点句柄ros::NodeHandle n;// 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);// 设置循环的频率ros::Rate loop_rate(1);int count = 0;while (ros::ok()){// 初始化learning_topic::Person类型的消息learning_topic::Person person_msg;person_msg.name = "Tom";person_msg.age  = 18;person_msg.sex  = learning_topic::Person::male;// 发布消息person_info_pub.publish(person_msg);ROS_INFO("Publish Person Info: name:%s  age:%d  sex:%d", person_msg.name.c_str(), person_msg.age, person_msg.sex);// 按照循环频率延时loop_rate.sleep();}return 0;
}

订阅者

/*** 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{// 将接收到的消息打印出来ROS_INFO("Subcribe Person Info: name:%s  age:%d  sex:%d", msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{// 初始化ROS节点ros::init(argc, argv, "person_subscriber");// 创建节点句柄ros::NodeHandle n;// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallbackros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);// 循环等待回调函数ros::spin();return 0;
}

CMakeLists.txt文件下的build下添加编译规则

add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)

编译与运行

cd ~/catkin_ws
catkin_make
roscore
rosrun learning_topic person_publisher
rosrun learning_topic person_subscriber
#查看节点图
rosrun rqt_graph rqt_graph
2、python文件

py文件都放置在功能包learning_topic下的scripts文件夹

为了方便代码提示,可在vscode中的settings.json下的python.autoComplete.extraPaths中添加编译好的头文件目录,若存在就无须配置,我的是"/home/shawn/catkin_ws/devel/lib/python3/dist-packages"

发布者

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Persondef velocity_publisher():# ROS节点初始化rospy.init_node('person_publisher', anonymous=True)# 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10)#设置循环的频率rate = rospy.Rate(10) while not rospy.is_shutdown():# 初始化learning_topic::Person类型的消息person_msg = Person()person_msg.name = "Tom"person_msg.age  = 18person_msg.sex  = Person.male# 发布消息person_info_pub.publish(person_msg)rospy.loginfo("Publsh person message[%s, %d, %d]", person_msg.name, person_msg.age, person_msg.sex)# 按照循环频率延时rate.sleep()
if __name__ == '__main__':try:velocity_publisher()except rospy.ROSInterruptException:pass

订阅者

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Persondef personInfoCallback(msg):rospy.loginfo("Subcribe Person Info: name:%s  age:%d  sex:%d", msg.name, msg.age, msg.sex)
def person_subscriber():# ROS节点初始化rospy.init_node('person_subscriber', anonymous=True)# 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallbackrospy.Subscriber("/person_info", Person, personInfoCallback)# 循环等待回调函数rospy.spin()
if __name__ == '__main__':person_subscriber()

运行

#获得可执行权限
chmod +x ~/catkin_ws/src/learning_topic/scripts/*.py
cd ~/catkin_ws
rosrun learning_topic person_subscriber.py
rosrun learning_topic person_publisher.py
#查看节点图
rosrun rqt_graph rqt_graph

3、Service服务通信

下载功能包

cd ~/catkin_ws/src
catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim

1、客户端Client编程实现

C++代码

/*** 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn*/
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "turtle_spawn");// 创建节点句柄ros::NodeHandle node;// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的serviceros::service::waitForService("/spawn");ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");// 初始化turtlesim::Spawn的请求数据turtlesim::Spawn srv;srv.request.x = 2.0;srv.request.y = 2.0;srv.request.name = "turtle2";// 请求服务调用ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]", srv.request.x, srv.request.y, srv.request.name.c_str());add_turtle.call(srv);// 显示服务调用结果ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());return 0;
};

CMakeLists.txt增加编译规则

add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})

运行

cd ~/catkin_ws
#编译
catkin_make
#三个窗口打开
roscore
rosrun turtlesim turtlesim_node
osrun learning_service turtle_spawn

python代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
import sys
import rospy
from turtlesim.srv import Spawndef turtle_spawn():# ROS节点初始化rospy.init_node('turtle_spawn')# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的servicerospy.wait_for_service('/spawn')try:add_turtle = rospy.ServiceProxy('/spawn', Spawn)# 请求服务调用,输入请求数据response = add_turtle(2.0, 2.0, 0.0, "turtle2")return response.nameexcept rospy.ServiceException as e:print("Service call failed: %s"%e)
if __name__ == "__main__":#服务调用并显示调用结果print("Spwan turtle successfully [name:%s]" %(turtle_spawn()))

运行

cd ~/catkin_ws
#三个窗口打开
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service turtle_spawn.py

2、服务端Service编程实现

C++代码

/*** 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger*/
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>
ros::Publisher turtle_vel_pub;
bool pubCommand = false;
// service回调函数,输入参数req,输出参数res
bool commandCallback(std_srvs::Trigger::Request  &req,std_srvs::Trigger::Response &res)
{pubCommand = !pubCommand;// 显示请求数据ROS_INFO("Publish turtle velocity command [%s]", pubCommand==true?"Yes":"No");// 设置反馈数据res.success = true;res.message = "Change turtle command state!"return true;
}
int main(int argc, char **argv)
{// ROS节点初始化ros::init(argc, argv, "turtle_command_server");// 创建节点句柄ros::NodeHandle n;// 创建一个名为/turtle_command的server,注册回调函数commandCallbackros::ServiceServer command_service = n.advertiseService("/turtle_command", commandCallback);// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);// 循环等待回调函数ROS_INFO("Ready to receive turtle command.");// 设置循环的频率ros::Rate loop_rate(10);while(ros::ok()){// 查看一次回调函数队列ros::spinOnce();// 如果标志为true,则发布速度指令if(pubCommand){geometry_msgs::Twist vel_msg;vel_msg.linear.x = 0.5;vel_msg.angular.z = 0.2;turtle_vel_pub.publish(vel_msg);}//按照循环频率延时loop_rate.sleep();}return 0;
}

python代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger
import rospy
import _thread,time
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse
pubCommand = False;
turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)
def command_thread():   while True:if pubCommand:vel_msg = Twist()vel_msg.linear.x = 0.5vel_msg.angular.z = 0.2turtle_vel_pub.publish(vel_msg)time.sleep(0.1)
def commandCallback(req):global pubCommandpubCommand = bool(1-pubCommand)# 显示请求数据rospy.loginfo("Publish turtle velocity command![%d]", pubCommand)# 反馈数据return TriggerResponse(1, "Change turtle command state!")
def turtle_command_server():# ROS节点初始化rospy.init_node('turtle_command_server')# 创建一个名为/turtle_command的server,注册回调函数commandCallbacks = rospy.Service('/turtle_command', Trigger, commandCallback)# 循环等待回调函数print("Ready to receive turtle command.")_thread.start_new_thread(command_thread, ())rospy.spin()
if __name__ == "__main__":turtle_command_server()

其他操作和客户端相似,海龟启动后通过rosservice call /turtle_command命令改变海龟运动状态

3、自定义数据服务

自定义请求和应答内容,通过请求进行控制

learning_service文件夹下创建srv文件夹,在里面创建Person.srv文件(注意一定要叫srv文件夹,在CMakeLists.txt规则有写),定义数据。三杠上方代表Request参数,下方代表Response参数

string name
uint8 age
uint8 sex
uint8 unknown=0
uint8 male=1
uint8 female=2
---
string result

在功能包的package.xml文件下添加依赖

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

CMakeLists.txt文件下添加编译规则,C++和python都需要。找到对应的配置文件下添加编译选项

find_package(catkin REQUIRED COMPONENTSgeometry_msgsroscpprospystd_msgsturtlesim#找到功能包作为依赖message_generation
)
#需要让编译器找到文件在哪
add_service_files(FILESPerson.srv
)
#产生文件依赖
generate_messages(DEPENDENCIESstd_msgs
)
catkin_package(#添加CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim message_runtime
)

最后编译生成相关依赖

cd ~/catkin_ws
#编译
catkin_make
1、C++文件

服务器

/*** 该例程将执行/show_person服务,服务数据类型learning_service::Person*/#include <ros/ros.h>
#include "learning_service/Person.h"
// service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request  &req,learning_service::Person::Response &res)
{// 显示请求数据ROS_INFO("Person: name:%s  age:%d  sex:%d", req.name.c_str(), req.age, req.sex);// 设置反馈数据res.result = "OK";return true;
}
int main(int argc, char **argv)
{// ROS节点初始化ros::init(argc, argv, "person_server");// 创建节点句柄ros::NodeHandle n;// 创建一个名为/show_person的server,注册回调函数personCallbackros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);// 循环等待回调函数ROS_INFO("Ready to show person informtion.");ros::spin();return 0;
}

客户端

/*** 该例程将请求/show_person服务,服务数据类型learning_service::Person*/
#include <ros/ros.h>
#include "learning_service/Person.h"int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "person_client");// 创建节点句柄ros::NodeHandle node;// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的serviceros::service::waitForService("/show_person");ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");// 初始化learning_service::Person的请求数据learning_service::Person srv;srv.request.name = "Tom";srv.request.age  = 20;srv.request.sex  = learning_service::Person::Request::male;// 请求服务调用ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]", srv.request.name.c_str(), srv.request.age, srv.request.sex);person_client.call(srv);// 显示服务调用结果ROS_INFO("Show person result : %s", srv.response.result.c_str());return 0;
};

CMakeLists.txt文件下的build下添加编译规则

add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)

编译运行

cd ~/catkin_ws
catkin_make
roscore
rosrun learning_service person_server
rosrun learning_service person_client
2、python文件

服务器

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将执行/show_person服务,服务数据类型learning_service::Person
import rospy
from learning_service.srv import Person, PersonResponse
def personCallback(req):# 显示请求数据rospy.loginfo("Person: name:%s  age:%d  sex:%d", req.name, req.age, req.sex)# 反馈数据return PersonResponse("OK")
def person_server():# ROS节点初始化rospy.init_node('person_server')# 创建一个名为/show_person的server,注册回调函数personCallbacks = rospy.Service('/show_person', Person, personCallback)# 循环等待回调函数print("Ready to show person informtion.")rospy.spin()
if __name__ == "__main__":person_server()

客户端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程将请求/show_person服务,服务数据类型learning_service::Person
import sys
import rospy
from learning_service.srv import Person, PersonRequest
def person_client():# ROS节点初始化rospy.init_node('person_client')# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的servicerospy.wait_for_service('/show_person')try:person_client = rospy.ServiceProxy('/show_person', Person)# 请求服务调用,输入请求数据response = person_client("Tom", 20, PersonRequest.male)return response.resultexcept rospy.ServiceException as e:print("Service call failed: %s"%e)
if __name__ == "__main__":#服务调用并显示调用结果print(Show person result : %s" %(person_client()))

运行

#获得可执行权限
chmod +x ~/catkin_ws/src/learning_service/scripts/*.py
#三个终端打开
roscore
rosrun learning_service person_server
rosrun learning_service person_client

4、全局参数使用与编程

参数服务器以共享的方式实现不同节点之间数据交互的通信模式,存储一些多节点共享的数据,类似于全局变量。

下载功能包

cd ~/catkin_ws/src
catkin_create_pkg learning_parameter roscpp rospy std_srvs
1、C++文件

和前面一样,需要添加编译规则

/*** 该例程设置/读取海龟例程中的参数*/
#include <string>
#include <ros/ros.h>
#include <std_srvs/Empty.h>int main(int argc, char **argv)
{int red, green, blue;// ROS节点初始化ros::init(argc, argv, "parameter_config");// 创建节点句柄ros::NodeHandle node;//第二种修改方法//node.setParam("")// 读取背景颜色参数ros::param::get("/turtlesim/background_r", red);ros::param::get("/turtlesim/background_g", green);ros::param::get("/turtlesim/background_b", blue);ROS_INFO("Get Backgroud Color[%d, %d, %d]", red, green, blue);// 设置背景颜色参数ros::param::set("/turtlesim/background_r", 255);ros::param::set("/turtlesim/background_g", 255);ros::param::set("/turtlesim/background_b", 255);ROS_INFO("Set Backgroud Color[255, 255, 255]");// 读取背景颜色参数ros::param::get("/turtlesim/background_r", red);ros::param::get("/turtlesim/background_g", green);ros::param::get("/turtlesim/background_b", blue);ROS_INFO("Re-get Backgroud Color[%d, %d, %d]", red, green, blue);// 调用服务,刷新背景颜色ros::service::waitForService("/clear");ros::ServiceClient clear_background = node.serviceClient<std_srvs::Empty>("/clear");std_srvs::Empty srv;clear_background.call(srv);sleep(1);return 0;
}

添加编译规则

add_executable(parameter_config src/parameter_config.cpp)
target_link_libraries(parameter_config ${catkin_LIBRARIES})

运行

cd ~/catkin_ws
catkin_make
roscore
rosrun turtlesim turtlesim_node
rosrun learning_parameter parameter_config
2、python文件
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 该例程设置/读取海龟例程中的参数
import sys
import rospy
from std_srvs.srv import Empty
def parameter_config():# ROS节点初始化rospy.init_node('parameter_config', anonymous=True)# 读取背景颜色参数red   = rospy.get_param('/turtlesim/background_r')green = rospy.get_param('/turtlesim/background_g')blue  = rospy.get_param('/turtlesim/background_b')rospy.loginfo("Get Backgroud Color[%d, %d, %d]", red, green, blue)# 设置背景颜色参数rospy.set_param("/turtlesim/background_r", 255);rospy.set_param("/turtlesim/background_g", 255);rospy.set_param("/turtlesim/background_b", 255);rospy.loginfo("Set Backgroud Color[255, 255, 255]");# 读取背景颜色参数red   = rospy.get_param('/turtlesim/background_r')green = rospy.get_param('/turtlesim/background_g')blue  = rospy.get_param('/turtlesim/background_b')rospy.loginfo("Get Backgroud Color[%d, %d, %d]", red, green, blue)# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的servicerospy.wait_for_service('/clear')try:clear_background = rospy.ServiceProxy('/clear', Empty)# 请求服务调用,输入请求数据response = clear_background()return responseexcept rospy.ServiceException as e:print("Service call failed: %s"%e)
if __name__ == "__main__":parameter_config()

运行

cd ~/catkin_ws
roscore
rosrun turtlesim turtlesim_node
rosrun learning_parameter parameter_config.py

5、launch启动文件

传统启动节点的方法:每启动一个节点都要打开一个新的终端运行一个新的命令,当系统中的节点数量许多时,显然是很不方便的,而且命令的输入也可能会发生错误。
launch启动文件的引入:可以同时启动多个节点,并且可以自动启动ROS Master节点管理器以及实现每个节点的各种配置,为多个节点的操作提供很大的便利。

http://wiki.ros.org/roslaunch/XML

1、基本介绍

< launch >
launch文件中的根元素采用< launch >标签定义,里面包含所有节点的启动配置内容

#该属性告知用户当前 launch 文件已经弃用
deprecated = "弃用声明"

< node >

启动节点

<node pkg="turtlesim" name="sim1" type="turtlesim_node"/>
  • pkg:节点所在的功能包名称
  • type:节点的可执行文件名称
  • name:节点运行时的名称
  • output:“log | screen” (可选)日志发送目标,可以设置为 log 日志文件,或 screen 屏幕,默认是 log
  • respawn, required, ns, args

< param > / < rosparam > 参数
设置ROS系统运行中的参数,存储在参数服务器中。其中不同层次的参数是不一样的,在标签中时,相当于私有命名空间。

<param name="output_frame" value="odom"/>
  • name:参数名
  • value:参数值

加载参数文件中的多个参数:

#command="load | dump | delete" (可选,默认 load)
<rosparam file="params.yaml" command="load" ns="params" />

< arg > 参数
launch文件内部的局部变量,仅限于launch文件使用

<arg name="arg-name" default="arg-value" />
  • name:参数名
  • value:参数值

param设置的参数是存储在ROS参数服务器中的,而arg设置的参数仅限于launch文件中
< remap > 重映射
重映射ROS计算图资源的命名。

<remap from="/turtlebot/cmd_vel" to="/cmd_vel"/>
  • from:原命名
  • to:映射之后的命名

< include > 嵌套
包含其他launch文件,类似C语言中的头文件包含。

<include file="$(dirname)/other.launch" />
  • file:包含的其他launch文件路径

< group > 组

  • 标签可以对节点分组,具有 ns 属性,可以让节点归属某个命名空间

< arg > 参数

  • 标签是用于动态传参,类似于函数的参数,可以增强launch文件的灵活性
<launch><arg name="xxx" default="xxx"/><param name="param" value="$(arg xxx)" />
</launch>

2、launch简单示例

首先创建功能包,在里面创建launch文件夹,一般launch都放在里面(可自定义文件夹名)

cd ~/catkin_ws/src
catkin_create_pkg learning_launch

简单订阅者发布者

名为simple.launch

<launch><node pkg="learning_topic" type="person_subscriber" name="talker" output="screen" /><node pkg="learning_topic" type="person_publisher" name="listener" output="screen" />
</launch>
#需要编译才能识别
cd ~/catkin_ws
catkin_make
#若找不到功能包
#运行rospack profile#一键启动
roslaunch learning_launch simple.launch

config的launch文件

config/param.yml文件下

A: 123
B: "hello"
group:C: 456D: "hello"

turtlesim_parameter_config.launch文件

<launch><param name="/turtle_number"   value="2"/><node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node"><param name="turtle_name1"   value="Tom"/><param name="turtle_name2"   value="Jerry"/><rosparam file="$(find learning_launch)/config/param.yaml" command="load"/></node><node pkg="turtlesim" type="turtle_teleop_key" name="turtle_teleop_key" output="screen"/>
</launch>

运行,可以看见节点参数的不同,不同位置显示的参数位置也不同

#回到工作空间目录运行
roslaunch learning_launch turtlesim_parameter_config.launch

remap使用

<launch><include file="$(find learning_launch)/launch/simple.launch" /><node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node"><remap from="/turtle1/cmd_vel" to="/cmd_vel"/></node>
</launch>

四、通信机制进阶

API文档: http://wiki.ros.org/APIs

1、常用API

1、初始化

C++

/** 该函数可以解析并使用节点启动时传入的参数(通过参数设置节点名称、命名空间...) ** 该函数有多个重载版本,如果使用NodeHandle建议调用该版本。 * \param argc 参数个数* \param argv 参数列表* \param name 节点名称,需要保证其唯一性,不允许包含命名空间* \param options 节点启动选项,被封装进了ros::init_options**/
void init(int &argc, char **argv, const std::string& name, uint32_t options = 0);
// 如果要一个节点启动多个
ros::init(argc,argv,"cppserver",ros::init_options::AnonymousName);

python

 """在ROS msater中注册节点@param name: 节点名称,必须保证节点名称唯一,节点名称中不能使用命名空间(不能包含 '/')@type  name: str@param anonymous: 取值为 true 时,为节点名称后缀随机编号@type anonymous: bool"""
def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=False, disable_rosout=False, disable_signals=False, xmlrpc_port=0, tcpros_port=0)

2、话题和服务

/**
* 在 ROS master 注册并返回一个发布者对象,该对象可以发布消息
* 使用示例如下:
*   ros::Publisher pub = handle.advertise<std_msgs::Empty>("my_topic", 1);
* \param queue_size 等待发送给订阅者的最大消息数量
* \param latch (optional) 如果为 true,该话题发布的最后一条消息将被保存,并且后期当有订阅者连接时会将该消息发送给订阅者
* \return 调用成功时,会返回一个发布对象
*/
Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false)

3、回旋函数

spin()和spinOnce()异同

**相同点:**二者都用于处理回调函数;

**不同点:**ros::spin() 是进入了循环执行回调函数,而 ros::spinOnce() 只会执行一次回调函数(没有循环),在 ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行。

4、时间

C++

//时刻
ros::init(argc,argv,"hello_time");
ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
ros::Time right_now = ros::Time::now();//将当前时刻封装成对象
ROS_INFO("当前时刻:%.2f",right_now.toSec());//获取距离 1970年01月01日 00:00:00 的秒数
ROS_INFO("当前时刻:%d",right_now.sec);//获取距离 1970年01月01日 00:00:00 的秒数
ros::Time someTime(100,100000000);// 参数1:秒数  参数2:纳秒
ROS_INFO("时刻:%.2f",someTime.toSec()); //100.10
ros::Time someTime2(100.3);//直接传入 double 类型的秒数
ROS_INFO("时刻:%.2f",someTime2.toSec()); //100.30
//持续时间
ros::Duration du(10);//持续10秒钟,参数是double类型的,以秒为单位
du.sleep();//按照指定的持续时间休眠
ROS_INFO("持续时间:%.2f",du.toSec());//将持续时间换算成秒
ROS_INFO("当前时刻:%.2f",ros::Time::now().toSec());
//PS: time 与 time 不可以相加运算,可以相减
ros::Rate rate(1);//指定频率
rate.sleep();
//定时器
//ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing); 默认自动启动
ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing,true);//只执行一次
// ros::Timer timer = nh.createTimer(ros::Duration(0.5),doSomeThing,false,false);//需要手动启动
// timer.start();
ros::spin(); //必须 spin

python

# 获取当前时刻
right_now = rospy.Time.now()
rospy.loginfo("当前时刻:%.2f",right_now.to_sec())
rospy.loginfo("当前时刻:%.2f",right_now.to_nsec())
# 自定义时刻
some_time1 = rospy.Time(1234.567891011)
# 从时间创建对象
some_time3 = rospy.Time.from_sec(543.21) # from_sec 替换了 from_seconds
rospy.loginfo("设置时刻3:%.2f",some_time3.to_sec())
#持续时间
du = rospy.Duration(3.3)
# 设置执行频率
rate = rospy.Rate(0.5)
rate.sleep()
#定时器
rospy.Timer(rospy.Duration(1),doMsg)
# rospy.Timer(rospy.Duration(1),doMsg,True) # 只执行一次
rospy.spin()

5、其他函数

在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:

  • 节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
  • 同名节点启动,导致现有节点退出;
  • 程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())
//C++
ROS_DEBUG("hello,DEBUG"); //不会输出
ROS_INFO("hello,INFO"); //默认白色字体
ROS_WARN("Hello,WARN"); //默认黄色字体
ROS_ERROR("hello,ERROR");//默认红色字体
ROS_FATAL("hello,FATAL");//默认红色字体
//python
rospy.logdebug("hello,debug")  #不会输出
rospy.loginfo("hello,info")  #默认白色字体
rospy.logwarn("hello,warn")  #默认黄色字体
rospy.logerr("hello,error")  #默认红色字体
rospy.logfatal("hello,fatal") #默认红色字体

2、自定义头文件和源文件

1、自定义头文件

在功能包下的 include/功能包名 目录下新建头文件: hello.h

#ifndef _HELLO_H
#define _HELLO_H
namespace hello_ns{class HelloPub {public:void run();
};
}
#endif

在 VScode 中,为了后续包含头文件时不抛出异常,配置 .vscode 下 c_cpp_properties.jsonincludepath属性

"/home/用户/工作空间/src/功能包/include/**"

在 src 目录下新建文件hello.cpp

#include "ros/ros.h"
#include "test_pkg/hello.h"
namespace hello_ns {void HelloPub::run(){ROS_INFO("自定义头文件的使用....");
}
}
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");ros::init(argc,argv,"test_head_node");hello_ns::HelloPub helloPub;helloPub.run();return 0;
}

最后进行文件的配置和运行

#取消include注释
include_directories(
include${catkin_INCLUDE_DIRS}
)add_executable(hello src/hello.cpp)
add_dependencies(hello ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(hello${catkin_LIBRARIES}
)

2、自定义源文件

头文件设置和上一节相似,命名为hello1.h,配置好VS。

在 src 目录下新建文件hello1.cpp,示例内容如下:

#include "test_pkg/hello1.h"
#include "ros/ros.h"
namespace hello_ns{void HelloPub::run(){ROS_INFO("hello,head and src ...");
}
}

在 src 目录下新建文件 use_head.cpp,示例内容如下:

#include "test_pkg/hello1.h"
#include "ros/ros.h"
namespace hello_ns{void HelloPub::run(){ROS_INFO("hello,head and src ...");
}
}

头文件与源文件相关配置:

include_directories(
include${catkin_INCLUDE_DIRS}
)
## 声明C++库
add_library(headinclude/test_pkg/hello1.hsrc/hello1.cpp
)
add_dependencies(head ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(head${catkin_LIBRARIES}
)

可执行文件配置:

add_executable(use_head src/use_head.cpp)
add_dependencies(use_head ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
#此处需要添加之前设置的 head 库
target_link_libraries(use_headhead${catkin_LIBRARIES}
)

3、python模块导入

在同一目录下导入另外的py文件,可能会报错:模块未找到。因为rosrun的工作目录时在当前工作空间中

import os
import sys
path = os.path.abspath(".")
# 核心
sys.path.insert(0,path + "/src/test_pkg/scripts")

五、ROS运行管理

1、元功能包

MetaPackage是Linux的一个文件管理系统的概念。是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。

1、新建一个功能包

**2、修改package.xml **

 <exec_depend>被集成的功能包</exec_depend>.....<export><metapackage /></export>

3、修改 CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)
project(demo)
find_package(catkin REQUIRED)
catkin_metapackage()

PS:CMakeLists.txt 中不可以有换行。

参考文档:http://wiki.ros.org/catkin/package.xml#Metapackages

2、ROS节点重命名

#1、重定向命名空间和运行名字可分开
rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn
#2、launch的重命名
<node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>
#3、两种方法代码中设置
ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);
rospy.init_node("lisi",anonymous=True)

3、ROS话题名称

在 ROS 中节点终端,不同的节点之间通信都依赖于话题,话题名称也可能出现重复的情况,这种情况下,系统虽然不会抛出异常,但是可能导致订阅的消息非预期的,从而导致节点运行异常。这种情况下需要将两个节点的话题名称由相同修改为不同。

话题种类

  • 全局(参数名称直接参考ROS系统,与节点命名空间平级)
  • 相对(参数名称参考的是节点的命名空间,与节点名称平级)
  • 私有(参数名称参考节点名称,是节点名称的子级)

在ROS中提供了一个比较好用的键盘控制功能包: ros-noetic-teleop-twist-keyboard,该功能包,可以控制机器人的运动,作用类似于乌龟的键盘控制节点。如果没有,则sudo apt install ros-noetic-teleop-twist-keyboard进行安装,然后执行: rosrun teleop_twist_keyboard teleop_twist_keyboard.py

#1、通过rosrun重命名
rosrun名称重映射语法: rorun 包名 节点名 话题名:=新话题名称
#2、launch 文件设置话题重映射语法
<node pkg="xxx" type="xxx" name="xxx"><remap from="原话题" to="新话题" />
</node>
#3、在代码中进行编写
#C++编写
#全局,/chatter
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000);
#相对,xxx/chatter
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
#私有,/xxx/hello/chatter
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
#python编写
pub = rospy.Publisher("/chatter",String,queue_size=1000)
pub = rospy.Publisher("chatter",String,queue_size=1000)
pub = rospy.Publisher("~chatter",String,queue_size=1000)

4、ROS参数名设置

关于参数重名的处理,没有重映射实现,为了尽量的避免参数重名,都是使用为参数名添加前缀的方式,实现类似于话题名称,有全局、相对、和私有三种类型之分。

#命令行设置
rosrun 包名 节点名称 _参数名:=参数值
#launch文件设置
#输出 /p1
#    /t1/p1
<launch><param name="p1" value="100" /><node pkg="turtlesim" type="turtlesim_node" name="t1"><param name="p2" value="100" /></node>
</launch>
#代码设置
#C++
ros::param::set("/set_A",100); #全局,和命名空间以及节点名称无关
ros::param::set("set_B",100); #相对,参考命名空间
ros::param::set("~set_C",100); #私有,参考命名空间与节点名称
#python
rospy.set_param("/py_A",100)  #全局,和命名空间以及节点名称无关
rospy.set_param("py_B",100)  #相对,参考命名空间
rospy.set_param("~py_C",100)  #私有,参考命名空间与节点名称

5、ROS分布式

ROS是一个分布式计算环境。一个运行中的ROS系统可以包含分布在多台计算机上多个节点。根据系统的配置方式,任何节点可能随时需要与任何其他节点进行通信。

1.准备

先要保证不同计算机处于同一网络中,最好分别设置固定IP,如果为虚拟机,需要将网络适配器改为桥接模式;

2.配置文件修改

分别修改不同计算机的 /etc/hosts 文件,在该文件中加入对方的IP地址和计算机名:

#主机端:
从机的IP    从机计算机名
#从机端:
主机的IP    主机算机名

设置完毕,可以通过 ping 命令测试网络通信是否正常。

#IP地址查看
ifconfig
#计算机名称查看
hostname
3.配置主机IP

配置主机的 IP 地址

~/.bashrc 追加

export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=主机IP
4.配置从机IP

配置从机的 IP 地址,从机可以有多台,每台都做如下设置:

~/.bashrc 追加

export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=从机IP

完毕后即可分布式通信

六、ROS常用组件

参考: http://wiki.ros.org/tf2

1、TF坐标变换

创建功能包

cd ~/catkin_ws/src
catkin_create_pkg learning_tf roscpp rospy tf2 tf2_ros tf2_geometry_msgs std_msgs geometry_msgs

1、坐标msg消息

订阅发布模型中数据载体 msg 是一个重要实现,首先需要了解一下,在坐标转换实现中常用的 msg:geometry_msgs/TransformStampedgeometry_msgs/PointStamped。前者用于传输坐标系相关位置信息,后者用于传输某个坐标系内坐标点的信息。在坐标变换中,频繁的需要使用到坐标系的相对关系以及坐标点信息。

2、静态坐标建立

使用命令行设置静态坐标变换(推荐)

rosrun tf2_ros static_transform_publisher x偏移量 y偏移量 z偏移量 z偏航角度 y俯仰角度 x翻滚角度 父级坐标系 子级坐标系

1、C++实现

发布方

#include "ros/ros.h"
#include "tf2_ros/static_transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"static_brocast");// 3.创建静态坐标转换广播器tf2_ros::StaticTransformBroadcaster broadcaster;// 4.创建坐标系信息geometry_msgs::TransformStamped ts;//----设置头信息ts.header.seq = 100;ts.header.stamp = ros::Time::now();ts.header.frame_id = "base_link";//----设置子级坐标系ts.child_frame_id = "laser";//----设置子级相对于父级的偏移量ts.transform.translation.x = 0.2;ts.transform.translation.y = 0.0;ts.transform.translation.z = 0.5;//----设置四元数:将 欧拉角数据转换成四元数tf2::Quaternion qtn;qtn.setRPY(0,0,0);ts.transform.rotation.x = qtn.getX();ts.transform.rotation.y = qtn.getY();ts.transform.rotation.z = qtn.getZ();ts.transform.rotation.w = qtn.getW();// 5.广播器发布坐标系信息broadcaster.sendTransform(ts);ros::spin();return 0;
}

订阅者

#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" //注意: 调用 transform 必须包含该头文件
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"tf_sub");ros::NodeHandle nh;// 3.创建 TF 订阅节点tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);ros::Rate r(1);while (ros::ok()){// 4.生成一个坐标点(相对于子级坐标系)geometry_msgs::PointStamped point_laser;point_laser.header.frame_id = "laser";point_laser.header.stamp = ros::Time::now();point_laser.point.x = 1;point_laser.point.y = 2;point_laser.point.z = 7.3;// 5.转换坐标点(相对于父级坐标系)//新建一个坐标点,用于接收转换结果  //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------try{geometry_msgs::PointStamped point_base;point_base = buffer.transform(point_laser,"base_link");ROS_INFO("转换后的数据:(%.2f,%.2f,%.2f),参考的坐标系是:",point_base.point.x,point_base.point.y,point_base.point.z,point_base.header.frame_id.c_str());}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("程序异常.....");}r.sleep();  ros::spinOnce();}return 0;
}
2、python实现

发布方

#! /usr/bin/env python
# 1.导包
import rospy
import tf2_ros
import tf
from geometry_msgs.msg import TransformStampedif __name__ == "__main__":# 2.初始化 ROS 节点rospy.init_node("static_tf_pub_p")# 3.创建 静态坐标广播器broadcaster = tf2_ros.StaticTransformBroadcaster()# 4.创建并组织被广播的消息tfs = TransformStamped()# --- 头信息tfs.header.frame_id = "world"tfs.header.stamp = rospy.Time.now()tfs.header.seq = 101# --- 子坐标系tfs.child_frame_id = "radar"# --- 坐标系相对信息# ------ 偏移量tfs.transform.translation.x = 0.2tfs.transform.translation.y = 0.0tfs.transform.translation.z = 0.5# ------ 四元数qtn = tf.transformations.quaternion_from_euler(0,0,0)tfs.transform.rotation.x = qtn[0]tfs.transform.rotation.y = qtn[1]tfs.transform.rotation.z = qtn[2]tfs.transform.rotation.w = qtn[3]# 5.广播器发送消息broadcaster.sendTransform(tfs)# 6.spinrospy.spin()

订阅者

#! /usr/bin/env python
# 1.导包
import rospy
import tf2_ros
# 不要使用 geometry_msgs,需要使用 tf2 内置的消息类型
from tf2_geometry_msgs import PointStamped
# from geometry_msgs.msg import PointStamped
if __name__ == "__main__":# 2.初始化 ROS 节点rospy.init_node("static_sub_tf_p")# 3.创建 TF 订阅对象buffer = tf2_ros.Buffer()listener = tf2_ros.TransformListener(buffer)rate = rospy.Rate(1)while not rospy.is_shutdown():    # 4.创建一个 radar 坐标系中的坐标点point_source = PointStamped()point_source.header.frame_id = "radar"point_source.header.stamp = rospy.Time.now()point_source.point.x = 10point_source.point.y = 2point_source.point.z = 3try:#5.调研订阅对象的 API 将 4 中的点坐标转换成相对于 world 的坐标point_target = buffer.transform(point_source,"world")rospy.loginfo("转换结果:x = %.2f, y = %.2f, z = %.2f",point_target.point.x,point_target.point.y,point_target.point.z)except Exception as e:rospy.logerr("异常:%s",e)#6.spinrate.sleep()

3、动态坐标建立

1、C++

发布方

// 1.包含头文件
#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "tf2_ros/transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"void doPose(const turtlesim::Pose::ConstPtr& pose){//  5-1.创建 TF 广播器static tf2_ros::TransformBroadcaster broadcaster;//  5-2.创建 广播的数据(通过 pose 设置)geometry_msgs::TransformStamped tfs;//  |----头设置tfs.header.frame_id = "world";tfs.header.stamp = ros::Time::now();//  |----坐标系 IDtfs.child_frame_id = "turtle1";//  |----坐标系相对信息设置tfs.transform.translation.x = pose->x;tfs.transform.translation.y = pose->y;tfs.transform.translation.z = 0.0; // 二维实现,pose 中没有z,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();//  5-3.广播器发布数据broadcaster.sendTransform(tfs);
}
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"dynamic_tf_pub");// 3.创建 ROS 句柄ros::NodeHandle nh;// 4.创建订阅对象//turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度ros::Subscriber sub = nh.subscribe<turtlesim::Pose>("/turtle1/pose",1000,doPose);// 5.回调函数处理订阅到的数据(实现TF广播)       // 6.spinros::spin();return 0;
}

订阅方

//1.包含头文件
#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" //注意: 调用 transform 必须包含该头文件
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"dynamic_tf_sub");ros::NodeHandle nh;// 3.创建 TF 订阅节点tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);ros::Rate r(1);while (ros::ok()){// 4.生成一个坐标点(相对于子级坐标系)geometry_msgs::PointStamped point_laser;point_laser.header.frame_id = "turtle1";point_laser.header.stamp = ros::Time();point_laser.point.x = 1;point_laser.point.y = 1;point_laser.point.z = 0;// 5.转换坐标点(相对于父级坐标系)//新建一个坐标点,用于接收转换结果  //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------try{geometry_msgs::PointStamped point_base;point_base = buffer.transform(point_laser,"world");ROS_INFO("坐标点相对于 world 的坐标为:(%.2f,%.2f,%.2f)",point_base.point.x,point_base.point.y,point_base.point.z);}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("程序异常:%s",e.what());}r.sleep();  ros::spinOnce();}return 0;
}
2、python

发布方

#! /usr/bin/env python
# 1.导包
import rospy
import tf2_ros
import tf
from turtlesim.msg import Pose
from geometry_msgs.msg import TransformStamped
#     4.回调函数处理
def doPose(pose):#         4-1.创建 TF 广播器broadcaster = tf2_ros.TransformBroadcaster()#         4-2.创建 广播的数据(通过 pose 设置)tfs = TransformStamped()tfs.header.frame_id = "world"tfs.header.stamp = rospy.Time.now()tfs.child_frame_id = "turtle1"tfs.transform.translation.x = pose.xtfs.transform.translation.y = pose.ytfs.transform.translation.z = 0.0qtn = tf.transformations.quaternion_from_euler(0,0,pose.theta)tfs.transform.rotation.x = qtn[0]tfs.transform.rotation.y = qtn[1]tfs.transform.rotation.z = qtn[2]tfs.transform.rotation.w = qtn[3]#         4-3.广播器发布数据broadcaster.sendTransform(tfs)
if __name__ == "__main__":# 2.初始化 ROS 节点rospy.init_node("dynamic_tf_pub_p")# 3.订阅 /turtle1/pose 话题消息sub = rospy.Subscriber("/turtle1/pose",Pose,doPose)#     4.回调函数处理#         4-1.创建 TF 广播器#         4-2.创建 广播的数据(通过 pose 设置)#         4-3.广播器发布数据#     5.spinrospy.spin()

订阅方

#! /usr/bin/env python
# 1.导包
import rospy
import tf2_ros
# 不要使用 geometry_msgs,需要使用 tf2 内置的消息类型
from tf2_geometry_msgs import PointStamped
# from geometry_msgs.msg import PointStamped
if __name__ == "__main__":# 2.初始化 ROS 节点rospy.init_node("static_sub_tf_p")# 3.创建 TF 订阅对象buffer = tf2_ros.Buffer()listener = tf2_ros.TransformListener(buffer)rate = rospy.Rate(1)while not rospy.is_shutdown():    # 4.创建一个 radar 坐标系中的坐标点point_source = PointStamped()point_source.header.frame_id = "turtle1"point_source.header.stamp = rospy.Time.now()point_source.point.x = 10point_source.point.y = 2point_source.point.z = 3try:#5.调研订阅对象的 API 将 4 中的点坐标转换成相对于 world 的坐标point_target = buffer.transform(point_source,"world",rospy.Duration(1))rospy.loginfo("转换结果:x = %.2f, y = %.2f, z = %.2f",point_target.point.x,point_target.point.y,point_target.point.z)except Exception as e:rospy.logerr("异常:%s",e)#6.spinrate.sleep()

最后启动即可监听到动态坐标

#启动海龟节点
rosrun turtlesim turtlesim_node
#启动海龟控制节点
rosrun turtlesim turtle_teleop_key

4、多坐标点变换

发布方

为了方便,使用静态坐标变换发布

<launch><node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="0.2 0.8 0.3 0 0 0 /world /son1" output="screen" /><node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="0.5 0 0 0 0 0 /world /son2" output="screen" />
</launch>

订阅方

c++实现

//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
#include "geometry_msgs/TransformStamped.h"
#include "geometry_msgs/PointStamped.h"
int main(int argc, char *argv[])
{   setlocale(LC_ALL,"");// 2.初始化 ros 节点ros::init(argc,argv,"sub_frames");// 3.创建 ros 句柄ros::NodeHandle nh;// 4.创建 TF 订阅对象tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer);// 5.解析订阅信息中获取 son1 坐标系原点在 son2 中的坐标ros::Rate r(1);while (ros::ok()){try{//   解析 son1 中的点相对于 son2 的坐标geometry_msgs::TransformStamped tfs = buffer.lookupTransform("son2","son1",ros::Time(0));ROS_INFO("Son1 相对于 Son2 的坐标关系:父坐标系ID=%s",tfs.header.frame_id.c_str());ROS_INFO("Son1 相对于 Son2 的坐标关系:子坐标系ID=%s",tfs.child_frame_id.c_str());ROS_INFO("Son1 相对于 Son2 的坐标关系:x=%.2f,y=%.2f,z=%.2f",tfs.transform.translation.x,tfs.transform.translation.y,tfs.transform.translation.z);// 坐标点解析geometry_msgs::PointStamped ps;ps.header.frame_id = "son1";ps.header.stamp = ros::Time::now();ps.point.x = 1.0;ps.point.y = 2.0;ps.point.z = 3.0;geometry_msgs::PointStamped psAtSon2;psAtSon2 = buffer.transform(ps,"son2");ROS_INFO("在 Son2 中的坐标:x=%.2f,y=%.2f,z=%.2f",psAtSon2.point.x,psAtSon2.point.y,psAtSon2.point.z);}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("异常信息:%s",e.what());}r.sleep();// 6.spinros::spinOnce();}return 0;
}

python实现

#!/usr/bin/env python
# 1.导包
import rospy
import tf2_ros
from geometry_msgs.msg import TransformStamped
from tf2_geometry_msgs import PointStampedif __name__ == "__main__":# 2.初始化 ROS 节点rospy.init_node("frames_sub_p")# 3.创建 TF 订阅对象buffer = tf2_ros.Buffer()listener = tf2_ros.TransformListener(buffer)rate = rospy.Rate(1)while not rospy.is_shutdown():try:# 4.调用 API 求出 son1 相对于 son2 的坐标关系#lookup_transform(self, target_frame, source_frame, time, timeout=rospy.Duration(0.0)):tfs = buffer.lookup_transform("son2","son1",rospy.Time(0))rospy.loginfo("son1 与 son2 相对关系:")rospy.loginfo("父级坐标系:%s",tfs.header.frame_id)rospy.loginfo("子级坐标系:%s",tfs.child_frame_id)rospy.loginfo("相对坐标:x=%.2f, y=%.2f, z=%.2f",tfs.transform.translation.x,tfs.transform.translation.y,tfs.transform.translation.z,)# 5.创建一依赖于 son1 的坐标点,调用 API 求出该点在 son2 中的坐标point_source = PointStamped()point_source.header.frame_id = "son1"point_source.header.stamp = rospy.Time.now()point_source.point.x = 1point_source.point.y = 1point_source.point.z = 1point_target = buffer.transform(point_source,"son2",rospy.Duration(0.5))rospy.loginfo("point_target 所属的坐标系:%s",point_target.header.frame_id)rospy.loginfo("坐标点相对于 son2 的坐标:(%.2f,%.2f,%.2f)",point_target.point.x,point_target.point.y,point_target.point.z)except Exception as e:rospy.logerr("错误提示:%s",e)rate.sleep()# 6.spin    # rospy.spin()

5、坐标位置查看

可视化

启动后输入rviz,可进入可视化界面,按要求添加即可观察

命令行

#没安装进行安装
sudo apt install ros-noetic-tf2-tools
#生成当前目录的pdf文件
rosrun tf2_tools view_frames.py

6、海龟跟随实战

这里使用python编写,C++和python原理一样

服务客户端(生成新海龟)

#! /usr/bin/env python
"""  调用 service 服务在窗体指定位置生成一只乌龟流程:1.导包2.初始化 ros 节点3.创建服务客户端4.等待服务启动5.创建请求数据6.发送请求并处理响应
"""
#1.导包
import rospy
from turtlesim.srv import Spawn, SpawnRequest, SpawnResponseif __name__ == "__main__":# 2.初始化 ros 节点rospy.init_node("turtle_spawn_p")# 3.创建服务客户端client = rospy.ServiceProxy("/spawn",Spawn)# 4.等待服务启动client.wait_for_service()# 5.创建请求数据req = SpawnRequest()req.x = 1.0req.y = 1.0req.theta = 3.14req.name = "turtle2"# 6.发送请求并处理响应try:response = client.call(req)rospy.loginfo("乌龟创建成功,名字是:%s",response.name)except Exception as e:rospy.loginfo("服务调用失败....")

发布方(发布两只乌龟坐标信息)

#! /usr/bin/env python
"""  该文件实现:需要订阅 turtle1 和 turtle2 的 pose,然后广播相对 world 的坐标系信息注意: 订阅的两只 turtle,除了命名空间(turtle1 和 turtle2)不同外,其他的话题名称和实现逻辑都是一样的,所以我们可以将所需的命名空间通过 args 动态传入实现流程:1.导包2.初始化 ros 节点3.解析传入的命名空间4.创建订阅对象5.回调函数处理订阅的 pose 信息5-1.创建 TF 广播器5-2.将 pose 信息转换成 TransFormStamped5-3.发布6.spin
"""
# 1.导包
import rospy
import sys
from turtlesim.msg import Pose
from geometry_msgs.msg import TransformStamped
import tf2_ros
import tf_conversionsturtle_name = ""def doPose(pose):# rospy.loginfo("x = %.2f",pose.x)#1.创建坐标系广播器broadcaster = tf2_ros.TransformBroadcaster()#2.将 pose 信息转换成 TransFormStampedtfs = TransformStamped()tfs.header.frame_id = "world"tfs.header.stamp = rospy.Time.now()tfs.child_frame_id = turtle_nametfs.transform.translation.x = pose.xtfs.transform.translation.y = pose.ytfs.transform.translation.z = 0.0qtn = tf_conversions.transformations.quaternion_from_euler(0, 0, pose.theta)tfs.transform.rotation.x = qtn[0]tfs.transform.rotation.y = qtn[1]tfs.transform.rotation.z = qtn[2]tfs.transform.rotation.w = qtn[3]#3.广播器发布 tfsbroadcaster.sendTransform(tfs)if __name__ == "__main__":# 2.初始化 ros 节点rospy.init_node("sub_tfs_p")# 3.解析传入的命名空间rospy.loginfo("-------------------------------%d",len(sys.argv))if len(sys.argv) < 2:rospy.loginfo("请传入参数:乌龟的命名空间")else:turtle_name = sys.argv[1]rospy.loginfo("///乌龟:%s",turtle_name)rospy.Subscriber(turtle_name + "/pose",Pose,doPose)#     4.创建订阅对象#     5.回调函数处理订阅的 pose 信息#         5-1.创建 TF 广播器#         5-2.将 pose 信息转换成 TransFormStamped#         5-3.发布#     6.spinrospy.spin()

订阅方(解析坐标信息并生成速度信息)

#! /usr/bin/env python
"""  订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布实现流程:1.导包2.初始化 ros 节点3.创建 TF 订阅对象4.处理订阅到的 TF4-1.查找坐标系的相对关系4-2.生成速度信息,然后发布
"""
# 1.导包
import rospy
import tf2_ros
from geometry_msgs.msg import TransformStamped, Twist
import mathif __name__ == "__main__":# 2.初始化 ros 节点rospy.init_node("sub_tfs_p")# 3.创建 TF 订阅对象buffer = tf2_ros.Buffer()listener = tf2_ros.TransformListener(buffer)# 4.处理订阅到的 TFrate = rospy.Rate(10)# 创建速度发布对象pub = rospy.Publisher("/turtle2/cmd_vel",Twist,queue_size=1000)while not rospy.is_shutdown():rate.sleep()try:#def lookup_transform(self, target_frame, source_frame, time, timeout=rospy.Duration(0.0)):trans = buffer.lookup_transform("turtle2","turtle1",rospy.Time(0))# rospy.loginfo("相对坐标:(%.2f,%.2f,%.2f)",#             trans.transform.translation.x,#             trans.transform.translation.y,#             trans.transform.translation.z#             )   # 根据转变后的坐标计算出速度和角速度信息twist = Twist()# 间距 = x^2 + y^2  然后开方twist.linear.x = 0.5 * math.sqrt(math.pow(trans.transform.translation.x,2) + math.pow(trans.transform.translation.y,2))twist.angular.z = 4 * math.atan2(trans.transform.translation.y, trans.transform.translation.x)pub.publish(twist)except Exception as e:rospy.logwarn("警告:%s",e)

运行(launch文件)

<launch><node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen" /><node pkg="turtlesim" type="turtle_teleop_key" name="key_control" output="screen"/><node pkg="learning_tf" type="generate_new.py" name="turtle2" output="screen"/><node pkg="learning_tf" type="demo04_pub.py" name="tf1_pub" args="turtle1" output="screen"/><node pkg="learning_tf" type="demo04_pub.py" name="tf2_pub" args="turtle2" output="screen"/><node pkg="learning_tf" type="demo04_sub.py" name="tf_sub" output="screen"/>
</launch>

2、rosbag

在ROS中关于数据的留存以及读取实现

命令行

#录制文件
rosbag record -a -O 目标文件
#查看文件
rosbag info 文件名
#回放文件
rosbag play 文件名

代码运行

1、C++实现

1.写 bag

#include "ros/ros.h"
#include "rosbag/bag.h"
#include "std_msgs/String.h"
int main(int argc, char *argv[])
{ros::init(argc,argv,"bag_write");ros::NodeHandle nh;//创建bag对象rosbag::Bag bag;//打开bag.open("test.bag",rosbag::BagMode::Write);//写std_msgs::String msg;msg.data = "hello world";bag.write("/chatter",ros::Time::now(),msg);bag.write("/chatter",ros::Time::now(),msg);bag.write("/chatter",ros::Time::now(),msg);bag.write("/chatter",ros::Time::now(),msg);//关闭bag.close();return 0;
}

2.读bag

#include "ros/ros.h"
#include "rosbag/bag.h"
#include "rosbag/view.h"
#include "std_msgs/String.h"
#include "std_msgs/Int32.h"
int main(int argc, char *argv[])
{setlocale(LC_ALL,"");ros::init(argc,argv,"bag_read");ros::NodeHandle nh;//创建 bag 对象rosbag::Bag bag;//打开 bag 文件bag.open("test.bag",rosbag::BagMode::Read);//读数据for (rosbag::MessageInstance const m : rosbag::View(bag)){std_msgs::String::ConstPtr p = m.instantiate<std_msgs::String>();if(p != nullptr){ROS_INFO("读取的数据:%s",p->data.c_str());}}//关闭文件流bag.close();return 0;
}
2、python实现

1.写 bag

#! /usr/bin/env python
import rospy
import rosbag
from std_msgs.msg import Stringif __name__ == "__main__":#初始化节点 rospy.init_node("w_bag_p")# 创建 rosbag 对象bag = rosbag.Bag("test.bag",'w')# 写数据s = String()s.data= "hahahaha"bag.write("chatter",s)bag.write("chatter",s)bag.write("chatter",s)# 关闭流bag.close()

2.读bag

#! /usr/bin/env python
import rospy
import rosbag
from std_msgs.msg import Stringif __name__ == "__main__":#初始化节点 rospy.init_node("w_bag_p")# 创建 rosbag 对象bag = rosbag.Bag("test.bag",'r')# 读数据bagMessage = bag.read_messages("chatter")for topic,msg,t in bagMessage:rospy.loginfo("%s,%s,%s",topic,msg,t)# 关闭流bag.close()

1、简单介绍与使用

#这里因为ubuntu20没有python命令,所以我复制了/usr/bin中的python3变成python
roslaunch turtle_tf turtle_tf_demo.launch
#这里可能会遇到bug
#TypeError: cannot use a string pattern on a bytes-like object
#sudo gedit /opt/ros/noetic/lib/tf/view_frames 修改89行
#m = r.search(str(vstr))
#文件会保存在当前目录下
rosrun turtlesim turtle_teleop_key
#坐标系命令行工具
rosrun tf tf_echo turtle1 turtle2
#可视化
rosrun rviz rviz -d `rospack find turtle_tf` /rviz/turtle_rviz.rviz

2、广播与监听编程实现

如何实现一个tf广播器

  • 定义TF广播器(TransformBroadcaster)
  • 创建坐标变换值;
  • 发布坐标变换(sendTransform)

如何实现一个TF监听器

  • 定义TF监听器;(TransformListener)
  • 查找坐标变换;(waitFor Transform、lookupTransform)
cd ~/catkin_ws/src
catkin_create_pkg learning_tf roscpp rospy tf2 tf2_ros tf2_geometry_msgs std_msgs geometry_msgs

这里有点问题

3、可视化工具

rqt

rqt的启动方式有两种:

  • 方式1:rqt
  • 方式2:rosrun rqt_gui rqt_gui

rqt_graph:计算图可视化工具

rqt_console:输出日志信息

rqt_plot:数据绘图工具

rqt_image_view:图像渲染工具

rqt_bag:录制和重放 bag 文件

Rviz:三维可视化工具

GazeBo:三维物理仿真平台

七、机器人系统仿真

1、概述

通过计算机对实体机器人系统进行模拟的技术,在 ROS 中,仿真实现涉及的内容主要有三:对机器人建模(URDF)、创建仿真环境(Gazebo)以及感知环境(Rviz)等系统性实现。

URDF是 Unified Robot Description Format 的首字母缩写,直译为统一(标准化)机器人描述格式,可以以一种 XML 的方式描述机器人的部分结构,比如底盘、摄像头、激光雷达、机械臂以及不同关节的自由度…该文件可以被 C++ 内置的解释器转换成可视化的机器人模型,是 ROS 中实现机器人仿真的重要组件。

RViz 是 ROS Visualization Tool 的首字母缩写,直译为ROS的三维可视化工具。它的主要目的是以三维方式显示ROS消息,可以将 数据进行可视化表达。例如:可以显示机器人模型,可以无需编程就能表达激光测距仪(LRF)传感器中的传感 器到障碍物的距离,RealSense、Kinect或Xtion等三维距离传感器的点云数据(PCD, Point Cloud Data),从相机获取的图像值等。

Gazebo是一款3D动态模拟器,用于显示机器人模型并创建仿真环境,能够在复杂的室内和室外环境中准确有效地模拟机器人。与游戏引擎提供高保真度的视觉模拟类似,Gazebo提供高保真度的物理模拟,其提供一整套传感器模型,以及对用户和程序非常友好的交互方式。

2、URDF集成Rviz基本流程

实现流程:

  1. 准备:新建功能包,导入依赖
  2. 核心:编写 urdf 文件
  3. 核心:在 launch 文件集成 URDF 与 Rviz
  4. 在 Rviz 中显示机器人模型

1.创建功能包,导入依赖

创建一个新的功能包,名称自定义,导入依赖包:urdfxacro

在当前功能包下,再新建几个目录:

urdf: 存储 urdf 文件的目录

meshes:机器人模型渲染文件(暂不使用)

config: 配置文件

launch: 存储 launch 启动文件

2.编写 URDF 文件

新建一个子级文件夹:urdf(可选),文件夹中添加一个demo01.urdf文件,复制如下内容:

<robot name="mycar"><link name="base_link"><visual><geometry><box size="0.5 0.2 0.1" /></geometry></visual></link>
</robot>

3.在 launch 文件中集成 URDF 与 Rviz

launch目录下,新建一个 launch 文件,该 launch 文件需要启动 Rviz,并导入 urdf 文件,Rviz 启动后可以自动载入解析urdf文件,并显示机器人模型

<launch><!-- 设置参数 --><param name="robot_description" textfile="$(find 包名)/urdf/urdf/demo01.urdf.urdf" /><!-- 启动 rviz --><node pkg="rviz" type="rviz" name="rviz" />
</launch>

4.在 Rviz 中显示机器人模型

5.优化 rviz 启动

重复启动launch文件时,Rviz 之前的组件配置信息不会自动保存,需要重复执行步骤4的操作,为了方便使用,需要保存配置,在file->save config as保存在之前的config文件夹中。最后在launch文件中添加即可。

<launch><param name="robot_description" textfile="$(find 包名)/urdf/urdf/urdf01_HelloWorld.urdf" /><node pkg="rviz" type="rviz" name="rviz" args="-d $(find 报名)/config/rviz/show_mycar.rviz" />
</launch>

3、URDF语法学习

https://wiki.ros.org/urdf/XML

robot标签

  • name: 指定机器人模型的名称

link标签

urdf 中的 link 标签用于描述机器人某个部件(也即刚体部分)的外观和物理属性。比如: 机器人底座、轮子、激光雷达、摄像头…每一个部件都对应一个 link, 在 link 标签内,可以设计该部件的形状、尺寸、颜色、惯性矩阵、碰撞参数等一系列属性

  • name:为连杆命名

    • visual —> 描述外观(对应的数据是可视的)

      • geometry 设置连杆的形状

        • 标签1: box(盒状)

          • 属性:size=长(x) 宽(y) 高(z)
        • 标签2: cylinder(圆柱)
          • 属性:radius=半径 length=高度
        • 标签3: sphere(球体)
          • 属性:radius=半径
        • 标签4: mesh(为连杆添加皮肤)
          • 属性: filename=资源路径(格式:package:文件)
      • origin 设置偏移量与倾斜弧度
        • 属性1: xyz=x偏移 y便宜 z偏移
        • 属性2: rpy=x翻滚 y俯仰 z偏航 (单位是弧度)
      • metrial 设置材料属性(颜色)
        • 属性: name
        • 标签: color
          • 属性: rgba=红绿蓝权重值与透明度 (每个权重值以及透明度取值[0,1])
    • collision —> 连杆的碰撞属性
    • Inertial —> 连杆的惯性矩阵
<!--  举例,执行需要通过launch-->
<robot name="mycar"><link name="base_link"><visual><!-- 形状 --><geometry><!-- 长方体的长宽高 --><!-- <box size="0.5 0.3 0.1" /> --><!-- 圆柱,半径和长度 --><!-- <cylinder radius="0.5" length="0.1" /> --><!-- 球体,半径--><!-- <sphere radius="0.3" /> --><mesh filename="package://urdf01_rviz/meshes/autolabor_mini.stl"/></geometry><!-- xyz坐标 rpy翻滚俯仰与偏航角度(3.14=180度 1.57=90度) --><origin xyz="0 0 0" rpy="1.57 0 0" /><!-- 颜色: r=red g=green b=blue a=alpha --><material name="black"><color rgba="0.7 0.5 0 0.5" /></material></visual></link>
</robot>

joint

urdf 中的 joint 标签用于描述机器人关节的运动学和动力学属性,还可以指定关节运动的安全极限,机器人的两个部件(分别称之为 parent link 与 child link)以"关节"的形式相连接,不同的关节有不同的运动形式: 旋转、滑动、固定、旋转速度、旋转角度限制…,比如:安装在底座上的轮子可以360度旋转,而摄像头则可能是完全固定在底座上。

  • name —> 为关节命名
  • type —> 关节运动形式
    • continuous: 旋转关节,可以绕单轴无限旋转
    • revolute: 旋转关节,类似于 continues,但是有旋转角度限制
    • prismatic: 滑动关节,沿某一轴线移动的关节,有位置极限
    • planer: 平面关节,允许在平面正交方向上平移或旋转
    • floating: 浮动关节,允许进行平移、旋转运动
    • fixed: 固定关节,不允许运动的特殊关节

子级标签

  • parent(必需的)

    parent link的名字是一个强制的属性:

    • link:父级连杆的名字,是这个link在机器人结构树中的名字。
  • child(必需的)

    child link的名字是一个强制的属性:

    • link:子级连杆的名字,是这个link在机器人结构树中的名字。
  • origin

    • 属性: xyz=各轴线上的偏移量 rpy=各轴线上的偏移弧度。
  • axis

    • 属性: xyz用于设置围绕哪个关节轴运动。

实例

创建demo03_joint.urdf

<robot name="mycar"><link name="base_footprint"><visual><geometry><sphere radius="0.001" /></geometry></visual></link><!-- 底盘 --><link name="base_link"><visual><geometry><box size="0.5 0.2 0.1" /></geometry><origin xyz="0 0 0" rpy="0 0 0" /><material name="blue"><color rgba="0 0 1.0 0.5" /></material></visual></link><!-- 摄像头 --><link name="camera"><visual><geometry><box size="0.02 0.05 0.05" /></geometry><origin xyz="0 0 0" rpy="0 0 0" /><material name="red"><color rgba="1 0 0 0.5" /></material></visual></link><joint name="link2foot" type="fixed"><parent link="base_footprint"/><child link="base_link" /><!-- 需要计算两个 link 的物理中心之间的偏移量 --><origin xyz="0 0 0.075" rpy="0 0 0" /></joint><!-- 关节 --><joint name="camera2baselink" type="continuous"><parent link="base_link"/><child link="camera" /><!-- 需要计算两个 link 的物理中心之间的偏移量 --><origin xyz="0.2 0 0.075" rpy="0 0 0" /><axis xyz="0 0 1" /></joint>
</robot>

创建demo03_joint.launch

<launch><!-- 设置参数 --><param name="robot_description" textfile="$(find urdf01_rviz)/urdf/urdf/demo03_joint.urdf" /><!-- 启动 rviz --><node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01_rviz)/config/show_mycar.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>

URDF工具

在 ROS 中,提供了一些工具来方便 URDF 文件的编写,比如在文件所在路径运行:

#检查复杂的 urdf 文件是否存在语法问题
check_urdf xxx.urdf
#查看 urdf 模型结构,显示不同 link 的层级关系
urdf_to_graphiz xxx.urdf
#查看生成的pdf
evince xxx.pdf

工具之前,首先需要安装,安装命令:sudo apt install liburdfdom-tools

4、URDF之xacro

Xacro 是 XML Macros 的缩写,Xacro 是一种 XML 宏语言,是可编程的 XML。通过封装固定的逻辑,将逻辑中需要的可变的数据以参数的方式暴露出去,从而提高代码复用率以及程序的安全性。

参考文档:http://wiki.ros.org/xacro

1、快速体验

在功能包下的urdf->xacro目录下新建demo01_urdf.xacro

<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro"><!-- 属性封装 --><xacro:property name="wheel_radius" value="0.0325" /><xacro:property name="wheel_length" value="0.0015" /><xacro:property name="PI" value="3.1415927" /><xacro:property name="base_link_length" value="0.08" /><xacro:property name="lidi_space" value="0.015" /><!-- 宏 --><xacro:macro name="wheel_func" params="wheel_name flag" ><link name="${wheel_name}_wheel"><visual><geometry><cylinder radius="${wheel_radius}" length="${wheel_length}" /></geometry><origin xyz="0 0 0" rpy="${PI / 2} 0 0" /><material name="wheel_color"><color rgba="0 0 0 0.3" /></material></visual></link><!-- 3-2.joint --><joint name="${wheel_name}2link" type="continuous"><parent link="base_link"  /><child link="${wheel_name}_wheel" /><!-- x 无偏移y 车体半径z z= 车体高度 / 2 + 离地间距 - 车轮半径--><origin xyz="0 ${0.1 * flag} ${(base_link_length / 2 + lidi_space - wheel_radius) * -1}" rpy="0 0 0" /><axis xyz="0 1 0" /></joint></xacro:macro><xacro:wheel_func wheel_name="left" flag="1" /><xacro:wheel_func wheel_name="right" flag="-1" />
</robot>

最后命令行进入 xacro文件 所属目录,执行:rosrun xacro xacro xxx.xacro > xxx.urdf, 会将 xacro 文件解析为 urdf 文件

2、xacro语法学习

属性与算数运算

用于封装 URDF 中的一些字段

<!--属性定义-->
<xacro:property name="xxxx" value="yyyy" />
<!--属性调用-->
${属性名称}
<!--算数运算-->
${数学表达式}
${PI / 2}

类似于函数实现,提高代码复用率,优化代码结构,提高安全性

<!--宏定义-->
<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)">.....参数调用格式: ${参数名}
</xacro:macro>
<!--宏调用-->
<xacro:宏名称 参数1=xxx 参数2=xxx/>

文件包含

机器人由多部件组成,不同部件可能封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整机器人

<robot name="xxx" xmlns:xacro="http://wiki.ros.org/xacro"><xacro:include filename="my_base.xacro" /><xacro:include filename="my_camera.xacro" /><xacro:include filename="my_laser.xacro" />....
</robot>

3、xacro模型实现

在xacro文件中

<!--使用 xacro 优化 URDF 版的小车底盘实现:实现思路:1.将一些常量、变量封装为 xacro:property比如:PI 值、小车底盘半径、离地间距、车轮半径、宽度 ....2.使用 宏 封装驱动轮以及支撑轮实现,调用相关宏生成驱动轮与支撑轮-->
<!-- 根标签,必须声明 xmlns:xacro -->
<robot name="my_base" xmlns:xacro="http://www.ros.org/wiki/xacro"><!-- 封装变量、常量 --><xacro:property name="PI" value="3.141"/><!-- 宏:黑色设置 --><material name="black"><color rgba="0.0 0.0 0.0 1.0" /></material><!-- 底盘属性 --><xacro:property name="base_footprint_radius" value="0.001" /> <!-- base_footprint 半径  --><xacro:property name="base_link_radius" value="0.1" /> <!-- base_link 半径 --><xacro:property name="base_link_length" value="0.08" /> <!-- base_link 长 --><xacro:property name="earth_space" value="0.015" /> <!-- 离地间距 --><!-- 底盘 --><link name="base_footprint"><visual><geometry><sphere radius="${base_footprint_radius}" /></geometry></visual></link><link name="base_link"><visual><geometry><cylinder radius="${base_link_radius}" length="${base_link_length}" /></geometry><origin xyz="0 0 0" rpy="0 0 0" /><material name="yellow"><color rgba="0.5 0.3 0.0 0.5" /></material></visual></link><joint name="base_link2base_footprint" type="fixed"><parent link="base_footprint" /><child link="base_link" /><origin xyz="0 0 ${earth_space + base_link_length / 2 }" /></joint><!-- 驱动轮 --><!-- 驱动轮属性 --><xacro:property name="wheel_radius" value="0.0325" /><!-- 半径 --><xacro:property name="wheel_length" value="0.015" /><!-- 宽度 --><!-- 驱动轮宏实现 --><xacro:macro name="add_wheels" params="name flag"><link name="${name}_wheel"><visual><geometry><cylinder radius="${wheel_radius}" length="${wheel_length}" /></geometry><origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" /><material name="black" /></visual></link><joint name="${name}_wheel2base_link" type="continuous"><parent link="base_link" /><child link="${name}_wheel" /><origin xyz="0 ${flag * base_link_radius} ${-(earth_space + base_link_length / 2 - wheel_radius) }" /><axis xyz="0 1 0" /></joint></xacro:macro><xacro:add_wheels name="left" flag="1" /><xacro:add_wheels name="right" flag="-1" /><!-- 支撑轮 --><!-- 支撑轮属性 --><xacro:property name="support_wheel_radius" value="0.0075" /> <!-- 支撑轮半径 --><!-- 支撑轮宏 --><xacro:macro name="add_support_wheel" params="name flag" ><link name="${name}_wheel"><visual><geometry><sphere radius="${support_wheel_radius}" /></geometry><origin xyz="0 0 0" rpy="0 0 0" /><material name="black" /></visual></link><joint name="${name}_wheel2base_link" type="continuous"><parent link="base_link" /><child link="${name}_wheel" /><origin xyz="${flag * (base_link_radius - support_wheel_radius)} 0 ${-(base_link_length / 2 + earth_space / 2)}" /><axis xyz="1 1 1" /></joint></xacro:macro><xacro:add_support_wheel name="front" flag="1" /><xacro:add_support_wheel name="back" flag="-1" />
</robot>

launch文件,注意文件名修改

<launch><param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/demo05_car_base.urdf.xacro" /><node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01_rviz)/config/show_mycar.rviz" /><node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" output="screen" /><node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" output="screen" /><node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" output="screen" />
</launch>

5、Rviz控制机器人运动(arbotix)

实现流程:

  1. 安装 Arbotix
  2. 创建新功能包,准备机器人 urdf、xacro 文件
  3. 添加 Arbotix 配置文件
  4. 编写 launch 文件配置 Arbotix
  5. 启动 launch 文件并控制机器人模型运动

安装

sudo apt install ros-noetic-arbotix

添加 arbotix 所需配置文件

# 该文件是控制器配置,一个机器人模型可能有多个控制器,比如: 底盘、机械臂、夹持器(机械手)....
# 因此,根 name 是 controller
controllers: {# 单控制器设置base_controller: {#类型: 差速控制器type: diff_controller,#参考坐标base_frame_id: base_footprint, #两个轮子之间的间距base_width: 0.2,#控制频率ticks_meter: 2000, #PID控制参数,使机器人车轮快速达到预期速度Kp: 12, Kd: 12, Ki: 0, Ko: 50, #加速限制accel_limit: 1.0 }
}

launch 示例代码

<launch><param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/car.urdf.xacro" /><node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01_rviz)/config/show_mycar.rviz" /><node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" output="screen" /><node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" output="screen" /><!-- < node> 调用了 arbotix_python 功能包下的 arbotix_driver 节点 --><node name="arbotix" pkg="arbotix_python" type="arbotix_driver" output="screen"><!--< rosparam> arbotix 驱动机器人运行时,需要获取机器人信息,可以通过 file 加载配置文件--><rosparam file="$(find urdf01_rviz)/config/control.yml" command="load" /><!--< param> 在仿真环境下,需要配置 sim 为 true--><param name="sim" value="true" /></node>
</launch>

启动launch后,命令行输入rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 0.2, y: 0, z: 0}, angular: {x: 0, y: 0, z: 0.5}}'机器人即可成功启动

6、URDF集成Gazebo

1、快速体验

1.创建功能包

创建新功能包,导入依赖包: urdf、xacro、gazebo_ros、gazebo_ros_control、gazebo_plugins

2.编写URDF文件

<!-- 创建一个机器人模型(盒状即可),显示在 Gazebo 中
-->
<robot name="mycar"><link name="base_link"><visual><geometry><box size="0.5 0.2 0.1" /></geometry><origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" /><material name="yellow"><color rgba="0.5 0.3 0.0 1" /></material></visual><collision><geometry><box size="0.5 0.2 0.1" /></geometry><origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" /></collision><inertial><origin xyz="0 0 0" /><mass value="6" /><inertia ixx="1" ixy="0" ixz="0" iyy="1" iyz="0" izz="1" /></inertial></link><gazebo reference="base_link"><material>Gazebo/Black</material></gazebo></robot>

注意, 当 URDF 需要与 Gazebo 集成时,和 Rviz 有明显区别:

1.必须使用 collision 标签,因为既然是仿真环境,那么必然涉及到碰撞检测,collision 提供碰撞检测的依据。

2.必须使用 inertial 标签,此标签标注了当前机器人某个刚体部分的惯性矩阵,用于一些力学相关的仿真计算。

3.颜色设置,也需要重新使用 gazebo 标签标注,因为之前的颜色设置为了方便调试包含透明度,仿真环境下没有此选项。

3.启动Gazebo并显示模型

launch 文件实现:

<launch><!-- 将 Urdf 文件的内容加载到参数服务器 --><param name="robot_description" textfile="$(find urdf02_gazebo)/urdf/demo01_hello.urdf" /><!-- 启动 gazebo --><include file="$(find gazebo_ros)/launch/empty_world.launch" /><!-- 在 gazebo 中显示机器人模型 --><node pkg="gazebo_ros" type="spawn_model" name="model" args="-urdf -model mycar -param robot_description"  /><!-- 在 Gazebo 中加载一个机器人模型,该功能由 gazebo_ros 下的 spawn_model 提供:-urdf 加载的是 urdf 文件-model mycar 模型名称是 mycar-param robot_description 从参数 robot_description 中载入模型-x 模型载入的 x 坐标-y 模型载入的 y 坐标-z 模型载入的 z 坐标
-->
</launch>

2、gazebo相关设置

1.collision

如果机器人link是标准的几何体形状,和link的 visual 属性设置一致即可。

2.inertial

惯性矩阵的设置需要结合link的质量与外形参数动态生成,标准的球体、圆柱与立方体的惯性矩阵公式如下(已经封装为 xacro 实现):

球体惯性矩阵

<!-- Macro for inertia matrix --><xacro:macro name="sphere_inertial_matrix" params="m r"><inertial><mass value="${m}" /><inertia ixx="${2*m*r*r/5}" ixy="0" ixz="0"iyy="${2*m*r*r/5}" iyz="0" izz="${2*m*r*r/5}" /></inertial></xacro:macro>

圆柱惯性矩阵

<xacro:macro name="cylinder_inertial_matrix" params="m r h"><inertial><mass value="${m}" /><inertia ixx="${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0"iyy="${m*(3*r*r+h*h)/12}" iyz = "0"izz="${m*r*r/2}" /> </inertial></xacro:macro>

立方体惯性矩阵

 <xacro:macro name="Box_inertial_matrix" params="m l w h"><inertial><mass value="${m}" /><inertia ixx="${m*(h*h + l*l)/12}" ixy = "0" ixz = "0"iyy="${m*(w*w + l*l)/12}" iyz= "0"izz="${m*(w*w + h*h)/12}" /></inertial></xacro:macro>

需要注意的是,原则上,除了 base_footprint 外,机器人的每个刚体部分都需要设置惯性矩阵,且惯性矩阵必须经计算得出,如果随意定义刚体部分的惯性矩阵,那么可能会导致机器人在 Gazebo 中出现抖动,移动等现象。

3.颜色设置

在 gazebo 中显示 link 的颜色,必须要使用指定的标签:

<gazebo reference="link节点名称"><material>Gazebo/Blue</material>
</gazebo>

**PS:**material 标签中,设置的值区分大小写,颜色可以设置为 Red Blue Green Black …

3、仿真环境的搭建与使用

对于使用已搭建完的世界,在空世界中包含world文件即可

<include file="$(find gazebo_ros)/launch/empty_world.launch"><arg name="world_name" value="$(find demo02_urdf_gazebo)/worlds/hello.world" />
</include>

对于环境的搭建(要占用大量电脑资源)

  • 方式1: 直接添加内置组件创建仿真环境
  • 方式2: 手动绘制仿真环境(更为灵活)

7、URDF、Rviz和Gazebo综合使用

关于URDF(Xacro)、Rviz 和 Gazebo 三者的关系。 URDF 用于创建机器人模型、Rviz 可以显示机器人感知到的环境信息,Gazebo 用于仿真,可以模拟外界环境,以及机器人的一些传感器
参考文档: http://gazebosim.org/tutorials?tut=ros_gzplugins

八、机器人导航

http://wiki.ros.org/navigation

1、导航概述

全局地图、自身定位、路径规划、运动控制、环境感知

2、导航实现

1、准备工作

  • 安装 gmapping 包(用于构建地图):sudo apt install ros-noetic-gmapping
  • 安装地图服务包(用于保存与读取地图):sudo apt install ros-noetic-map-server
  • 安装 navigation 包(用于定位以及路径规划):sudo apt install ros-noetic-navigation

最后创建新功能包,导入依赖gmapping map_server amcl move_base

2、SLAM建图

官方文档

创建launch文件

<launch>
<param name="use_sim_time" value="true"/><node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen"><remap from="scan" to="scan"/><!-- 雷达话题 --><param name="base_frame" value="base_footprint"/><!--底盘坐标系--><param name="odom_frame" value="odom"/> <!--里程计坐标系--><param name="map_update_interval" value="5.0"/><param name="maxUrange" value="16.0"/><param name="sigma" value="0.05"/><param name="kernelSize" value="1"/><param name="lstep" value="0.05"/><param name="astep" value="0.05"/><param name="iterations" value="5"/><param name="lsigma" value="0.075"/><param name="ogain" value="3.0"/><param name="lskip" value="0"/><param name="srr" value="0.1"/><param name="srt" value="0.2"/><param name="str" value="0.1"/><param name="stt" value="0.2"/><param name="linearUpdate" value="1.0"/><param name="angularUpdate" value="0.5"/><param name="temporalUpdate" value="3.0"/><param name="resampleThreshold" value="0.5"/><param name="particles" value="30"/><param name="xmin" value="-50.0"/><param name="ymin" value="-50.0"/><param name="xmax" value="50.0"/><param name="ymax" value="50.0"/><param name="delta" value="0.05"/><param name="llsamplerange" value="0.01"/><param name="llsamplestep" value="0.01"/><param name="lasamplerange" value="0.005"/><param name="lasamplestep" value="0.005"/></node><node pkg="joint_state_publisher" name="joint_state_publisher" type="joint_state_publisher" /><node pkg="robot_state_publisher" name="robot_state_publisher" type="robot_state_publisher" /><node pkg="rviz" type="rviz" name="rviz" /><!-- 可以保存 rviz 配置并后期直接使用--><!--<node pkg="rviz" type="rviz" name="rviz" args="-d $(find my_nav_sum)/rviz/gmapping.rviz"/>-->
</launch>

首先启动 Gazebo 仿真环境

再启动地图绘制的 launch 文件:

然后启动键盘键盘控制节点,用于控制机器人运动建图

rosrun teleop_twist_keyboard teleop_twist_keyboard.py

最后在 rviz 中添加组件,显示栅格地图

3、地图服务

依次启动仿真环境,键盘控制节点与SLAM节点,绘制地图后启动保存地图的launch文件,即可保存地图信息

<launch><arg name="filename" value="$(find nav_demo)/map/nav" /><node name="map_save" pkg="map_server" type="map_saver" args="-f $(arg filename)" />
</launch>

对于地图的获取,启动新的launch文件,即可发布map地图信息了,之后只需要在rviz订阅/map即可

<launch><!-- 设置地图的配置文件 --><arg name="map" default="nav.yaml" /><!-- 运行地图服务器,并且加载设置的地图--><node name="map_server" pkg="map_server" type="map_server" args="$(find nav_demo)/map/$(arg map)"/>
</launch>

nav.yaml部分详解

#1.声明地图图片资源的路径
image: /home/shawn/catkin_demo/src/nav_demo/map/nav.pgm
#2.地图刻度尺单位是米/像素
resolution: 0.050000
#3.地图的位姿(相对于rViz中的原点的位姿)
origin: [-50.000000,-50.000000,0.0]
#4.占用阈值
occupied thresh: 0.65
#5空闲间值
free thresh: 0.196
#6.取反
negate: 0

4、定位

编写launch文件

<launch>
<node pkg="amcl" type="amcl" name="amcl" output="screen"><!-- Publish scans from best pose at a max of 10 Hz --><param name="odom_model_type" value="diff"/><!-- 里程计模式为差分 --><param name="odom_alpha5" value="0.1"/><param name="transform_tolerance" value="0.2" /><param name="gui_publish_rate" value="10.0"/><param name="laser_max_beams" value="30"/><param name="min_particles" value="500"/><param name="max_particles" value="5000"/><param name="kld_err" value="0.05"/><param name="kld_z" value="0.99"/><param name="odom_alpha1" value="0.2"/><param name="odom_alpha2" value="0.2"/><!-- translation std dev, m --><param name="odom_alpha3" value="0.8"/><param name="odom_alpha4" value="0.2"/><param name="laser_z_hit" value="0.5"/><param name="laser_z_short" value="0.05"/><param name="laser_z_max" value="0.05"/><param name="laser_z_rand" value="0.5"/><param name="laser_sigma_hit" value="0.2"/><param name="laser_lambda_short" value="0.1"/><param name="laser_lambda_short" value="0.1"/><param name="laser_model_type" value="likelihood_field"/><!-- <param name="laser_model_type" value="beam"/> --><param name="laser_likelihood_max_dist" value="2.0"/><param name="update_min_d" value="0.2"/><param name="update_min_a" value="0.5"/><param name="odom_frame_id" value="odom"/><!-- 里程计坐标系 --><param name="base_frame_id" value="base_footprint"/><!-- 添加机器人基坐标系 --><param name="global_frame_id" value="map"/><!-- 添加地图坐标系 --><param name="resample_interval" value="1"/><param name="transform_tolerance" value="0.1"/><param name="recovery_alpha_slow" value="0.0"/><param name="recovery_alpha_fast" value="0.0"/>
</node>
</launch>

测试launch文件

<launch><!-- 设置地图的配置文件 --><arg name="map" default="nav.yaml" /><!-- 运行地图服务器,并且加载设置的地图--><node name="map_server" pkg="map_server" type="map_server" args="$(find nav_demo)/map/$(arg map)"/><!-- 启动AMCL节点 --><include file="$(find nav_demo)/launch/amcl.launch" /><!-- 运行rviz --><node pkg="rviz" type="rviz" name="rviz"/>
</launch>
执行

1.先启动 Gazebo 仿真环境

2.启动键盘控制节点:

rosrun teleop_twist_keyboard teleop_twist_keyboard.py

3.启动上一步中集成地图服务、amcl 与 rviz 的 launch 文件;

4.在启动的 rviz 中,添加RobotModel、Map组件,分别显示机器人模型与地图,添加 posearray 插件,设置topic为particlecloud来显示 amcl 预估的当前机器人的位姿,箭头越是密集,说明当前机器人处于此位置的概率越高;

5、路径规划

创建nav05_path.launch文件

<launch><node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen" clear_params="true"><rosparam file="$(find nav_demo)/param/costmap_common_params.yaml" command="load" ns="local_costmap" /><rosparam file="$(find nav_demo)/param/costmap_common_params.yaml" command="load" ns="global_costmap" /><rosparam file="$(find nav_demo)/param/local_costmap_params.yaml" command="load" /><rosparam file="$(find nav_demo)/param/global_costmap_params.yaml" command="load" /><rosparam file="$(find nav_demo)/param/base_local_planner_params.yaml" command="load" /></node>
</launch>

costmap_common_params.yaml

该文件是move_base 在全局路径规划与本地路径规划时调用的通用参数,包括:机器人的尺寸、距离障碍物的安全距离、传感器信息等。配置参考如下:

#机器人几何参,如果机器人是圆形,设置 robot_radius,如果是其他形状设置 footprint
robot_radius: 0.12 #圆形
# footprint: [[-0.12, -0.12], [-0.12, 0.12], [0.12, 0.12], [0.12, -0.12]] #其他形状obstacle_range: 3.0 # 用于障碍物探测,比如: 值为 3.0,意味着检测到距离小于 3 米的障碍物时,就会引入代价地图
raytrace_range: 3.5 # 用于清除障碍物,比如:值为 3.5,意味着清除代价地图中 3.5 米以外的障碍物#膨胀半径,扩展在碰撞区域以外的代价区域,使得机器人规划路径避开障碍物
inflation_radius: 0.2
#代价比例系数,越大则代价值越小
cost_scaling_factor: 3.0#地图类型
map_type: costmap
#导航包所需要的传感器
observation_sources: scan
#对传感器的坐标系和数据进行配置。这个也会用于代价地图添加和清除障碍物。例如,你可以用激光雷达传感器用于在代价地图添加障碍物,再添加kinect用于导航和清除障碍物。
scan: {sensor_frame: laser, data_type: LaserScan, topic: scan, marking: true, clearing: true}

global_costmap_params.yaml

该文件用于全局代价地图参数设置:

global_costmap:global_frame: map #地图坐标系robot_base_frame: base_footprint #机器人坐标系# 以此实现坐标变换update_frequency: 1.0 #代价地图更新频率publish_frequency: 1.0 #代价地图的发布频率transform_tolerance: 0.5 #等待坐标变换发布信息的超时时间static_map: true # 是否使用一个地图或者地图服务器来初始化全局代价地图,如果不使用静态地图,这个参数为false.

local_costmap_params.yaml

该文件用于局部代价地图参数设置:

local_costmap:global_frame: odom #里程计坐标系robot_base_frame: base_footprint #机器人坐标系update_frequency: 10.0 #代价地图更新频率publish_frequency: 10.0 #代价地图的发布频率transform_tolerance: 0.5 #等待坐标变换发布信息的超时时间static_map: false  #不需要静态地图,可以提升导航效果rolling_window: true #是否使用动态窗口,默认为false,在静态的全局地图中,地图不会变化width: 3 # 局部地图宽度 单位是 mheight: 3 # 局部地图高度 单位是 mresolution: 0.05 # 局部地图分辨率 单位是 m,一般与静态地图分辨率保持一致

base_local_planner_params

基本的局部规划器参数配置,这个配置文件设定了机器人的最大和最小速度限制值,也设定了加速度的阈值。

TrajectoryPlannerROS:# Robot Configuration Parametersmax_vel_x: 0.5 # X 方向最大速度min_vel_x: 0.1 # X 方向最小速速max_vel_theta:  1.0 # min_vel_theta: -1.0min_in_place_vel_theta: 1.0acc_lim_x: 1.0 # X 加速限制acc_lim_y: 0.0 # Y 加速限制acc_lim_theta: 0.6 # 角速度加速限制# Goal Tolerance Parameters,目标公差xy_goal_tolerance: 0.10yaw_goal_tolerance: 0.05# Differential-drive robot configuration
# 是否是全向移动机器人holonomic_robot: false# Forward Simulation Parameters,前进模拟参数sim_time: 0.8vx_samples: 18vtheta_samples: 20sim_granularity: 0.05

最后文件集成

运行仿真环境、运行launch文件

<launch><!-- 设置地图的配置文件 --><arg name="map" default="nav.yaml" /><!-- 运行地图服务器,并且加载设置的地图--><node name="map_server" pkg="map_server" type="map_server" args="$(find nav_demo)/map/$(arg map)"/><!-- 启动AMCL节点 --><include file="$(find nav_demo)/launch/nav04_amcl.launch" /><!-- 运行move_base节点 --><include file="$(find nav_demo)/launch/nav05_path.launch" /><!-- 关节以及机器人状态发布节点 --><node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" /><node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /><!-- 运行rviz --><node pkg="rviz" type="rviz" name="rviz" args="-d $(find nav_demo)/config/nav.rviz" />
</launch>

6、导航与SLAM建图

1.编写launch文件

当前launch文件实现,无需调用map_server的相关节点,只需要启动SLAM节点与move_base节点,示例内容如下:

<launch><!-- 启动SLAM节点 --><include file="$(find nav_demo)/launch/nav01_slam.launch" /><!-- 运行move_base节点 --><include file="$(find nav_demo)/launch/nav05_path.launch" /><!-- 运行rviz --><!-- <node pkg="rviz" type="rviz" name="rviz" args="-d $(find nav_demo)/config/nav.rviz" /> -->
</launch>

2.测试

1.首先运行gazebo仿真环境;

2.然后执行launch文件;

3.在rviz中通过2D Nav Goal设置目标点,机器人开始自主移动并建图了;

4.最后可以使用 map_server 保存地图。

九、学习资料

ROS: https://www.ros.org
ROS Wiki: http://wiki.ros.org
ROSCon 2012~2019:https://roscon.ros.org
ROS Robots: https://robots.ros.org
Ubuntu Wiki: https://wiki.ubuntu.org.cn
古月居:http://www.guyuehome.com
古月居泡泡:https://www.guyuehome.com/Bubble
古月学院:https://class.guyuehome.com
如何学习ROS:
https://mp.weixin.qq.com/s/uYvGuiG-TlOalWUynR2Nzg
一起从零手写URDF模型:
https://class.guyuehome.com/detail/p5eleea4fe1e5c_lgm126Xn/6
如何从Solidworks导出URDF模型
https://class.guyuehome.com/detail/p5e32dce7906e0_6TqS7BwX/6
如何在Gazebo中实现移动机器人仿真:
https://class.guyuehome.com/detail/p_5eb2366befe4aE4rbNmXt/6
Movelt可视化配置及仿真指南
https://class.guyuehome.com/detail/p_5e71966b3fdfd_g4DpRGg9/6

ROS Noetic入门完整版相关推荐

  1. ROS Noetic入门笔记(二)ROS Noetic创建工作空间和功能包

    ROS Noetic入门笔记(一)在ubuntu20.04中安装ROS Noetic并简单测试 ROS Noetic入门笔记(二)ROS Noetic创建工作空间和功能包 ROS Noetic入门笔记 ...

  2. 区块链入门-完整版V1.0-Part10

    EOS柚子 EOS环境部署 作者-磨链社区-KY 随着EOS主网上线的时间越来越近,对于超级节点竞选的话题也越来越多.很多人认为它是区块链3.0技术,可以推动区块链技术的商用落地.作为开发者,我们可以 ...

  3. 尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版(二)

    书城项目 JavaEE三层架构介绍 搭建书城项目环境 IDEA工具Debug的使用 JSP 什么是jsp jsp页面的本质 jsp的page指令 虽然 / 在浏览器解析的时候是端口号,但是jsp本质最 ...

  4. 尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版(一)

    HTML和CSS ctrl + shift + / 在同一行代码中的后半截产生注释 HTML标签的介绍 标签拥有自己的属性,分为 基本属性 和 事件属性 标签的基本属性 :bgcolor=" ...

  5. 尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版(四)

    文件的上传和下载 文件上传的介绍 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns= ...

  6. 尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版(三)

    EL表达式 什么是EL表达式 <%@ page contentType="text/html;charset=UTF-8" language="java" ...

  7. reactjs redux入门完整版示例:store reducer getState dispatch subscribe action

    Redux原理图 暴露store 创建reducer 引入store getState获取redux中保存的state store.dispatch传给reducer 组件内部解决数据更新后页面刷新问 ...

  8. 【资源】100页机器学习入门完整版,初学者必备!

    [导读]近日,作者Andriy Burkov放出了他撰写的<The Hundred-Page Machine Learning Book>的这本书的最新版,只有100页,目标是任何只要有基 ...

  9. Oracle基础入门完整版(课程笔记)

    一 简介 1.为何需要数据库?存储大量数据,方便检索和访问. 2.文件组成: 数据文件:扩展名是.DBF,用于存储数据库数据的文件,数据库表和数据文件不存在一对一对应关系 控制文件:扩展名是.CTL, ...

  10. c语言零基础入门(完整版)

    1软件下载 官网下载:  https://sourceforge.net/projects/orwelldevcpp/ 百度网盘:https://pan.baidu.com/s/1mhHDjO8    ...

最新文章

  1. jquery click 第一次没用_【通知】同济大学研究生会20202021学年第一次主席联席会...
  2. 有效用例模式阅读笔记三
  3. Contains Duplicate II
  4. python计算2的平方代码_python – NumPy计算向量的范数2的平方
  5. 精简改良(生成树dp)
  6. Track your visitors using an HttpModule
  7. 汽车之家的安全框架,是如何从0到1搭建的?
  8. 推荐一个Oracle数据库学习的网站
  9. AWVS下载、安装步骤教程
  10. 基于Spring Boot房产销售平台的设计与实现【源码+论文】分享
  11. Big Faceless Java PDF Viewer library简介
  12. Win7有多条隧道适配器(isatap、teredo、6to4)的原因及关闭方法(转)
  13. 【Mac新手必看】苹果macOS桌面壁纸设置技巧
  14. Ftp-下载较大文件不完整
  15. 2018.12.06 课后习题作业
  16. 数据库的前世今生04
  17. 怎么节省PayPal收款手续费?
  18. 军队文职初试资料 计算机,2020军队文职备考,计算机基础知识!
  19. 一些考研考博的资料(2022.10.16)
  20. 2022 年 CCPC 河南省赛 (A,E,F,G,H)

热门文章

  1. 【常用表】三角函数基本公式
  2. 使用阿里云对象存储OSS搭建网盘
  3. 程序员如何学习量化交易,一文总结
  4. C++入门基础知识总结(2022整理)
  5. 【名企招聘】4月26日19点,涛思数据带着高薪岗位JD和精美周边来啦~
  6. 电子邮件营销的十大技巧
  7. 程序员都知道的二维码扫码登录的底层原理
  8. 计算机关机键 自动重启,我的电脑总是关机后自动重启怎么办?
  9. response.addheader详解
  10. 【C++教程】04.求1加到100