Introducing a new robot to CRAM

To understand how to introduce a new robot to CRAM, let us first look at how PR2 is integrated into the system.

Motion designators on PR2

What are they?

First of all, we need to define the atomic actions that the robot is supposed to execute.

For mobile manipulation robots that would include driving, moving-arm, opening-gripper, moving-neck, etc.

For a flying robot with a distance sensor that would include flying, distance-sensing, etc.

For a human-robot interaction robot that would include saying, listening.

For mobile manipulation robots of CRAM the defined motion designators can be found in “cram_common_designators” package in motions.lisp file:

This is how we define motion designator grounding aka referencing for looking motions:

(def-fact-group ptu-motions (motion-grounding)
 
  (<- (desig:motion-grounding ?designator (move-head :pose ?pose))
    (desig:desig-prop ?designator (:type :looking))
    (desig:desig-prop ?designator (:target ?location-designator))
    (desig:designator-groundings ?location-designator ?poses)
    (member ?pose ?poses))
 
  (<- (desig:motion-grounding ?designator (move-head :frame ?frame))
    (desig:desig-prop ?designator (:type :looking))
    (desig:desig-prop ?designator (:frame ?frame)))
 
  (<- (desig:motion-grounding ?designator (move-head :direction ?direction))
    (desig:desig-prop ?designator (:type :looking))
    (desig:desig-prop ?designator (:direction ?direction))))

Motions are executed with PERFORM calls:

(on-real-pr2
  (perform
    (a motion
       (type going)
       (target (a location (pose xyz))))))
 
(on-simulated-pr2
  (perform
    (a motion
       (type going)
       (target (a location (pose xyz))))))
 
(on-real-boxy
  (perform
    (a motion
       (type going)
       (target (a location (pose xyz))))))     

As you can see, the PERFORM call stays exactly the same, only the environment setup commands (ON-REAL-BOXY etc) change, which completely change command execution.

Motion designators are the abstraction layer for plans from robot hardware platform

Executing motion designators on real PR2

Low-level ROS interfaces

We start with low-level ROS clients for our PR2: it's the cram_pr2_low_level package.

This package has clients for different ROS actions / services / topics that the real PR2 robot provides.

E.g., let's look at the looking action:

https://github.com/cram2/cram/blob/master/cram_pr2/cram_pr2_low_level/src/ptu.lisp

Connecting low-level functions to motion designators -- process modules

A process module is a software component that is responsible for one resource of the robot, e.g. it's base, it's camera, it's neck, it's arms or it's left arm etc.

When you call

(perform (a motion
            (type looking)
            (direction forward)))

The PTU (pan-tilt-unit) process module automatically finds the low-level Lisp function that should be executed for motions of type looking.

It also does primitive resource management of the robot's neck resource: it enqueues all PERFORM calls that go to the neck such that they don't conflict with each other when you have a concurrent program.

Defining process modules

Question: how does the PM (process module) know which low-level function to call?

For the PR2 we can see the code in the cram_pr2_process_modules package, e.g. in ptu.lisp file.

