uorb uorb(micro object request broker)

uORB (Micro Object Request Broker) is a crucial middleware in the open-source flight control system PX4. It is a pub-sub-based message bus primarily responsible for data transmission between multiple modules. Based on a lock-free design philosophy, uORB internally implements Inter-Process Communication (IPC) among tasks through shared memory and achieves low-latency data exchange with an optimal memory footprint. Its implementation does not rely on threads or work queues.

The main concepts related to uORB include roles, and device nodes.

Roles

In the uORB bus, there are two roles: subscribers and advertisers. The transmitted content is referred to as a topic message, described by its metadata (meta), including name, size, etc.

Each topic supports multiple instances, with the advertiser frequency being the sampling rate (interval) and the maximum publication delay being the batch latency.

For example, if the accelerometer sensor has a sampling rate of 50Hz and a maximum publication delay of 100ms, the hardware generates an interrupt every 100ms, publishing 5 data points each time (100/(1000/50)).

Two Tpyes

Additionally, NuttX categorizes topics into two types: notification topics and general topics.

Subscribers to notification topics are not concerned with the presence of an advertiser or whether a publication has occurred; they directly obtain the current state.When an application subscribes, it directly obtains the latest data from the current device node as the current state.

Subscribers to general topics are not concerned with past states; the data they obtain must be current or future occurrences. For example, with an accelerometer sensor, an application only cares about the data reported by the next data ready interrupt from the sensor, rather than data from a past moment.

To publish notification topic messages, APIs with the persist suffix are used for topic publication, and subscriber behavior is consistent with that of general topics.

Device Nodes

Each topic being published or subscribed to corresponds to a character device node, with subscribers and advertisers sharing data through an internal circular buffer. In implementation, a caller who opens a device node in O_WRONLY mode is considered an advertiser, and one who opens it in O_RDONLY mode is considered a subscriber.

The advertiser writes to the node to publish data, while the subscriber reads from the node to subscribe to data. Both parties can use poll to monitor the node to receive events of interest. Control over device nodes by advertisers and subscribers is achieved through ioctl operations.

Code Location

The directory apps/system/uorb contains the uORB wrapper, unit tests, physical sensor topic definitions, and the uorb_listener tool.

├── Kconfig
├── listener.c   // Implementation of uorb_listener.c
├── Make.defs
├── Makefile
├── sensor       // Definitions for physical sensor topics
│   ├── accel.c
│   ├── accel.h
│   ├── baro.c
│   ├── baro.h
│   ├── cap.c
│   ├── cap.h
│   ├── ...c
│   ├── tvoc.c
│   ├── tvoc.h
│   ├── uv.c
│   └── uv.h
├── test
│   ├── unit_test.c   // Unit tests for uORB
│   ├── utility.c
│   └── utility.h
└── uORB
    ├── uORB.c   // Implementation of uORB wrapper
    └── uORB.h

Introduction to Key Data Structures

Each uORB topic corresponds to a struct orb_metadata structure, which is used to describe the metadata of the topic, including the name o_name, the message size o_size, and the debug format pointer o_format.

struct orb_metadata
{
  FAR const char   *o_name;       // Name of the topic
  uint16_t          o_size;       // Size of the message
  #ifdef CONFIG_DEBUG_UORB
  FAR const char   *o_format;     //  Format string used for structure input and output
  #endif
};
typedef FAR const struct orb_metadata *orb_id_t; // Pointer to orb_metadata as an identifier for uORB topics

After advertising or subscribing to each uORB topic, the caller can obtain the topic’s state by calling orb_get_state, which includes the current maximum publication frequency max_frequency, the minimum batch interval min_batch_interval, the length of the internal circular queue queue_size, the number of subscribers nsubscribers, and the generation index for the main thread generation.

struct orb_state
{
  uint32_t max_frequency;     // Maximum publication frequency
  uint32_t min_batch_interval;// Minimum batch interval
  uint32_t queue_size;        // Length of the internal circular queue
  uint32_t nsubscribers;      // Number of subscribers
  uint64_t generation;        // Generation index for the main thread
};

