话题Topic是订阅器与发布器节点之间的,而服务则是客户端(Client)和服务器(Server)间的,前者是异步的,后者是同步的。而且话题是单项的不需要服务器上线,而服务是双向的。在开启服务之前要先打开客户端,所以流程应该是客户端的建立→服务的建立,就像是要现有饭店才能有菜单,客人才能点菜,才能凭借饭店给客人提供服务。
创建功能包
src文件夹下输入一下内容
客户端实现
引入库
在这里python程序多了一个sys库但是后面的代码里并没有看见。
c++ python
ROS 节点初始化
c++ python
这里还是一样的定义节点的名称,python函数的名字和节点的名字一样都是turtle_spawn 。
定义客户端
c++ python
客户端等待
c++ python
客户端定义
c++ python
可以看出在定义客户端的时候,C++语言依旧创建了一个句柄,之前创建的句柄是n在n.publicer这里定义的句柄是node所以是node.serviceClient,老规矩定义了服务名是/spawn,客户端的名字是add_turtle,服务的数据类型是Spawn它存在于turtlesim依赖下。特别的是,客户端定义的时候多了一个等待要等待到名为/spawn的服务的时候才开始定义客户端。
调用客户端
c++ python
在这里C++是通过客户端.call的方式调用数据,call也是自带等待的,等待service的应答处理结果,一直等着直到小海龟建立成功,就像百度搜索的时候网不好它会一直转圈圈一样。ROS_INFO也不过是print一下数据,同样字符串的位置是多了一个c_str()的;而python的服客户端调用显然更加方便,之前定义的add_turtle直接就可以送入数据得到响应变量reponse,只需要返回输出一下reponse.name,这里我觉的返回那个值都行,反正函数也不输出什么。except是try对应出现的,如果出现错误就输出一下错误。
数据初始化(设定数据输入)
c++ python
客户端调用
C++文件将数据送入到srv 变量中,在通过服务器调用,python我们之前也提过了直接就能送入数据。
主程序调用
c++ python
这一部分是根据代码逻辑来的,C++输出一下,然后返回个0就结束了。python也需要print一下,这个print的语句打印的是名字,所有函数turtle_spwan函数的返回值必须是名字,所以之前说的可以返回其他选项就不合适了,但是这套程序是将try封装在了函数turtle_spawn中,之前的publisher是封装在主函数外面的,其实也是可以的。
CMakeList.txt文件
封装成可执行文件,python函数不用调用的时候输入程序名.py就可以
python调用的时候输入程序名.py
c++ python
多生成几个海龟是一样的,多输入数据,然后返回就可以了。
服务器实现
在这之前,我们已经建立好了一个客户端,这个客户端就好比一个店面,现在有了店面,客人就可以点菜了,我们现在就要开始构建一个服务了,自然一个服务的沟通是需要通过语言来传达命令的,所以在服务中我们需要嵌套话题来发布指令。
引入库
c++ python
老规矩,不过是多了一个服务的类型Trigger,python多了个响应TriggerResponse。,接下来我们将按照主程序的逻辑来介绍函数。
节点初始化
c++ python
创建一个服务器节点,名字叫turtle_command_server。
创建Server服务器
创建节点句柄,这个节点句柄不光用于服务器,之后
的话题发布也是用的这个句柄n来生成的。
c++ python
服务器的名字叫/turtle_command,这里同样出现了回调函数commandCallback上一次我们使用回调函数是在订阅器中,订阅器在诞生后一直在等待消息的进入,所以需要回调等待消息的进入在开始执行。
回调函数
在这里需要区分一下回调函数和客户端里call的区别,在客户端中,数据代码都是提前封装好在程序里的,执行程序的时候,小海龟就已经生成了,生成的位置,个数都是固定的,而在服务器这一面,小海龟的运动方向,速度也是封装在程序里的,但是应该是可以做成后续键入的。call用于送入数据,回调函数则是为了反馈数据,那么在订阅器中的回调函数充当着什么角色呢?
当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
回调可用于通知机制。例如,有时要在A程序中设置一个计时器,每到一定时间,A程序会得到相应的通知,但通知机制的实现者对A程序一无所知。那么,就需一个具有特定原型的函数指针进行回调,通知A程序事件已经发生。
所以回调函数其实就是个开关,有人按下开关,回调函数就去干活。
所以订阅器时的回调函数就是一个print没别的东西,似乎删掉也没啥(留一个课后问题2)
开关取反
c++ python
Trigger类型的服务的输入参数的req输出参数是res但其实req是不需要的,如果想查看一个类型的详细数据,可以在终端中输入rossrv show 《数据类型》“---”前是req后是res。
pubCommand 默认是0,此时小海龟是静止的,因此在开启服务后我们要开始送入数据了,所以把其改成1。
显示请求数据
c++ python
就是看起来清晰明了,没啥具体的用处
反馈数据
c++ python
刚刚已经说过了Trigger的消息类型就是req和res,req的请求数据,res就是返回数据,包括success和message,python就用了TriggerResponse,输入两个对应的数据内容就好了。
服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。
消息发布
发布器创建
c++ python
那么在创建一个发布器后我们需要发布一个消息,这个消息就是话题,那么就需要先建立一个发布器turtle_vel_pub,发布的消息名称是/turtle/cmd_vel,类型是Twist。结果发现C++中发布器的定义是可以拆开的。
发布消息
while循环
用while循环来不断查询队列,默认while就是true的,不同的是c++要不停的查看回调函数的队列。而定义的command_thread要新开一个线程来发布消息。这里python少了一个spinOnec,这是查询一次的代码,而python只能在while中不断循环,所有才需要再开一个线程来发布消息,换句话说尽管c++和python在一部分都是循环,但是由于spinOnec的存在,c++while循环内的代码只执行一次,但是python在command_thread函数开始执行的时候就不断的循环执行while内的内容,所以才需要再开一个线程(应该是可以优化代码逻辑的,课后问题1)。
1.线程是依次创建的。
2.thread.start_new_thread( a,())创建线程后立即返回,继续执行后面的代码。线程执行成败与创建线程的函数没有关系。a是执行的函数也是新开的线程,()是无用的返回值。
注意,python2版本是没有问题的但是python3改了,thread要换成_thread
3.线程执行的顺序是随机的。
发布消息
c++ python
当,开关pubCommand的开关打开的状态下,用之前设定好的发布器来发布消息,再按照频率延时一会。
循环等待回调函数
c++ python
python的这个必须放到程序的最后一段,要不然就一直循环后面的程序就不执行了。但是c++不一定非要放最后,因为c++的是按照频率循环,该循环代码后面的代码还是能执行的。
主函数调用
c++ python
c++是直接运行main段,但是python是需要指定哪一个函数是主函数的。而main函数是需要return一个值的,没有就返回0。
值得一提的是:
python中函数的定义都是def而c++中子函数定义用bool比如说回调函数的定义。
python语言中也存在全局变量和局部变量,global就是把变量变成全局的。开关pubCommand就是全局的,c++通过bool 确保回调函数返回bool值。
布尔型变量可以看做整型变量,布尔变量的值 true 和 false,分别可以当做整数 1 和 0。
CMakeList.txt文件链接
python不用,执行的时候多个.py的后缀即可
c++ python
程序编译
前几步骤和之前是没有区别的
最后多的是节点的调用。
rosservice call /turtlre_command %这一句是调用服务器节点,/turtlre_command是我们之前定义好的节点名称(是服务器节点的名称!不是话题节点的!)。
python最后一步多不多‘{}’都能执行
这时候小海龟才会动起来。
那么我们再执行一下该语句,开关就会取反,小海龟就会停止了。