对于实际应用场景,我们希望使用一个指令控制机械臂抓起面前的方块。这种情况可以用话题,机械臂节点订阅该话题,我们向话题中发布消息,从而使机械臂移动。也可以使用服务,客户端向机械臂节点发送消息,机械臂作为服务端在确认收到消息后返回消息,告知消息已接收
但以上两种操作中,我们都无法得知机械臂具体进行了什么操作。于是动作的出现,完美解决了这个问题
动作的本质是在发送了请求后,接收端重复周期性返还数据,以告知消息发送者当前动作进行到了哪里,以此来实现对动作的全程跟踪。
动作接口
我们将消息发送者成为客户端,消息执行者成为服务端
在设计动作的接口中,我们需要考虑以下几点信息传输:
1、启动时发送消息,这是动作的开始阶段,由客户端发往服务端
2、结束时发送消息,这标志着动作执行完毕,由服务端发往客户端
3、反馈,这是动作过程中,由服务端发往客户端
由以上三个变量,ros2规定动作接口如下书写:注:文件后缀名为.action
#{启动消息}
---
#{结束消息}
---
#{反馈消息}
动作:从累加开始
为了实现最简单的动作,我们设计以下动作:由节点A发送消息给节点B一个int型,节点B执行int型次从0开始的自加,每次自加反馈给节点A消息,告知自加的进度,最终完成自加后返回节点A已经自加完毕的信息。
接口设计
int32 num
---
bool finish
---
int32 x
服务端设计
class PlusService(Node):
def __init__(self,name):
super().__init__(name)
self.serv=ActionServer(self,Plus,'action_a',self.doPlus)
def doPlus(self,goal):
self.get_logger().info("get Request")
feedback=Plus.Feedback()
i=0
for j in range(goal.request.num):
i=i+1
feedback.x=i
goal.publish_feedback(feedback)
time.sleep(1)
goal.succeed()
result=Plus.Result()
result.finish=True
return result
def main(args=None):
rclpy.init(args=args)
node=PlusService("Node_Plus_Service")
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
在服务端中,我们创建了一个节点,在节点中调用ros2的ActionServer函数创建了服务端实例,并声明了它的回调函数,每当收到请求后便会把请求作为参数执行回调函数。
信息的传递都需要借助接口方法创建反馈体对象、响应体对象,在doPlus方法中我们创建了feedback作为反馈,每次循环中i做自加,赋值feedback的x并调用publish_feedback传递给客户端
执行完毕循环后我们调用request中succeed方法来告知执行完毕,使用Result方法创建result类,赋值后返回给客户端。
客户端设计
客户端设计相对来说较为复杂
在客户端设计中,我们需要考虑如何发送请求、接收反馈的回调函数、接受最终结果的回调函数
至关重要的是,为了最终结果,我们需要在发送请求后追踪。咱们先来看代码吧!
class Plus_client(Node):
def __init__(self,name):
super().__init__(name)
self.clienter=ActionClient(self,Plus,'action_a')
def send(self,num):
msg=Plus.Goal()
msg.num=num
self.clienter.wait_for_server()
self.future=self.clienter.send_goal_async(msg,feedback_callback=self.feedbackCallback)
self.future.add_done_callback(self.linkCallback) #连接到服务器后的回调函数
def feedbackCallback(self,msg):
self.get_logger().info("Get message:%d" % msg.feedback.x)
def doneCallback(self,msg):
self.get_logger().info("End Plus!")
def linkCallback(self,Result):
doPlus=Result.result()
if not doPlus.accepted:
self.get_logger().info("Goal rejected")
return
self.get_logger().info("Goal Begin!")
self.getResult=doPlus.get_result_async()
self.getResult.add_done_callback(self.doneCallback)
def main(args=None):
rclpy.init(args=args)
node=Plus_client("ClientNode")
node.send(5)
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
在这段程序中,调用逻辑相对来说较为复杂,我们梳理一下:
构造函数构造clienter类进行动作客户端实例
主函数调用send方法
send方法中声明msg作为请求体
调用clienter中的send_goal_async来进行发送数据,此处告知反馈的回调函数feedbackCallback,并且把结果赋值给future
future中告知连接服务器的回调函数为linkCallback
在linkCallback函数执行时,说明已经完成服务器连接,把连接结果赋值给doPlus,此时判断指令是否被接受,若是,则调用doPlus实例中get_result_async方法,异步获取最终执行完毕的结果。同时将最后结束动作的回调函数设定为doneCallback
最终,结束时调用doneCallback。