uORB supports the instantiation of a topic into multiple entities, each with a corresponding instance number starting from 0 and incrementing. struct orb_object contains information about one such entity: its metadata meta and instance number instance.

struct orb_object
{
  orb_id_t meta;      // Pointer to the metadata of the topic
  int      instance;  // Instance number of the topic entity
};

Topic Definition

A large number of uORB topics are defined in PX4 at https://docs.px4.io/main/en/middleware/uorb_graph.html.

Defining a new topic involves creating the topic’s data structure, declaring a global variable for the topic’s metadata, and optionally defining a debug output function for the topic’s data. Three macros are frequently used in the definition process:

ORB_ID: Used to obtain the global metadata handle for the topic.

ORB_DECLARE: Used to declare the global metadata for the topic.

ORB_DEFINE: Used to define the global metadata for the topic.

API Description

The uORB has a total of 30 APIs, which can be categorized into four groups: advertiser class, subscriber class, general API class, and tool class. Almost all of these APIs operate using file descriptors, so special attention should be paid to avoid using them across processes (tasks).

Subscribe Class APIs

Subscribe to a Topic

The orb_subscribe function is internally implemented by orb_subscribe_multi, with the main difference between them being whether an instance needs to be specified for the subscription. Upon successful subscription, it returns an fd (file descriptor); upon failure, it returns -1 and sets errno.

int orb_subscribe_multi(FAR const struct orb_metadata *meta,
                        unsigned instance);

static inline int orb_subscribe(FAR const struct orb_metadata *meta)
{
  return orb_subscribe_multi(meta, 0);
}

Unsubscribe

The orb_unsubscribe function takes the fd (file descriptor) returned by a subscription as its parameter and internally calls orb_close to unsubscribe.

static inline int orb_unsubscribe(int fd)
{
  return orb_close(fd);
}

Retrieve Data

The orb_copy function takes the topic metadata meta, the subscription handle fd, and a pointer to the buffer buffer where the data will be stored as its parameters. It can only read one piece of data each time. In contrast, orb_copy_multi allows batch reading. The return values of the two functions differ: orb_copy returns 0 upon success and -1 upon failure, setting errno in the latter case, while orb_copy_multi returns the length of data read upon success.

ssize_t orb_copy_multi(int fd, FAR void *buffer, size_t len);

static inline int orb_copy(FAR const struct orb_metadata *meta,
                           int fd, FAR void *buffer)
{
  int ret;

  ret = orb_copy_multi(fd, buffer, meta->o_size);
  return ret == meta->o_size ? 0 : -1;
}

Normal Class APIs

Open/Close Device Nodes

orb_open is used to open a character device node. The parameters include name (the topic name), instance (the topic instance index), and flags (the open mode). Subscribers typically open with O_RDONLY, publishers with O_WRONLY, and a third party can open the node with “0” to retrieve device node information. It corresponds to orb_close for closing the node.

int orb_open(FAR const char *name, int instance, int flags);
int orb_close(int fd);

Retrieve Topic Status

orb_get_state this function or method is not explicitly named in your list but is implied by the context.)

int orb_get_state(int fd, FAR struct orb_state *state);

Check for Updates

orb_check is used to check if there is new data for the current topic. It takes a pointer to an updated variable as an input parameter.

int orb_check(int fd, FAR bool *updated);

Control the Topic

Applications can use orb_ioctl to control physical sensor topics, such as adjusting the range, resolution, etc., of accelerometers, gyroscopes, magnetometers, and PPG sensors.

int orb_ioctl(int fd, int cmd, unsigned long arg);

Set/Get Topic Batch Parameters

orb_set_batch_interval/orb_get_batch_interval are used to set/get the maximum delay reporting time for a topic, in microseconds (μs). This API is only applicable to physical sensors with hardware FIFO support.

int orb_set_batch_interval(int fd, unsigned batch_interval);
int orb_get_batch_interval(int fd, FAR unsigned *batch_interval);

Set/Get Topic Interval Parameters

Frequency and sampling interval are reciprocals of each other. orb_set_interval/orb_get_interval are used to set/get the sampling rate, in microseconds (μs), while orb_set_frequency/orb_get_frequency are used to set/get the sampling frequency, in Hertz (Hz).

