Telemetry suite for logging data from your robot π€.
- Windows Server 2022 / Visual Studio 2022
- Ubuntu 22.04, 24.04
- macOS >= 10.15
It is possible to install on linux, macOS and Windows via conda, just running:
conda install -c conda-forge librobometryA compiler supporting C++17 is required (including GCC 11 on Ubuntu 22.04,
GCC 13 on Ubuntu 24.04, and Visual Studio 2022). CMake selects C++17 and
requires compiler support automatically; no -DCMAKE_CXX_STANDARD option is needed.
Linking to robometry::robometry also propagates the C++17 minimum to client targets.
The dependencies are:
- CMake (minimum version 3.12)
- matio-cpp (minimum version 0.1.1)
- nlohmann_json (minimum version 3.10.0)
- Catch2 (v3.7.1, for the unit tests)
The optional dependencies are:
git clone https://github.com/robotology/robometry
cd robometry
mkdir build && cd build
cmake ../
make
[sudo] make install
Notice: sudo is not necessary if you specify the CMAKE_INSTALL_PREFIX. In this case it is necessary to add in the .bashrc or .bash_profile the following lines:
export robometry_DIR=/path/where/you/installed/
With IDE build tool facilities, such as Visual Studio:
git clone https://github.com/robotology/robometry
cd robometry
mkdir build && cd build
cmake ..
cmake --build . --target ALL_BUILD --config Release
cmake --build . --target INSTALL --config Release
In order to allow CMake finding robometry, you have to specify the path where you installed in the CMAKE_PREFIX_PATH or exporting the robometry_DIR env variable pointing to the same path.
In order to use this library in your own application, add this lines in your CMakeLists.txt
find_package(robometry)
add_executable(myApp)
target_link_libraries(myApp robometry::robometry)Robometry no longer requires Boost. Existing code using Buffer,
Buffer::iterator, range-based loops, or auto for getBufferSharedPtr() can
continue using those interfaces without source changes. Copies of Buffer
still share their storage, and getBufferSharedPtr() keeps its name and shared
ownership behavior.
For code that explicitly names Boost types, use the public aliases instead:
using RecordBuffer = robometry::Buffer::container_type;
using SharedRecordBuffer = robometry::Buffer::shared_buffer_type;
SharedRecordBuffer storage = buffer.getBufferSharedPtr();
// Or, when an explicit type is unnecessary:
auto same_storage = buffer.getBufferSharedPtr();container_type currently resolves to robometry::CircularBuffer<robometry::Record>.
It implements the operations used by robometry and its examples, rather than the
entire Boost container API. Copies of the container own independent storage.
Structural modifications invalidate its iterators. Capacity reductions and size
reductions preserve the oldest records, matching the previous behavior.
This change is not ABI compatible: rebuild all clients. The shared library ABI
version (SOVERSION) is now 1. To stage a migration across releases, the same
aliases can first be introduced in a Boost-based release; clients can then adopt
them before the backend changes in a subsequent release. Code that explicitly
uses boost::circular_buffer cannot retain that exact type without Boost.
Here is the code snippet for dumping in a .mat file 3 samples of the scalar variables "one" and "two". The type of the channel is inferred when pushing the first time
robometry::BufferConfig bufferConfig;
// We use the default config, setting only the number of samples (no auto/periodic saving)
bufferConfig.n_samples = n_samples;
robometry::BufferManager bm(bufferConfig);
bm.setFileName("buffer_manager_test");
robometry::ChannelInfo var_one{ "one", {1} };
robometry::ChannelInfo var_two{ "two", {1} };
bool ok = bm.addChannel(var_one);
ok = ok && bm.addChannel(var_two);
if (!ok) {
std::cout << "Problem adding variables...."<<std::endl;
return 1;
}
for (int i = 0; i < 3; i++) {
bm.push_back(i , "one");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
bm.push_back(i + 1.0, "two");
}
if (bm.saveToFile())
std::cout << "File saved correctly!" << std::endl;
else
std::cout << "Something went wrong..." << std::endl;
And here is the resulting .mat file:
buffer_manager_test =
struct with fields:
description_list: {[1Γ0 char]}
two: [1Γ1 struct]
one: [1Γ1 struct]
buffer_manager_test.one =
struct with fields:
data: [1Γ3 int32]
dimensions: [1 3]
elements_names: {'element_0'}
units_of_measure: {'n.d.'}
name: 'one'
timestamps: [1.6481e+09 1.6481e+09 1.6481e+09]
It is possible to save and dump also vector variables.
Here is the code snippet for dumping in a .mat file 3 samples of the 4x1 vector variables "one" and "two".
robometry::BufferConfig bufferConfig;
bufferConfig.auto_save = true; // It will save when invoking the destructor
bufferConfig.channels = { {"one", {4,1}, {}, {"meters"}}, {"two", {4,1}, {}, {"degrees"}} };
bufferConfig.filename = "buffer_manager_test_vector";
bufferConfig.n_samples = 3;
robometry::BufferManager bm_v(bufferConfig); //Only vectors of doubles are accepted
for (int i = 0; i < 3; i++) {
bm_v.push_back({ i+1.0, i+2.0, i+3.0, i+4.0 }, "one");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
bm_v.push_back({ (double)i, i*2.0, i*3.0, i*4.0 }, "two");
}
buffer_manager_test_vector =
struct with fields:
description_list: {[1Γ0 char]}
two: [1Γ1 struct]
one: [1Γ1 struct]
>> buffer_manager_test_vector.one
ans =
struct with fields:
data: [4Γ1Γ3 double]
dimensions: [4 1 3]
elements_names: {4Γ1 cell}
units_of_measure: {4Γ1 cell}
name: 'one'
timestamps: [1.6481e+09 1.6481e+09 1.6481e+09]
>> buffer_manager_test_vector.one.elements_names
ans =
4Γ1 cell array
{'element_0'}
{'element_1'}
{'element_2'}
{'element_3'}
>> buffer_manager_test_vector.one.units_of_measure
ans =
4Γ1 cell array
{'m'}
{'m'}
{'m'}
{'m'}
It is also possible to specify the name of the elements of each variable with
robometry::ChannelInfo var_one{ "one", {4,1}, {"A", "B", "C", "D"}, {"m", "cm", "mm", "nm"}};Here is the code snippet for dumping in a .mat file 3 samples of the 2x3 matrix variable"one" and of the 3x2 matrix variable "two".
BufferManager expects all the inputs to be of vector types, but then input is remapped into a matrix of the specified type.
robometry::BufferManager bm_m;
bm_m.resize(3);
bm_m.setFileName("buffer_manager_test_matrix");
bm_m.enablePeriodicSave(0.1); // This will try to save a file each 0.1 sec
bm_m.setDefaultPath("/my/preferred/path");
bm_m.setDescriptionList({"head", "left_arm"});
std::vector<robometry::ChannelInfo> vars{ { "one",{2,3} },
{ "two",{3,2} } };
bool ok = bm_m.addChannels(vars);
if (!ok) {
std::cout << "Problem adding variables...."<<std::endl;
return 1;
}
for (int i = 0; i < 3; i++) {
bm_m.push_back({ i + 1, i + 2, i + 3, i + 4, i + 5, i + 6 }, "one");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
bm_m.push_back({ i * 1, i * 2, i * 3, i * 4, i * 5, i * 6 }, "two");
}
>> buffer_manager_test_matrix.one
ans =
struct with fields:
data: [2Γ3Γ3 int32]
dimensions: [2 3 3]
name: 'one'
timestamps: [112104.7605783 112104.9608881 112105.1611651]
It is possible to save and dump vectors and matrices into nested mat structures. To add an element into the matlab struct the you should use the separator ::. For instance the to store a vector in A.B.C.my_vector you should define the channel name as A::B::C::my_vector
Here is the code snippet for dumping in a .mat file 3 samples of the 4x1 vector variables "one" and "two" into struct1 and struct2.
robometry::BufferConfig bufferConfig;
bufferConfig.auto_save = true; // It will save when invoking the destructor
bufferConfig.channels = { {"struct1::one",{4,1}}, {"struct1::two",{4,1}}, {"struct2::one",{4,1}} }; // Definition of the elements into substruct
bufferConfig.filename = "buffer_manager_test_nested_vector";
bufferConfig.n_samples = 3;
robometry::BufferManager bm_v(bufferConfig);
for (int i = 0; i < 3; i++) {
bm_v.push_back({ i+1.0, i+2.0, i+3.0, i+4.0 }, "struct1::one");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
bm_v.push_back({ (double)i, i*2.0, i*3.0, i*4.0 }, "struct1::two");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
bm_v.push_back({ (double)i, i/2.0, i/3.0, i/4.0 }, "struct2::one");
}
buffer_manager_test_nested_vector =
struct with fields:
description_list: {[1Γ0 char]}
struct2: [1Γ1 struct]
struct1: [1Γ1 struct]
>> buffer_manager_test_nested_vector.struct1
ans =
struct with fields:
two: [1Γ1 struct]
one: [1Γ1 struct]
>> buffer_manager_test_nested_vector.struct1.one
ans =
struct with fields:
data: [4Γ1Γ3 double]
dimensions: [4 1 3]
name: 'one'
timestamps: [1.6415e+09 1.6415e+09 1.6415e+09]
BufferManager can be used to store channels of different types, including structs. In order to store a struct, it is necessary to use the VISITABLE_STRUCT macro (see https://github.com/garbageslam/visit_struct). The available conversions depend on matio-cpp.
struct testStruct
{
int a;
double b;
};
VISITABLE_STRUCT(testStruct, a, b);
...
robometry::BufferManager bm;
robometry::BufferConfig bufferConfig;
robometry::ChannelInfo var_int{ "int_channel", {1}};
robometry::ChannelInfo var_double{ "double_channel", {1}};
robometry::ChannelInfo var_string{ "string_channel", {1}};
robometry::ChannelInfo var_vector{ "vector_channel", {4, 1}};
robometry::ChannelInfo var_struct{ "struct_channel", {1}};
bm.addChannel(var_int);
bm.addChannel(var_double);
bm.addChannel(var_string);
bm.addChannel(var_vector);
bm.addChannel(var_struct);
bufferConfig.n_samples = 3;
bufferConfig.filename = "buffer_manager_test_multiple_types";
bufferConfig.auto_save = true;
bm.configure(bufferConfig);
testStruct item;
for (int i = 0; i < 3; i++) {
bm.push_back(i, "int_channel");
bm.push_back(i * 1.0, "double_channel");
bm.push_back("iter" + std::to_string(i), "string_channel");
bm.push_back({i + 0.0, i + 1.0, i + 2.0, i + 3.0}, "vector_channel");
item.a = i;
item.b = i;
bm.push_back(item, "struct_channel");
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
The above snippet of code generates channels of different types. It produces the following output.
>> buffer_manager_test_multiple_types
buffer_manager_test_multiple_types =
struct with fields:
description_list: {[1Γ0 char]}
yarp_robot_name: [1Γ0 char]
struct_channel: [1Γ1 struct]
vector_channel: [1Γ1 struct]
string_channel: [1Γ1 struct]
double_channel: [1Γ1 struct]
int_channel: [1Γ1 struct]
>> buffer_manager_test_multiple_types.string_channel
ans =
struct with fields:
data: {1Γ3 cell}
dimensions: [1 3]
elements_names: {'element_0'}
units_of_measure: {'n.d.'}
name: 'string_channel'
timestamps: [1.6512e+09 1.6512e+09 1.6512e+09]
>> buffer_manager_test_multiple_types.vector_channel
ans =
struct with fields:
data: [4Γ1Γ3 double]
dimensions: [4 1 3]
elements_names: {4Γ1 cell}
units_of_measure: {'n.d.'}
name: 'vector_channel'
timestamps: [1.6512e+09 1.6512e+09 1.6512e+09]
BufferManager can call an additional callback every time the save function is called. The
following example define a custom callback that saves a dummy txt file along with the mat saved
by the telemetry
bool myCallback(const std::string& file_name, const SaveCallbackSaveMethod& method) {
std::string file_name_with_extension = file_name + ".txt";
std::ofstream my_file(file_name_with_extension.c_str());
// Write to the file
my_file << "Dummy file!";
// Close the file
my_file.close();
return true;
};
robometry::BufferManager bm;
bm.setSaveCallback(myCallback);It is possible to load the configuration of a BufferManager from a json file
robometry::BufferManager bm;
robometry::BufferConfig bufferConfig;
bool ok = bufferConfigFromJson(bufferConfig,"test_json.json");
ok = ok && bm.configure(bufferConfig);Where the file has to have this format:
{
"yarp_robot_name": "robot",
"description_list": [
"This is a test",
"Or it isn't?"
],
"path":"/my/preferred/path",
"filename": "buffer_manager_test_conf_file",
"n_samples": 20,
"save_period": 1.0,
"data_threshold": 10,
"auto_save": true,
"save_periodically": true,
"channels": [
{
"dimensions": [1,1],
"elements_names": ["element_0"],
"name": "one",
"units_of_measure": ["meters"]
},
{
"dimensions": [1,1],
"elements_names": ["element_0"],
"name": "two",
"units_of_measure": ["degrees"]
}
],
"enable_compression": true,
"file_indexing": "%Y_%m_%d_%H_%M_%S",
"mat_file_version": "v7_3"
}The configuration can be saved to a json file
robometry::BufferConfig bufferConfig;
bufferConfig.n_samples = 10;
bufferConfig.save_period = 0.1; //seconds
bufferConfig.data_threshold = 5;
bufferConfig.save_periodically = true;
std::vector<robometry::ChannelInfo> vars{ { "one",{2,3} },
{ "two",{3,2} } };
bufferConfig.channels = vars;
auto ok = bufferConfigToJson(bufferConfig, "test_json_write.json");The telemetryDeviceDumper is a yarp device that has to be launched through the yarprobotinterface for dumping quantities from your robot(e.g. encoders, velocities etc.) in base of what specified in the configuration. It currently needs icub-main version equal or higher than 2.7.0. Specificially this is needed when enabling the parameter logIRawValuesPublisher, which is used for dumping any type of raw data values coming from the low level, e.g. raw encoder data. Moreover, if you would like to enable the flag logIMotorTemperatures, which will allow to collect motor temperatures in motors_state::temperatures, you need to use the yarp framework on this branch, as explained in this PR.
Note
Note that since robometry depends on icub-main, if you would like to enable the streaming of the motor temperatures, you need to also modify manually these lines in the CMakeLists.txt file in icub-main otherwise you will get a complain about the yarp minimum version used. Moreover, consider that, since this is still not merged on a stable branch, you might have compilation issues at the moment.
- Add
${CMAKE_INSTALL_PREFIX}/share/yarp(where${CMAKE_INSTALL_PREFIX}needs to be substituted to the directory that you choose as theCMAKE_INSTALL_PREFIX) to yourYARP_DATA_DIRSenvironment variable (for more on theYARP_DATA_DIRSenv variable, see YARP documentation on data directories ). - Once you do that, you should be able to find the
telemetryDeviceDumperdevice compiled by this repo using the commandyarp plugin telemetryDeviceDumper, which should have an output similar to:
Yes, this is a YARP plugin
* library: CMAKE_INSTALL_PREFIX/lib/yarp/yarp_telemetryDeviceDumper.dll
* system version: 5
* class name: robometry::TelemetryDeviceDumper
* base class: yarp::dev::DeviceDriver
If this is not the case, there could be some problems in finding the plugin. In that case, just move yourself to the ${CMAKE_INSTALL_PREFIX}/share/yarp directory and launch the device from there.
Further documentation about the configuration parameters and the mapping of the variables inside the .mat file can be browsed here
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.