ROSClingo provides a generic way by which an ASP program may be used within the popular open-source Robot Operating System (ROS). To be more precise, the ROSClingo package integrates the ASP solver clingo 4 into the ROS service and actionlib architecture. The ROSoClingo package is build on top of ROSClingo and is specialized for the purpose of (interactive) task planning for robots. By running as a ROS actionlib ROSoClingo provides an elegant way to processes observations and requests issued. ROSoClingo provides a number of solving modes specialized for different user scenarios and allows for an easy customation of the solving precedure.
The ROS(o)Clingo package is build for ROS indigo on ubuntu 14.04, although ROS hydro on ubuntu 12.04 was also tested. And depends on the python module of gringo (version 4.5.4). In the follwing it is assumed that ubuntu 14.04 is used and running.
A c++11 conforming compiler, gcc version 4.8 (earlier versions will not work):
sudo apt-get install gcc-4.8 <br>
sudo apt-get install g++-4.8
Bison and other tools for parsing and making:
sudo apt-get install bison
sudo apt-get install re2c
sudo apt-get install scons
NOTE: the above automatically pulls in the correct versions of the following tools/libraries (provided Ubuntu 14.04 is used); for more advanced setups, see the Gringo INSTALL file.
To install ROS indigo please follow steps from the ROS page. Afterwards you need to set up a catkin workspace. You find a tutorial explaining how to do so here.
You need to build the gringo python module (4.5.4) on your computer. You can obtain the source here. Note that this can be placed anywhere in your filesystem. Unpack the archive, cd into the unpacked folder (gringo-4.5.4-source), and do the following:
scons configure --build-dir=release
You will then need to to configure the build/release.py
file. Assuming you
are using python2.7 and it is located in /usr/include/python2.7
modify the
following variables in build/release.py
:
CPPPATH = ['/usr/include/python2.7']
WITH_PYTHON = 'python2.7'
WITH_TBB = 'tbb'
Now run:
scons --build-dir=release pyclingo
Finally, add to ~/.bashrc
the line - replacing ${GRINGO_PATH}
with the path to the gringo-4.5.4 source folder:
PYTHONPATH=$PYTHONPATH:${GRINGO_PATH}/build/release/python/
Download and install the software - replacing ${CATKIN_PATH}
with the path for your catkin workspace:
cd ${CATKIN_PATH}/src
wget https://www.cs.uni-potsdam.de/wv/projects/rosoclingo/rosclingo-2.0.tar.xz
tar -zxvf rosclingo.tar.gz
cd ..
catkin_make
After ROSClingo is installed correctly download and install ROSoClingo -
replacing ${CATKIN_PATH}
with the path for your catkin workspace:
cd ${CATKIN_PATH}/src
wget https://www.cs.uni-potsdam.de/wv/projects/rosoclingo/rosoclingo-2.0.tar.xz
tar -zxvf rosoclingo.tar.gz
cd ..
catkin_make
When starting ROSClingo you may pass gringo control options as command line
parameter. In addition you may use -f PATH_TO_FILE
to add ASP encodings to
clingo.
While ROSClingo is running it provides a service rosclingo/control
to modify
the encoding. The service expects a ROSClingoControl type as parameter
consisting of a XML string describing the modifications. The following commands
are currently supported:
add, assign_external, cleanup_domains, ground, load, release_external, restart, get_externals, get_stats
With the exception of the last three, the commands correspond directly to the
functions described at http://potassco.sourceforge.net/gringo.html The
command get_externals
returns all external atoms of the current grounding,
while get_stats
returns the stats of the solver. The restart
command
generates a new solver object with the comandline arguments given at program
start. All commands must be enclosed in a <commands>
tag and are executed in
order. Example:
<commands>
<add name=NAME_OF_THE_PROGRAM_TO_BE_ADDED>
<parameter_definition name=NAME_OF_THE_PARAMETER/>
...
<program> PROGRAM </program>
</add>
<assign_external name=EXTERNAL_TO_BE_ASSIGNED value=TRUTH_VALUE_TO_BE_ASSIGNED/>
<cleanup_domains/>
<ground>
<program name=NAME_OF_THE_PROGRAM_TO BE_GROUNEDED>
<parameter value=VALUE_OF_THE_PARAMETER/>
...
</program>
...
</ground>
<load file=PATH_TO_THE_FILE/>
<release_external name=EXTERNAL_TO_BE_RELEASED/>
<get_externals/>
<get_stats/>
<restart/>
</commands>
The return value of a service call is a xml string with the requested information, as well as information whether the commands where executed successfully, i.e.:
<return>
<success>
True
</success>
</return>
For solving the ASP problem ROSClingo provides an Acationlib interface. A solving request is issued by sending ROSClingo a ROSClingoGoal data type. ROSClingoGoals consist of a XML string describing the assumption given to Clingo for the solve call.
<assumptions>
<assumption name=ATOM_TO_BE_ASSUMED value=TRUTH_VALUE_OF_THE_ATOM/>
...
</assumptions>
The result of a ROSClingo solve call is of the type ROSClingoResult and includes three values:
result
sat
, unsat
or unknown
) stating the result of the
clingo solve callinterrupted
answerset
Please note that calling the ROSClingo service while a goal is issued results in ROSClingo aborting the request.
The one-shot approach tries to solve the problem given as an instance with a
fixed number of actions do/3
. The number of actions may be changed via
command line parameter of rosclingo -c max=N
. The default is 14. Please run
in different terminals:
roscore
rosrun rosclingo run.py -f [PATH TO rosclingo]/example/instance.lp -f [PATH TO rosclingo]/example/toh.lp
python [PATH TO rosclingo]/example/start.py
The expected outcome in the last terminal is:
##### result
result: sat
interrupted: False
answerset: ['do(arm,putdown(2),12)', 'do(arm,putdown(1),10)', 'do(arm,pickup(2),5)', 'do(arm,putdown(4),4)', 'do(arm,pickup(3),11)', 'do(arm,putdown(3),6)', 'do(arm,pickup(4),1)', 'do(arm,putdown(b),2)', 'do(arm,pickup(4),13)', 'do(arm,pickup(3),3)', 'do(arm,pickup(1),7)', 'do(arm,putdown(3),14)', 'do(arm,putdown(c),8)', 'do(arm,pickup(2),9)']
#####
The incremental approach tries to solve the problem by increasing the number of allowed actions each time the solver returns unsat as result. WARNING: This may result in an infinite loop, if no solution is possible. Please run the following commands in different terminals to run the example:
roscore
rosrun rosclingo run.py -f [PATH TO rosclingo]/example/instance.lp -f [PATH TO rosclingo]/example/itoh.lp
python [PATH TO rosclingo]/example/istart.py
The expected outcome in the last terminal is:
solving with horizon 0
solving with horizon 1
solving with horizon 2
solving with horizon 3
solving with horizon 4
solving with horizon 5
solving with horizon 6
solving with horizon 7
solving with horizon 8
solving with horizon 9
solving with horizon 10
solving with horizon 11
solving with horizon 12
solving with horizon 13
solving with horizon 14
##### result<br>
result: sat<br>
interrupted: False<br>
answerset: ['do(arm,pickup(4),13)', 'do(arm,putdown(1),10)', 'do(arm,pickup(3),3)', 'do(arm,putdown(3),14)', 'do(arm,pickup(3),11)', 'do(arm,pickup(2),9)', 'do(arm,putdown(c),8)', 'do(arm,pickup(4),1)', 'do(arm,putdown(4),4)', 'do(arm,pickup(2),5)', 'do(arm,putdown(2),12)', 'do(arm,putdown(3),6)', 'do(arm,pickup(1),7)', 'do(arm,putdown(b),2)']<br>
#####
To function properly ROSoClingo needs two running instances of ROSClingo named “controller” and “planner”. The planner holds the ASP programm generating the task plan while the controller controls its execution. For communication purposes ROSoClingo provides an actionlib interface to issue requests to the planner and two topics for incoming / outcoming messages. Their usage is optional and depends on the mode the controller is running.
The controller modifies the planner’s encoding as well as the execution of the found task plan. There are currently 5 modes implemented available to be used in the rosoclingo/modes folder.
static.fixed.recording.lp
static.incremental.recording.lp
dynamic.fixed.recording.lp
dynamic.incremental.recording.lp
dynamic.incremental.reset.lp
dynamic.incremental.dyn_reset.lp
A static mode ignores all outside input and publishes the task plan to the
topic /rosoclingo/out
, if one is found, and terminates rosoclingo if no plan
is possible within a maximal horizon. A dynamic mode forwards received
messages and goal requests to the planner and organizes a successive execution
of the task plan’s actions. The mode terminates rosoclingo if the maximum
horizon is reached with no task plan. A fixed mode grounds the planer’s
encoding to the maximal horizon before trying to find a task plan. If the
planner returns unsatisfiable, rosoclingo is terminated. An incremental mode
only extends the planner’s horizon if no plan is found for the current one.
After the extension the planner is called again to find a plan with the new
horizon. This continues until either a plan is found or the maximum horizon is
reached in which case rosoclingo terminates. A recording mode keeps all
information accumulated during plan execution. The reset mode sets the current
state as inital state every specified number of steps, thus forgetting all
information between these steps. The dyn_reset mode
functions as the reset
mode, with the exception that the reset is initialized each time a goal request
is terminated and not every number of steps. The maximum horizon may be
changed via command line option -c steps=S
and the number of steps before
reset with -c r=R
. Both S
and R
are integers. To implement these modes
the controller’s encoding must respect the following special key words:
do(controller,Command,1)
holds(Fluent,1)
event(save,Fluent,0))
event(info,result(sat),0)
event(info,result(unsat),0)
event(info,pending_actions,0)
/rosoclingo/out
topic).event(info,new_messages,0)
/rosoclingo/in
topic).event(info,request_reset,0)
Currently the following commands are supported by rosoclingo. Additional
commands may be added into the ROSCommands class in the
rosoclingo/nodes/commands.py
file.
assign_externals(E,V)
assign_messages(T)
event(ID,V,T)
to true for all currently pending messages, with ID = message.id
and V = message.value
.commit_actions(T)
ROSoClingoOut(R,A)
message to the /rosoclingo/out
topic for
each yet unpublished action do(R,A,T)
of the time step T
. Also sets the
planner’s external event(commit,do(R,A),T)
to true.exit
ground(T)
T
as
parameter.idle
initialize
publish
/rosoclingo/out
topic.release_external(E)
E
in the planner’s encoding (same as the gringo.so
function).release_step(T)
T
that are not made true
previously.reset(T)
event(save,F,0)
to true
for all fluents F
that are true at time step T
. Also deletes all
tracted commited actions and messages in the rosoclingo system as well as
freeing all terminated goal request slots for later use.solve
status_update(T)
T
of the task plan.The planner’s encoding is divided into four program parts.
base
state(t)
transition(t)
query(t)
To function properly with rosoclingo the planner’s encoding must respect the following special predicates:
rslot(ID)
status(ID,S,T)
ID
to S
at time step T
.
Allowed status are accepted, rejected, succeeded and canceled. These
correspond to the actionlib status of ROS.do(R,A,T)
holds(F,T)
F
as true at time step T
. This is important for
the reset command, and may be omitted if reset is not used.As well as the externals:
query(T)
T
as the current horizon of the task plan.event(ID,Request,T)
ID
with a specific goal request Request at
time step T
.event(ID,cancel,T)
ID
is cancelled at time step T
.event(commit,do(R,A),T)
R
to action A
at time step T
of the task plan.event(S,C,T)
S
being the source
of the message, C
the content and T
the time step the message is
received. Note that this also includes all possible results C
of an
action the actuator S
executed.A ROSoClingo actionlib goal has the format ROSoClingoGoal(string request,
string[] information)
. With request being the goal forwarded to the planner
and information an array containing additonal information about the goal not
needed by the solver but by other ROS nodes. The gringo.parse_term
function
must be able to parse the string request, i.e. request must resemble an ASP
atom.
ROSoClingo opens two topics rosoclingo/in
for messages to rosoclingo and by
extension the planner and rosoclingo/out
for messages from rosoclingo to
other ROS nodes. The message format of rosoclingo/in
is ROSoClingoIn(string
id, string value)
, with id identifying the source of the message and value its
content. An incoming message results in the controller external
event(info,new_messages,0)
to become true until the command assign_messages
is issued. The string value must be parsable by gringo.parse_term
.
Analog to rosoclingo/in
, the message format of rosoclingo/out
is
ROSoClingoOut(string id, string action)
, with id
identifying the actuator
requested to execute an action. Note that the controller’s external
event(info,pending_actions,0)
is true as long as the set of
outgoing_messages.id
is not a subset of the set incoming_messages.id
.
Meaning that each action issued to an actuator needs a response from the
actuator signifying the termination of the action’s execution.
Some of the examples need you to get additional packages in your catkin workspace:
cd ${CATKIN_PATH}/src
wget https://www.cs.uni-potsdam.de/wv/projects/rosoclingo/examples.tar.xz
cd ..
catkin_make
This runs the incremental blocks world problem from the ROSClingo package. NOTE: In contrast to the ROSClingo example this one has an upper limit for the number of actions to solve the problem (specified py steps). Thus no infinite loop is possible. Pelease run in different terminals:
roscore
rosrun rosclingo run.py __name:=controller -f [PATH TO rosoclingo]/modes/static.incremental.recording.lp -c steps=20
rosrun rosclingo run.py __name:=planner -f [PATH TO rosclingo]/example/instance.lp -f [PATH TO rosclingo]/example/itoh.lp
rosrun rosoclingo run.py run.py
rostopic echo /rosoclingo/out
The expected outcome on the /rosoclingo/out topic is:
id: solver
action: [do(arm,pickup(4),13), do(arm,putdown(2),12), do(arm,pickup(2),9), do(arm,putdown(b),2), do(arm,pickup(4),1), do(arm,putdown(3),6), do(arm,pickup(1),7), do(arm,pickup(3),3), do(arm,putdown(4),4), do(arm,putdown(c),8), do(arm,pickup(2),5), do(arm,putdown(1),10), do(arm,putdown(3),14), do(arm,pickup(3),11)]
---
A robot is given requests of delivering objects from one office to an other.
Whenever a delivery request is issued, the robot has to navigate to the office
requesting the delivery, pick up the respective object, and then navigate and
deliver the object at the destination office. Requests may be canceled at any
time, resulting in the object being delivered back to it’s origin if already
picked up. To start the example in a static.fixed.recording
mode please run
the following commands in different terminals:
roscore
rosrun rosclingo run.py __name:=controller -f [PATH TO rosoclingo]/modes/static.fixed.recording.lp -c steps=14
rosrun rosclingo run.py __name:=planner -f [PATH TO rosoclingo_aspprograms]/mailbot/instances/graph_bench.lp -f [PATH TO rosoclingo_aspprograms]/mailbot/mailbot/requests/request01.lp -f [PATH TO rosoclingo_aspprograms]/mailbot/mailbot -c slots=1 -c x=2 -c y=2
rosrun rosoclingo run.py run.py
The graph_bench.lp
file contains a description of the environment as shown in
the figure below with 1 to 4 being offices to pickup and deliver package. The
starting position of the mailbot is c(1)
. The request01.lp
file contains
the request bring(1,3)
issued at time step 0, stating that the robot should
deliver a package from office 3 to office 1. The task plan found is published
in the /rosoclingo/out
topic. The same problem may be solved in an
incremental mode by changing static.fixed.recording
above into
static.fixed.recording
.
To utilitize dynamic modes a ROSoClingo client node needs running to issue
requests and actions issued by ROSoClingo need to be executeded (or simulated).
The following commands start ROSoClingo in dynamic.incremental.recording
mode:
roscore
rosrun rosclingo run.py __name:=controller -f [PATH TO rosoclingo]/modes/dynamic.incremental.recording.lp -c steps=14
rosrun rosclingo run.py __name:=planner -f [PATH TO rosoclingo_aspprograms]/mailbot/instances/graph_bench.lp -f [PATH TO rosoclingo_aspprograms]/mailbot/mailbot -c slots=1 -c x=2 -c y=2
rosrun rosoclingo run.py run.py
rosrun rosoclingo_interfaces fulfill.py
rosrun rosoclingo_examples mailbot_manual.py
The dynamic.incremental.recording
mode may be changed to another dynamic mode
to experiment. Note that the dynamic.fixed.recording
mode may cause problems
because requests are not issued fast enough before the task plan is executed.
The environment is now simulated by the Gazebo 3D simulator using an openly accessible world model available for the Willow Garage offices. The robot is a TurtleBot equipped with a Microsoft Kinect 3D scanner, which is a cost-effective and well supported robot suitable for small delivery tasks. To start the example please run the following commands in different terminals:
export ROBOT_INITIAL_POSE="-y -8 -x 7 -z 0 -R 0 -P 0 -Y 0"
roslaunch rosoclingo_gazebo mailbot_scenario.launch
This sets up and starts the gazebo simulator. The Willow Gerage environment is loaded and the turtle bot is placed in an open space (open1).
rosrun rosoclingo run-1.0.py --file [PATH TO rosoclingo_aspprograms]/mailbot/instances/graph_wg.lp --file [PATH TO rosoclingo_aspprograms]/mailbot/mailbot.lp
This terminal runs the ROSoClingo actionlib server with an ASP encoding of the mailbot problem and an instance file describing the Willow Gerage environment.
roslaunch rosoclingo_interfaces mailbot_interfaces.launch
This terminal runs the interface layer, transforming actions found in the task plan into actionlib requests. Note that while go actions are simulated by gazebo, pickup and deliver actions are abstracted into just succeeding.
rosrun rosoclingo_examples mailbot.py
This terminal runs a ROSoClingo actionlib client. To issure requests to the
mailbot, please specify the origin, the destination and the object. E.g. to
deliver a box of chocolate from o9
to o14
type o9 o14 box_of_chocolate
.
This instance of the mailbot problem supports office o1
-o14
and any kind
object (string without space). Requests issued may be canceled by typing the
request id provided. Any number of requests may be issued, but only 3 will be
considered by the ASP encoding at the same time. All others wait until a slot
becomes available.
If the robot is not able to traverse a path (e.g. because it is blocked by an
obstacle) the ASP encoding derives this information from a failed go action and
tries to find an alternative route. You may declare paths as (un-)blocked
yourself by running the un_block
interface in a new terminal:
rosrun rosoclingo_interface un_block.py
With b FORM TO
the path from FROM
to TO
is marked as blocked and with u
FROM TO
unblocked again. All paths between waypoints may be found in the
instance file graph_wg
(named connection(FROM,TO)
).