Skip to content

Recorder example

This example shows how to use the Sen recorder component.

Recording some local objects

Here we just set it up to record some objects that are always published by Sen. They live in the local.kernel bus.

sen run config/6_recorder/1_recorder_kernel.yaml

This will automatically start the recording. You can inspect the objects (including the recording).

After a while you can close the process (with a shutdown command or a Ctrl+D keystroke).

The recording should be present in a new folder called kernel_recording. To inspect it:

sen archive info kernel_recording

You should see something similar to this:

  path:            kernel_recording
  duration:        8.06667s
  start:           2025-05-19 14:34:45 966836
  end:             2025-05-19 14:34:54 033502
  objects:         4
  types:           30
  annotations:     0
  keyframes:       5
  indexed objects: 4

Recording the school example

Similar to the previous example, but now we also start the school example and record more information (don't run it for too long, as we will be iterating over the entries later on).

sen run config/6_recorder/2_recorder_school.yaml

Once closed, we want to explore the data using a Python script.

Ensure you have your Python path configured:

For bash:

export PYTHONPATH=$PYTHONPATH;$SEN_PATH/bin

For fish:

set -xa PYTHONPATH $SEN_PATH/bin

Then, run:

python3 config/6_recorder/3_recorder_school_print.py

Using Python to inspect the recordings

Sen comes with a Python binding to access the recorded data. The following Python code prints the entries:

# === 3_recorder_school_print.py =======================================================================================
#                                               Sen Infrastructure
#                   Released under the Apache License v2.0 (SPDX-License-Identifier Apache-2.0).
#                                    See the LICENSE.txt file for more information.
#                   © Airbus SAS, Airbus Helicopters, and Airbus Defence and Space SAU/GmbH/SAS.
# ======================================================================================================================

import sen_db_python as sen
from datetime import datetime

epoch = datetime(1970,1,1)

try:
    input = sen.Input("school_recording")
    print(f"Opened archive '{input.path}' with the following summary:")
    print(f" - firstTime: {epoch+input.summary.firstTime}")
    print(f" - lastTime: {epoch+input.summary.lastTime}")
    print(f" - keyframeCount: {input.summary.keyframeCount}")
    print(f" - objectCount: {input.summary.objectCount}")
    print(f" - typeCount: {input.summary.typeCount}")
    print(f" - annotationCount: {input.summary.annotationCount}")
    print(f" - indexedObjectCount: {input.summary.indexedObjectCount}")
    print("")

    print("Iterating over the entries:")
    print("")

    cursor = input.begin()
    while not cursor.atEnd:
        entry = cursor.entry

        if type(entry.payload) is sen.Keyframe:
            print(f"{epoch+entry.time} -> Keyframe:")
            for obj in entry.payload.snapshots:
                print(f"  - object: {obj.name}")
                print(f"    class:  {obj.className}")
            print("")

        elif type(entry.payload) is sen.Creation:
            print(f"{epoch+entry.time} -> Object Creation:")
            print(f"  - name:  {entry.payload.name}")
            print(f"  - class: {entry.payload.className}")
            print("")

        elif type(entry.payload) is sen.Deletion:
            print(f"{epoch+entry.time} -> Object Deletion:")
            print(f"  - object id:  {entry.payload.objectId}")
            print("")

        elif type(entry.payload) is sen.PropertyChange:
            print(f"{epoch+entry.time} -> Property Changed:")
            print(f"  - object id:  {entry.payload.objectId}")
            print(f"  - property:   {entry.payload.name}")
            print("")

        elif type(entry.payload) is sen.Event:
            print(f"{epoch+entry.time} -> Event:")
            print(f"  - object id:  {entry.payload.objectId}")
            print(f"  - event:      {entry.payload.name}")
            print("")

        cursor.advance()

except Exception as err:
    print(err)

Using C++ to inspect the recordings

You can access the recorded data using the provided C++ API (this is how the Sen replayer works):

// === main.cpp ========================================================================================================
//                                               Sen Infrastructure
//                   Released under the Apache License v2.0 (SPDX-License-Identifier Apache-2.0).
//                                    See the LICENSE.txt file for more information.
//                   © Airbus SAS, Airbus Helicopters, and Airbus Defence and Space SAU/GmbH/SAS.
// =====================================================================================================================

// sen
#include "sen/core/base/class_helpers.h"
#include "sen/core/meta/type_registry.h"
#include "sen/core/obj/object.h"
#include "sen/db/creation.h"
#include "sen/db/deletion.h"
#include "sen/db/event.h"
#include "sen/db/input.h"
#include "sen/db/keyframe.h"
#include "sen/db/property_change.h"

// std
#include <algorithm>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <string>
#include <utility>
#include <variant>

int main(int argc, char* argv[])
{
  if (argc != 2)
  {
    std::cerr << "expecting recording path" << std::endl;
    return EXIT_FAILURE;
  }

  try
  {
    // open the recording
    sen::CustomTypeRegistry reg;
    auto input = sen::db::Input(argv[1], reg);  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

    // print the summary
    std::cout << "summary:\n" << input.getSummary() << "\n";

    // helpers
    auto objs = input.getObjectIndexDefinitions();
    auto findObj = [&](sen::ObjectId id) -> const sen::db::ObjectIndexDef&
    { return *std::find_if(objs.begin(), objs.end(), [&](auto& e) { return id == e.objectId; }); };

    // iterate over the entries
    for (auto cursor = input.begin(); !cursor.atEnd(); ++cursor)
    {
      auto time = cursor.get().time;

      // print things depending on each entry
      std::visit(sen::Overloaded {[&](const sen::db::PropertyChange& entry)
                                  {
                                    std::cout
                                      << "- " << time.toLocalString() << ": " << findObj(entry.getObjectId()).name
                                      << " " << entry.getProperty()->getName()
                                      << " changed: " << entry.getValueAsVariant().getCopyAs<std::string>() << "\n";
                                  },
                                  [&](const sen::db::Event& entry)
                                  {
                                    std::cout << "- " << time.toLocalString() << ": "
                                              << findObj(entry.getObjectId()).name << " " << entry.getEvent()->getName()
                                              << " emitted\n";
                                  },
                                  [&](const sen::db::Creation& entry)
                                  {
                                    std::cout << "- " << time.toLocalString() << ": "
                                              << findObj(entry.getSnapshot().getObjectId()).name << " created\n";
                                  },
                                  [&](const sen::db::Deletion& entry)
                                  {
                                    std::cout << "- " << time.toLocalString() << ": deleted object "
                                              << findObj(entry.getObjectId()).name << "\n";
                                  },
                                  [&](const sen::db::Keyframe& entry)
                                  {
                                    std::cout << "- " << time.toLocalString() << ": keyframe\n";
                                    for (const auto& elem: entry.getSnapshots())
                                    {
                                      std::cout << "  - " << findObj(elem.getObjectId()).name << "\n";
                                    }
                                  },
                                  [&](const sen::db::End& entry)
                                  {
                                    std::ignore = entry;
                                    std::cout << time.toLocalString() << ": end of file\n";
                                  },
                                  [&](const auto&) {}},
                 cursor.get().payload);
    }
  }
  catch (const std::exception& err)
  {
    std::cerr << "error: " << err.what() << std::endl;
    return EXIT_FAILURE;
  }
  catch (...)
  {
    std::cerr << "unexpected error" << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}