Skip to content Link Search Menu Expand Document
ModalAI DOCS
Store

libmodal_pipe

libmodal_pipe is a C helper library (with a Python interface, pympa) to make publishing and subscribing to data via POSIX named pipes easy and flexible without adding any extra overhead. This serves as the foundation for Modal Pipe Architecture.

POSIX pipes are a fast, portable, and reliable method of inter-process communication. However, they only practically support transferring data in one direction from a single process to another. libmodal_pipe offers more flexible functionality on top of this along with features such as automatically connecting/disconnecting, and bidirectional communication.

Table of contents

  1. Server & Client Relationship
    1. Info File
    2. Pipe Size
  2. Client Interface Features
    1. Client Helpers
    2. Pause & Resume
    3. Claiming Channels
  3. Sinks
  4. Server Interface
  5. Control Commands
  6. Zero-Copy ION Buffers
  7. Message Types
  8. Support Utilities
  9. Python: pympa
  10. API Reference and Examples

Server & Client Relationship

An MPA server advertises and publishes data. An MPA client opens the pipe created by a server and reads data from it. A single process can be a server and a client to multiple pipes.

When a server wishes to advertise data, it creates a new pipe. In this context, a ‘pipe’ is actually a directory in the file system consisting of multiple POSIX pipes and an information file. These directories can live anywhere in the file system chosen by the server, but typically live in /run/mpa/ since this is a non-permanent directory that is not saved to disk.

Within each directory lives:

  • Request pipe. A client will request their own dedicated POSIX pipe by sending a request to the server through the request pipe.
  • Control pipe: Clients can send commands back to the server through the control pipe, for example to put voxl-imu-server into calibration mode or to request voxl-qvio-server to reset.
  • An info file.
  • An individual POSIX pipe for each connected client to read from.

Info File

When a Server creates a new pipe with pipe_server_create(), an info file is created advertising basic information about the pipe in json format. The first 6 json fields are automatically populated when the server creates a new pipe. The server may publish additional fields to provide extra information that doesn’t need to be updated frequently. This information has the advantage of being readable with the pipe_get_info() and pipe_get_info_json() functions without needing to subscribe to the pipe.

For example, voxl-imu-server publishes the state of the IMU calibration and also commands that can be sent to the control pipe.

voxl2:/$ cat /run/mpa/imu_apps/info 
{
	"name":		"imu_apps",
	"location":	"/run/mpa/imu_apps/",
	"type":		"imu_data_t",
	"server_name":	"voxl-imu-server",
	"size_bytes":	131072,
	"server_pid":	1742,
	"is_calibrated":	true,
	"is_temp_calibrated":true,
	"available_commands":["start_calibration", "stop_calibration"]
}

The ‘type’ field is typically the name of the C-struct that is sent over the pipe. It is used by clients to ensure that the pipe is publishing the correct data type that it expects before subscribing. It is also used by bash auto-completion so that tools like voxl-inspect-cam and voxl-inspect-imu can tab-complete available pipes that they can subscribe to.

For example you can type out voxl-inspect-imu {TAB}{TAB} and bash will suggest and auto-complete any pipes publishing imu data, typically both imu0 and imu1 are being published.

voxl2:/$ voxl-inspect-imu {TAB}{TAB}
imu_apps

The server_pid field is used so that the server publishing a particular pipe can be identified and shut down with the voxl-kill-pipe tool.

The available_commands field is optional but should be set if text commands can be sent to the server as this enables bash-autocompletion for the voxl-send-command tool. The voxl-inspect-pipe-info tool pretty-prints any pipe’s info file for you.

Another good example are the camera pipes published by voxl-camera-server, voxl-uvc-server, and voxl-lepton-server. Anything publishing image data should, if possible, also advertise the image resolution and format in the info json file so subscribers like voxl-streamer can validate and initialize based on this information before actually subscribing to the pipe data. Here is the hires_large_encoded pipe info for reference:

voxl2:/$ cat /run/mpa/hires_large_encoded/info
{
	"name":		"hires_large_encoded",
	"location":	"/run/mpa/hires_large_encoded/",
	"type":		"camera_image_metadata_t",
	"server_name":	"voxl-camera-server",
	"size_bytes":	134217728,
	"server_pid":	1368,
	"available_commands":["set_exp_gain", "set_exp", "set_gain", "start_ae", "stop_ae", "snapshot", "snapshot-no-save"],
	"string_format":	"H265",
	"int_format":	4,
	"width":		4096,
	"height":		2160,
	"framerate":	30
}

