目录
一、安装ROS
虚拟机版本:Ubuntu18.04
ROS版本:Melodic
可以使用鱼香ros简易安装。
# 鱼香ros
sudo wget http://fishros.com/install -O fishros && . fishros
二、第一个ROS例程(小乌龟仿真)
roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
每执行一句命令后,快捷键 ctrl + shift + t 开启新的终端窗口执行下一句命令。
三、创建工作空间
工作空间(workspace)是一个存放工程开发相关文件的文件夹。
src:代码空间
build:编译空间
devel:开发空间
install:安装空间
## 创建工作空间
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
## 编译工作空间
cd ~/catkin_ws/
catkin_make
## 设置环境变量
### 两种方法(建议先使用永久方法):
#### 一次性方法
source devel/setup.bash
#### 永久方法
sudo echo "source ~/catkin_ws/devel/setup.bash">>~/.bashrc
### 重新加载 .bashrc 文件
source ~/.bashrc
## 检查环境变量
echo $ROS_PACKAGE_PATH
四、创建和编译功能包
通常将实现同一具体功能的程序代码放到一个功能包中。
同一工作空间下,不允许存在同名功能包(不同工作空间允许)。
## 创建功能包
## 语法:catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
cd ~/catkin_ws/src
catkin_create_pkg learning_communication std_msgs rospy roscpp
## 编译功能包
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash
catkin_make运行后:
五、ROS通讯编程——话题编程
以下编程内容均使用python代码完成。
5.1 发布话题
cd ~/catkin_ws/src/learning_communication/src
## 创建名为talker_py的python文件,用于发布话题
touch talker_py.py
## 给文件添加执行权限(ls如果py文件没有变绿证明没有执行权限)
chmod 755 talker_py.py
talker_py.py内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy # 导入rospy库
from std_msgs.msg import String # 从std_msgs消息包导入String消息类型
def talker():
# 创建一个Publisher对象,发布到名为'chatter'的话题
# 消息类型为String,队列大小为10
pub = rospy.Publisher('chatter', String, queue_size=10)
# 初始化ROS节点,节点名为'talker_py'
# anonymous=True表示为节点名添加一个随机数后缀以避免重名
rospy.init_node('talker_py', anonymous=True)
# 设置发布频率为5Hz
rate = rospy.Rate(5)
# 循环发布消息,直到节点关闭
while not rospy.is_shutdown():
# 创建一个包含当前时间的字符串消息
hello_str = "hello world %s" % rospy.get_time()
# 输出日志信息
rospy.loginfo(hello_str)
# 发布消息
pub.publish(hello_str)
# 按照之前定义的频率休眠一段时间
rate.sleep()
# 主函数入口
if __name__ == '__main__':
try:
# 调用talker函数
talker()
except rospy.ROSInterruptException:
# 捕捉ROS中断异常并进行处理
pass
保存python文件,运行以下命令:
roscore
# 打开新终端
rosrun learning_communication talker_py.py
运行结果如下:
打开新终端,查看话题列表,发现chatter话题已发布。
# 查看话题列表
rostopic list
5.2 订阅话题
接下来创建话题订阅者,订阅chatter话题:
cd ~/catkin_ws/src/learning_communication/src
## 创建名为talker_py的python文件,用于发布话题
touch listener_py.py
# 添加权限
chmod 755 listener_py.py
listener_py.py内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo("I heard %s", data.data)
def listener():
# 初始化节点
rospy.init_node('listener_py', anonymous=True)
# 创建订阅者,订阅名为chatter的话题,消息类型为String,当接收到消息时调用callback函数
rospy.Subscriber('chatter', String, callback)
# rospy.spin()调用将使节点持续运行,等待回调函数被调用
rospy.spin()
if __name__ == '__main__':
listener()
保存python文件,运行以下命令:
# 不要关闭之前命令
rosrun learning_communication listener_py.py
运行结果如下:
5.3 自定义话题消息
如果我们需要提前自定义消息或服务(下一部分)内的结构,我们需要:
1、定义msg文件
2、在package.xml中添加功能包依赖
3、在CMakeLists.txt添加编译选项
例如,我们定义一个Person的消息,里面包含姓名、年龄、身高、性别。
1、定义msg文件
mkdir -p ~/catkin_ws/src/learning_communication/msg
# 使用roscd更方便,可以直接实现功能包目录跳转
roscd learning_communication/msg
touch Person.msg
Person.msg内容:
string name
int32 age
float32 height
bool is_male
2、在package.xml中添加功能包依赖
打开learning_communication包中的package.xml,添加以下依赖:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
添加后:
3、在CMakeLists.txt添加编译选项
打开learning_communication包中的CMakeLists.txt,添加编译选项:
# (1)
find_package(
......
## 添加
message_generation
)
# (2)
## 添加
add_message_files(
FILES
Person.msg
)
# (3)
## 添加
catkin_package(CATKIN_DEPENDS
roscpp
rospy
std_msgs
message_runtime
)
添加后:
4、查看自定义的消息
rosmsg show Person
六、ROS通讯编程——服务编程
本部分要实现一个ROS服务客户端节点,用于传入两个整数,并打印出它们的和。
6.1 自定义服务请求应答
1、定义srv文件
2、在package.xml中添加功能包依赖
3、在CMakeLists.txt添加编译选项
1、定义srv文件
mkdir -p ~/catkin_ws/src/learning_communication/srv
# 使用roscd更方便,可以直接实现功能包目录跳转
roscd learning_communication/srv
touch AddTwoInts.srv
AddTwoInts.srv内容:
int64 a
int64 b
---
int64 sum
2、在package.xml中添加功能包依赖
按照 2、在package.xml中添加功能包依赖 添加依赖即可
3、在CMakeLists.txt添加编译选项
## 添加
add_service_files(
FILES
AddTwoInts.srv
)
6.2 实现服务器
cd ~/catkin_ws/src/learning_communication/src
touch add_two_server_py.py
# 添加权限
chmod 755 add_two_server_py.py
add_two_server_py.py内容:
#!/usr/bin/env python
from __future__ import print_function
import rospy
from learning_communication.srv import AddTwoInts, AddTwoIntsResponse
def handle_add_two_ints(req):
print("Returning [%s + %s = %s]" % (req.a, req.b, (req.a + req.b)))
return AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node('add_two_server_py')
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
print("Ready to add two ints.")
rospy.spin()
if __name__ == "__main__":
add_two_ints_server()
6.3 实现客户端
cd ~/catkin_ws/src/learning_communication/src
touch add_two_client_py.py
# 添加权限
chmod 755 add_two_client_py.py
add_two_client_py内容:
#!/usr/bin/env python
from __future__ import print_function
import sys
import rospy
from learning_communication.srv import AddTwoInts
def add_two_ints_client(x, y):
rospy.wait_for_service('add_two_ints')
try:
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
resp = add_two_ints(x, y)
return resp.sum
except rospy.ServiceException as e:
print("Service call failed: %s" % e)
if __name__ == "__main__":
if len(sys.argv) == 3:
x = int(sys.argv[1])
y = int(sys.argv[2])
else:
print("%s [x y]" % sys.argv[0])
sys.exit(1)
print("Requesting %s+%s" % (x, y))
print("%s + %s = %s" % (x, y, add_two_ints_client(x, y)))
6.4 运行程序
roscore
# 打开新终端
rosrun learning_communication add_two_server_py.py
# 打开新终端
rosrun learning_communication add_two_client_py 27 3
运行结果如下 :
Server节点启动后的日志信息
Client启动后发布服务请求,并成功接收到反馈结果
Server接收到服务调用后完成加法,将结果反馈给Client
七、ROS通讯编程——动作编程
动作编程相较于服务编程,其动作是异步的,适用于复杂任务处理。
该部分要完成一个洗盘子功能。首先需要实现一个动作服务器,处理洗盘子的任务,并实时反馈进度;还需实现一个动作客户端,发送洗盘子的任务目标给服务器,并接收进度反馈和最终结果。
7.1 自定义动作信息
1、定义action文件
mkdir -p ~/catkin_ws/src/learning_communication/action
# 使用roscd更方便,可以直接实现功能包目录跳转
roscd learning_communication/action
touch WashDishes.action
WashDishes.action内容:
# Example action definition for WashDishes.action
# Define the goal
uint32 total_dishes
---
# Define the result
uint32 washed_dishes
---
# Define the feedback
uint32 percent_complete
2、在package.xml中添加功能包依赖
打开learning_communication包中的package.xml,添加以下依赖:
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
添加后:
3、在CMakeLists.txt添加编译选项
打开learning_communication包中的CMakeLists.txt,添加编译选项:
# (1)
find_package(
......
## 添加
actionlib_msgs
actionlib
)
# (2)
## 添加
add_action_files(
FILES
WashDishes.action
)
# (3)
## 添加
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
# (4)
catkin_package(
......
## 添加
actionlib_msgs
)
添加后:
7.2 实现洗盘子动作服务器
cd ~/catkin_ws/src/learning_communication/src
touch wash_dishes_server_py.py
# 添加权限
chmod 755 wash_dishes_server_py.py
wash_dishes_server_py.py内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
import actionlib
from learning_communication.msg import WashDishesAction, WashDishesGoal, WashDishesFeedback, WashDishesResult
class WashDishesServer:
def __init__(self):
self._as = actionlib.SimpleActionServer('wash_dishes', WashDishesAction, execute_cb=self.execute_cb, auto_start=False)
self._as.start()
rospy.loginfo("洗盘子动作服务器已启动.")
def execute_cb(self, goal):
rospy.loginfo("收到洗盘子请求,洗 %d 个盘子", goal.total_dishes)
feedback = WashDishesFeedback()
result = WashDishesResult()
if self._as.is_preempt_requested():
rospy.loginfo('目标被取消,停止洗盘子任务.')
self._as.set_preempted()
return
for i in range(1, goal.total_dishes + 1):
if self._as.is_preempt_requested():
rospy.loginfo('目标被取消,停止洗盘子任务.')
self._as.set_preempted()
return
feedback.percent_complete = i * 100.0 / goal.total_dishes
self._as.publish_feedback(feedback)
rospy.loginfo("洗盘子 %d", i)
rospy.sleep(1.0)
result.washed_dishes = goal.total_dishes
self._as.set_succeeded(result)
rospy.loginfo("洗盘子任务完成,共洗了 %d 个盘子", result.washed_dishes)
if __name__ == '__main__':
rospy.init_node('wash_dishes_server')
server = WashDishesServer()
rospy.spin()
7.3 实现洗盘子动作客户端
cd ~/catkin_ws/src/learning_communication/src
touch wash_dishes_client_py.py
# 添加权限
chmod 755 wash_dishes_client_py.py
wash_dishes_client_py.py内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
import actionlib
from learning_communication.msg import WashDishesAction, WashDishesGoal, WashDishesFeedback
class WashDishesActionClient:
def __init__(self):
self._ac = actionlib.SimpleActionClient('wash_dishes', WashDishesAction)
self._ac.wait_for_server()
rospy.loginfo("等待动作服务器启动...")
rospy.loginfo("动作服务器已启动.")
def send_goal(self, total_dishes):
goal = WashDishesGoal()
goal.total_dishes = total_dishes
rospy.loginfo("发送洗盘子的目标:%d 个盘子", total_dishes)
self._ac.send_goal(goal, feedback_cb=self.feedback_callback)
def feedback_callback(self, feedback):
rospy.loginfo("洗盘子进行中,已洗 %d%% 的盘子", feedback.percent_complete)
def cancel_goal(self):
rospy.loginfo("取消洗盘子任务")
self._ac.cancel_goal()
def wait_for_result(self):
self._ac.wait_for_result()
result = self._ac.get_result()
if result:
rospy.loginfo("完成洗盘子任务,共洗了 %d 个盘子", result.washed_dishes)
else:
rospy.logwarn("无法获取洗盘子任务的结果")
if __name__ == '__main__':
rospy.init_node('wash_dishes_client')
client = WashDishesActionClient()
while not rospy.is_shutdown():
num_dishes = raw_input("请输入要洗的盘子数量(或输入 'c' 退出): ")
if num_dishes.lower() == 'c':
break
try:
num_dishes = int(num_dishes)
client.send_goal(num_dishes)
except ValueError:
rospy.logwarn("无效的输入,请输入一个有效的数字.")
continue
while True:
command = raw_input("输入 'c' 取消任务,或者 'q' 退出等待结果: ")
if command == 'c':
client.cancel_goal()
break
rospy.loginfo("结束洗盘子任务")
7.4 运行程序
roscore
# 打开新终端
rosrun learning_communication wash_dishes_server_py.py
# 打开新终端
rosrun learning_communication wash_dishes_client_py.py
8
运行结果如下:
洗盘子动作服务器运行
洗盘子动作客户端运行
八、创建一个工作空间以及功能包,在功能包中完成如下工作
话题与服务编程:
(1)通过代码新生成一只海龟,放置在(5,5)点,命名为"turtle2";
(2)通过代码订阅turtle2的实时位置并在终端打印;
(3)控制tuitle2实现运动;
加分项:通过launch启动文件的方式执行。