Skip to content
This repository has been archived by the owner on May 26, 2023. It is now read-only.
jubeemer edited this page Mar 6, 2020 · 1 revision

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:

For more examples, tutorials, and documentation on LCM, check out the website.

Table of Contents

  1. Define a Message Type
  2. Initialize LCM
  3. Publishing Messages
  4. Subscribing and Receiving Messages
  5. Going Further

1. Define a Message Type

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/

2. Initialize LCM

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

3. Publishing Messages

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.

4. Subscribing and Receiving Messages

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 appropriate decode() 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.

5. Going Further

Other Languages

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.

Asynchronous Coroutines

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.

Additional Features

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.