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.
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:
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).
Once closed, we want to explore the data using a Python script.
Ensure you have your Python path configured:
For bash:
For fish:
Then, run:
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;
}