Version: 1.0.0
Last Updated: February 12, 2026
This document provides a comprehensive reference for the CommRaT API. For detailed Doxygen documentation, run make docs and open docs/api/html/index.html.
Table of Contents
- Application Template
- Module Base Class
- I/O Specifications
- Metadata Accessors
- Configuration
- Threading Abstractions
- Timestamp Abstractions
- Mailbox Interface
- Message Definitions
- Introspection System
Application Template
CommRaT<MessageDefs...>
The main application template that defines your messaging system.
template<typename... MessageDefs>
class CommRaT;
Usage:
>;
CommRaT Application Template - Main User-Facing Interface.
Message definition with compile-time ID assignment.
Provides:
MyApp::Module<OutputSpec, InputSpec, ...Commands> - Module template
MyApp::Mailbox<T> - Mailbox template for type T
MyApp::HistoricalMailbox<HistorySize> - Buffered mailbox with getData()
MyApp::Introspection - Schema export helper
MyApp::get_message_id<T>() - Compile-time message ID lookup
MyApp::is_registered<T> - Check if type is in registry
MyApp::serialize<T>(message) - Serialize message to bytes
MyApp::deserialize<T>(buffer) - Deserialize message from bytes
MyApp::visit(buffer, visitor) - Type dispatch with visitor pattern
MyApp::dispatch(buffer, overload_set) - Type dispatch with overload set
MyApp::max_message_size - Maximum message size constant
MyApp::payload_types - Type alias to tuple of all payload types
Header: <commrat/commrat.hpp>
Module Base Class
Module<OutputSpec, InputSpec, ...CommandTypes>
Base class for all CommRaT modules. Handles mailbox management, threading, subscription protocol, and message dispatch.
template<typename UserRegistry,
typename OutputSpec,
typename InputSpec,
typename... CommandTypes>
class Module;
Template Parameters:
OutputSpec - Output specification: Output<T>, Outputs<T, U, ...>, or NoOutput
InputSpec - Input specification: Input<T>, Inputs<T, U, ...>, PeriodicInput, or LoopInput
CommandTypes... - Optional command types handled by this module
Pure Virtual Methods:
User must override the appropriate process() method based on I/O specification:
void process(T& output) override;
void process(T1& out1, T2& out2, ...) override;
void process(const T& input, U& output) override;
void process(const T& input, U1& out1, U2& out2, ...) override;
void process(const T& in1, const U& in2, const V& in3, W& output) override;
void process(const T& in1, const U& in2, const V& in3, W1& out1, W2& out2, ...) override;
void process(T& output) override;
Lifecycle Methods:
void start();
void stop();
Command Handling:
virtual void on_command(const CommandType& cmd);
Protected Members:
const ModuleConfig& config_;
Header: <commrat/registry_module.hpp>
Example:
class SensorModule : public MyApp::Module<
Output<SensorData>,
PeriodicInput
> {
protected:
void process(SensorData& output) override {
output = SensorData{.value = read_sensor()};
}
};
I/O Specifications
Output Specifications
Output<T> - Single output type
template<typename T>
struct Output;
Outputs<Ts...> - Multiple output types
template<typename... Ts>
struct Outputs;
NoOutput - No output (sink module)
Example:
Module<Output<Data>, Input<Sensor>>
Module<Outputs<Raw, Filtered, Stats>, PeriodicInput>
Module<NoOutput, Input<LogData>>
Input Specifications
PeriodicInput - Timer-driven execution
- Executes
process() at fixed intervals
- Period specified in
ModuleConfig::period
- No input parameters to
process()
LoopInput - Maximum throughput execution
- Executes
process() as fast as possible
- No blocking, continuous loop
- Use for computation-heavy modules
Input<T> - Single continuous input
template<typename T>
struct Input;
- Blocks on
receive() for messages of type T
process(const T& input, OutputType& output) called for each message
- Automatic subscription to source module
Inputs<Ts...> - Multiple synchronized inputs
template<typename... Ts>
struct Inputs;
- First type is primary (blocking receive)
- Secondary inputs synchronized via
getData(primary_timestamp)
process(const T1& in1, const T2& in2, ..., OutputType& output) called when primary receives
Example:
Module<Output<Data>, PeriodicInput>
Module<Output<Filtered>, Input<Raw>>
Module<Output<Fused>, Inputs<IMU, GPS, Lidar>>
Module<Output<Result>, LoopInput>
Header: <commrat/io_spec.hpp>
Metadata Accessors
Modules with inputs can access metadata about received messages.
Index-Based Access
Works for any number of inputs:
auto meta = get_input_metadata<0>();
auto meta = get_input_metadata<1>();
uint64_t ts = get_input_timestamp<0>();
bool fresh = has_new_data<1>();
bool valid = is_input_valid<2>();
Type-Based Access
Works when input types are unique (limited to first 2 inputs currently):
auto imu_meta = get_input_metadata<IMUData>();
uint64_t gps_ts = get_input_timestamp<GPSData>();
bool fresh = has_new_data<LidarData>();
Note: Type-based access limited to first 2 input types due to tuple unpacking implementation. Use index-based access for 3+ inputs.
InputMetadata Structure
template<typename T>
struct InputMetadata {
uint64_t timestamp;
uint32_t sequence_number;
uint32_t message_id;
bool is_new_data;
bool is_valid;
};
Example:
class FusionModule : public MyApp::Module<
Output<FusedData>,
Inputs<IMUData, GPSData>
> {
protected:
void process(const IMUData& imu, const GPSData& gps, FusedData& output) override {
auto imu_meta = get_input_metadata<0>();
auto gps_meta = get_input_metadata<1>();
if (!gps_meta.is_new_data) {
std::cout << "GPS data is stale\n";
}
output = fuse_sensors(imu, gps);
}
};
Header: <commrat/module/metadata/input_metadata_accessors.hpp>
Configuration
ModuleConfig
Configuration structure for module initialization.
struct ModuleConfig {
const char* name;
uint8_t system_id;
uint8_t instance_id;
Duration period{Milliseconds(0)};
uint8_t source_system_id{0};
uint8_t source_instance_id{0};
uint32_t source_primary_output_type_id{0};
struct InputSource {
uint8_t system_id;
uint8_t instance_id;
};
sertial::fixed_vector<InputSource, 8> input_sources;
uint64_t sync_tolerance_ns{100'000'000};
};
std::chrono::milliseconds Milliseconds
Example:
ModuleConfig sensor_config{
.name = "Sensor",
.system_id = 10,
.instance_id = 1,
.period = Milliseconds(100)
};
ModuleConfig filter_config{
.name = "Filter",
.system_id = 20,
.instance_id = 1,
.source_system_id = 10,
.source_instance_id = 1
};
ModuleConfig fusion_config{
.name = "Fusion",
.system_id = 30,
.instance_id = 1,
.input_sources = {
{.system_id = 10, .instance_id = 1},
{.system_id = 11, .instance_id = 1}
},
.sync_tolerance_ns = 50'000'000
};
Header: <commrat/registry_module.hpp>
Threading Abstractions
CommRaT provides platform-agnostic threading primitives for portability and future real-time platform support (e.g., libevl).
Thread
Wrapper around std::thread (or platform equivalent).
class Thread {
public:
template<typename Func>
Thread(Func&& func);
void join();
void detach();
};
Synchronization Primitives
class Mutex;
class SharedMutex;
class Lock;
class SharedLock;
Example:
Mutex mutex_;
std::vector<int> data_;
void add_data(int value) {
Lock lock(mutex_);
data_.push_back(value);
}
Header: <commrat/threading.hpp>
Note: Always use CommRaT abstractions instead of std:: types directly to enable future platform-specific implementations.
Timestamp Abstractions
Platform-agnostic time and duration types.
Timestamp
using Timestamp = std::chrono::steady_clock::time_point;
Duration
using Duration = std::chrono::nanoseconds;
using Nanoseconds = std::chrono::nanoseconds;
using Microseconds = std::chrono::microseconds;
using Milliseconds = std::chrono::milliseconds;
using Seconds = std::chrono::seconds;
Time Utilities
class Time {
public:
static Timestamp now();
static void sleep(Duration duration);
};
Example:
auto start = Time::now();
auto end = Time::now();
auto elapsed = std::chrono::duration_cast<Milliseconds>(end - start);
Time::sleep(Milliseconds(100));
Header: <commrat/timestamp.hpp>
Mailbox Interface
Low-level mailbox interface (typically not used directly - Module class handles this).
Mailbox<Registry>
template<typename Registry>
class Mailbox {
public:
explicit Mailbox(uint32_t address);
template<typename T>
auto send(const T& message, uint32_t dest_address) -> MailboxResult<void>;
template<typename T>
auto receive() -> MailboxResult<ReceivedMessage<T>>;
template<typename Visitor>
auto receive_any(Visitor&& visitor) -> MailboxResult<void>;
};
ReceivedMessage<T>
template<typename T>
struct ReceivedMessage {
TimsHeader header;
T message;
};
TimsHeader
struct TimsHeader {
uint64_t timestamp;
uint32_t sequence_number;
uint32_t message_id;
uint8_t priority;
uint8_t flags;
uint16_t source_system_id;
uint16_t source_instance_id;
};
Header: <commrat/mailbox.hpp>
Note: Most users work with Module class and never directly use mailboxes.
Message Definitions
Message Namespace
Helper types for defining messages in the registry:
namespace Message {
template<typename T>
struct Data;
template<typename T>
struct Command;
}
Usage:
struct TemperatureData { float temp; };
struct ResetCmd { bool clear_state; };
using MyApp = CommRaT<
Message::Data<TemperatureData>,
Message::Command<ResetCmd>
>;
System Messages
Automatically included in every registry:
enum class SystemMessages : uint32_t {
SubscribeRequest = 0x00000001,
SubscribeReply = 0x00000002,
UnsubscribeRequest = 0x00000003,
UnsubscribeReply = 0x00000004
};
Header: <commrat/messages.hpp>
Serialization Integration
CommRaT uses SeRTial for automatic serialization. Your message types must be SeRTial-compatible (POD structs work automatically).
Requirements
Message types must:
- Be POD (Plain Old Data) or SeRTial-serializable
- Have default constructors
- Use bounded containers:
sertial::fixed_vector<T, N>, sertial::fixed_string<N>
- Avoid
std::vector, std::string in real-time paths
Valid:
struct SensorData {
float value;
uint64_t timestamp;
sertial::fixed_vector<float, 10> history;
};
Invalid (dynamic allocation):
struct BadData {
std::vector<float> values;
std::string name;
};
Address Calculation
Modules have hierarchical addressing:
uint32_t base = calculate_base_address(system_id, instance_id);
uint32_t cmd_mbx = base + output_index * 16 + 0;
uint32_t work_mbx = base + output_index * 16 + 4;
uint32_t pub_mbx = base + output_index * 16 + 8;
uint32_t data_mbx = base + 12;
Note: Address calculation is handled internally by Module class.
Error Handling
CommRaT uses std::optional and result types for error handling (no exceptions in real-time paths).
template<typename T>
using MailboxResult = std::optional<T>;
Example:
auto result = mailbox.receive<SensorData>();
if (result) {
auto& msg = result->message;
process(msg);
} else {
}
Compile-Time Features
Message ID Calculation
Message IDs computed at compile time:
constexpr uint32_t id = MyApp::get_message_id<TemperatureData>();
static_assert(id == expected_id, "Message ID mismatch");
Type Safety
All type mismatches caught at compile time:
class BadModule : public MyApp::Module<
Output<DataA>,
Input<DataB>
> {};
Zero Overhead
All registry lookups and type dispatch resolved at compile time - no runtime cost.
Introspection System
MyApp::Introspection
Helper class for exporting message schemas (CommRaT metadata + SeRTial layout).
Header: <commrat/introspection.hpp>
export_as<T, Writer>()
Export complete schema for a single message type.
template<typename T, typename Writer = rfl::json::Writer>
static std::string export_as();
Returns: Formatted string containing:
- CommRaT metadata: message_id, payload_type, full_type, max_message_size, registry_name
- SeRTial layout: Full
TimsMessage<T> structure (header + payload) with field names, types, sizes, offsets
- JSON schema: Embedded schema in
type_schema field
Example:
auto json = MyApp::Introspection::export_as<TemperatureData>();
auto yaml = MyApp::Introspection::export_as<TemperatureData, rfl::yaml::Writer>();
Output structure:
{
"commrat": {
"message_id": 16777219,
"payload_type": "TemperatureData",
"full_type": "commrat::TimsMessage<TemperatureData>",
"max_message_size": 104,
"registry_name": "MyApp"
},
"layout": {
"name": "commrat::TimsMessage<TemperatureData>",
"sizeof_bytes": 40,
"base_packed_size": 40,
"max_packed_size": 40,
"has_variable_fields": false,
"field_count": 2,
"field_names": ["header", "payload"],
"field_types": ["commrat::TimsHeader", "TemperatureData"],
"field_sizes": [24, 16],
"field_offsets": [0, 24],
"type_schema": "{...embedded JSON schema...}"
}
}
export_all<Writer>()
Export schemas for all registered message types.
template<typename Writer = rfl::json::Writer>
static std::string export_all();
Returns: JSON array of MessageSchema objects (one per registered type)
Example:
auto all_schemas = MyApp::Introspection::export_all();
std::cout << all_schemas;
write_to_file<Writer>(filename)
Convenience method to write all schemas to a file.
template<typename Writer = rfl::json::Writer>
static void write_to_file(const std::string& filename);
Example:
MyApp::Introspection::write_to_file("schemas.json");
MyApp::Introspection::write_to_file<rfl::yaml::Writer>("schemas.yaml");
MessageSchema<PayloadT, Registry>
Complete schema structure combining CommRaT and SeRTial metadata.
template<typename PayloadT, typename Registry>
struct MessageSchema {
struct CommRaTMetadata {
uint32_t message_id;
std::string payload_type;
std::string full_type;
size_t max_message_size;
std::string registry_name;
sertial::StructLayout<TimsMessage<PayloadT>> layout;
};
CommRaT - Modern C++ Real-Time Communication Framework.
Direct usage:
constexpr auto num_fields = Schema{}.layout.field_count;
Complete schema for a CommRaT message type.
Use Cases:
- Generic logger/replay tools (type-agnostic logging)
- Web-based message viewers (display schemas)
- JSON configuration validation (check field names/types)
- Documentation generation (auto-generate API docs)
- ROS 2 adapter (map CommRaT ↔ ROS message types)
See Also
- User Guide - Comprehensive framework guide
- Getting Started - First application tutorial
- Architecture - Design decisions and internals
- Introspection Example - Complete working example
- Examples - Working code examples
- Doxygen Docs - Generated API documentation (after
make docs)