ROS2-BDI

A planning based Multi-Agent BDI architecture for ROS2


Bringup

The bringup requires essentially the following:

  • PDDL domain defined for the agent, expressing relevant objects and properties of the environment in which is put into wrt. to its knowledge.
  • Action nodes defined in the PDDL domain for the agent implemented by inheriting from the abstract class BDIActionExecutor, respecting its requirements and exploiting the provided API calls enabling feedback on the progress, status and leveraging communication among agents.

Other components might be considered optional, nonetheless you need to take them seriously to further boost the capabilities of your agent:

  • Sensor nodes implemented by inheriting from the abstract class Sensor, respecting its requirements and exploiting the provided API calls leveraging the update of the corresponding belief set wrt. what's been detected and internal processing performed within the sensor. Technically, they're not necessary, but in most real scenarios you'd find them essential.
  • Core behaviour tuning via the initialization parameters that can be specified (overriding the default ones) in the launch python script for the agent. They are all documented below.

PDDL domain example

The pddl file below is supposed to be a simple domain definition for a ROS2-BDI agent which acts as a cleaner.


  ;; domain file: cleaner-domain.pddl

  (define (domain cleaner-domain)

      (:requirements 
        :strips :typing 
        :fluents :durative-actions
      )

      ;; Types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      (:types
          waypoint
          robot
      );; end Types ;;;;;;;;;;;;;;;;;;;;;;;;;

      (:predicates
          (in ?r - robot ?wp - waypoint)
          (workfree ?r - robot)
          (recharging_station ?wp - waypoint)
          (cleaned ?wp - waypoint)
      )

      (:functions
          (battery_charge ?r - robot)
      )

      (:durative-action movetoward
          :parameters (?r - robot ?wp_from ?wp_to - waypoint)
          :duration (= ?duration 4)
          :condition (and
              (at start (in ?r ?wp_from))
              (at start (workfree ?r))
              (at start (> (battery_charge ?r) 20))
              (over all (> (battery_charge ?r) 10))
          )
          :effect (and
              (at start (not(workfree ?r)))
              (at start (not(in ?r ?wp_from)))
              (at end (in ?r ?wp_to))
              (at end (workfree ?r))
              (at end (decrease (battery_charge ?r) 10))
          )
      )

      (:durative-action doclean
          :parameters (?r - robot ?wp - waypoint)
          :duration (= ?duration 4)
          :condition (and
              (at start (workfree ?r))
              (at start (> (battery_charge ?r) 30))
              (over all (> (battery_charge ?r) 10))
              (over all (in ?r ?wp))
          )
          :effect (and
              (at start (not(workfree ?r)))
              (at end (workfree ?r))
              (at end (cleaned ?wp))
              (at end (decrease (battery_charge ?r) 20))
          )
      )

      (:durative-action recharge
          :parameters (?r - robot ?wp - waypoint)
          :duration (= ?duration 4)
          :condition (and
              (at start (workfree ?r))
              (over all (in ?r ?wp))
              (over all (recharging_station ?wp))
          )
          :effect (and
              (at start (not(workfree ?r)))
              (at end (workfree ?r))
              (at end (assign (battery_charge ?r) 100))
          )
      )

    )


Instances of interest might be robotic agent (itself or others) and points of interest in a map.
Predicates of interest are the following:

Predicate Semantic
(in ?r - robot ?wp - waypoint) robotic agent ?r is situated in ?wp
(workfree ?r - robot) robotic agent ?r is currently "free" to work
(recharging_station ?wp - waypoint) robotic agents can recharge their battery in ?wp
(cleaned ?wp - waypoint) ?wp is clean
The only property which probably needs further explanation is workfree, which has been added to avoid the parallel execution of actions that are not supposed to run simultaneously (i.e. cleaning and recharging). It’s going to act as a sort of ”lock” for guaranteeing mutual exclusion and sequential execution among certain actions. It could seem a bit strange, but it’s important to avoid unpleasant situations in which the planner might think that they could be made run together. That’s an explicit condition specifically added to fulfill this purpose.
The only defined Function takes track of the battery charge of a robotic agent.

Actions essentially defines when, which and how properties are supposed to be and change before, after and/or during their executions. The cleaner agent we have envisioned can essentially move, clean and recharge. IT'S REALLY IMPORTANT THAT THE FIRST PARAMETER OF EVERY ROS2-BDI ACTION DEFINED IN ITS PDDL DOMAIN WITHOLDS THE REFERENCE TO ITS agent_id value . The user won't need to worry to put it there, if the provided python libraries are used to generate the launch description for the agent. The takeout information is that it's not possible to simply define a move, clean,... action, without leaving room in the first argument for a reference to the agent, so the proper action executor node (the one belonging to the agent) will be selected and triggered when the action needs to be run.
NOTE: This is not valid anymore if you break the default behaviour by setting agent_id_as_specialized_arg to false, go back here for further info.

Python bringup example

The following script can basically be copied and pasted in order to be tuned to comply with the user's needs. It allows to easily generate a launch description to boot up all the ROS2-BDI core and auxiliary nodes as well as an instance of PlanSys2 for the agent, while hiding all the complexity. User just needs to put the reference for the implemented Sensors and Actions. as below, specifying the required parameters and the needed tunable ones too.

                          
import sys
from ament_index_python.packages import get_package_share_directory

ros2_bdi_bringup_dir = get_package_share_directory('ros2_bdi_bringup')
sys.path.append(ros2_bdi_bringup_dir + '/launch/')
from bdi_agent import AgentLaunchDescription
from bdi_agent_skills import AgentAction
from bdi_agent_skills import AgentSensor

