ROS2-BDI

A planning based Multi-Agent BDI architecture for ROS2


Skills

Tip!

Check ros2_bdi_tests for demo examples regarding the implementation, dependencies needed for the package(s) in which sensor/action nodes are implemented.

Sensors

Setting up a sensor node for a ROS2-BDI agent is very simple. It mainly requires to create a cpp class inheriting from the Sensor abstract class, as provided within the framework, implementing the constructor and the performSensing() methods. Anything else is optional, not-required and strictly related to advanced logic specific to the sensor you might need. Keep in mind that the Sensor class is nonetheless than an extension of the base rclcpp::Node class providing additional features to easily and correctly manage the sensed properties forwarding them to update the belief set of the agent. This entails that any usual ROS2 node additional feature (e.g. topic subscription, calling/handling services....) you want to setup will be available, but it's suggested to strictly limit them in relation to the specific logic of the sensor.

Sensor constructor
Sensor(const std::string& sensor_name, const ros2_bdi_interfaces::msg::Belief& proto_belief, const bool match_param_by_param = true, const bool enable_perform_sensing = true)
Parameter Required Description
sensor_name Name for the sensor; must be unique among the nodes in the ROS2-BDI agent namespace
proto_belief Belief prototype for the sensor
match_param_by_param Defines whether in a function/fluent sensing all params should match or not (default behaviour is set to true to match the rule defined in the table below, but you can override it so that fluent/function case is basically equivalent to the one of the predicate-type prototype.
enable_perform_sensing Default behaviour is to have a periodic sensing callback, but one can disable it to invoke the sense() call exclusively in other (subscription) callbacks

IMPORTANT NOTE: the behaviour of the sensor is strictly bounded to the prototype belief it'll work with (setup in the constructor and not updatable later on at run-time). This is done mainly because we imagine sensor nodes to be very ”task-specific” in terms of detection, meaning that we could not have a single sensor detecting the distance from environmental objects on a given dimension in a three-dimensional space and at the same time recognizing what kind of objects they are. At a higher level, this is supposed to be interpreted as tasks for different sensors, even if it could be the case that both are accessing the same raw data stream.

The belief prototype a sensor node is tightly bounded to has different implications depending on the belief type. They are all summarised in the following table.

Belief type Sensing behaviour Example
INSTANCE
pddl_type=1
The sensor node is bounded to a specific instance class (e.g. agent, waypoint) and could detect just the presence of instances of such type, i.e. triggering their addition/deletion to/from the belief set.
Sensor bounded to trigger add/del of instances of type waypoint
PREDICATE
pddl_type=2
The sensor node is bounded to a specific predicate definition (e.g. ”(in ?r - robot ?wp - waypoint)”), triggering either additions (or deletions) to (or from) the belief set of predicates of such type.
Sensor bounded to trigger add/del of ”in” predicates, as per definition given in the PDDL domain defined for the agent
FLUENT
pddl_type=3
The sensor node is bounded to a specific function instance (e.g. ”(battery charge ?cleaner 90)”), triggering first the addition (if not already there), then the update of its value by always publishing to the topic /agent_id/add_belief for further explanation). This does not imply that deletion is forbidden, but the most common usage scenarios shouldn’t foresee it.
Sensor bounded to trigger update of ”battery_charge” for ”cleaner” agent
Sensing methods
void performSensing()

Assuming the default behaviour (i.e. enable_perform_sensing set to true), any Sensor node must implement the virtual method void performSensing() specified in the provided abstract class from which it's inheriting. The call will be regularly called respecting the value of the corresponding init. parameters and it's supposed to withold the logic responsible for the sensing operations. If and when these require to update the belief set in some way, the sense(belief, op) API call is provided as described below and can be invoked within the performSensing() implementation.

void sense(const ros2_bdi_interfaces::msg::Belief& belief, const UpdOperation& op)
Parameter Description
belief Belief to be added/updated/deleted to/from the belief set of the ROS2-BDI agent. MUST be compliant to the proto_belief specified in the constructor.
op Specifying whether the belief must be added/updated/deleted, respectively the ammissible values are the following: {ADD, UPD, DEL}
Sensor init. ROS2 parameters
Parameter Type Description Default
init_sleep int Initial sleep period expressed in seconds for the sensor node, waiting for the first trigger of the performSensing call 0
sensing_freq float Frequency for calling performSensing, expressed in Hz 8.0

