[Documentation] [TitleIndex] [WordIndex

Note: This tutorial assumes that you have completed the previous tutorials: basic usage of roslisp.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Writing a Simple Action Server using the Execute Callback (Common Lisp)

Description: This tutorial presents the macros offered by actionlib_lisp.

Tutorial Level: INTERMEDIATE

Next Tutorial: Writing actionlib clients

Actionlib is a ros library that you are encouraged to use for any kind of action your robot could perform. Make yourself familiar with the concepts before doing this tutorial.

Creating the Action Messages

First, let's create a ROS package for this tutorial. Let's call it learning_actionlib:

$ cd YOUR_CATKIN_WS/src
$ catkin_create_pkg learning_actionlib roslisp actionlib_lisp actionlib_msgs std_msgs
$ cd .. && catkin_make

As we're going to write our own ActionLib server with our own action type, we'll need to add the message_generation and message_runtime dependencies into our package.xml as we do for custom .msg and .srv files.

package.xml:

<?xml version="1.0"?>
<package>
  <name>learning_actionlib</name>
  <version>0.0.0</version>
  <description>The learning_actionlib package</description>
  <maintainer ...
  <license ...

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>actionlib_lisp</build_depend>
  <build_depend>actionlib_msgs</build_depend>
  <build_depend>roslisp</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>message_generation</build_depend>
  <run_depend>actionlib_lisp</run_depend>
  <run_depend>actionlib_msgs</run_depend>
  <run_depend>roslisp</run_depend>
  <run_depend>std_msgs</run_depend>
  <run_depend>message_runtime</run_depend>
</package>

For compiling the action file correctly see the C++ version of this tutorial and come back when you've finished creating messages.

To make sure that the ASDF systems for your action have been generated properly, do the following:

$ ls devel/share/common-lisp/ros/learning_actionlib/msg/
FibonacciActionFeedback.lisp  _package_FibonacciActionFeedback.lisp
FibonacciActionGoal.lisp      _package_FibonacciActionGoal.lisp
FibonacciAction.lisp          _package_FibonacciAction.lisp
FibonacciActionResult.lisp    _package_FibonacciActionResult.lisp
FibonacciFeedback.lisp        _package_FibonacciFeedback.lisp
FibonacciGoal.lisp            _package_FibonacciGoal.lisp
FibonacciResult.lisp          _package_FibonacciResult.lisp
learning_actionlib-msg.asd    _package.lisp

Writing a Simple Server

The actionlib_lisp package provides some useful macros for writing actionlib servers with minimum effort.

Consider the action defined in the previous section:

$ rosed actionlib_tutorials Fibonacci.action
#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence

We need to refer to the message structure names "order" and "sequence" later.

The code

We will put the code of our server into src/fibonacci-server.lisp. Let's first create the ASDF system for our package:

learning-actionlib.asd:

(asdf:defsystem learning-actionlib
    :depends-on (roslisp std_msgs-msg actionlib actionlib_msgs-msg learning_actionlib-msg)
    :components
    ((:module "src"
              :components
              ((:file "package")
               (:file "fibonacci-server" :depends-on ("package"))))))

and then the corresponding src directory and the two files package.lisp and fibonacci-server.lisp therein:

src/package.lisp:

(defpackage learning-actionlib
    (:nicknames :lisp-acts)
  (:use :cl :roslisp :actionlib))

Here is the code for a Fibonacci server compatible to the examples for c++ and python:

src/fibonacci-server.lisp:

(in-package :learning-actionlib)

(def-exec-callback fib-callback (order)
  "This function takes in the FibonacciGoal message and pursues the action."
  (ros-debug (fib callback) "entering callback with goal ~a" order)
  ;; using a and b to count in Fibonacci steps
  (let ((a 1)
        (b 1)
        (seq (make-array 0 :adjustable t :fill-pointer 0)))
    (dotimes (i order)
      (when (cancel-request-received)
        (ros-debug (fib callback) "goal ~a canceled" order)
        (preempt-current :sequence seq)) ; this exits the callback

      ;; independently set a to value of b, and b to value of (+ a b)
      (psetq 
        a b 
        b (+ a b))
      ;; extend the Fibonacci sequence with the new element
      (vector-push-extend a seq)

      (ros-debug (fib callback) "publishing feedback for goal ~a" order)
      (publish-feedback :sequence seq)

      ;; sleeping just to represent long running action
      (sleep 1.0))
    (ros-debug (fib callback) "succeeding on goal ~a" order)
    (succeed-current :sequence seq)))

(defun fib-server ()
  (with-ros-node ("lisp_fib")
    (start-action-server
     "fibonacci"                          ; action namespace
     "learning_actionlib/FibonacciAction" ; action type
     #'fib-callback)))

The code explained

Let's first look at the main function fib-server. This function requires a ROS node to be started, and it creates all necessary topics to communicate with ActionLib clients. It gets a namespace and the action type as parameters, and a callback that will be executed for each client call.

The namespace is arbitrary, but this is what clients will need to refer to.

The actiontype is a concatenation of the packagename of the .action file, and the base filename of the .action file, plus the "Action" suffix. In this case, the action is defined in learning_actionlib/actions/Fibonacci.action. Hence the action type is "learning_actionlib/FibonacciAction".

The callback function calculates the Fibonacci numbers in an array. Here are the parts relevant for actionlib:

(def-exec-callback fib-callback (order)

We use the def-exec-callback macro instead of a defun as it provides several convenient properties. As you can see, we could just specify (order) as an argument.

The argument list is treated like the argument list of with-fields: it binds (possibly nested) fields of the goal object. The goal object with field "order" was defined in the .action file, see above.

When using def-exec-callback, it is important that the body of this function must not terminate normally, this will be interpreted as a failure by the macro. Instead, one of:

must be called. They all do a dynamic non-local exit, meaning the function terminates.

Succeed and abort have obvious semantics. Preempt is special. An action should be preemptable, meaning the client should be able to terminate the action before it finished, leaving a robot in a safe state.

While calculating the Fibonacci numbers is not unsafe, the example shows how this can be achieved in a loop:

(when (cancel-request-received)
  ...
  (preempt-current :sequence seq))

Here, cancel-request-received is a special variable provided by def-exec-callback, and it is T if the action server got a preempt request. We use the preempt-current macro to terminate the callback.

All of succeed-current, preempt-current, abort-current allow to send results in the result message format by giving a structure with keyword arguments matching the names in the .action file, for convenience, such that roslisp:make-message does not need to be called explicitly.

Finally, the call

(publish-feedback :sequence seq)

is interesting because it publishes a feedback message, and it is given :sequence as a keyword argument, matching the feedback message of the .action file. This publishes feedback to the client.

Calling the server

Once you loaded the code, you can invoke the server by calling:

CL-USER> (learning-actionlib::fib-server)

Not much will happen by just doing so, you will also need to call the action using an actionlib client. You can do this with any of the C++, Python or LISP clients. The LISP client is presented in the next tutorial.


2019-11-09 12:29