Pipe Size

POSIX Pipes act as FIFO buffers and the server initially sets their size. It is important that the server set a pipe size reasonable for the data being transferred to prevent overflowing the buffer. For example, 4k color images require larger pipes than imu data. The largest pipe size varies from one platform to another, but 256MB is a typical max.

Each data type’s header under library/include/pipe_interfaces/ provides a recommended pipe size (*_RECOMMENDED_PIPE_SIZE), and pipe_suggest_cam_pipe_size() computes a sensible size for camera pipes from the image format and resolution.

If a client expects to have long processing time between pipe reads, then it may elect to increase the size of the pipe for its specific application. Functions are available in both the client and server APIs for reading the current size of a pipe, the amount of data waiting to be read in a pipe, and for setting the size of a pipe. Generally these are not necessary and it is sufficient for the server to set a reasonable safe size for pipes from the beginning.

Client Interface Features

The client-side interface defined in modal_pipe_client.h has the most features, options, and configurability.

Client Helpers

A basic call to pipe_client_open() will request a new dedicated POSIX pipe for a client to read data from. You can then retrieve a file descriptor and read from the pipe however you wish. However, we HIGHLY recommend using one of the client helpers by adding one of these flags to the call to pipe_client_open()

#define CLIENT_FLAG_EN_SIMPLE_HELPER        (1<<0)
#define CLIENT_FLAG_EN_CAMERA_HELPER        (1<<1)
#define CLIENT_FLAG_EN_POINT_CLOUD_HELPER   (1<<2)
#define CLIENT_FLAG_EN_DEBUG_PRINTS         (1<<3)
#define CLIENT_FLAG_DISABLE_AUTO_RECONNECT  (1<<5)
#define CLIENT_FLAG_START_PAUSED            (1<<6)
#define CLIENT_FLAG_EN_ION_BUF_HELPER       (1<<7)
#define CLIENT_FLAG_MANUAL_ION_BUF_RELEASE  (1<<8)

All of these helpers will start a background thread to handle automatic opening/closing/reopening of the pipe as the server starts, stops, and restarts. They also handle allocating memory for read buffers and reading data from the pipe. The client simply needs to set callback functions to be set when the helper thread either connects, disconnects, or has read data from the pipe. See the examples for how to use the helpers.

Pause & Resume

After opening a pipe with pipe_client_open() it can be paused and resumed again with pipe_client_pause() and pipe_client_resume() which disconnect and reconnect to the server but the channel remains claimed and configured such that calls to pipe_client_get_next_available_channel() won’t grab a paused channel, and you don’t need to pass in pipe name and flag arguments when reconnecting.

This feature is primarily used when a process wants to preemptively open any pipes that it will need, then quickly resume to start getting data only once needed. This sort of behavior can take advantage of simply opening the pipe in a paused state from the beginning, then calling pipe_client_resume() when data is needed. voxl-mpa-to-ros is a good example of this.

To open a pipe in a paused state, simply used the CLIENT_FLAG_START_PAUSED flag when calling pipe_client_open().

Claiming Channels

Typical clients will subscribe to a known fixed number of pipes, such as voxl-qvio-server which only subscribes to one IMU and one camera pipe. In this case it’s easy to just pre-define which pipe channel numbers correspond to which data stream like this:

#define IMU_CH	0
#define CAM_CH	1
pipe_client_set_connect_cb(IMU_CH, _imu_connect_cb, NULL);
pipe_client_set_disconnect_cb(IMU_CH, _imu_disconnect_cb, NULL);
pipe_client_set_simple_helper_cb(IMU_CH, _imu_helper_cb, NULL);
pipe_client_set_connect_cb(CAM_CH, _cam_connect_cb, NULL);
pipe_client_set_disconnect_cb(CAM_CH, _cam_disconnect_cb, NULL);
pipe_client_set_camera_helper_cb(CAM_CH, _cam_helper_cb, NULL);

For clients like voxl-mpa-to-ros which must dynamically subscribe to an unknown number of pipes, it can be helpful to call pipe_client_get_next_available_channel() to claim the next available channel. Any channel that has has already been opened is also considered claimed.

Sinks

