Skip to main content

Writing a Module

A Module is a Git repository owner/Repo whose primary header Repo.hpp declares a plain global C++ class Repo. Its public constructor is the interface: configurations fill its parameters by name and the generator calls it directly.


Creating a Skeleton​

xrobot new-module MySensor --desc "IMU driver" \
--constructor "LibXR::I2C& i2c" \
--constructor "uint32_t period_ms = 10" \
--depends xrobot-org/BlinkLED
OptionMeaning
--descModule description
--constructorOne C++ parameter declaration; repeat per parameter
--templateOne template parameter declaration; repeat per parameter
--template-argA template argument the Module CI compiles with; repeat per argument; template parameters with defaults may be left out
--includeAnother header to include; libxr.hpp and the headers of the LibXR hardware interfaces the constructor uses (such as i2c.hpp) are always included
--dependsA dependency owner/Repo[@ref]; repeat per dependency; the default ref is same-or-dev
--outParent folder in which <NAME>/ is created, default the current directory

Before writing anything, new-module checks the assembled header with the rules of setup and instance add: every parameter has a name, dependencies come before parameters with defaults, and a template parameter without a default has a --template-arg. When the check fails it reports the error and creates nothing.

Result:

MySensor/
├── MySensor.hpp # class, constructor and manifest
├── CMakeLists.txt # adds the sources to LibXR's xr target
├── README.md # laid out like a Module README; purposes are left to the author
└── .github/workflows/build.yml # calls the shared Module CI
#pragma once

// clang-format off
/* === MODULE MANIFEST V2 ===
module_description: IMU driver
depends:
- id: xrobot-org/BlinkLED
ref: same-or-dev
=== END MANIFEST === */
// clang-format on

#include "i2c.hpp"
#include "libxr.hpp"

class MySensor
{
public:
MySensor(LibXR::I2C& i2c, uint32_t period_ms = 10) {}
};
Code block collapsed

xrobot module show <folder, header or Module id> prints the manifest and constructors; with owner/Repo or Repo it reads the locked Module of the current BSP. A YAML error in the manifest names the line and column in the header file.


Constructor Conventions​

  • Dependencies (hardware or other Modules) come first, as references or pointers without defaults. An optional dependency is a pointer without a default; configurations may pass nullptr.
  • Value configuration follows, with defaults. xrobot instance add writes the defaults into the configuration.
  • Every parameter needs a name; declare a type alias for arrays, function pointers and similar types.
  • Configurations fill struct parameters field by field, so define the struct in a header in the Module root, or in a header inside the Module that one of them brings in with #include "...".
  • A class may have several public constructors; configurations select one by parameter names, and overloads with equal names need explicit types to be told apart.
  • The constructor does all initialization. Interfaces generated by macros or changed by conditional compilation must be written as explicit declarations.

An optional public non-static void OnMonitor() is called periodically by the main loop, in configuration order.


Manifest​

The manifest accepts only these keys:

module_description: Description
depends:
- id: owner/Repo
ref: same-or-dev
standalone: false
  • depends: dependency Modules and refs; see ref rules. same-or-dev makes dependencies follow the BSP's branch.
  • standalone: false: a library used only as a dependency; it cannot be instantiated.

Using and Developing in a BSP​

xrobot module add owner/MySensor@dev
xrobot setup
xrobot instance add owner/MySensor

Edit and build directly in the BSP's Modules/owner/MySensor/, keeping the changes uncommitted. When they are ready, commit and push them to a branch of the Module repository, then run xrobot setup --update owner/MySensor in the BSP to update the lock.

To be added to a BSP, the Module must be listed in a Source; see Sources.

The .github/workflows/build.yml that new-module writes calls the shared Module CI; its steps and inputs are described in CI and Firmware Release.