int orb_set_interval(int fd, unsigned interval);
int orb_get_interval(int fd, FAR unsigned *interval);
static inline int orb_set_frequency(int fd, unsigned frequency)
static inline int orb_get_frequency(int fd, FAR unsigned *frequency)

orb_flush

orb_flush supports the flush operation for topics with hardware FIFO. After the flush operation is completed, a POLLPRI event will be generated for the fd, and then orb_get_events can be called to retrieve the corresponding event.

int orb_flush(int fd);

orb_get_events

orb_get_events retrieves events. Currently, the supported events include ORB_EVENT_FLUSH_COMPLETE.

int orb_get_events(int fd, FAR unsigned int *events);

Tool Class APIs

Check if a Publisher Exists for a Topic

orb_exists is used to check if there is a publisher for a topic. It takes the topic metadata meta and the topic instance index instance as parameters. It returns 0 if the check is successful and an ERROR code if it fails.

int orb_exists(FAR const struct orb_metadata *meta, int instance);

Timestamp Calculation

orb_absolute_time returns the current timestamp. orb_elapsed_time returns the difference between two timestamps.

orb_abstime orb_absolute_time(void);
static inline orb_abstime orb_elapsed_time(FAR const orb_abstime *then)

Get the Number of Topic Instances

orb_group_count returns the number of instances for a specific topic.

int orb_group_count(FAR const struct orb_metadata *meta);

Get Topic Metadata

orb_get_meta is used to retrieve a pointer to the topic metadata by using a string. Currently, this function has significant limitations: for non-physical sensors, you must explicitly subscribe to or advertise the topic to successfully obtain the metadata pointer.

FAR const struct orb_metadata *orb_get_meta(FAR const char *name);

These tool class APIs provide additional utilities for working with ORB (Object Request Broker) topics, allowing for tasks such as checking the existence of publishers, calculating timestamps, retrieving the number of topic instances, and accessing topic metadata. These functions are crucial for managing and interacting with topics in an ORB-based system.

Usage in NuttX

In NuttX, all physical sensor drivers automatically advertise their topics upon system startup. The sensors are then controlled to turn on or off based on whether any applications have subscribed to the related topics, enabling intelligent low-power consumption management.

All virtual topics (algorithms, states, cross-core topics) automatically register character device nodes upon their first publication or subscription. Once registered, these nodes remain active even if the publication or subscription is later canceled.

When subscribers and publishers in an application need to monitor each other’s status, the poll function is used for status notifications. Subscribers and publishers synchronize their status via the POLLPRI signal. When a publisher publishes data, a POLLIN event is generated to notify all subscribers. POLLPRI events are triggered in the following scenarios:

When a new subscriber or publisher joins the topic.

When a subscriber sets the sampling rate or batch parameters for the topic.

When a subscriber or publisher leaves the topic.

Whenever a POLLPRI event occurs, you can call orb_get_state to retrieve the current status of the topic, including the maximum publishing frequency (max_frequency), the minimum batch interval (min_batch_interval), the internal ring buffer size (queue_size), the number of subscribers (nsubscribers), and the data generation count.

In summary, if subscribers and publishers are interdependent, it is recommended to use a polling or libuv-based programming structure. If they are independent, a notification-based topic approach is preferred.

Fusion Algorithm Models

If multiple applications are interconnected, with one serving as the input and another as the output, shared topics can be established to facilitate decoupling between applications. For example, a calibration algorithm module can subscribe to an uncalibrated sensor data topic and publish calibrated sensor data. A motion algorithm module can subscribe to both calibrated and uncalibrated data to generate algorithmic topics, with multiple applications ignoring each other’s existence.

Subscriber and Publisher Monitoring

Subscribers can check for update events via POLLIN events, while publishers can monitor changes in subscriber count, sampling rates, and other statuses via POLLPRI events. The optimal status can be obtained using orb_get_state. By leveraging these mechanisms, NuttX provides a robust framework for efficient data communication and management in an embedded system environment.

Tools

uorb_listener

