Skip to content
Maximillian Kirsch edited this page Apr 19, 2023 · 18 revisions

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.

Action definition Interface

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.

Add new Message package for Action definitions

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.

Action Manager

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.

Service Manager

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.

ExogManager

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;
}

Sensing Actions

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.