-
Notifications
You must be signed in to change notification settings - Fork 2
Home
Welcome to the gologpp-ros wiki!
Golog++ is a safe and accessible agent programming framework and language. This repository offers a ROS 2 Platform Backend to execute your agents on real or simulated robots by interfacing ROS.
This wiki provide a documentation and instructions to map between ROS 2 topics, services and actions on one side and golog++ (sensing)-actions and exogenous events/functions.
The golog++ language is described in its own Wiki.
The gologpp-ros uses a template implementation to integrate ROS actions, services and topics into the PlatformBackend to be called by a golog++ agent. To integrate those ROS functions, an explicit template definition has to be created for every action. Those definition has to be created in a .cpp file. Our convention is to create an own .cpp file for every message packages.
First, we describe how custom ROS 2 actions, services and topics are interfacing golog++ and then we show to create Action-/Service- and Exog-Manager for the ROS 2 turtlesim example.
To integrate a new set of actions a message package has to be provided. This message package has to build with the gologpp-ros package.
First open the package.xml
and add the package before :
<depend>$YOUR_MSGS_PACKAGE</depend>
Next open the CMakeLists.txt
and add inside the set
instruction at line 27 your message package:
set(gologpp_action_pkgs
turtlesim
gpp_action_examples_interface
$YOUR_MSGS_PACKAGE
)
Now we need to create an own .cpp file where we can define our action definitions or explicit template implementation.
Therefore, we have to create this .cpp inside the folder src/actions/
. The name of the file has to be:
gologpp_$YOUR_PACKAGE.cpp
If you look at line 67, there are instructions to install all .cpp files that have a corresponding message package. So if the message package is found by find_package()
, it will automatic try to install the .cpp if the name matches.
foreach(pkg ${found_action_pkgs})
if ("${${pkg}_FOUND}")
target_sources(gologpp_agent PRIVATE "src/actions/gologpp_${pkg}.cpp")
target_compile_definitions(gologpp_agent PUBLIC "-D${pkg}_FOUND")
endif()
endforeach()
Next, open your gologpp_$YOUR_MSGS_PACKAGE.cpp
file and include necessary headers:
#include "action_manager.h"
#include "exog_manager.h"
#include "ros_backend.h"
#include <execution/controller.h>
Now we can create so called ActionManager
, ServiceManager
and ExogManager
objects that handles the action execution.
To create those Manager objects we have to create a function. The name is not important, but we like to have as name conventation define_$YOUR_MSGS_PACKAGE_actions
i.g.:
void RosBackend::define_$YOUR_MSGS_PACKAGE_actions()
{
}
After we define this function we need to mention it inside the header and call the function from the RosBackend constructor.
Therefore open src/ros_backend.h
and your functions behind the other definitions in the private
section of the RosBackend
in line 82 i.g.:
private:
virtual void terminate_() override;
// May have an implementation iff the corresponding package has been found
void define_turtlesim_actions();
void define_action_examples_actions();
define_$YOUR_MSGS_PACKAGE_actions(); // <---------------- Your Function
template<class ActionT>
void create_ActionManager(const std::string &name);
AbstractActionManager &get_ActionManager(shared_ptr<Activity>);
Now we can call our function inside the constructor of the RosBackend
for initializing our ActionManager objects that interfacing our agent.
Open ros_backend.cpp
inside tie src
and the call of your action definition function before spin_exog_thread()
with #ifdef #endif
instructions.
I.g.:
#ifdef $YOUR_MSGS_PACKAGE_FOUND
define_$YOUR_MSGS_PACKAGE_actions();();
#endif
Finally we have a dedicated .cpp
to add Action-/Service- and Exog-Manager that interfacing golog++ actions for a specific message package.
The action definitions for the turtlesim can be found in src/actions/gologpp_turtlesim
.
The turtlesim package has his own messages. In our agent we want to use the /turtle1/rotate_absolute
action. Therefore we have to include the messages that is used by this action, so first include it by:
#include "turtlesim/action/rotate_absolute.hpp"
Then we use the create_ActionManager()
to initialize the manager object with the name of the action server
:
void RosBackend::define_turtlesim_actions()
{
create_ActionManager<turtlesim::action::RotateAbsolute>("/turtle1/rotate_absolute");
}
Now our ActionManager is created, but to use it we have to create an explicit template definition for that transforms golog++ params to the goal message of our action server
.
Therefore we have to create an explicit template definition for the build_goal()
function inside our .cpp
.
I.g.:
template<>
ActionManager<turtlesim::action::RotateAbsolute>::GoalT
ActionManager<turtlesim::action::RotateAbsolute>::build_goal(const gpp::Activity &a)
{
auto goal = turtlesim::action::RotateAbsolute::Goal();
goal.theta = a.mapped_arg_value("theta").numeric_convert<float>();
return goal;
}
We just have to create the goal variable for our action server
, convert the golog++ variable into a goal type and return the goal.
Now we have to define the action
inside our .gpp agent. The action with mapping of the /turtle1/rotate_absolute
action can look like this:
action move_theta(number angle) {
mapping:
"/turtle1/rotate_absolute" {
theta = angle
}
}
Note in the mapping section we pass the name of our action server
and then the params for the goal. On the right side we have the golog++ value and on the left the name of the param used the access it inside our build_goal()
function. In this example the name the param inside the agent theta
, so the mapped_arg_value
has to be called with "theta".
colcon build
the gologpp-ros package.
The ServiceManager is working similar to the ActionManager, but instead of executing a ROS 2 action we invoke ROS 2 services.
We use the service /spawn
as an example.
First, we have to include the message for the service:
#include "turtlesim/srv/spawn.hpp"
Then we create a ServiceManager object to interface the gpp action with the ROS 2 service.
void RosBackend::define_turtlesim_actions()
{
create_ServiceManager<turtlesim::srv::Spawn>("/spawn");
}
To use this service we have to transform the golog++ values to a ROS 2 service request similar to ROS 2 action.
Therefore we have to provide an explicit definition for the build_request()
function.
template<>
ServiceManager<turtlesim::srv::Spawn>::RequestT
ServiceManager<turtlesim::srv::Spawn>::build_request(const gpp::Activity &a)
{
auto request = std::make_shared<turtlesim::srv::Spawn::Request>();
request->x = a.mapped_arg_value("x").numeric_convert<float>();
request->y = a.mapped_arg_value("y").numeric_convert<float>();
request->theta = a.mapped_arg_value("theta").numeric_convert<float>();
return request;
}
After we colcon build
we can use this service and define an action for this service inside our agent like:
action spawn_turtle(number x, number y, number theta) {
mapping:
"/spawn" {
x = x,
y = y,
theta = theta
}
}
The mapping inside the agent for ROS 2 services is equal to the mapping for ROS 2 actions.
Exogenous actions are used to react on events that occur outside of the agent. As an example, we create the topic "/exog_event" and if a message arrives on that topic the corresponding exog_action
has to fire by setting an effect or assigning values.
First we include bool messages to be used by our topic.
#include "std_msgs/msg/bool.hpp"
Then we create an ExogManager. The ExogManager will subscribe on the topic passed as param, here /exog_event
:
void RosBackend::define_turtlesim_actions()
{
create_ExogManger<std_msgs::msg::Bool>("/exog_event");
}
Then we have to create an explicit definition for params_to_map()
to transform the message from our ROS 2 topic to a golog++ value.
For example:
template<>
std::unordered_map< std::string, gpp::unique_ptr<gpp::Value> >
ExogManager<std_msgs::msg::Bool>::params_to_map(const std_msgs::msg::Bool::ConstPtr& msg) {
gpp::unique_ptr<gpp::Value> param (new gpp::Value(gpp::get_type<gpp::BoolType>(), bool(msg->data)));
std::unordered_map< std::string, gpp::unique_ptr<gpp::Value> > params_to_map;
params_to_map.insert({"data", std::move(param)});
return params_to_map;
}
Now we can create an exog_action
for this topic inside our .gpp agent like:
exog_action exog_trigger(bool data) {
mapping:
"/exog_event" {
data = data
}
effect:
exog_state() = true;
}
In golog++ an action can provide none or one result. At the moment we can return as result a number
or a string
. Therefore we have to create an exog_function
inside our .gpp agent that returns either a number or string. The param string ros_action_name
is a unique identifier.
An example for a sensing result for the /turtle1/rotate_absolute
action looks like this:
number fluent delta_result() {
initially:
() = 0.0;
}
number exog_function sense_number(string ros_action_name);
action move_theta(number angle) {
senses:
delta_result() = sense_number("/turtle1/rotate_absolute");
mapping:
"/turtle1/rotate_absolute" {
theta = angle
}
}
First we have to create a fluent like delta_result()
to store our result. Then we create a exog_function
that waits for the action server
to finish and pass the result.
Inside the gologpp_turtlesim.cpp
we have to create an explicit template definition for to_golog_constant
that transform our ROS 2 action server result into a golog++ value. An example for this function:
template<>
gpp::optional<gpp::Value>
ActionManager<turtlesim::action::RotateAbsolute>::to_golog_constant(ResultT::WrappedResult result){
std::cout<<"result"<<std::endl;
return gpp::Value(gpp::get_type<gpp::NumberType>(), result.result->delta);
}
Passing a result or a response for the ServerManager works the same way.