py_trees实践:实现机器人循迹任务

书接上回的py_trees快速实践,写了一个机器人沿着拓扑路径循迹移动,最后到达目标点后,执行一个任务动作的行为树。在行为树中,增加了在每个tick检查机器人电量的逻辑。在电量低于一定阈值时,会中断当前任务并触发充电动作。这个逻辑体现了行为树响应性(Reactive)的特点,希望对学习行为树的同学有一点参考价值。

下面直接给出相应的代码:

#!/usr/bin/python3
# coding: utf-8
# robot_mission_behaviors.py

from py_trees import behaviour,behaviours, blackboard, common, meta, composites, display, decorators, idioms, trees, visitors
import functools
import operator
import sys
import time
import threading

class Plan(behaviour.Behaviour):
    def __init__(self, name):
        super(Plan,self).__init__(name)
        self.blackboard = self.attach_blackboard_client()
        self.blackboard.register_key(key="goal_list", access=common.Access.WRITE)
        self.blackboard.register_key(key="new_goal_activate", access=common.Access.WRITE)
    
    def initialise(self) -> None:
        pass

    def update(self) -> common.Status:
        # path planing
        planned_path = [1,2,3,4,5,6,7,8]
        succeed = True
        self.blackboard.goal_list = planned_path
        if succeed:
            self.feedback_message = "path plan succeed."
            self.blackboard.new_goal_activate = True
            return common.Status.SUCCESS
        else:
            self.feedback_message = "path plan failed!"
            self.blackboard.new_goal_activate = False
            return common.Status.FAILURE
        
class SendNavGoal(behaviour.Behaviour):
    def __init__(self, name):
        super(SendNavGoal, self).__init__(name)
        self.blackboard = self.attach_blackboard_client()
        self.blackboard.register_key(key="new_goal_activate", access=common.Access.WRITE)
        self.blackboard.register_key(key="goal_list", access=common.Access.READ)
        self.blackboard.register_key(key="nav_return_code", access=common.Access.WRITE)
    
    def initialise(self) -> None:
        pass
    
    def send_nav_goal(self,callback):
        # send nav_point
        pass

    def nav_gaol_cb(self,error_code):
        self.blackboard.nav_return_code = error_code

    def update(self) -> common.Status:
        if self.blackboard.new_goal_activate and self.blackboard.goal_list:
            goal = self.blackboard.goal_list.pop(0)
            print("-*-*- Go to path: {}".format(goal))
            #self.thread = threading.Thread(target=self.send_nav_goal,args=(self.nav_gaol_cb))
            #self.thread.setDaemon(True)
            #self.thread.start()
            #self.blackboard.new_goal_activate = False
            #self.feedback_message = "send nav goal."
        return common.Status.SUCCESS
    
class CheckPowerStatus(behaviour.Behaviour):
    def __init__(self, name):
        super(CheckPowerStatus, self).__init__(name)
        self.count = 3

    def initialise(self) -> None:
        pass

    def update(self) -> common.Status:
        # check power status
        if self.count == 0:
            power_ok = False
        else:
            power_ok = True
            self.count -= 1
        if power_ok:
            return common.Status.SUCCESS
        else:
            return common.Status.FAILURE
        
class SetGoalToCharge(behaviour.Behaviour):
    def __init__(self, name):
        super(SetGoalToCharge,self).__init__(name)
        self.blackboard = self.attach_blackboard_client()
        self.blackboard.register_key(key="goal_list", access=common.Access.WRITE)
        self.blackboard.register_key(key="new_goal_activate", access=common.Access.WRITE)
    
    def initialise(self) -> None:
        pass
    
    def update(self) -> common.Status:
        # charge re-plan
        self.blackboard.goal_list = [100,99,98,97,96,95]
        return common.Status.SUCCESS


class QueryNavStatus(behaviour.Behaviour):
    def __init__(self, name):
        super(QueryNavStatus, self).__init__(name)
        self.blackboard = self.attach_blackboard_client()
    
    def initialise(self) -> None:
        pass
    
    def update(self) -> common.Status:
        # ask_nav_statsus
        status = 0
        if status == 0:
            self.feedback_message = "Nav succeed."
            return common.Status.SUCCESS
        elif status == 0:
            return common.Status.RUNNING 
        else:
            return common.Status.FAILURE