def generate_launch_description():
    AGENT_ID = 'cleaner'
    AGENT_GROUP_ID = 'cleaner'

    bdi_tests_share_dir = get_package_share_directory('ros2_bdi_tests')

    # perform moving toward
    action_movetoward = AgentAction(
        package='ros2_bdi_tests',
        executable='movetoward_bdi',
        name='movetoward'
    )

    # perform cleaning action
    action_doclean = AgentAction(
        package='ros2_bdi_tests',
        executable='doclean_bdi',
        name='doclean'
    )
    
    # recharging action
    action_recharge = AgentAction(
        package='ros2_bdi_tests',
        executable='recharge_bdi',
        name='recharge'
    )

    # waypoint sensor 
    wp_sensor = AgentSensor(
        package='ros2_bdi_tests',
        executable='wp_sensor',
        name='wp_sensor',
        specific_params=[
            {"init_sleep": 2}
        ])

    ld = AgentLaunchDescription(
        agent_id=AGENT_ID,
        agent_group=AGENT_GROUP_ID,
        init_params={
            'pddl_file': bdi_tests_share_dir + '/pddl/cleaner_simple/cleaner-domain.pddl',
            'init_bset': bdi_tests_share_dir + '/launch/init_cleaner_simple/init_bset.yaml',
            'init_dset': bdi_tests_share_dir + '/launch/init_cleaner_simple/init_dset.yaml',
            'autosub_prec': True,
            'autosub_cont': True,
            'reschedule_policy': 'NO_PREEMPT'
        },
        actions=[action_movetoward, action_doclean, action_recharge],
        sensors=[wp_sensor]
    ) 

    return ld
                          
                        

Launch Parameters

Required parameters

For every agent, the user must define:

  • its unique id
  • the id for the agents’ group it belongs to
  • its PDDL domain expressing the relevant part of the world it’s interested in and the actions it’s capable of carrying on
If any of these three parameters were missing, the ROS2 BDI agent wouldn’t be able to operate properly. Hence, they are handled as mandatory parameters that need to be passed to the script building a launch description for it, causing an immediate crash if are not there.

Parameter Type Description
agent_id string Unique id for the given agent, defining the prefix for all of its nodes, topics and services. It’s left to the user the task of ensuring that there are no agents sharing the same id.
agent_group string Shared id for a group of agents the given agent belongs to. This is necessary to allow or forbid certain communication exchanges.
pddl_file string Filepath to the PDDL domain loaded by the PlanSys2 instance of the agent, defining everything relevant to the agent wrt. the environment it’s put into and the actions is capable of executing.
Tunable parameters

Many of the ROS2-BDI agent behaviours can be properly tune wrt. the properties of the agent and/or the context in which it ends up operating. The user can tune them via the following parameters detailed in the table below. Note how all of them have already some default values: it's up to the user to change them or not.

Parameter Type Default value Description
init_bset string null Filepath to YAML file compliant to the BeliefSet value prototype definition, which is nonetheless than an array of Belief describing the initial current vision of the world of the ready-to-be launched ROS2-BDI agent. If missing, the set is initialized as empty.
init_dset string null Filepath to YAML file compliant to the DesireSet value prototype definition, which is nonetheless than an array of Desire. describing the initial current set of desirable state of the world for the ready-to-be launched ROS2-BDI agent. If missing, the set is initialized as empty.
init_reactive_rules_set string null Filepath to YAML file containing an array of items compliant to the Event Listener rules that are specified here. The conditions are checked every time a new belief set update is received to see if a condition matches, entailing further updates to the belief and/or desire set.
reschedule_policy enum
{PREEMPT, NO_PREEMPT}
NO_PREEMPT Defines rescheduling policy:
  • with preemption (PREEMPT)
  • no preemption (NO_PREEMPT)
comp_plan_tries integer
in [1-∞]
8 Number of times the ROS2-BDI agent tries to compute a plan for a desire before discarding it.
exec_plan_tries integer
in [1-∞]
8 Number of times the ROS2-BDI agent demands the execution of a plan for a desire before discarding the latter.
autosub_prec boolean False If True, performs preconditions autosubmission, meaning that one or more desires will be autosubmitted by the ROS2-BDI agent to fulfill the unsatisfied preconditions of a desire currently present in its desire set. They will be set with a slightly higher priority than the original desire.
autosub_context boolean False If True, performs context autosubmission, meaning that one or more desires will be autosubmitted by the ROS2-BDI agent to fulfill the unsatisfied context conditions of a desire currently present in its desire set. They will be set with a slightly lower priority than the original desire.
rtc_deadline float
in [1-∞]
2.0 rtc_deadline*target_desire.deadline represents the limited time before the Plan Director requests the abortion of the current plan in execution fulfilling target_desire.
belief_ck string[] [] List of agents’ groups whose belief CHECK requests can be accepted by the ROS2-BDI agent.
belief_w string[] [] List of agents’ groups whose belief WRITE requests can be accepted by the ROS2-BDI agent.
desire_ck string[] [] List of agents’ groups whose desire CHECK requests can be accepted by the ROS2-BDI agent.
desire_w string[] [] List of agents’ groups whose desire WRITE requests can be accepted by the ROS2-BDI agent.
desire_pr float[]
values in [0-1]
[] desire pr[i] is the max accepted priority for a desire addition requested by agents belonging to the agent group desire_w[i]