Skip to content

timn/roslua

Repository files navigation

roslua: ROS client library for Lua
==================================

This module constitutes a client library which allows to write ROS
nodes in the Lua programming language. It allows to publish and
subscribe to topics, to provide and use services, interact with ROS
masters, slaves and param servers, and provides general utilities for
time and duration handling, and logging.

It does most likely not contain all the features roscpp and rospy can
offer. It does /not/ aim to replace any of these, rather it provides
an option for situations where Lua might be more suitable or more
desirable, e.g. on constrained systems or for behavior description and
programming.

The implementation is written purely in Lua, with some functionality
coming from external Lua modules written in Lua and C
(close-to-operating-system functionality like network transmissions
and unpacking of data of the TCPROS protocol, XML parsing, HTTP web
server etc.).

The original client library roslua has been written during an
internship at Intel Labs Pittsburgh by Tim Niemueller. It continues to
be used and developed at Intel Labs Pittsburgh. Current development is
sponsored by SRI International.

Notable differences
-------------------
There are some differences from client libraries written in and for
other languages that are noteworthy.

No offline code generation is necessary for message and service
description files, i.e. the message and service code generation steps
of the build system will not produce any output for Lua. roslua
directly reads the definition files at run-time and creates the
appropriate representations at run-time. Since these specifications
are read only once on startup when the topics or services are
registered, the overhead is negligible and only occurs during
initialization of the node.

Lua is inherently single-threaded. There is only so-called cooperative
multi-threading in the form of coroutines, which is not used for this
implementation. The most notable drawback is that long message
processing steps influence the whole program, including processing
incoming messages and XML-RPC requests. This can pose a problem to
certain programs and node writers should be aware of this. It also
allows for the simplicity and briefness of the code, since many
constructs like locking are unnecessary.


Requirements
------------
The following software packages are needed on your system in order to
use roslua. Requirements marked with (*) have specific information
further down below, modules marked with (i) are included in the roslua
module. Versions only denote tested minimum version, since several
patches have been contributed upstream, earlier version will most
likely not work.

- Lua 5.1			http://www.lua.org
- Lua POSIX 5.1.7 (i)(*)	http://luaforge.net/projects/luaposix/
- Xavante 2.2.0	(*)		http://keplerproject.github.com/xavante/
- WSAPI 1.3.4			http://keplerproject.github.com/wsapi/
- Lua XML-RPC 1.2.0		http://keplerproject.github.com/lua-xmlrpc/
- Lua struct lib (i)(*)		http://www.inf.puc-rio.br/~roberto/struct/
- Lua MD5 1.1.2	 		http://www.keplerproject.org/md5/
- Lua Signal 1.0.0		http://luaforge.net/projects/luasignal/

roslua comes with a rosdep file with the requirements that can be
provided by the operating system (Ubuntu and Fedora in particular).


Additional notes
----------------
Xavante:
  Nothing needs to be done for Fedora 13 and later.

  If using 2.2.0 on Ubuntu 10.04 a small patch is needed allow for using
  an ephemeral port. To get this either install from git, or clone the
  repository and replace the httpd.lua file of your local Xavante
  installation. The latter way is much easier and recommended if you
  have Xavante 2.2.0 installed (and not an older version). I cannot
  tell for other distributions, but these steps work for Ubuntu:
  git clone git://github.com/keplerproject/xavante.git
  cp xavante/src/xavante/httpd.lua /usr/share/lua/5.1/xavante
Lua POSIX:
  A patched version currently comes with roslua. Patches to extend
  the functionality have been accepted upstream. Once the new version
  arrives downstream in the distros the module will be removed.
Lua struct lib:
  The library is usually not available as a distribution package,
  therefore it has been bundled with roslua. It has been patched to
  extend functionality (and actually provide functionality promised
  in the documentation).


Documentation
-------------
All public API functions and modules have been documented. The
documentation is generated using LuaDoc by calling "make doc" in the
roslua directory. Pre-generated documentation can be found in the
doc/ directory (open doc/index.html in a web browser of your choice).


Starting roslua
---------------
To start a node written using roslua, you need to set an appropriate
package path and run the script. To setup the package path set the
LUA_PATH variable. It must include the roslua src directory (see
example below). If you write or use other Lua-based ROS modules you do
not need to add them to your package path, as long as the module's
directory can be found by roslua (see below about extension modules).
Likewise C modules are found automatically by roslua.
Example (no spaces before backslash!).
 export LUA_PATH="$HOME/ros/local/roslua/src/?/init.lua;"\
"$HOME/ros/local/roslua/src/?.lua;"\
"/usr/share/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua" export

Note that Lua modules bundled with roslua and written in C have an
extension of ".luaso" as to avoid confusion of ROS tools which
otherwise would crash.

Once the paths are set you can simply call Lua to run your script.
See src/examples for example scripts. To run for instance the
subscriber and publisher examples, which resemble talker and listener
from the roscpp and rospy tutorials, just call "lua subscriber.lua"
and "lua publisher.lua" respectively (assuming you have set LUA_PATH
as adviced above).


