| Note: This tutorial assumes that you have completed the previous tutorials: basic usage of roslisp. |
Organizing files for roslisp
Description: This tutorial explains how the ASDF standard is used in roslisp, with a few added conventions.Tutorial Level:
Next Tutorial: Unit testing
Contents
Creating executable scripts
The quickest way to create an executable with roslisp is to create a roslisp script. This is similar to creating python scripts, except that we need a little more shell magic in the beginning of the file.
Hacking into shell to invoke ROS SBCL
Here is a quick Hello World example. Create a file called "helloworld" with the following contents:
#!/usr/bin/env sh "true";exec /usr/bin/env rosrun sbcl run-sbcl.sh --script "$0" "$@" (format t "Hello world~%") (format t "Args: ~a " sb-ext:*posix-argv*)
Make the file executable (e.g. "chmod u+x helloworld"). Then run it from the shell.
$ ./helloworld Hello world Args: (</path/to/roslisp_support>/sbcl/sbcl/bin/sbcl)
How does this work? The shebang line declare the script to be a shell script, so the program sh is invoked with this file.
sh ignores the first line, and executes the second line.
"true" is a shell command doing nothing, we need it here only to be able to use the ";" next, as the ";" will be important a little later. Then sh executes "exec /usr/bin/env rosrun sbcl run-sbcl.sh --script ..." which ends the sh}} process and starts the SBCL LISP interpreter provided with ROS. The detour over {{{rosrun ensures that you can use the released or also the latest sbcl ROS package to run your scripts.
This loads the file, ignoring the first line thanks to the "--script command", interprets "true" as a harmless string evaluating to itself, and treats anything after the ";" as a comment.
It then loads anything else that comes as LISP S-Expressions, so you can use (DEFUN ...) etc.
The example just prints "Hello world", and the arguments provided by the command line. As you can see the first argument is the SBCL LISP shell interpreter, so if you want to create a script using arguments, always ignore the first argument. This is the same behavior as for other scripts.
The downside is that such a LISP script file is not a valid LISP file anymore, due to the initial line, so some IDEs and editors will not be able to parse it. So we recommend that you use this only for small examples or to call functions defined in other ASDF systems.
Using roslisp in scripts
New in ROS electric since October 2011.
To start a node, listen to topics, provide services, etc. your script can just load ASDF systems as provided by other ROS packages. To get access to other ROS packages easily, you need the function (ros-load:load-system), which wraps (asd:load-system). To include the function in your script, you can use a different shebang line using the ROS script run-roslisp-script.sh.
e.g.
#!/usr/bin/env sh
"true";exec /usr/bin/env rosrun roslisp_runtime run-roslisp-script.sh --script "$0" "$@"
(ros-load:load-system "roslisp" "roslisp")
(in-package :roslisp)
(with-ros-node ("talker")
(let ((i 0)
(pub (advertise "chatter" "std_msgs/String")))
(loop-at-most-every .1
(publish-msg pub :data (format nil "foo ~a" (incf i))))))Several popular LISP libraries (e.g. Alexandria, cffi) are already provided in the CRAM PL library, you can depend on those and have a look there to get an idea how to integrate further libraries as ROS packages.
Loading other lisp files
If you want to load functions from other lisp files in your script, you will need to collect those other files in an ASDF system in a ROS Package. See below for doing that. You can do the same for loading files from the same ROS package that your script lies in. So if your script lies in ROS package my_package, and you have defined further function in the ASDF system my-system in the same ROS Package my_package, you can load the function from your script by invoking:
(ros-load:load-system "my_package" "my-system")
Since script files are not valid LISP files due to the initial line, you cannot by default load them from other scripts or provide them via ASDF.
Setting up a new roslisp ROS package
Writing scripts is a quick way to create nodes. However LISP scripts do not allow to easily split up your files or use functions defined in other ROS packages. To achieve this, you'd rather have to define ASDF systems. ASDF is the de facto standard LISP build system.
roslisp allows to reference any ASDF system that is in any ROS package in the ROS_PACKAGE_PATH, given a few conventions are met.
In a nutshell, all you need to do is to place your .asd file (or a softlink to it) into the root of the ROS package, or a folder named asdf below the root.
The following gives a detailed example to follow for LISP beginners.
The following is a non-standard package setup with distinctive names for the ROS package and asdf system names in order to make finding errors easier. Normally, the system should be called as the ros package with underscores replaced by dashes. We will try and set up a roslisp to control the turtles in the ros turtlesim.
We will use distinct names here for ROS package, asdf system and LISP package, as error messages may be confusing for LISP novices when the same name is used.
- ROS Package : lispturtles_pkg
- ASDF System : lispturtles-system
- LISP Package : lispturtles
For advanced users, it is more common to use similar or same names for ros package, asdf system, and Lisp package. Note that one ROS package may contain several ASDF systems, e.g. -main and -test, and that each ASDF system may contain several LISP packages, e.g. -core, -util, -internal...
Create a ros package as usual including a dependency to roslisp. Somewhere in your ROS_PKG_PATH, call
$ roscreate-pkg lispturtles_pkg roslisp_runtime turtlesim
We include the dependency to turtlesim to be able to access the service and message types.
Inside the new folder "lispturtles_pkg", create 2 folders named src and asdf.
The asdf folder is where ros will look by default for system definitions. As you may want to define more than one system per package, it is good practice to put corresponding .asd files in subfolders, and softlink to them from the asd folder.
Let's do so, in src, create a file named lispturtles-system.asd with this content:
(asdf:defsystem lispturtles-system
:depends-on (roslisp
turtlesim-msg
turtlesim-srv)
:components
((:module "lispturtles"
:components
((:file "package")
(:file "lispturtles-core" :depends-on ("package"))))))This declares the system components, a subfolder named lispturtles, and within 2 files, package.lisp and lispturtlecore.lisp.
Note: ALWAYS name the .asd file like the system. Else ASDF will fail to load the system.
One thing to notice is the way the turtlesim messages and services are referenced. This is a concatenation of the ros package name and "-msg" or "-srv", so e.g. to use the messages in the ros package geometry_msgs, one would need to use :depends-on (geometry_msgs-msg) .
Create a softlink inside the asdf folder to this file:
$ roscd lispturtles_pkg $ cd asdf $ ln -s ../src/lispturtles-system.asd
Now create the declared structure inside src.
$ roscd lispturtles_pkg $ cd src $ mkdir lispturtles $ cd lispturtles
package.lisp:
(defpackage lispturtles
(:nicknames :lturtle)
(:use :roslisp :cl))This defines our main package, a short nickname, and a dependency to roslisp to allow using the functions within roslisp without qualifying them.
lispturtles-core.lisp
(in-package :lturtle)
(defun start-node ()
(roslisp:start-ros-node "lispturtles"))
(defun stop-node ()
(roslisp:shutdown-ros-node))
(defun reset-turtlesim ()
(roslisp:call-service "/reset" 'std_srvs-srv:empty))
(defun clear-turtlesim ()
(roslisp:call-service "/clear" 'std_srvs-srv:empty))
(defun spawn-turtle (&key (x 0) (y 0) (theta 0))
(turtlesim-srv:name-val
(roslisp:call-service "/spawn" 'turtlesim-srv:spawn
:x x :y y :theta theta)))
(defun unspawn-turtle (name)
(roslisp:call-service "/kill" 'turtlesim-srv:kill :name name))We defined some functions to manipulate the turtlesim, by now they should explain themselves.
Your src directory should now look like this:
src/lispturtles/package.lisp src/lispturtles/lispturtles-core.lisp src/lispturtles-system.asd
In Emacs, we can now load this system using rosemacs. In the REPL, press ",", enter ros-load-system, then choose the "lispturtles_pkg" ros package, then the "turtlesim-system". If rosemacs does not find those, check that the package is in your ROS_PKG_PATH, that the link in your asdf folder is not broken, and that the name of the asd file and the name of the asd system within are the same exactly.
After that, you can can call your functions in the repl, after having started the turtlesim. In 2 terminals, call roscore and
rosrun turtlesim turtlesim_node
In the REPL, call
CL-USER> (in-package :LTURTLE) #<PACKAGE "LISPTURTLES"> LTURTLE> (START-NODE) [(ROSLISP TOP) INFO] 1287314273.963: Node name is lispturtles [(ROSLISP TOP) INFO] 1287314273.964: Namespace is / [(ROSLISP TOP) INFO] 1287314273.964: Params are NIL [(ROSLISP TOP) INFO] 1287314273.964: Remappings are: [(ROSLISP TOP) INFO] 1287314273.964: master URI is 127.0.0.1:11311 [(ROSLISP TOP) INFO] 1287314275.014: Node startup complete LTURTLE> (spawn-turtle :x 1 :y 1 ) "turtle2"
First we switch into the LTURTLE package in which our functions are defined. Then we start a node in roslisp which will serve for all following calls to topics and services. Finally we spawn a second turtle at (1, 1) using the corresponding turtlesim service.
To do more useful things with the turtlesim, add these functions:
(defun set-turtle-velocity (name &key (lin 0) (ang 0))
"publishes a velocity command once"
(let ((pub (advertise
(concatenate 'string "/" name "/command_velocity")
"turtlesim/Velocity")))
(publish-msg pub :linear lin :angular ang)))
(defun setpen (name &key (r 0) (g 0) (b 0) (width 1) (off 0))
"changes some turtle pen param"
(roslisp:call-service
(concatenate 'string "/" name "/set_pen")
'turtlesim-srv:setpen :r r :g g :b b :width width :off off))We use the fact that the topics created by turtlesim include the name of the turtle.
Generating compiled executables
Sometimes you may wish to create into an executable file that can be called by rosrun. For each executable you want to create, add the following line to your CMakeLists.txt:
rospack_add_lisp_executable(<targetfile> <asd-system> <fully-qualified-main-function>)
As an example, look into the CMakeLists.txt of roslisp_tutorials:
rospack_add_lisp_executable(bin/talker roslisp-tutorials roslisp-tutorials:talker)
In this case bin/talker is the name of the executable to create. roslisp-tutorials is the name of the asdf system declared in asdf/roslisp-tutorials.asd, and roslisp-tutorials:talker is the name of a function declared in src/talker.lisp.
The makefile basically ensure that the asdf system is loaded before the function is called. Thus it does not need to know the source filename, as that is declared in the .asd file.
A sample executable
Let's try this out. We add an application main function to our lispturtles-core.lisp:
(defun turtle-circle ()
"main function, draws a circle"
(with-ros-node ("mynode")
(setpen "turtle1" :r 40 :g 90 :b 10)
(dotimes (x 10)
(set-turtle-velocity "turtle1" :lin 1 :ang 0.9)
(sleep 1))))This starts a new node at runtime, changes the turtle pen, and 10 times draws a small arc and sleeps for one second. This should be sufficient to make a full circle.
We also need to export this function in the package to be able to build an executable from it.
So in our package.lisp, add until you have the following:
(defpackage lispturtles
(:nicknames :lturtle)
(:use :roslisp :cl)
(:export
;; application
:turtle-circle))And in the CMakeLists.txt, add:
rospack_add_lisp_executable(bin/circle lispturtles-system lispturtles:turtle-circle)
Run rosmake to build the executable
rosmake lispturtles_pkg
Remember to redo this after changes to the source, of course.
Now, if you have a roscore and a turtlesim running, you can start your application using rosrun:
rosrun lispturtles_pkg circle