Actions

Setting up an action executor node for a ROS2-BDI agent can be seen as a little trickier, but hopefully the guide below will guide you through all the needed steps and available features.

Action constructor
BDIActionExecutor(const std::string& action_name, const int& working_freq, const bool agent_id_as_specialized_arg = true)
Parameter Required Description
action_name Name for the action; should match the one within the pddl domain definition
working_freq Frequency at which the doWork method is called (expressed in Hz)
agent_id_as_specialized_arg Set to true if the action node should contain within its ROS2 parameters, the agent id, entailing that only actions that contains within their arguments the agent_id will trigger and progress the enforcement of the action via the logic defined in this Action Executor node
Action Executor progress/status methods
float advanceWork()

Any BDIActionExecutor node must implement the virtual method float advanceWork() specified in the provided abstract class from which it's inheriting. The call will be regularly called respecting the value of the corresponding init. parameters passed in the constructor ( i.e. working_freq ) and it's supposed to withold the logic responsible for the action advancement operations, returning a float value in the [0-1] range which expresses how much progress has been made in that step. The latter starts from 0.0 when the action execution is triggered and advance toward completion (i.e. 1.0) through the cumulative returned value of the advanceWork() call. Reaching 1.0 will always be considered a successful execution. Immediate success or failure can be triggered too, regardless of the current progress value and/or the one for the "step" just executed. Within the method implementation, any of the API methods (status/communication) provided below can be called.

std::vector<std::string> getArguments()

returns arguments of the actions


float getProgress()

returns current progress state of the action in the [0-1] range


void execSuccess()

Communicates action execution has successfully completed; no additional log provided


void execSuccess(const std::string& success_log)
Parameter Description
success_log additional log for the successfully execution

Communicates action execution has successfully completed; additional log provided


void execFailed()

Communicates action execution has aborted; no additional log provided


void execFailed(const std::string& err_log)
Parameter Description
error_log additional log for the failed execution

Communicates action execution has aborted; additional log provided


Action Executor communication methods
BDICommunications::CheckBeliefResult
sendCheckBeliefRequest(const std::string& agent_ref, const ros2_bdi_interfaces::msg::Belief& belief)
Parameter Description
agent_ref agent_id of the ROS2-BDI agent that is going to be queried with the request
belief belief sent into the check request

returns whether update request has been accepted and belief has been found


BDICommunications::UpdBeliefResult
sendUpdBeliefRequest(const std::string& agent_ref, const ros2_bdi_interfaces::msg::Belief& belief, const BDICommunications::UpdOperation& op)
Parameter Description
agent_ref agent_id of the ROS2-BDI agent that is going to be queried with the request
belief belief sent into the update request
op specify whether the belief should be added or deleted; acceptable values among {ADD, DEL}

returns whether update request has been accepted and performed


BDICommunications::CheckDesireResult
sendCheckDesireRequest(const std::string& agent_ref, const ros2_bdi_interfaces::msg::Desire& desire)
Parameter Description
agent_ref agent_id of the ROS2-BDI agent that is going to be queried with the request
desire desire sent into the check request

returns whether update request has been accepted and desire has been found


BDICommunications::UpdDesireResult
sendUpdDesireRequest(const std::string& agent_ref, const ros2_bdi_interfaces::msg::Desire& desire, const BDICommunications::UpdOperation& op, const bool& monitor_fulfill)
Parameter Description
agent_ref agent_id of the ROS2-BDI agent that is going to be queried with the request
desire desire sent into the update request
op specify whether the desire should be added or deleted; acceptable values among {ADD, DEL}
monitor_fulfill specify whether the action should monitor the fulfillment of the desire (in case it is added through the update)

returns whether update request has been accepted and performed


bool isMonitoredDesireFulfilled(const std::string& agent_ref, const ros2_bdi_interfaces::msg::Desire& desire)
Parameter Description
agent_ref agent_id of the ROS2-BDI agent that has been already queried with the corresponding ADD desire request
desire desire sent into the update request

returns true if the desire has been fulfilled, false in any other case


Action init. ROS2 parameters
Parameter Type Description Default
- - - -