class QueryNavCallbackBlackboard(behaviour.Behaviour):
    def __init__(self, name):
        super(QueryNavCallbackBlackboard, self).__init__(name)
        self.blackboard = self.attach_blackboard_client()
        self.blackboard.register_key(key="nav_return_code", access=common.Access.READ)
    
    def initialise(self) -> None:
        pass
    
    def update(self) -> common.Status:
        # check_nav_cb_balckboard
        status = 1
        if status == 0:
            self.feedback_message = "Nav succeed."
            return common.Status.SUCCESS
        elif status == 0:
            return common.Status.RUNNING 
        else:
            return common.Status.FAILURE
        
       
class PtzAction(behaviour.Behaviour):
    def __init__(self, name):
        super(PtzAction, self).__init__(name)
    
    def initialise(self) -> None:
        pass
    
    def update(self) -> common.Status:
        # ptz_ctrl & take_picture & save_picture & update 
        self.feedback_message = "Run Action succeed."
        return common.Status.SUCCESS
        

def create_root() -> behaviour.Behaviour:
    root = composites.Sequence(name="RobotMission", memory=True)
    plan = Plan(name="Plan")
    seq1 = composites.Sequence(name="Sequence", memory=False)
    # PowerChecking
    power_check = composites.Selector(name="PowerChecking", memory=False)
    check_power_status = CheckPowerStatus(name="PowerStatusOK")
    set_goal_to_charge=decorators.OneShot(name="OneShot",child=SetGoalToCharge(name="SetGoalToCharge"),policy=common.OneShotPolicy.ON_COMPLETION)
    power_check.add_children([check_power_status, set_goal_to_charge])

    nav_sequence = composites.Sequence(name="NavSequence", memory=False)
    # GoalSelector
    new_goal_selector = composites.Selector(name="GoalSelector", memory=False)
    check_new_goal_variable =decorators.Inverter(
        name = "No New Goal?",
        child = behaviours.CheckBlackboardVariableValue(
                    name="CheckNewGoalActivate",
                    check=common.ComparisonExpression(variable="new_goal_activate", value=True, operator=operator.eq)
                )
        
    )
    send_nav_goal = SendNavGoal(name="SendNavGoal")
    new_goal_selector.add_children([check_new_goal_variable, send_nav_goal])
    # NavSelector
    nav_selector = composites.Selector(name="NavSelector", memory=False)
    query_nav_status = decorators.RunningIsFailure(
            name="RunningIsFailure",
            child=QueryNavStatus(name="QueryNavStatus")
    )
    query_nav_cb_blackboard = decorators.Timeout(
            name="TimeOut",
            duration=20.0,
            child=QueryNavCallbackBlackboard(name="QueryNavCallbackBlackboard")
    )
    nav_selector.add_children([query_nav_status, query_nav_cb_blackboard])
    # GoalFinishChecking
    goal_finish = composites.Selector(name="GoalFinishChecking", memory=False)
    check_goal_list_empty  = behaviours.CheckBlackboardVariableValue(
                name="CheckGoalListEmpty",
                check=common.ComparisonExpression(variable="goal_list", value=[], operator=operator.eq)
    )
    set_goal_activate= decorators.SuccessIsRunning(
            name="SuccessIsRunning",
            child = behaviours.SetBlackboardVariable(
                name="ActivateNewGoal",
                variable_name="new_goal_activate",
                variable_value=True,
                overwrite=True
            )
    )
    goal_finish.add_children([check_goal_list_empty, set_goal_activate])

    nav_sequence.add_children([new_goal_selector, nav_selector, goal_finish])
    ptz_action = PtzAction(name="PtzAction")
    seq1.add_children([power_check, nav_sequence, ptz_action])
    root.add_children([plan, seq1])
    return root

def pre_tick_handler(behaviour_tree: trees.BehaviourTree) -> None:
    print("\n--------- Run %s ---------\n" % behaviour_tree.count)

def post_tick_handler(
    snapshot_visitor: visitors.SnapshotVisitor,
    behaviour_tree: trees.BehaviourTree,
) -> None:
    """Print an ascii tree with the current snapshot status."""
    print(
        "\n"
        + display.unicode_tree(
            root=behaviour_tree.root,
            visited=snapshot_visitor.visited,
            previously_visited=snapshot_visitor.previously_visited,
        )
    )

