-
Notifications
You must be signed in to change notification settings - Fork 276
LCM
Lightweight Communications and Marshalling (LCM) provides functions for simple message passing between processes. It uses a simple publisher/subscriber message passing model. On the rover, it is used to facilitate communication between applications running on computers connected by a shared Ethernet network. Using LCM, messages can be passed from base_station/gui
running on the base station to onboard/teleop
(running on the Jetson), as well as from onboard/teleop
to onboard/odrive_bridge
(both running on the Jetson).
This guide will provide a walkthrough for creating an LCM message type, publishing/subscribing to a channel, and passing a message between two Python programs. We'll create one program that reads input from the terminal and publishes it over LCM, and another program that subscribes to the same channel and prints out the value from the message in its own terminal.
Assumptions:
- You have completed the Development Environment Setup
For more examples, tutorials, and documentation on LCM, check out the website.
- Define a Message Type
- Initialize LCM
- Publishing Messages
- Subscribing and Receiving Messages
- Going Further
We'll first create a type definition for our message. Message definitions are language independent, and thus can be used by rover applications of any language. The syntax for type definition is similar to C.
The following primitives are supported in LCM messages. Note that there are no unsigned integer types.
type | Description |
---|---|
int8_t |
8-bit signed integer |
int16_t |
16-bit signed integer |
int32_t |
32-bit signed integer |
int64_t |
64-bit signed integer |
float |
32-bit IEEE floating point value |
double |
64-bit IEEE floating point value |
string |
UTF-8 string |
boolean |
true/false logical value |
byte |
8-bit value |
For our example, we'll create a message type example_t
:
$ touch rover_msgs/example_t.lcm
// example_t.lcm
package rover_msgs;
struct example_t {
int8_t val;
}
To use the example_t
message type, we'll need to generate language-specific bindings for the type. Jarvis handles creating bindings for both C++ and Python - simply rebuild the rover_msgs
sub-directory.
$ jarvis build rover_msgs/
To use LCM in a rover application, you'll need to import it. We'll now create our first program, which we'll call pub
.
$ mkdir -p lcm_example/pub
$ mkdir lcm_example/pub/src
$ touch lcm_example/pub/src/__main__.py
$ touch lcm_example/pub/project.ini
Configure pub
as a Python application with the following project.ini
:
[build]
lang=python
executable=True
We'll now set up the source code for pub
to initialize LCM, read an integer from user input, and print it back to the terminal:
import sys
import lcm # import lcm library
lcm_ = lcm.LCM() # initialize LCM
def main():
inval = input("Please enter an integer: ")
try:
num = int(inval)
except ValueError:
print("Error! Enter an integer.")
sys.exit()
print(num)
if __name__ == "__main__":
main()
At this point, we can build and test the program with Jarvis to make sure everything is working:
$ jarvis build lcm_example/pub
$ jarvis exec lcm_example/pub
Please enter an integer: 10
10
Now, we'll modify __main__.py
to initialize an example_t
message and publish it to the /lcm_test
channel.
import sys
import lcm # import lcm library
from rover_msgs import example_t # import example_t message type
lcm_ = lcm.LCM() # initialize LCM
def main():
inval = input("Please enter an integer: ")
try:
num = int(inval)
except ValueError:
print("Error! Enter an integer.")
sys.exit()
msg = example_t()
msg.val = num
lcm_.publish("/lcm_test", msg.encode())
if __name__ == "__main__":
main()
Note the encode()
function. This function is provided by LCM for each type definition.
Finally, we'll create a second program called sub
that will subscribe to the /lcm_test
channel and print out the value contained in the received message. The creation of the program directory structure and project.ini
is omitted here; it is the same as pub
above. Here is the __main__.py
for sub
:
import lcm
from rover_msgs import example_t
lcm_ = lcm.LCM()
def example_callback(channel, msg):
msg = example_t.decode(msg)
print(msg.val)
def main():
lcm_.subscribe("/lcm_test", example_callback)
while True:
lcm_.handle()
if __name__ == "__main__":
main()
Some things to note:
- The
subscribe()
function accepts a channel name and callback function. The callback will be run each time a message is received. - The
decode()
function is another provided function for each message type. Pass the received message to the appropriatedecode()
function to produce a message struct whose members can be accessed. - The
handle()
function is called repeatedly, which waits for a message to arrive and calls the appropriate callback.
To test the program, build and execute it with Jarvis. In a separate terminal, run our pub
program. Now, when you provide an integer to pub
as input, you should see it printed out by sub
in the other terminal.
This guide focused on using LCM with Python. For C++, there is little conceptual difference. You can take a look at the syntax for using LCM in C++ here or take a look at a C++ rover application (e.g., onboard/nav
, onboard/cv
). For JavaScript, you'll need to utilize lcm_bridge/client
and lcm_bridge/server
as LCM does not natively support JavaScript. See base_station/gui
for examples.
Often, you'll want to periodically send and receive LCM messages simultaneously in the same program. To do so, you'll probably want multiple "concurrent" while(1) {}
loops. The run_coroutines()
function in rover_common/aiohelper.py
provides this functionality using the asyncio
Python module (this isn't truly concurrent programming, but rather asynchronous. An in-depth discussion of asyncio
would be too much for this wiki page, but there are plenty of examples of how to use run_coroutines()
throughout the repository - onboard/teleop
is one example). If you use this functionality in your rover application, you'll need to use the aiolcm.AsyncLCM
class provided in rover_common
instead of lcm.LCM
. Rather than implementing a while
loop that calls lcm.handle()
repeatedly, pass AsyncLCM.loop()
to your run_coroutines()
function call.
LCM contains many features not discussed here. This guide is intended to provide you with the basic framework for using LCM in rover applications. If you want to explore LCM's other capabilities, check out the website.
Something not right? Contact the Software Branch lead.