[Documentation] [TitleIndex] [WordIndex

Describe cram_pl/InternalTutorials here.

Designators overview

Designators are a means in CRAM to specify plan parameters using high-level symbolic concepts, and to defer decisions to the latest possible moment. As an example, we could describe a location to pick up an item as (location '((to-reach 'cup))) without committing to any x,y,z coordinates. This is in particular useful if we have not decided which cup yet, or when the cup location is unknown.

In CRAM, (reference desig) returns a dereferenced value for the symbolic description. The type of the value is not specified by the API, causing tight coupling between designator resolution and clients of the reference method.

Every designator class in CRAM has a reference method defined, such as:

(defmethod reference ((desig action-designator))
  (or (slot-value desig 'data)
    ;;some code to dereference a fresh designator here
    ...))

The existing designator classes provided by CRAM should be sufficient for most robotic purposes, so most user will not need to create new classes.

The existing classes are: * location designator * object designator * action designator referencing an object e.g. to manipulate, a location e.g. to move to or to place an entity, and a general action to take beyond moving an manipulating.

Designators are implemented as pointers that become immutable once they are set. This means they have a value slot that points at a dereferenced value, but may initially be nil. It may be set by calling reference, with the assumption being that once set, it will never change again. The reference methods are implemented to support that assumption. This reflects the robot decision being made as late as possible, avoiding premature commitment to actions, objects or locations.

Using an action designator

We'll use the action designator class for the tutorial example.

We want to describe the turtle drawing a symmetrical polygon shape as provided by the turtle_actionlib ROS package.

We can create action designators by descriptions using (make-designator) or (with-designator). That way, we can describe a concept in a high-level plan symbolically.

As an example, in a REPL we can load the designator system and create action designators like this:

CL_USER> (ros-load:load-system "designators" "designators")
...
CL_USER> (in-package :desig)
#<PACKAGE "CRAM-DESIGNATORS">
DESIG> (make-designator 'action `((type trajectory) (pose open) (side :left)))
#<ACTION-DESIGNATOR ((TYPE TRAJECTORY) (POSE OPEN) (SIDE :LEFT)) {10042289E1}>
DESIG> (make-designator 'action `((type shape) (shape triangle) (radius 3)))
#<ACTION-DESIGNATOR ((TYPE SHAPE) (SHAPE TRIANGLE) (RADIUS 3)) {1002D26F01}>
DESIG> (make-designator 'action `((type shopping) (list (milk eggs flour))))
#<ACTION-DESIGNATOR ((TYPE SHOPPING) (LIST (MILK EGGS FLOUR))) {10044ECD91}>

The items of the description can then be retrieved using the desig-prop-value function, e.g.:

DESIG> (defparameter mydesig (make-designator 'action `((type shape) (shape triangle) (radius 3)))) 
MYDESIG
DESIG> (desig-prop-value mydesig 'radius)
3

The key benefit of using designators lies in powerful dereferencing strategies. Dereferencing means getting a numeric representation of a real-world concept from the symbolic description.

In CRAM, action designators are resolved using PROLOG rules. The actual rules need to analyze the description and return some representation.

To show how this is done, we implement the rules for resolving action designators having type shape for the turtlesim simulator. Feel free to create a new ROS package and asdf system for the example. For the following you will need the following rospackage and asdf dependencies in your manifest and .asd files.

manifest.xml:

  <depend package="designators"/>
  <depend package="cram_reasoning"/>

turtle-high-level.asd

:depends-on (
               designators
               cram-reasoning)

We create a new LISP package in a file called package.lisp like this:

(in-package :cl-user)

(desig-properties:def-desig-package turtle-high-level
    (:nicknames :turtle-hl)
  (:use 
    #:cl
    #:cram-reasoning
    #:cram-designators)
  (desig-properties
    #:shape 
    #:radius 
    #:triangle 
    #:square
    #:pentagon  
    #:hexagon))

We use the desig-properties:def-desig-package macro instead of defpackage, to achieve a unified language of symbols. Else due to the nature of lisp. symbol namespace conflicts could occur between users of designators. Apart from declaring a new package, this declares the symbols that can be used in designator-related functions.

If you intend use those designator properties from a different LISP package, that other lisp package will also have to use the desig-properties:def-desig-package macro and declare all symbols it will use.

If we try to resolve the designator now, we will get a PROLOG error:

TURTLE-HL> (defparameter mydesig (make-designator 'action `((type shape) (shape triangle) (radius 3))))
MYDESIG
TURTLE-HL> (reference mydesig)
WARNING:
   Trying to prove goal (ACTION-DESIG
                          #<ACTION-DESIGNATOR
                            ((TYPE SHAPE) (SHAPE TRIANGLE) (RADIUS 3))
                            {10031A3EF1}>
                          ?ACT) with undefined functor ACTION-DESIG.
; Evaluation aborted on #<CRAM-DESIGNATORS:DESIGNATOR-ERROR "Cannot resolve action designator." {1003718711}>.

As you can see designator resolution is PROLOG based, a PROLOG query is executed and when it returns nil, an error is raised that no resolution was found.

So we need some PROLOG rules to allow dereferencing action designators having type shape. Create a new file called turtle-high-level.lisp with this content

(in-package turtle-high-level)

(defstruct turtle-shape
  "represents an object in continuous space matching a symbolic description"
  radius
  edges)


(cram-reasoning:def-fact-group shape-actions (action-desig)

  ;; for each kind of shape, call make-turtle-shape with the right number of edges

  ;; triangle
  (<- (action-desig ?desig (shape ?act))
    (desig-prop ?desig (type shape))
    (desig-prop ?desig (shape triangle))
    (lisp-fun make-turtle-shape :radius 1 :edges 3  ?act))

  ;; square
  (<- (action-desig ?desig (shape ?act))
    (desig-prop ?desig (type shape))
    (desig-prop ?desig (shape square))
    (lisp-fun make-turtle-shape :radius 1 :edges 4  ?act))

  ;; pentagon
  (<- (action-desig ?desig (shape ?act))
    (desig-prop ?desig (type shape))
    (desig-prop ?desig (shape pentagon))
    (lisp-fun make-turtle-shape :radius 1 :edges 5  ?act))

  ;; hexagon
  (<- (action-desig ?desig (shape ?act))
    (desig-prop ?desig (type shape))
    (desig-prop ?desig (shape hexagon))
    (lisp-fun make-turtle-shape :radius 1 :edges 6  ?act)))

These are a PROLOG rules embedded in a LISP file. They check whether the designator contains a property, and if so call the make-turtle-shape defined along with the structure with a suitable number of edges.

The def-fact-group macro merely makes sure that all following rules are loaded as one when compiling from emacs.

PROLOG code explained

  (<- (action-desig ?desig (shape ?act))
    (desig-prop ?desig (type shape))
    (desig-prop ?desig (shape hexagon))
    (lisp-fun make-turtle-shape :radius 1 :edges 6  ?act))

The prolog rule consists of a head and body. The head is the first s-exp after the arrow "<-". The body of the PROLOG rules check if they can unify with the given designator. In this case, (desig-prop ?desig (type shape)) checks that the designator has a property type set to shape, and (desig-prop ?desig (shape hexagon)) checks whether the designator has a property (shape hexagon). This is equivalent to (eql 'hexagon (desig-prop-value mydesig 'shape)) in LISP. Finally we call a lisp function:

    (lisp-fun make-turtle-shape :radius 1 :edges 6  ?act)

The first argument is the name of the function, then come the arguments, and finally the result ?act also used in the rule head. If the function returns nil, then the prolog rule will not be satisfied, and a different rule may unify.

Now we can resolve designators:

TURTLE-PM> (defparameter mydesig (make-designator 'action `((type shape) (shape triangle) (radius 3))))
MYDESIG
TURTLE-PM> (reference mydesig)
(SHAPE #S(TURTLE-SHAPE :RADIUS 1 :EDGES 3))

As you can see, (reference) returns a list, as specified in the PROLOG rule head, of the symbol shape and the result of the make-turtle-shape function.

PROLOG glue code

In different domains, different domain specific designator properties may be required. CRAM only provides a core set for primitive actions. To support more properties, it is possible (and necessary) to define own resolution methods.

Some helper functions exist in cram_reasoning/src/built-in-predicates.lisp and cram_reasoning/src/prolog_facts

Designator Classes

The existing designator classes should be sufficient for most tasks of a general manipulation platform. New classes should only be introduced if the present classes cannot be extended for the new purpose, e.g. by using new types in the class or specialized resolution rules. The more a single designator class is extended, the more useful it becomes.

We can create new Designator class by creating a simple CLOS class inheriting from desig:designator. As an example:

(defclass action-designator (designator)
  ())

Inheriting from designator means that the class will have several useful slots. The description slot will contain the s-expression used when creating the designator with make-designator or with-designators.

(register-designator-class typename class) allows to register designators so that they may be globally resolved. The class name is going to be the first symbol in the actual designator. The class must be a subclass of designator, such as action-designator above.

Process modules Overview

Process modules control different robot actuators. Every robot type needs an adapted set of process modules matching its own actuator types. We use the process module API to write high-level plans that are robot independent. Plans thus just need to know the API of process modules, and can run on different kinds of robots by invoking different process modules.

Process modules are referenced by an alias. as an example, in the cram_highlevel stack for manipulation platforms the following process module are organized using these aliases:

Plans in the cram_highlevel stack will assume the presence of a set of process modules, and if the robot has suitable process modules, the plan will run.

An example is:

(with-designators ((my-designator (location '((close-to 'fridge)))))
  (pm-execute :navigation my-designator))

This part of a plan would call the navigation process module by its alias :navigation and pass to it the designator (action turn-around). It is then up to the process module of the individual robot to move the robot to a location matching the designator. This can be very different on a robot with differential drive, a biped robot, or a robotic car. A typical way of dealing with designators is to first resolve them to numeric values and then execute actions based on those values. Designator resolution however is not specific to process modules and is therefore not necessarily part of process modules, it can be implemented in modules that are auxiliary to the whole system.

A process module for the Turtlesim simulator

As process modules should when possible use ROS actionlib actions, in this example we will rely on the turtle_actionlib action server to draw shapes. We will use a designator to define shapes symbolically, and a process module taking a shape designator and making the turtle draw the shape.

The process module

The process module will use the shape designator and draw a shape using the shape action. First we need an action client that calls the shape action server.

Using an actionlib client

As action client, reuse the one created in the actionlib_lisp tutorial Create a new package named turle-acs, with all due dependencies as given in the actionlib tutorial. Create a new source file, add the new sources to your .asd.

  (in-package turtle-acs)

  <Copy the code from the link, really.>

You will need to do this in a rospackage having dependencies to the actionlib_lisp and turtle_actionlib package in the manifest.xml:

  <depend package="actionlib_lisp"/>
  <depend package="turtle_actionlib"/>

do not forget to rosmake your package.

Also you will need to define the dependencies in your .asd file:

:depends-on (...
              actionlib
              turtle_actionlib-msg)

The process module code

To use the process module, you need additional dependencies in your manifest.xml and .asd file, to process_module, but also to cram_roslisp_common:

  <depend package="process_modules"/>

Don't forget to rosmake.

:depends-on (...
              process-modules)

Here is finally the process module code.

(in-package :tut)

(cram-process-modules:def-process-module turtle-actuators (action-designator)
  (roslisp:ros-info (turtle-process-modules) "Turtle navigation invoked with action designator `~a'." action-designator)
  (destructuring-bind (cmd action-goal) (reference action-designator)
    (ecase cmd
      (shape
         (turtle-acs:call-shape-action
          :edges (turtle-shape-edges action-goal)
          :radius (turtle-shape-radius action-goal))))))

The process module code explained

(cram-process-modules:def-process-module turtle-actuators (action-designator)...)

this defines the process module, one parameter is allowed to be named and used in the body.

    (destructuring-bind (cmd action-goal) (reference action-designator)

Since the designator reference is a list of 2 elements, we separate them, using the cmd to do a typecase, and the action-goal for the parameters to pass to the client.

The rest of the code is not specific to process modules.

Running the code

In the REPL, when the code is loaded, we can try this out. For that, we need a turtlesim and the turtlesim action server to be running, so first let's do this:

$ roscore
$ rosrun turtlesim turtlesim_node
$ rosrun turtle_actionlib shape_server

Now in the REPL, start a node and any action client:

TUT> (startup-ros)
[(ROSLISP TOP) INFO] 1298207247.654: Node name is pm-lisp-client
[(ROSLISP TOP) INFO] 1298207247.655: Namespace is /
[(ROSLISP TOP) INFO] 1298207247.655: Params are NIL
[(ROSLISP TOP) INFO] 1298207247.655: Remappings are:
[(ROSLISP TOP) INFO] 1298207247.655: master URI is 127.0.0.1:11311
[(ROSLISP TOP) INFO] 1298207248.701: Node startup complete
#<SB-THREAD:THREAD RUNNING {10058BEF71}>

Process modules run in an own lisp thread, to start and use one you would have to use multi-threading. For that CRAM language could be used with a pursue.

However, a more convenient method is to use util methods from cram_plan_library. For those, we assign to our process module one of 4 aliases (:navigation, :ptu, :manipulation and :perception).

Make sure to register the process module alias.

  (cpm:process-module-alias :manipulation 'turtle-actuators)

 process-module-alias alias process-module defines an alias for the process module. As an example, :navigation, :ptu, :manipulation and :perception have been used in the TUM cram_plan_library.

Having defined those aliases, it is possible to use the cram_plan_library util methods for process modules.

 ; use in the REPL, for many calls
 (plan-lib:start-process-modules)
  ;; use in the REPL to stop
 (plan-lib:stop-process-modules)
  ;; ;; use within top-level plans in pursue
 (plan-lib:maybe-start-process-modules)

So once you have registered the process module, start all process modules with:

(plan-lib:start-process-modules)

If that was successful, we can now use the designator and the process module to run a shape action:

(with-designators (( my-desig (action '((type shape) (shape triangle)))))
             (cram-process-modules:pm-execute :manipulation my-desig))

The turtle should now draw a triangle.

Important classes and macros

(def-process-module name (designator-var) body) creates a process-module and a run method for instances of this class. So this wraps a (defclass)

Example:

  (def-process-module pr2-navigation (goal-pose) (...)).

(pm-run alias starts a process module registered under the given alias. The invocation will never return, therefore it should be invoked in a multi-threaded way.

Example for sbcl:

(defvar process-modules-thread nil)

(defun start-process-module
  (setf process-modules-thread
    (sb-thread:make-thread (lambda () (pm-run :navigation)))))

(defun stop-process-module
  (sb-thread:terminate-thread process-modules-thread))


CategoryCategory


2022-05-28 12:32