Wheras the server interface allows multiple clients to read data from one server, a sink is intended for one or multiple processes to send data to a single process. For example, voxl-vision-hub creates a sink into which one or more processes can pass in relocalization data for it to use. This is a typical use for for POSIX pipes and the sink interface does little more than create a single POSIX pipe along with a read helper thread. This is useful for cases where the full server/client interface is unnecessary.

Server Interface

A server advertises a pipe with pipe_server_create(), optionally with SERVER_FLAG_EN_CONTROL_PIPE (accept commands from clients), SERVER_FLAG_EN_DEBUG_PRINTS, or SERVER_FLAG_EN_ION_BUF (zero-copy buffers, below). Data is published with pipe_server_write() or one of the type-aware writers: pipe_server_write_list(), pipe_server_write_string(), pipe_server_write_camera_frame(), pipe_server_write_stereo_frame(), pipe_server_write_point_cloud(), and pipe_server_write_ion_buffer(). Callbacks are registered with pipe_server_set_control_cb(), pipe_server_set_connect_cb(), and pipe_server_set_disconnect_cb(), and the info file can be extended at runtime with pipe_server_update_info() and pipe_server_set_available_control_commands().

Channel limits: 64 server channels, 16 clients per channel, 128 client channels, 16 sink channels.

Control Commands

Connected clients send commands to a server’s control pipe with pipe_client_send_control_cmd() (or _bytes() for binary payloads). A process that isn’t a subscribed client can also fire a one-shot command with pipe_send_control_cmd() / pipe_send_control_cmd_bytes() — this is what the voxl-send-command tool uses under the hood.

Zero-Copy ION Buffers

For large frames (4K images, MISP streams), copying through a pipe costs real CPU. libmodal_pipe supports zero-copy sharing of ION/DMA buffers: the server allocates a pool (mpa_ion_buf_pool_t, mpa_ion_buf_pool_alloc_bufs()) and publishes buffer handles with pipe_server_write_ion_buffer(); clients enable CLIENT_FLAG_EN_ION_BUF_HELPER and receive mpa_ion_buf_t references via pipe_client_set_ion_buf_helper_cb(). By default the helper releases each buffer when the callback returns; a client that needs to hold buffers longer sets CLIENT_FLAG_MANUAL_ION_BUF_RELEASE and returns them explicitly with pipe_client_report_mpa_ion_buf_unused(). The libmodal-flow page shows a complete client-side example.

Message Types

Every standard MPA message type lives in its own header under library/include/pipe_interfaces/: camera_image_metadata_t, imu_data_t, vqf_data_t, baro_data_t, vio_data_t, pose_4dof_t, pose_vel_6dof_t, tof_data_t, tof2_data_t, point_cloud_metadata_t, tag_detection_t, object_tracking_data_t, vfc_data_t, rc_channels_t, crsf_raw_data_t, cpu_stats2_t, payload_status_t, mpa_ion_buf_t, and more. Each header carries its type’s magic number, recommended read-buffer and pipe sizes, and a pipe_validate_*() helper. (modal_pipe_interfaces.h still exists but is now just an umbrella include.)

Support Utilities

modal_start_stop.h provides the process scaffolding used by every MPA service: make_pid_file() / kill_existing_process() / remove_pid_file(), enable_signal_handler() with the main_running idiom, pipe_pthread_create() with RT priority levels, CPU affinity helpers, and the loop timing helpers mpa_nanosleep() / mpa_loop_sleep(). Services can also report structured faults to the shared /run/mpa/fault channel with write_fault_code() (see modal-test-fault-code.c).

Python: pympa

A Python binding ships in the repo’s python/ directory: pympa_create_pub(), pympa_publish_imu(), pympa_publish_image(), pympa_get_image(), and autogenerated Python versions of the interface types. Useful for quick prototyping and test scripts where C is overkill.

API Reference and Examples

The libmodal_pipe headers are thoroughly self-documented and human readable. Please read them carefully for details on using the API.

https://gitlab.com/voxl-public/voxl-sdk/core-libs/libmodal-pipe/-/tree/master/library/include

Complete working examples live in the examples directory: modal-hello-server, modal-hello-client, modal-hello-sink, modal-hello-pause (the pause/resume pattern above), modal-pipe-ping (latency measurement), modal-kill-pipe, and modal-test-fault-code.