C++ is the preferred language for new Basilisk simulation modules. It provides object-oriented encapsulation, private variables, and template-based type-safe messaging. The cppModuleTemplate folder in src/moduleTemplates/cppModuleTemplate/ is the canonical starting point — copy it, rename the files, and replace the placeholder logic.
This documentation accelerates the process but does not replace reading the Basilisk source code. Study how existing modules are written before writing your own.
Module file structure
Every C++ module lives in its own directory:
| File | Purpose |
|---|
MyModule.h | Class declaration inheriting from SysModel |
MyModule.cpp | Constructor, destructor, Reset, UpdateState |
MyModule.i | SWIG interface — exposes the module to Python |
MyModule.rst | RST documentation page |
_UnitTest/test_myModule.py | pytest unit test |
Defining a message payload
Message payloads are defined in standalone .h files in src/architecture/msgPayloadDefC/. See Creating C modules for the full payload file format. After adding a new *MsgPayload.h file, re-run python3 conanfile.py to generate the C++ template classes.
Every C++ module inherits from SysModel and declares Reset and UpdateState.
// cppModuleTemplate.h (from src/moduleTemplates/cppModuleTemplate/)
#ifndef CPP_MODULE_TEMPLATE_H
#define CPP_MODULE_TEMPLATE_H
#include "architecture/_GeneralModuleFiles/sys_model.h"
#include "architecture/msgPayloadDefC/CModuleTemplateMsgPayload.h"
#include "architecture/utilities/bskLogging.h"
#include "architecture/messaging/messaging.h"
#include <array>
/*! @brief basic Basilisk C++ module class */
class CppModuleTemplate: public SysModel {
public:
CppModuleTemplate();
~CppModuleTemplate();
void Reset(uint64_t CurrentSimNanos);
void UpdateState(uint64_t CurrentSimNanos);
Message<CModuleTemplateMsgPayload> dataOutMsg; //!< attitude navigation output msg
ReadFunctor<CModuleTemplateMsgPayload> dataInMsg; //!< translation navigation input msg
BSKLogger bskLogger; //!< BSK Logging
/** setter for `dummy` property */
void setDummy(double value);
/** getter for `dummy` property */
double getDummy() const { return this->dummy; }
/** setter for `dumVector` property */
void setDumVector(std::array<double, 3> value);
/** getter for `dumVector` property */
std::array<double, 3> getDumVector() const { return this->dumVector; }
private:
double dummy = {}; //!< [units] sample module variable declaration
std::array<double, 3> dumVector = {}; //!< [units] sample vector variable
};
#endif
Key points about the header:
- All
#include paths are relative to basilisk/src.
sys_model.h must be included — every module is a subclass of SysModel.
messaging.h provides Message<> and ReadFunctor<> template classes.
- Output messages use
Message<PayloadType> and are declared public.
- Input messages use
ReadFunctor<PayloadType> and are declared public.
- User-configurable variables are
private and accessed through setter/getter methods.
bskLogger must be a public member.
- Use
= {} on private variables to zero-initialize them at declaration.
Setter validation
Setters should validate their input and log a BSK_ERROR if the value is invalid:
void CppModuleTemplate::setDummy(double value)
{
if (value > 0) {
this->dummy = value;
} else {
bskLogger.bskLog(BSK_ERROR,
"CppModuleTemplate: dummy variable must be strictly positive, "
"you tried to set %f", value);
}
}
void CppModuleTemplate::setDumVector(std::array<double, 3> value)
{
for (int i = 0; i < 3; i++) {
if (value[i] <= 0.0) {
bskLogger.bskLog(BSK_ERROR,
"CppModuleTemplate: dumVariable variables must be strictly positive");
return;
}
}
this->dumVector = value;
}
Vector of messages
To accept a variable number of input messages, declare a std::vector of ReadFunctor objects paired with a private buffer vector:
public:
std::vector<ReadFunctor<SomeMsgPayload>> moreInMsgs; //!< variable description
private:
std::vector<SomeMsgPayload> moreInMsgsBuffer; //!< variable description
For a variable number of output messages, declare a vector of message pointers:
public:
std::vector<Message<SomeMsgPayload>*> moreOutMsgs; //!< variable description
Implementing the .cpp file
Constructor and destructor
The constructor sets up default values. The destructor frees dynamically allocated output message objects.
// cppModuleTemplate.cpp
#include "moduleTemplates/cppModuleTemplate/cppModuleTemplate.h"
#include <iostream>
#include "architecture/utilities/linearAlgebra.h"
/*! This is the constructor for the module class. It sets default variable
values and initializes the various parts of the model */
CppModuleTemplate::CppModuleTemplate()
{
}
/*! Module Destructor. */
CppModuleTemplate::~CppModuleTemplate()
{
return;
}
If the module owns dynamically allocated output messages (e.g. from a vector), free them in the destructor:
SomeModule::~SomeModule()
{
for (long unsigned int c = 0; c < this->moreOutMsgs.size(); c++) {
delete this->moreOutMsgs.at(c);
}
}
Reset
/*! This method is used to reset the module. */
void CppModuleTemplate::Reset(uint64_t CurrentSimNanos)
{
/*! - reset any required variables */
this->dummy = 0.0;
bskLogger.bskLog(BSK_INFORMATION, "Variable dummy set to %f in reset.", this->dummy);
/* zero output message on reset */
CModuleTemplateMsgPayload outMsgBuffer = {}; /*!< local output message copy */
this->dataOutMsg.write(&outMsgBuffer, this->moduleID, CurrentSimNanos);
}
Use Reset to:
- Restore time-varying state variables (integral terms, counters, filters).
- Perform one-time reads of configuration messages.
- Verify that required input messages are linked and log
BSK_ERROR if not.
- Check that required variables have been set to valid values.
Example of a required message check:
if (!this->someInMsg.isLinked()) {
bskLogger.bskLog(BSK_ERROR, "SomeModule does not have someInMsg connected!");
}
UpdateState
/*! This is the main method that gets called every time the module is updated. */
void CppModuleTemplate::UpdateState(uint64_t CurrentSimNanos)
{
double Lr[3]; /*!< [unit] variable description */
CModuleTemplateMsgPayload outMsgBuffer; /*!< local output message copy */
CModuleTemplateMsgPayload inMsgBuffer; /*!< local copy of input message */
double inputVector[3];
// always zero the output buffer first
outMsgBuffer = this->dataOutMsg.zeroMsgPayload;
v3SetZero(inputVector);
/*! - Read the optional input messages */
if (this->dataInMsg.isLinked()) {
inMsgBuffer = this->dataInMsg();
v3Copy(inMsgBuffer.dataVector, inputVector);
}
/*! - Add the module specific code */
v3Copy(inputVector, Lr);
this->dummy += 1.0;
Lr[0] += this->dummy;
/*! - store the output message */
v3Copy(Lr, outMsgBuffer.dataVector);
/*! - write the module output message */
this->dataOutMsg.write(&outMsgBuffer, this->moduleID, CurrentSimNanos);
bskLogger.bskLog(BSK_INFORMATION,
"C++ Module ID %lld ran Update at %fs",
this->moduleID, (double) CurrentSimNanos / (1e9));
}
Always zero the output message buffer at the start of UpdateState. Writing stale data to a message is a common source of hard-to-diagnose bugs.
C++ message object API
Output message (Message<PayloadType>)
| Method | Description |
|---|
write(&payload, moduleID, simTime) | Write payload to the message |
zeroMsgPayload | Zero’d payload struct (property, not a method) |
isLinked() | true if a reader or recorder is connected |
addSubscriber() | Return a ReadFunctor connected to this message |
addAuthor() | Return a write functor for this message |
Input message (ReadFunctor<PayloadType>)
| Method | Description |
|---|
operator()() | Read and return a copy of the payload |
isLinked() | true if connected to an output message |
isWritten() | true if the connected message has been written |
timeWritten() | Write time in nanoseconds (uint64_t) |
moduleID() | ID of the writing module (int64_t) |
zeroMsgPayload | Zero’d payload struct (property) |
Call these with standard C++ syntax:
if (this->someInMsg.isLinked()) {
SomeMsgPayload buf = this->someInMsg();
}
Writing the SWIG interface file
// cppModuleTemplate.i
%module cppModuleTemplate
%include "architecture/utilities/bskException.swg"
%default_bsk_exception();
%{
#include "cppModuleTemplate.h"
%}
%pythoncode %{
from Basilisk.architecture.swig_common_model import *
%}
%include "std_string.i"
%include "swig_conly_data.i"
%include "sys_model.i"
%include "cppModuleTemplate.h"
%include "architecture/msgPayloadDefC/CModuleTemplateMsgPayload.h"
struct CModuleTemplateMsg_C;
%pythoncode %{
import sys
protectAllClasses(sys.modules[__name__])
%}
The struct SomeMsg_C; forward declaration is required for every C-payload message type. Without it, setting message variables from Python silently has no effect.
Additional SWIG includes for other Python types:
| Include | Enables |
|---|
%include "std_string.i" | Module string variables |
%include "std_vector.i" | Standard vectors |
%include "swig_eigen.i" | Eigen vectors and matrices |
When you use a std::vector of messages, python3 conanfile.py auto-generates these SWIG template instantiations (available via the messaging package):
%template(SomeMsgOutMsgsVector) std::vector<Message<SomeMsgPayload>>;
%template(SomeMsgOutMsgsPtrVector) std::vector<Message<SomeMsgPayload>*>;
%template(SomeMsgInMsgsVector) std::vector<ReadFunctor<SomeMsgPayload>>;
Using the module from Python
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.architecture import messaging
from Basilisk.moduleTemplates import cppModuleTemplate
scSim = SimulationBaseClass.SimBaseClass()
dynProcess = scSim.CreateNewProcess("dynamicsProcess")
dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(1.0)))
# Instantiate the C++ module
mod = cppModuleTemplate.CppModuleTemplate()
mod.ModelTag = "myModule"
scSim.AddModelToTask("dynamicsTask", mod)
# Set module parameters via setters
mod.setDummy(1.0)
# Create and connect an input message
inputData = messaging.CModuleTemplateMsgPayload()
inputData.dataVector = [1.0, -0.5, 0.7]
inputMsg = messaging.CModuleTemplateMsg().write(inputData)
mod.dataInMsg.subscribeTo(inputMsg)
# Record the output
dataLog = mod.dataOutMsg.recorder()
scSim.AddModelToTask("dynamicsTask", dataLog)
scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(1.0))
scSim.ExecuteSimulation()
print(dataLog.dataVector)
C++ module with a C-wrapped output message
FSW modules sometimes need to write the same payload to both a C++ message object and a C-wrapped message object. This is required when multiple modules write to a shared external gateway message and the gateway consumer expects C-wrapped messages.
Update the header include
Replace the payload-only include with the C wrapper include, which pulls in both the payload definition and the C wrapper:// Replace:
#include "architecture/msgPayloadDefC/SomeMsgPayload.h"
// With:
#include "cMsgCInterface/SomeMsg_C.h"
Add SelfInit and the C message variable
Add a SelfInit() method declaration and the C-wrapped output message as a public member:void SelfInit();
SomeMsg_C someOutMsgC = {}; //!< C-wrapped output message
The = {} zero-initializes the C struct so the message pointers are null before SelfInit runs. Define SelfInit in the .cpp file
void ModuleClass::SelfInit()
{
SomeMsg_C_init(&this->someOutMsgC);
}
Write to the C message in UpdateState
After writing to the C++ message, also write to the C-wrapped message:this->someOutMsg.write(&outMsgBuffer, this->moduleID, CurrentSimNanos);
SomeMsg_C_write(&outMsgBuffer, &this->someOutMsgC, this->moduleID, CurrentSimNanos);