|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.|
Organizing files for roslispDescription: This tutorial explains how the ASDF standard is used in roslisp, with a few added conventions.
Next Tutorial: Unit testing
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 sbcl --noinform --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: (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 sbcl --noinform --script ..." which ends the sh process and starts the SBCL Lisp interpreter.
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. "--noinform" surpresses the printing of any sbcl banner or other informational message at startup.
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
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 the following shebang line.
#!/usr/bin/env sh "true"; exec /usr/bin/env sbcl --noinform --load `rospack find roslisp`/scripts/roslisp-sbcl-init --script "$0" "$@" (ros-load:load-system "roslisp" "roslisp") (in-package :roslisp) (with-ros-node ("talker") (let ((i 0) (pub (advertise "chatter" "std_msgs/String"))) (ros-info (talker) "Publishing on topic /chatter") (loop-at-most-every .1 (publish-msg pub :data (format nil "foo ~a" (incf i))))))
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
The full sources for the code that you'll be writing in this step can be found in the roslisp_tutorials metapackage in the roslisp_tutorials_turtlesim 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 that you can roscd to, 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. It 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 : roslisp_tutorials_turtles
- ASDF System : turtles-system
- Lisp Package : turtles-lisp-pkg
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. For catkin, in the src of your catkin workspace call
$ catkin_create_pkg roslisp_tutorials_turtles roslisp turtlesim geometry_msgs
It is a good practice to compile your package after creating it and updating the rospack profile:
$ cd YOUR_CATKIN_WORKSPACE && catkin_make && rospack profile
For old rosbuild, somewhere in your ROS_PACKAGE_PATH call
$ roscreate-pkg roslisp_tutorials_turtles roslisp turtlesim geometry_msgs
We include the dependency to turtlesim to be able to access the service and message types. Certain commands of turtlesim need a message of type that is specified in geometry_msgs, so we include that package as a dependency as well.
Inside the new folder roslisp_tutorials_turtles, create a folder named src where the Lisp files will go. The ASDF files should either stay in the root, or be placed in a subfolder called asdf. 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 root folder (or the asdf folder if you choose to use it).
Let's do so, in src, create a file named turtles-system.asd with this content:
(asdf:defsystem turtles-system :depends-on (roslisp turtlesim-msg geometry_msgs-msg turtlesim-srv) :components ((:module "turtles" :components ((:file "package") (:file "turtles-core" :depends-on ("package"))))))
This declares the system components: a subfolder named turtles, and within it 2 files, package.lisp and turtlecore.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 we write :depends-on (geometry_msgs-msg).
Create a softlink inside the root folder (or the asdf folder if you choose to use it) to this file:
$ roscd roslisp_tutorials_turtles $ ln -s src/turtles-system.asd
Now create the declared structure inside src.
$ roscd roslisp_tutorials_turtles $ cd src $ mkdir turtles $ cd turtles
(defpackage turtles-lisp-pkg (:nicknames :lturtle) (:use :roslisp :cl))
This defines our main package, a short nickname, and adds roslisp to the namespace of our package to allow using the functions within roslisp without qualifying them (e.g., roslisp:make-msg turns into simply make-msg).
(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 (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, if you've gone through the previous tutorial you should be able to understand the functions by now.
We still use the roslisp: prefix for calling roslisp functions for the sake of clarity, although it is not obligatory now that we have (:use :roslisp) in our package.lisp.
Your src directory should now look like this:
src/turtles/package.lisp src/turtles/turtles-core.lisp src/turtles-system.asd
In Emacs, we can now load this system using rosemacs. In the REPL, press ",", type in ros-load-system, enter, then choose the "roslisp_tutorials_turtles" ROS package, then the "turtles-system"; see the code below. If rosemacs does not find those, check that the package can be found by ROS (roscd into it from a terminal), that the link to the .asd file is not broken, and that the name of the .asd file and the name of the ASDF system within are exactly the same. You might need to restart your REPL or manually reinitialize the source registry of ASDF if you started the REPL before creating the ROS package.
Now you should be able to call the Lisp functions in the REPL, after having started the turtlesim. In 2 terminals, call
$ rosrun turtlesim turtlesim_node
respectively. In the REPL, call
CL-USER> (ros-load:load-system "roslisp_tutorials_turtles" "turtles-system") CL-USER> (in-package :LTURTLE) #<PACKAGE "TURTLES-LISP-PKG"> 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. Now you should see a second turtle in the turtlesim window.
To do more useful things with the turtlesim, add these functions to the file turtles-core.lisp and recompile it (in Slime that would be <Ctrl>+C <Ctrl>+K, or in the worst case restart your Lisp or reload the ASDF system):
(defun set-turtle-velocity (name &key (lin 0) (ang 0)) "Publishes a velocity command once." (let ((pub (advertise (concatenate 'string "/" name "/cmd_vel") "geometry_msgs/Twist"))) (publish pub (make-msg "geometry_msgs/Twist" (x linear) lin (z angular) ang)))) (defun set-pen (name &key (r 0) (g 0) (b 0) (width 1) (off 0)) "Changes the color of the turtle trajectory." (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 Lisp executables
Sometimes you may wish to create an executable file that can be called by rosrun. For each executable you want to create, add the following line to your CMakeLists.txt:
add_lisp_executable(<targetfile> <asd-system> <fully-qualified-main-function>)
As an example, look into the CMakeLists.txt of roslisp_tutorials_basics:
add_lisp_executable(talker roslisp-tutorials-basics roslisp-tutorials-basics:talker)
In this case bin/talker is the name of the executable to create. roslisp-tutorials-basics is the name of the ASDF system declared in roslisp-tutorials-basics.asd, and roslisp-tutorials-basics:talker is the name of a function declared in src/talker.lisp.
The makefile basically ensures that the ASDF system is loaded before the function is called. Thus, it does not need to know the name of the sourcefile, as that is declared in the .asd file.
A sample executable
Let's try this out. We add an application main function to our turtles-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 turtles-lisp-pkg (:nicknames :lturtle) (:use :roslisp :cl) (:export ;; application-specific :turtle-circle))
And in the CMakeLists.txt, found in the root folder, add:
add_lisp_executable(circle turtles-system turtles-lisp-pkg:turtle-circle)
Compile your package with catkin_make to build the executable (or rosmake roslisp_tutorials_turtles if you're still on rosbuild). 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 roslisp_tutorials_turtles circle