260326

昨天据此提的 PR 被接受了:

简历上又可以吹牛逼了:咱 PR 被 ROS2 官方项目 backport 合并进去了。

咳咳...我们继续我们的动作学习。

我们上面有一个execute_callback的方法,这是一旦目标被接受后,会被调用来进行执行的方法。

接下我们试着运行,并列出来看看:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 action list
/fibonacci
(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 interface list | grep -i "fibonacci"
    example_interfaces/action/Fibonacci
    tutorial_interfaces/action/Fibonacci

可以看到我们的环境中已经有这个动作了。

接下来我们查看一个这个动作的定义:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 interface show tutorial_interfaces/action/Fibonacci 
int32 order
---
int32[] sequence
---
int32[] sequence

可以看到这个定义完全符合我们的预期。

接下来我们试着去调用一下看看:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 action send_goal fibonacci tutorial_interfaces/action/Fibonacci "{order: 5}"
Waiting for an action server to become available...
Sending goal:
     order: 5

Goal accepted with ID: e3d9c63f55ca4e199fe2dc7f6062e1e3

Result:
    sequence: []

Goal finished with status: ABORTED

可以看到我们结束的状态为中途失败,同时,我们可以看到我们的动作提供者打印了如下的内容:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 run py_srvcli action_service 
[INFO] [1774512529.559766996] [fibonacci_action_server]: Executing goal...
[WARN] [1774512529.560137592] [fibonacci_action_server.action_server]: Goal state not set, assuming aborted. Goal ID: [227 217 198  63  85 202  78  25 159 226 220 127  96  98 225 227]

这是因为执行回调中未设置目标处理状态,则假设该状态为中止状态。

我们可以调用目标处理器的 sucess方法来表示目标成功:

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        goal_handle.succeed()
        result = Fibonacci.Result()
        return result

我们重新构建并运行后,就可以看到我们成功运行的结果了:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 action send_goal fibonacci tutorial_interfaces/action/Fibonacci "{order: 5}"
Waiting for an action server to become available...
Sending goal:
     order: 5

Goal accepted with ID: e1dc4cc1c4a34ec2b301df7d04df6173

Result:
    sequence: []

Goal finished with status: SUCCEEDED
(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 run py_srvcli action_service 
[INFO] [1774512813.524211724] [fibonacci_action_server]: Executing goal...

现在实现实际的斐波那契数列的逻辑吧:

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        sequence = [0, 1]

        for i in range(1, goal_handle.request.order):
            sequence.append(sequence[i] + sequence[i-1])

        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = sequence
        return result

在计算出结果后,会将值赋值给结果并进行返回。

现在我们构建并运行后,就可以看到我们预期的结果:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 action send_goal fibonacci tutorial_interfaces/action/Fibonacci "{order: 5}"
Waiting for an action server to become available...
Sending goal:
     order: 5

Goal accepted with ID: 70ebe4ce47f44dc095b1e4d3570704ba

Result:
    sequence:
- 0
- 1
- 1
- 2
- 3
- 5

Goal finished with status: SUCCEEDED

除了发布结果以外,我们还需要发布反馈。

    def execute_callback(self, goal_handle: ServerGoalHandle):
        from rclpy.action.server import ServerGoalHandle

        self.get_logger().info('Executing goal...')

        feedback_msg = Fibonacci.Feedback()
        feedback_msg.sequence = [0, 1]

        assert type(goal_handle.request) is Fibonacci.Goal

        for i in range(1, goal_handle.request.order):
            feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
            goal_handle.publish_feedback(feedback=feedback_msg) # type: ignore
            time.sleep(1)

        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = feedback_msg.sequence
        return result

这样就可以每秒发布一次反馈和进行一次计算了:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 action send_goal fibonacci tutoria
l_interfaces/action/Fibonacci "{order: 5}" --feedback 
Waiting for an action server to become available...
Sending goal:
     order: 5

Goal accepted with ID: 4266f107e4f04a29bf080ed6d3913449

Feedback:
    sequence:
- 0
- 1
- 1

Feedback:
    sequence:
- 0
- 1
- 1
- 2

Feedback:
    sequence:
- 0
- 1
- 1
- 2
- 3

Feedback:
    sequence:
- 0
- 1
- 1
- 2
- 3
- 5

Result:
    sequence:
- 0
- 1
- 1
- 2
- 3
- 5

Goal finished with status: SUCCEEDED

可以看到每次反馈我们都得到了当前计算的序列。

除了动作提供者,我们还需要编写一个动作的客户端,我们可以创建 src/py_srvcli/py_srvcli/client_member_action.py文件,并写入如下内容:

import rclpy
from rclpy.action import ActionClient
from rclpy.action.client import ClientGoalHandle
from rclpy.executors import ExternalShutdownException
from rclpy.task import Future
from rclpy.node import Node

from tutorial_interfaces.action import Fibonacci


class FibonacciActionClient(Node):

    def __init__(self):
        super().__init__('fibonacci_action_client')
        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

    def send_goal(self, order):
        goal_msg = Fibonacci.Goal()
        goal_msg.order = order

        self._action_client.wait_for_server()
        self.get_logger().info(f'Sending goal: order={order}')

        future = self._action_client.send_goal_async(
            goal_msg,
            feedback_callback=self.feedback_callback,
        )
        future.add_done_callback(self.goal_response_callback)

    def goal_response_callback(self, future: Future) -> None:
        goal_handle = future.result()
        if goal_handle is None:
            self.get_logger().error('No goal response received')
            rclpy.shutdown()
            return

        assert isinstance(goal_handle, ClientGoalHandle)

        if not goal_handle.accepted:
            self.get_logger().info('Goal rejected')
            rclpy.shutdown()
            return

        self.get_logger().info('Goal accepted')

        result_future = goal_handle.get_result_async()
        result_future.add_done_callback(self.get_result_callback)

    def feedback_callback(self, feedback_msg: Fibonacci.Impl.FeedbackMessage) -> None:
        feedback = feedback_msg.feedback
        self.get_logger().info(f'Feedback: {feedback.sequence}')

    def get_result_callback(self, future: Future) -> None:
        result_response = future.result()
        if result_response is None:
            self.get_logger().error('No result received')
            rclpy.shutdown()
            return

        result = result_response.result
        self.get_logger().info(f'Result: {result.sequence}')
        rclpy.shutdown()


def main(args=None):
    try:
        with rclpy.init(args=args):

            action_client = FibonacciActionClient()

            action_client.send_goal(10)
            rclpy.spin(action_client)
    except (KeyboardInterrupt, ExternalShutdownException):
        pass


if __name__ == '__main__':
    main()

这里我们的动作客户端也同样是通过新建一个动作客户端的实例来进行实现的,通过传入动作客户端节点、动作类型、动作名称即可完成这一客户端的实例化。

在通过 wait_for_server 开启等待的线程后,我们便可以通过 send_goal_async 来发送一个异步的新目标了。

同时,通过 add_done_callback 来保证该异步线程执行完毕后可以正常获取到内容。

将action_client = py_srvcli.client_member_action:main添加到入口后,进行构建并运行一下看看:

(.venv) jese--ki@KiBall:~/Projects/learn/ros2/pub_sub$ ros2 run py_srvcli action_client 
[INFO] [1774516872.430626752] [fibonacci_action_client]: Sending goal: order=10
[INFO] [1774516872.432119133] [fibonacci_action_client]: Goal accepted
[INFO] [1774516872.442922995] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1])
[INFO] [1774516873.443162051] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2])
[INFO] [1774516874.443334230] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3])
[INFO] [1774516875.443705945] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5])
[INFO] [1774516876.443690272] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5, 8])
[INFO] [1774516877.443933335] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13])
[INFO] [1774516878.444193951] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21])
[INFO] [1774516879.444524878] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34])
[INFO] [1774516880.444670979] [fibonacci_action_client]: Feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55])
[INFO] [1774516881.445282073] [fibonacci_action_client]: Result: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55])

可以看到响应基本符合我们的预期。

以上。

我们的参数部分也有中级的教程可以进行学习,主要是对于参数的监控这部分,这部分简单概括可以通过使用 ParameterEventHandler 来实例化一个处理器,然后通过该实例的 add_parameter_callback 即可添加一个参数到监控中并触发指定的回调函数,当然,我们也可以直接使用 add_parameter_event_callback 来直接注册一个回调,这个回调会在任意参数发生变化的时候都进行触发,这是监控所有参数的方法。

以上。