uorb_listener is a testing tool located above the uORB layer. It calls the uORB API to subscribe to and obtain topic information, further verifying whether the underlying system is functioning correctly. uorb_listener only monitors topics that have been advertised. The entire listening process can be paused using Ctrl+C. Usage instructions can be viewed by running uorb_listener -h. Below are some commonly used cases:

listener <command> [arguments...]
 Commands:
        <topics_name> Topic name. Multi name are separated by ','
        [-h       ]  Listener commands help
        [-f       ]  Record uorb data to file
        [-n <val> ]  Number of messages, default: 0
        [-r <val> ]  Subscription rate (unlimited if 0), default: 0
        [-b <val> ]  Subscription maximum report latency in us(unlimited if 0), default: 0
        [-t <val> ]  Time of listener, in seconds, default: 5
        [-T       ]  Top, continuously print updating objects
        [-l       ]  Top only execute once.

uorb_listener continuously prints information for all topics at the frequency of topic publications.

uorb_listener -f continuously saves information for all topics to /data/uorb/*/*.csv at the frequency of topic publications. If the -f flag is used and the file cannot be created, the data will be output to the terminal instead.

uorb_listener -f sensor_accel0 continuously saves information for the specified topic to a file at the frequency of topic publications.

uorb_listener n 1 prints a snapshot of the current information for all topics.

uorb_listener n num prints information for all topics until num messages have been received, at the frequency of topic publications.

uorb_listener r 1 prints information for all topics at a frequency of 1Hz.

uorb_listener r x n num prints information for all topics at a frequency of xHz until num messages have been received.

uorb_listener [specified_topic_list] r 1 continuously prints information for the specified topics at a frequency of 1Hz. In the specified topic list, topics are separated by commas (,). Each entry can be a topic name, such as sensor_accel, which will print information for all instances of that topic. It can also be a topic instance name, such as sensor_mag0, which will only print information for that specific topic instance.

This tool provides a flexible way to monitor and log uORB topic data, aiding in the debugging and verification of the system’s behavior.

Generator Debugging Tool Instructions

uorb_generator this tool can be used in conjunction with uorb_listener.

Before using the tool, it is necessary to set the NSH_LINELEN parameter to a sufficiently long length to ensure that the terminal can accept complete input data. A recommendation is to set it to 256 or 512.

Incoming data can be printed via uorb_listener or concatenated manually using format information, but it must be ensured that the string and struct information are consistent. Topics saved using uorb_listener -f can be pulled and imported into the simulator for debugging (mount -t hostfs -o fs=/home/xxx/ /data).

Parameter Description:

-f specifies the path to the input playback file. -n specifies the number of times to playback the data. This option is only effective when -s is enabled. -r specifies the playback frequency (in HZ, e.g., 5hz, 20hz). This option is only effective when -s is enabled. -t specifies the topic for playback, with the option to specify a specific instance value afterward. -s enables playback of simulated (fake) data, generating struct data from input entered via the terminal. It will modify the timestamp of the current data to real-time. Simulated data should be placed at the end of the entire command.

By following these instructions, users can effectively utilize the Generator debugging tool in conjunction with uorb_listener for system debugging and verification.

The tool publishes topic data via uorb.
Notice:NSH_LINELEN must be set to 128 or more.

generator <command> [arguments...]
  Commands:
    <topics_name> The playback topic name.
    [-h       ]  Listener commands help.
    [-f <val> ]  File path to be played back(absolute path).
    [-n <val> ]  Number of playbacks(fake model), default: 1
    [-r <val> ]  The rate for playing fake data is only valid when parameter 's' is used.
                 default:10hz.
    [-s <val> ]  Playback fake data.
    [-t <val> ]  Playback topic.
     e.g.:
        sim - sensor_accel0:
          uorb_generator -n 100 -r 5 -s -t sensor_accel0 timestamp:23191100,x:0.1,y:9.7,z:0.81,temperature:22.15

        sim - sensor_baro0:
          uorb_generator -n 100 -r 5 -s -t sensor_baro0 timestamp:23191100,pressure:999.12,temperature:26.34

        fies - sensor_accel1
        uorb_generator -f /data/uorb/20240823061723/sensor_accel0.csv -t sensor_accel1