Writing Extension Modules using roslua
--------------------------------------
A natural concern is to write Lua modules based on roslua, providing
extended functionality, for example actionlib_lua to provide actionlib
facilities from Lua. roslua comes with some utility support to make
this easier, in particular it adds a module loader that searches for
Lua modules in the ROS universe without having to extend the LUA_PATH
and LUA_CPATH environment variables for new modules.

When a module is loaded, and the regular Lua loaders cannot find the
module, i.e. the module cannot be found by looking at LUA_PATH and
LUA_CPATH, roslua loaders kick in to look for Lua and C modules. Some
constraints apply on how these can be named for the automatic process
to work. These are as follows.

Modules written in plain Lua must reside in a ROS package with the
name of the Lua package with an optional "_lua" suffix. For example
the Lua module actionlib is in a ROS package named
actionlib_lua. Within the ROS package, the files must reside in the
src/ sub-directory with appropriate path names. In the case of
actionlib for instance, the main module resides in the file
actionlib_lua/src/actionlib/init.lua. Modules may have the pattern
?.lua or ?/init.lua, where ? is replaced by the module name (cf. Lua
documentation). C modules must reside in the lib subdirectory the ROS
package, have a suffix of .luaso (instead of .so) and may not have the
"lib" prefix. To achieve this take the following CMakeLists.txt lines
as an example:
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".luaso")
rosbuild_add_library(posix src/lua_modules/lposix.c)

With this C module residing in the roslua ROS package it can be used
in Lua with require("roslua.posix").  Lua C module names are mapped to
file names like the following: Either it is simply the ROS package
name, in that case a similarly named Lua C module file is searched in
the lib sub-directory. Or it is a name separated with a dot, where the
first part is again the ROS package name with an optional _lua
suffix. The part after the dot is converted to a filename by replacing
the dots with underscores and the file must exist in the lib
subdirectory. The luaopen function within the C file must be suffixed
by the same converted name. It is recommended of using only one
sub-level, i.e. only one dot in C module names.


Performance comparison
----------------------
Some simple performance comparisons have been made using
actionlib_lua and actionlib_benchmark modules (to be released
separately).

# ./cpp_roundtrip_client 100
SEND   Average: 0.000400    Deviation: 0.000035
RECV   Average: 0.000311    Deviation: 0.000094
TOTAL  Average: 0.000711    Deviation: 0.000099

# ./python_roundtrip_client 100
SEND   Average: 0.000589    Deviation: 0.000043
RECV   Average: 0.002129    Deviation: 0.000661
TOTAL  Average: 0.002718    Deviation: 0.000661

# ./lua_roundtrip_client 100
SEND   Average: 0.000447    Deviation: 0.000116
RECV   Average: 0.002326    Deviation: 0.001108
TOTAL  Average: 0.002774    Deviation: 0.001152


# ./cpp_roundtrip_client 1000
SEND   Average: 0.000393    Deviation: 0.000036
RECV   Average: 0.000269    Deviation: 0.000025
TOTAL  Average: 0.000662    Deviation: 0.000054

# ./python_roundtrip_client 1000
SEND   Average: 0.000653    Deviation: 0.000041
RECV   Average: 0.002178    Deviation: 0.000927
TOTAL  Average: 0.002831    Deviation: 0.000934

# ./lua_roundtrip_client 1000
SEND   Average: 0.000517    Deviation: 0.000090
RECV   Average: 0.003955    Deviation: 0.001797
TOTAL  Average: 0.004471    Deviation: 0.001814

The roundtrip client issues a goal with a timestamp initialized on
sending. The server will set a timestamp in the result message and
immediately send the result as soon as possible. The client can then
compare the start time, the time it was received by the server and the
time it was received by itself to get information about the time spans
for sending to the server, receiving from the server, and the total
time. The performance data shown above is for sending 100 goals with a
50ms offset between goals. Since the goal server accumulates goal
status information in the actionlib_msgs/GoalStatusArray message over
time the time to process these messages increases linearly over time
until saturation is reached (goals expire at the rate they are sent
after the initial timeout has been reached).  For the given scenario
of 100 concurrent goals (which we consider to be more than required at
the moment, given that actions are usually used for macroscopic
events), the Lua implementation reaches about the average performance
of the Python implementation. As the larger deviation shows, the
message processing time increases with an increased number of
concurrent goals. At 1000 goals it peaks at about 4.5 ms (C++ 0.7 ms,
Python 2.8 ms). This is due to crossing the C/Lua boundary for
unpacking the struct often, and the GoalStatusArray message being
particularly bad for performance because it includes strings, and an
array of another message type, requiring conditional deserialization.
Results have been produced on an Intel Xeon 3.6 GHz machine with 2 GB
of RAM against a C++ action server (performance against Python server
is much worse, Lua server not yet written).