您现在的位置是:首页 >其他 >基于诺亚无人船ROS与Dronekit之间的通信浅析网站首页其他
基于诺亚无人船ROS与Dronekit之间的通信浅析
阿木实验室的诺亚无人船上市已经有一段时间,经过对开发者们的多次调研,我们发现不少开发者都对诺亚无人船的通信实现方式感兴趣,为了帮助大家更好地理解并使用该产品,本期我们将针对诺亚无人船中所使用的linux编程技术以及ROS系统,为大家分享一些经验。
基于Ardupilot的开源自主无人船https://www.bilibili.com/video/BV1AR4y167ne/?vd_source=83d699d29d0a56e0274cd41e59a6c3a9
基于Ardupilot平台开发的无人船——“ 诺亚(2022款) ”,不仅行驶速度快,续航时间长,还具备4G异地控制、航点规划、视频回传等功能,可应用于多种开发场景。阿木实验室,公众号:阿木实验室新品上架 | 2022款“诺亚”无人船正式开售!
提到无人船,就不得不先聊一聊ROS,什么是ROS?
ROS是机器人操作系统(Robot Operating System)的缩写,实际上它并不是我们传统的操作系统,它之所以称之为操作系统,是因为它提供的分布式软件框架以及消息通信机制让我们能够协调并控制机器人的各个组件,就像操作系统管理计算机的各个部分,让机器人开发既简单又高效。
下面我们以运行小乌龟的仿真来为大家举个例子,帮助大家更好的理解ROS之间的通信:
首先我们打开三个终端,分别输入roscore打开rosmaster节点,输入rosrun turtlesim turtlesim_nod运行小乌龟仿真节点,输入 px -ef查看系统全部进程:
这里注意看上图中圈起来的进程,前面两个进程是输入roscore命令生成的进程,因为可以看到两者父进程是4069,且4069进程挂载到pts/0当中,并且执行了命令/usr/bin/python3/opt/ros/noetic/bin/roscore。这条命令就是经过输入roscore后经过ROS的命令行脚本解释之后真正运行到Linux当中的命令,试一下就会发现直接在终端输入上面的命令也能运行roscore。那么我们就可以猜测一下,当我们启动roscore的时候发生了什么?
启动roscore时,ROS生成了两个进程,第一个是master进程,后面跟有参数--core -p 11311这里的11311是ROS系统中默认的TCP端口号(既然用的TCP网络通信来作为进程间通信禁止,那么这就意味着ROSMaster只需要和节点在同一局域网就行了,而不需要在同一主机,这也是ROS多机通信的关键),它用于ROS节点之间进行通信和信息交换。当一个ROS节点想要与另一个节点通信时,它需要首先连接到主节点(ROS Master),并向其注册自己的名称、类型、URI等信息。而这些信息的传输就是通过TCP/IP协议在11311端口上完成。
第二个进程是rosout,用来接收和发布ROS日志消息。在ROS中,日志消息被用来记录程序的运行状态信息、警告和错误等信息。上述步骤完成之后,启动的小乌龟仿真就生成了一个进程。
总的来说,ROS的节点就是一个又一个的进程,它们之间通过linux当中的进程间通信方式来交换数据,促使进程间通信更加简单。
在诺亚无人船中,我们之所以选择通过dronekit来与ROS进行通信,是因为dronekit基于Python语言开发,语法相对简单,并且对于APM飞控支持更友好,易于开发者上手。
那么问题来了,怎么让dronekit得到ROS节点的数据呢?换言之,进程间通信的方式是什么?
进程间通信的方式有很多种,比如共享内存、有名管道等,这次我们使用的是套接字通信方式。
首先,我们在无人船的机载电脑(树莓派)里实现了一个ROS节点,通过订阅话题的方式来从无人机得到想要的数据,再通过套接字通信的方式发送给用dronekit来写的控制代码。在整个流程中,无人船上的ROS节点作为一个中转站,收到无人机的话题后,就用套接字转发给无人船,接收到无人船的套接字信息就用话题转发给无人机。
在这个通信过程中,ROS节点可能遇到的问题是:ROS话题的回调函数(也就是Subscribe的最后一个参数)是一个线程,这个线程触发由ROS来决定,也就是ros::spinOnce()和ros::spin()决定,当程序运行到ros::spinOnce()的时候,才会执行一下回调函数(如果有消息),而ros::spin()则是会一直执行回调函数(如果有消息),为了解决这一问题,就需要使用到进程间通信中的套接字方式的非阻塞模式,来保持回调函数一直被监听。
ROS节点接收船信息的伪代码如下所示。主循环之前主要是套接字编程必要的初始化过程,然后等待其接收信息,若没有接收到信息就要让ROS检查一次回调函数。
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //创建套接字sockfd,指定协议为UDP
struct sockaddr_in server_addr;//创建用来绑定自身信息的结构体server_addr
memset(&server_addr, 0, sizeof(server_addr));//将server_addr数据全部置0
server_addr.sin_family = AF_INET;//设置协议族
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//设置本地IP
server_addr.sin_port = htons(8080);//设置本地端口
bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr))//将IP和端口绑定
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;//得到的有关于发送端的数据都会保存在clint_addr里面
socklen_t addr_len = sizeof(client_addr);
while(ros::ok){
int recv_bytes = recvfrom(sock, buffer, sizeof(buffer),
MSG_DONTWAIT,
(struct sockaddr*)&addr, &client_address_len);
//第四个参数为设置成非阻塞,recv_bytes为接收到的数据大小
if(recv_bytes > 0){
//说明接收到船的数据,就可以进行处理后,用ROS的topic发送出去
}
ros::spinOnce();
rate.sleep();
}
Dronekit部分实现套接字进程间通信就更加简单方便了,代码注释非常清楚,示例如下:
localaddr = ("127.0.0.1",12345)
server_address = "127.0.0.1"
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#套接字初始化
udp_socket.setblocking(False)#设置为非阻塞
udp_socket.bind(localaddr)
sendbuf = '(' + str(vehicle.location.local_frame.east) + ','
+ str(vehicle.location.local_frame.east)+')'
while True:
#recvfrom如果没有接收到信息会抛出异常,
try:
data, address = udp_socket.recvfrom(1024)
print("got server data: ", data, "IP address", address)
except socket.error as e: #捕获这个异常,就可以知道没收到信息
udp_socket.sendto(sendbuf, server_address)#将信息发给ROS节点
由此可见,套接字通信方式操作简单且上手难度低,可以轻松实现ROS+dronekit之间的通信。除此之外,套接字通信方式还可以实现网络通信,支持不同的主机之间进行通信,应用场景丰富。后续开发者也可以通过该通信方式在QT写一个上位机界面,通过套接字来发布指令给dronekit控制部分,甚至可以用matlab发布指令用来控制船的行进方向,可操作性强。
由于篇幅内容有限,今天的分享就先到这里了,在学习ROS+dronekit之间的通信之前,建议大家提前了解线程、进程、网络编程及Linux等方面的基础知识,避免后续的学习中略感吃力。此外,如果大家对于我们的诺亚无人船感兴趣或者在使用期间有其他方面的问题,欢迎给我们留言,共同探讨相关技术~