Bota Control Toolkit C++

Definitions

BotaControlBlock

BotaControlBlock is defined as a parent class from which all control blocks inherit.

class BotaControlBlock

Abstract base class providing the interface for all control processing blocks.

Constructor and Destructor

BotaControlBlock(const std::string &config_path) = default

Default constructor with path to JSON configuration file. Prefered method for instantiating blocks.

BotaControlBlock(const nlohmann::json &config_json) = default

Default constructor with a JSON object containing the configuration data.

BotaControlBlock(const BotaControlConfig &config_object) = default

Default constructor with a configuration object populated somewhere else in the code.

virtual ~BotaControlBlock() = default

Virtual destructor for proper inheritance handling.

Runtime Update Methods

virtual BotaControlReturnCode update(std::span<const double> input_signal, std::span<double> output_signal) = 0

Reads input data and performs processing.

Parameters:
  • input_signal – Reference to a span containing the input signal data.

  • output_signal – Reference to a span where the processed output signal should be stored.

Returns:

A return code indicating status of the update operation.

virtual BotaControlReturnCode updateInplace(std::span<double> signal) = 0

Reads data and performs processing in place.

Parameters:

signal – Reference to a span containing the signal data to be processed in place.

Returns:

A return code indicating status of the update operation.

virtual BotaControlReturnCode updateObserver(std::span<const double> measurement, std::span<double> state_estimate) = 0

Reads input data and performs processing.

Parameters:
  • measurement – Reference to a span containing the measurement data.

  • state_estimate – Reference to a span where the state estimate should be stored.

Returns:

A return code indicating status of the update operation.

virtual BotaControlReturnCode updateController(std::span<const double> reference, std::span<const double> state_estimate, std::span<double> control_action) = 0

Reads input data and performs processing.

Parameters:
  • reference – Reference to a span containing the reference data.

  • state_estimate – Reference to a span containing the state estimate data.

  • control_action – Reference to a span where the control action should be stored.

Returns:

A return code indicating status of the update operation.

In-line configuration methods

The specific implementations of the control blocks may include additional in-line configuration methods that allow users to modify block parameters at configuration or runtime without needing to re-instantiate the block. These methods are specific to each block and are not part of the base class interface.

BotaControlReturnCode

enum struct BotaControlReturnCode
enumerator OK
enumerator Stale
enumerator Degraded
enumerator DeadlineMissed
enumerator NonFatalError
enumerator FatalError

Pipeline Wiring

Here’s how to connect multiple blocks to create a processing pipeline:

 ////////////////////////////////////////////////////////////////////////////////////
 // EXAMPLE OF A BUNDLED CONTROL PIPELINE USING THE BOTA CONTROL BLOCKS //
 ///////////////////////////////////////////////////////////////////////////////////

 #include <chrono>
 #include <thread>

 // Define signals - they can be std::array or std::vector as long as they are std::span compatible
 std::array<double, N> signal_1;
 std::array<double, M> signal_2;
 std::vector<double> signal_3;

 // Path to the JSON configuration file for the blocks
 std::string config_1 = "path/to/config_1.json";
 std::string config_2 = "path/to/config_2.json";
 std::string config_3 = "path/to/config_3.json";

// Instantiation & Configuration at initialization time
std::unique_ptr<Block1> block_1 = std::make_unique<Block1>(config_1);
std::unique_ptr<Block2> block_2 = std::make_unique<Block2>(config_2);
std::unique_ptr<Block3> block_3 = std::make_unique<Block3>(config_3);

// Block in-line static configuration
 block_1->setParameter(value_1);
 block_2->setParameter(value_2);
 block_3->setParameter(value_3);

// Set the desired processing rate
const double rate_hz = 500.0;  // 500 Hz
const auto dt = std::chrono::duration<double>(1.0 / rate_hz);

bool running = true;

// loop reactor lamda function to handle return codes and control flow
auto loop_reactor = [&](BotaControlReturnCode code) {
    switch (code) {
        case BotaControlReturnCode::OK:
            // Normal operation, continue processing
            break;
        {...other cases...}
        case BotaControlReturnCode::FatalError:
            // Handle fatal errors (e.g., log the error, shut down the system safely, etc.)
            running = false; // Example: stop the loop on fatal error
            break;
    }

while (running) {
    auto start_time = std::chrono::steady_clock::now();

     // Update signals with new data here (e.g., from sensors, reference generators, etc.)
     signal_1 = getNewDataForSignal1();

     // Process data through the pipeline

     loop_reactor(block_1->update(signal_1, signal_2));
     //=> signal_2 freshly updated by block_1 algorithm can be used HERE
     loop_reactor(block_2->updateInplace(signal_2));
     //=> signal_2 freshly updated by block_2 algorithm can be used HERE
     loop_reactor(block_3->update(signal_2, signal_3));
     //=>  signal_3 freshly updated by block_3 algorithm can be used HERE

     ////////////////////////
     // Control logic here //
     ////////////////////////

     // Rate control - sleep to maintain desired frequency
     auto elapsed_time = std::chrono::steady_clock::now() - start_time;
     auto sleep_time = dt - elapsed_time;
     if (sleep_time > std::chrono::duration<double>::zero()) {
         std::this_thread::sleep_for(sleep_time);
     }
}

Distribution

The Bota Control Toolkit definitions are distributed as a collection of header files, available here.

Important

They must be included in every C++ project using any of the Bota Control Toolkit blocks.