Skip to main content
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:
FilePurpose
MyModule.hClass declaration inheriting from SysModel
MyModule.cppConstructor, destructor, Reset, UpdateState
MyModule.iSWIG interface — exposes the module to Python
MyModule.rstRST documentation page
_UnitTest/test_myModule.pypytest 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.

Writing the header file

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>)
MethodDescription
write(&payload, moduleID, simTime)Write payload to the message
zeroMsgPayloadZero’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>)
MethodDescription
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)
zeroMsgPayloadZero’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:
IncludeEnables
%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.
1

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"
2

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.
3

Define SelfInit in the .cpp file

void ModuleClass::SelfInit()
{
    SomeMsg_C_init(&this->someOutMsgC);
}
4

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);

Build docs developers (and LLMs) love