if __name__ == "__main__":
    root = create_root()

    render = False

    if render:
        display.render_dot_tree(root,with_blackboard_variables=True)
        sys.exit()

    root.setup_with_descendants()

    behaviour_tree = trees.BehaviourTree(root)
    behaviour_tree.add_pre_tick_handler(pre_tick_handler)
    behaviour_tree.visitors.append(visitors.DebugVisitor())
    snapshot_visitor = visitors.SnapshotVisitor()
    behaviour_tree.add_post_tick_handler(
        functools.partial(post_tick_handler, snapshot_visitor)
    )
    behaviour_tree.visitors.append(snapshot_visitor)
    succeed = False
    while not succeed:
        try:
            behaviour_tree.tick()
            print("behaviour_tree status: ",root.status)
            succeed = behaviour_tree.root.status == common.Status.SUCCESS
            time.sleep(1.0)
        except KeyboardInterrupt:
            break
    print("\n")


根据代码,py_trees会基于pydot自动生成行为树的可视化模型,如下所示:

运行程序,反映行为树流转的日志如下:

:~$ python3 robot_mission_behaviors.py

--------- Run 0 ---------

-*-*- Go to path: 1

{-} RobotMission [*]
    --> Plan [✓] -- path plan succeed.
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✓]
            -^- OneShot
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [2, 3, 4, 5, 6, 7, 8]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 1 ---------

-*-*- Go to path: 2

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✓]
            -^- OneShot
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [3, 4, 5, 6, 7, 8]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 2 ---------

-*-*- Go to path: 3

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✓]
            -^- OneShot
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [4, 5, 6, 7, 8]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 3 ---------

-*-*- Go to path: 100

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge [✓]
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [99, 98, 97, 96, 95]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 4 ---------

-*-*- Go to path: 99

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [98, 97, 96, 95]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 5 ---------

-*-*- Go to path: 98

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [97, 96, 95]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 6 ---------

-*-*- Go to path: 97

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [96, 95]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 7 ---------

-*-*- Go to path: 96

{-} RobotMission [*]
    --> Plan
    [-] Sequence [*]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge
        [-] NavSequence [*]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [*]
                --> CheckGoalListEmpty [✕] -- 'goal_list' comparison failed [v: [95]][e: []]
                -^- SuccessIsRunning [*] -- success is running []
                    --> ActivateNewGoal [✓]
        --> PtzAction

behaviour_tree status:  Status.RUNNING

--------- Run 8 ---------

-*-*- Go to path: 95

{-} RobotMission [✓]
    --> Plan
    [-] Sequence [✓]
        [o] PowerChecking [✓]
            --> PowerStatusOK [✕]
            -^- OneShot [✓] -- oneshot completed
                --> SetGoalToCharge
        [-] NavSequence [✓]
            [o] GoalSelector [✓]
                -^- No New Goal? [✕] -- success -> failure
                    --> CheckNewGoalActivate [✓] -- 'new_goal_activate' comparison succeeded [v: True][e: True]
                --> SendNavGoal [✓]
            [o] NavSelector [✓]
                -^- RunningIsFailure [✓] -- Nav succeed.
                    --> QueryNavStatus [✓] -- Nav succeed.
                -^- TimeOut
                    --> QueryNavCallbackBlackboard
            [o] GoalFinishChecking [✓]
                --> CheckGoalListEmpty [✓] -- 'goal_list' comparison succeeded [v: []][e: []]
                -^- SuccessIsRunning [-]
                    --> ActivateNewGoal
        --> PtzAction [✓] -- Run Action succeed.

behaviour_tree status:  Status.SUCCESS

观察日志可以看到,机器人最开始沿着[1,2, 3, 4, 5, 6, 7, 8]这个拓扑路径依次执行。但是在第3个tick,机器人发现电量不足,在当前位置重新规划了[100,99,98,97,96,95]的路径去充电。最终沿着这个路径运行到达充电点。

一些tips:

1. 采用OneShot装饰器,可以确保充电重规划动作只执行一次。

2. 行为树根节点的Sequence设置为有memory,确保第一次路径规划只做一次。

3. 如果不强调Reactive,即不希望中途检查和打断,则可把电量检查移到根节点下面,此时任务的表达类似于状态机。

4. 如果程序中没有发生电量不足,机器人将沿着原来规划的路径走到底,并执行任务动作。而本文充电打断后,tick也会流转到任务动作执行,读者可以思考下该如何处理:-》。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值