Develop Custom Behaviours
The IUB classes have inherited significant functionality from the three base classes of the task trees framework, namely, ConditionalBehaviour
, ConditionalCommanderBehaviour
, and SceneConditionalCommanderBehaviour
. The py-tree class py_trees.behaviour.Behaviour
is their common ancestor.
ConditionalBehaviour
inherits fromBehaviour
and adds conditional execution, which determines whether the behaviour needs execution at ticking.ConditionalCommanderBehaviour
inherits fromConditionalBehaviour
and adds the capability of moving the robot manipulator through the arm commander.SceneConditionalCommanderBehaviour
inherits fromConditionalCommanderBehaviour
and adds the capability of logical scene binding according to a scene configuration.
Class: task_trees.behavious_base.ConditionalBehaviour
The class ConditionalBehaviour
is the base for specialized behaviours adopting the conditional behaviour notion. The condition_fn parameter is pre-processed at the class creation phase and converted into a form facilitating evaluation during the tick-tock phase.
class ConditionalBehaviour(Behaviour):
SUCCESS_IF_FALSE = 0
FAILURE_IF_FALSE = 1
def __init__(self, name, condition_fn=True, condition_policy=ConditionalBehaviour.SUCCESS_IF_FALSE):
super(ConditionalBehaviour, self).__init__(name)
def preprocess_condition_fn():
....
def is_condition_satisfied():
....
The parameter condition_policy
determines whether to return SUCCESS or FAILURE when the condition is False. The default value of None
will make the update
function returns according to the parent node. Returning SUCCESS is suitable for a Sequence parent with memory while FAILURE is for a Selector parent with memory.
Subclasses of ConditionalBehaviour
should override the function update_if_true
and implement actions to take if condition_fn
returns True
. The following describes the logic flow for the execution of update_if_true
.
Class: task_trees.behavious_base.ConditionalCommanderBehaviour
The class ConditionalCommanderBehaviour
implements the common logic of a behaviour in dealing with the states of the arm commander. The following shows the structure of the update function of ConditionalCommanderBehaviour. Subclasses of CommanderBehaviour will implement the three functions highlighted in the code snippet.
The main job of the update
or update_if_true
function is to handle the different states of the arm commander. The following table summarizes the action taken according to the current arm commander state.
Arm Commander State |
Action to be Taken |
Return Value |
---|---|---|
READY |
Call |
The return value of the call is returned as the new behaviour status |
BUSY |
Call |
The return value of the call is returned as the new behaviour status |
SUCCEEDED |
Call |
|
ABORTED |
Set the task’s state to |
|
ERROR |
Set the task’s state to |
|
Subclasses of ConditionalCommanderBehaviour
should override the function update_if_ready
, and optionally override the functions update_if_busy
and tidy_up
. The function update_if_ready
is where the arm commander is invoked. The function update_if_busy
usually houses code for monitoring the progress including any alert to raise, such as an impending collision. The function tidy_up
usually contains code for reset the arm commander to the previous configuration if something has been changed by this behaviour in update_if_ready
. The following figure summarizes the above roles of the functions.
Example: DoMoveJointPose
The DoMoveJointPose
IUB class is an example subclass of ConditionalCommanderBehaviour
class DoMoveJointPose(ConditionalCommanderBehaviour):
def __init__(self, name, condition_fn=True, condition_policy=None, arm_commander=None, target_joint_pose=None):
super(DoMoveJointPose, self).__init__(name=name, condition_fn=condition_fn, condition_policy=condition_policy, arm_commander=arm_commander)
...
self.joint_pose = target_joint_pose
def update_when_ready(self):
# obtain the named pose, and if it is a function, call the function to get the string
joint_pose = self.joint_pose
if hasattr(joint_pose, '__call__'):
joint_pose = joint_pose()
# validate the named_pose
if joint_pose is None or type(joint_pose) != list:
logger.error(f'DoMoveJointPose ({self.name}): invalid joint pose "{joint_pose}" parameter')
return Status.FAILURE
# send the command to the General Commander in an asynchronous manner
self.arm_commander.move_to_joint_pose(joint_pose, wait=False)
return Status.RUNNING
Notes:
The implementation
update_when_ready
is executed when the condition function (condition_fn
) returns True and the arm commander is in theREADY
state at the ticking of the behaviour instance.The
self.joint_pose
instance variable supports physical and dynamic binding, which is determined by its type. Ifself.joint_pose
is a function reference, then a function call is made to resolve the physical joint values.Issue the move command by passing the physical joint values through the arm commander’s
move_to_joint_pose
function.Return
Status.RUNNING
to set the behaviour in the RUNNING status.From now on, the base class
ConditionalCommanderBehaviour
will take over the follow-up management of the move command, querying the arm commander for whether the commander is switched to a completion state. It will then change the behaviour toStatus.SUCCESS
orStatus.FAILURE
according to the outcome of the command. This is transparent to the developer ofDoMoveJointPose
Class: task_trees.behavious_base.SceneConditionalCommanderBehaviour
The class ConditionalCommanderBehaviour
implements the resolution of logical positions and rotations of the task scene into physical positions and rotations. It uses the model provided throught the parameter scene
for the necessary information for the conversion.
Custom move behaviours that work at the logical scene level should subclass ConditionalCommanderBehaviour
.
Example: DoMoveNamedPose
The DoRotate
IUB class is an example subclass of SceneConditionalCommanderBehaviour
. It accepts a move target that is utimately resolvesd into a rotation in the rpy format.
class DoRotate(SceneConditionalCommanderBehaviour):
def __init__(self, name, condition_fn, condition_policy=None, arm_commander=None, scene=None, target_rpy=None, reference_frame=None):
super(DoRotate, self).__init__(name=name, condition_fn=condition_fn, condition_policy=condition_policy,
arm_commander=arm_commander, scene=scene, reference_frame=reference_frame)
if target_rpy is None:
logger.error(f'{__class__.__name__} ({self.name}): parameter (target_rpy) is None -> fix the missing value at behaviour construction')
raise AssertionError(f'A parameter should not be None nor missing')
if scene is None:
logger.warning(f'{__class__.__name__} ({self.name}): no scene model is provided -> acceptable if logical pose is not involved in this behaviour')
self.target_rpy = target_rpy
def update_when_ready(self):
# evaluate physical target rpy
if self.the_scene is None:
target_rpy = self.compute_physical_target(self.target_rpy)
else:
target_rpy = self.compute_physical_target(self.target_rpy, self.the_scene.query_rotation_as_rpy)
if self.target_rpy is None or target_rpy is None:
logger.error(f'DoRotate ({self.name}): invalid target_rpy parameter {self.target_rpy}')
return Status.FAILURE
# send the command to the General Commander in an asynchronous manner
binded_reference_frame = self._bind_reference_frame(self.reference_frame)
logger.info(f'DoRotate ({self.name}): started rotate to orientation: {target_rpy} in reference frame "{binded_reference_frame}"')
self.arm_commander.rotate_to_orientation(roll=target_rpy[0], pitch=target_rpy[1], yaw=target_rpy[2], reference_frame=binded_reference_frame, wait=False)
return Status.RUNNING
Notes:
The
self.compute_physical_target
is a function ofSceneConditionalCommanderBehaviour
that performs the resolution of thetarget_rpy
parameter using logical binding or dynamic binding if needed.The constructor of
SceneConditionalCommanderBehaviour
requires ascene
object, which is the scene configuration for logical binding.The class
DoRotate
however is designed to accommodate the situation where no scene configuration is provided, hence the if-else structure in the functionupdate_when_ready
. If ascene
object is not given, the class does not support logical binding.