First, the PM references the motion designator that it gets (that's implemented in a file we looked at before – motions.lisp).

Then it says, if the command from the referenced motion designator is cram-common-designators:move-head then I'll call the low-level function pr2-ll:call-ptu-action.

Matching process modules to designators

Question: how does the PTU PM get only motion designators of type looking and not of type moving-arm?

That is done by assigning different types of designators to different types of PMs – let's look in the designators.lisp file.

Performing the motions

(cram-process-modules:with-process-modules-running
    (pr2-pms:pr2-grippers-pm pr2-pms:pr2-ptu-pm)
  (cpl:top-level
    (exe:perform
     (desig:a motion
              (type looking)
              (frame "r_gripper_tool_frame")))))
 
(cram-process-modules:with-process-modules-running
    (pr2-pms:pr2-grippers-pm pr2-pms:pr2-ptu-pm)
  (cpl:top-level
    (let ((?pose-stamped
            (cl-transforms-stamped:make-pose-stamped
             "map"
             0.0
             (cl-transforms:make-3d-vector 0 0 0)
             (cl-transforms:make-quaternion 0 0 0 1))))
      (exe:perform
       (desig:a motion
                (type looking)
                (target (desig:a location
                                 (pose ?pose-stamped))))))))

Executing motion designators on projected PR2

Now let's take a different robot – the projected PR2 – and do the exercise one more time.

Low-level

Let's define the low-level interfaces for projected PR2 – the low-level.lisp file.

Process modules

Defining process modules

Now let's define a PTU process module – process-modules.lisp file.

Matching process modules to designators

Now let's say which PM corresponds to which motion designator – same file bottom.

Performing motions

The first line is something very specific for projection, so ignore it, if you're not writing projection code you will not need this.

(let ((cram-projection:*projection-environment* 'pr2-proj:pr2-bullet-projection-environment))
  (cram-process-modules:with-process-modules-running
      (pr2-proj::pr2-proj-ptu)
    (cpl:top-level
      (let ((?pose-stamped
              (cl-transforms-stamped:make-pose-stamped
               "map"
               0.0
               (cl-transforms:make-3d-vector 1.0 0 0.5)
               (cl-transforms:make-quaternion 0 0 0 1))))
        (exe:perform
         (desig:a motion
                  (type looking)
                  (target (desig:a location
                                   (pose ?pose-stamped)))))))))

Summary: your ToDo checklist to implement performing motion designators on your robot

  • Create a new CRAM package with dependency on cram-prolog and cram-designators. In your namespace use cram-prolog for convenience. Look at creating a CRAM package tutorial for reference.
  • Add a new file (or create a new package) for your low-level functionality. It should add dependencies on all ROS message packages that you use for communication, on the ROS Lisp API roslisp, on actionlib if you use ActionLib, roslisp-utilities if you use the ROS startup functionality etc. Message packages in ASDF are defined by adding -msg suffix to the ROS package name, e.g. std_msgs-msg. See roslisp tutorials for more on this.
  • Define process modules for your low-level functions. This will add dependency on cram-process-modules. See PM tutorial. Also define Prolog predicates matching-process-module and available-process-module for your PMs (see corresponding tutorial.
  • Finally, to test, perform the motions through with-process-modules-running macro. This will add a dependency on cram-executive, which defines the perform command.

Action designators and plans

This is TODO…

Projection for your new robot

URDF

The first thing you need is a URDF of your robot. Find the package that upload your robot's URDF to the ROS parameter server.

For example, for HSR you can find the urdf here: https://github.com/ToyotaResearchInstitute/hsr_description.

Then simply execute in your terminal:

 roslaunch hsr_description upload.launch 
The HSR does not have so many links and joins as the PR2 so our life is much easier on that point. Open your roslisp_repl and load every packages you will need.

Spawn the robot in Bullet

Use the Prolog assertion predicates. See example of how PR2 is spawned from a URDF in the bullet tutorial: http://cram-system.org/tutorials/intermediate/bullet_world#bullet_world_initialization.

You will find the code from the bullet world tutorial in short form here. For explanation what the single commands are doing please visit the tutorial above mentioned.

Example for HSR:

CL-USER> (ros-load:load-system "cram_bullet_world_tutorial" :cram-bullet-world-tutorial)
CL-USER> (in-package :cram-bullet-world-tutorial)
BTW-TUT> (roslisp:start-ros-node "bullet_world")
 
BTW-TUT> (cram-occupancy-grid-costmap::init-occupancy-grid-costmap) ; Inits the occupancy map for navigation.
BTW-TUT> (cram-bullet-reasoning-belief-state::ros-time-init) ; Resets the simulated time.
BTW-TUT> (cram-location-costmap::location-costmap-vis-init) ; Inits publishing of markers in RViz.
BTW-TUT> (cram-tf::init-tf) ; Inits the TF listener object.
 
 
BTW-TUT> (prolog:prolog '(btr:bullet-world ?world))
 
BTW-TUT> (prolog:prolog '(and (btr:bullet-world ?world)
                              (btr:debug-window ?world)))
 
BTW-TUT> (prolog:prolog '(and (btr:bullet-world ?world)
                              (assert (btr:object ?world :static-plane :floor ((0 0 0) (0 0 0 1))
                                                  :normal (0 0 1) :constant 0))))
 
BTW-TUT> (let ((robot-urdf
                   (cl-urdf:parse-urdf
                    (roslisp:get-param "robot_description"))))
             (prolog:prolog
              `(and (btr:bullet-world ?world)
                    (cram-robot-interfaces:robot ?robot)
                    (assert (btr:object ?world :urdf ?robot ((0 0 0) (0 0 0 1)) :urdf ,robot-urdf))
                    (cram-robot-interfaces:robot-arms-parking-joint-states ?robot ?joint-states)
                    (assert (btr:joint-state ?world ?robot ?joint-states))
                    (assert (btr:joint-state ?world ?robot (("torso_lift_joint" 0.15d0)))))))

If you follow all the instruction corretly the HSR will be spawned. And should currently looking like this or in total grey (depend on which URDF is used).

At this moment the command: (cram-tf::init-tf) loads some variables:

(defvar *robot-base-frame* nil
  "Robot's TF tree root frame. Be careful, this parameter and the ones below
are only initialized when a ROS node is started. DO NOT USE THESE IN OTHER PARAMETERS,
or in general at compile-time.")
 
(defvar *robot-torso-frame* nil)
 
(defvar *robot-torso-joint* nil)
 
(defvar *robot-left-tool-frame* nil
  "Tool frame of the left arm. Initialized from CRAM robot description.")
(defvar *robot-right-tool-frame* nil
  "Tool frame of the right arm. Initialized from CRAM robot desciption.")
Usually u will define those parameters in the ROBOT_description package. For now to simply test the beginning it is okay that this parameters got filled from the PR2_description package. But be aware that some joint and links does not have the same name. For example the hsr does not contain any joint or links with the name gripper like the PR2 does.

Try to move the robot joints using Bullet Prolog predicates or Lisp functions. E.g.

(btr:set-robot-state-from-joints
 '(("torso_joint"  0.1))
 (btr:object btr:*current-bullet-world* 'my-robot))

If you are unsure what joints are possible to move use:

(btr:object btr:*current-bullet-world* 'cram-pr2-description:pr2)
to get the instance of the spawned Bullet object there is a function.

After lifting the torso the HSR should looks like this:

The next step is to define the robot_description and robot_projection packages, so that we can use the robot fully in the bullet world.

Create a CRAM robot description package

Similar to cram_pr2_description or cram_boxy_description, it should contain Prolog queries describing the robot semantically. You need to decide how you define each part of the robot for example Boxy does have a own neck.lisp file in the package. The HSR is similar to the PR2, hence the description package contains arm.lisp and knowledge.lisp.

The queries are implementations of the interfaces defined in cram_robot_interfaces. It is important that the joints and links are correctly sorted. Define the most important links and joints there e.g.

;;arm.lisp file
(in-package :cram-hsrb-description)
 
(defparameter *standard-to-hsrb-gripper-transform*
  (cl-transforms-stamped:make-identity-transform))
 
(def-fact-group hsrb-arm-facts (end-effector-link
                                robot-tool-frame
                                arm-joints arm-links
                                gripper-joint
                                gripper-link
                                standard-to-particular-gripper-transform)
 
  (<- (end-effector-link hsrb :left "wrist_roll_link"))
 
 
  (<- (robot-tool-frame hsrb :left "gripper_tool_frame"))
  (<- (robot-tool-frame hsrb :right "ATTENTION please don't use this frame"))
 
  (<- (arm-joints hsrb :left ("arm_flex_joint"
                              "arm_roll_joint"
                              "wrist_flex_joint"
                              "wrist_roll_joint")))
 
  (<- (arm-links hsrb :left ("arm_flex_link"
                             "arm_roll_link"
                             "wrist_flex_link"
                             "wrist_roll_link")))
 
 
  (<- (gripper-joint hsrb :left "hand_motor_frame_joint"))
 
  (<- (gripper-link hsrb :left ?link)
    (bound ?link)
    (lisp-fun search "hand_palm_link" ?link ?pos)
    (lisp-pred identity ?pos))
 
 
  (<- (standard-to-particular-gripper-transform hsrb ?transform)
    (symbol-value *standard-to-hsrb-gripper-transform* ?transform)))
First we define a fact-group that containts end-effector-link, robot-tool-frame, arm-joints arm-links, gripper-joint, gripper-link and standard-to-particular-gripper-transform. The end-effector-link of a robot is the tool that replace the hand from a human and it is connected to the arm. With the end-effector-link the robot is interacting with his environment. We also use this for calculating distances from object to gripper. Since you can replace the grippers mostly on robots you need to add a specific offset for calculating distances (gripper length).

As you can see for the HSR (the full name is HSRB, that's why we called him like that in the lisp files) the arm is defined as :LEFT arm. This makes everything more easier, since all of the other lisp functions are working with the key words :LEFT or :RIGHT. So we highly recommence to take either :LEFT or :RIGHT for one handed or both for two handed. For the cram_tf packages it's necessary to describe left and right robot-tool-frame, even tho the HSR only has one arm. This will be fixed soon but for now we use this hack:

 (<- (robot-tool-frame hsrb :right "ATTENTION please don't use this frame")). 

If no gripper_tool_joint is defined for your robot you need to add one in the URDF it self. It is important that the parent will be the wrist_palm_link and the joint is fixed. The origin you will need to change yourself it should be directly in the middle of the gripper:

<joint name="gripper_tool_joint" type="fixed">
        <origin xyz="0 0 0.07" rpy="0 0 1.6" />
         <parent link="${prefix}_palm_link" />
          <child link="gripper_tool_frame"/>
        </joint>
        <link name="gripper_tool_frame"/>

Then the moveable arm-joints/links are defined and the gripper-joints/links.

Next step is to define the general-knowledge.lisp:

(in-package :hsrb-descr)
 
(def-fact-group hsrb-metadata (robot
                               robot-odom-frame
                               robot-base-frame robot-torso-link-joint
                               arm
                               camera-frame)
  (<- (robot hsrb))
 
  (<- (robot-odom-frame hsrb "odom"))
 
  (<- (robot-base-frame hsrb "base_footprint"))
  (<- (robot-torso-link-joint hsrb "torso_lift_joint" "torso_lift_link"))
 
  (<- (arm hsrb :left))
 
  (<- (camera-frame hsrb "/hsrb/head_rgbd_sensor")))

It is important to describe the robot-base-frame here and the torso-link/joint. What arms does the robot have and the camera he is using. The HSR does have two cameras (left-eye right-eye) but there is one topic that contains the information of both.

Create a CRAM YOUR_ROBOT_NAME projection package

Similar to cram_pr2_projection and cram_boxy_projection, it should first of all have a low-level.lisp file, which defines interactions with the Bullet world.