Robometry

:warning: LIBRARY UNDER DEVELOPMENT :warning:

Since it is under development, we cannot guarantee that the API of librobometry and the user interface of telemetryDeviceDumper(the configuration parameters) will not implement breaking changes. Be aware of this if you start using the code contained in this repository, sorry for the unconvenience.

Image

Image Image Image

Telemetry suite for logging data from your robot 🤖.

Tested OSes

  • Windows 10
  • Ubuntu 20.04, 22.04
  • macOS >= 10.15

Installation from binaries

Conda packages

It is possible to install on linux, macOS and Windows via conda, just running: conda install -c robotology robometry

Installation from sources

Dependencies

The dependencies are:

The optional dependencies are:

  • YARP (minimum version 3.5.0)

Linux/macOS

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/

Windows

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.

librobometry

In order to use this library in your own appliction add this lines in your CMakeLists.txt

find_package(robometry)

add_executable(myApp)
target_link_libraries(myApp robometry::robometry)

Example scalar variable

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

{c++}
   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 < 10; 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'}
              name: 'one'
        timestamps: [1.6481e+09 1.6481e+09 1.6481e+09]

Example vector variable

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".

{c++}
   robometry::BufferConfig bufferConfig;
   bufferConfig.auto_save = true; // It will save when invoking the destructor
   bufferConfig.channels = { {"one",{4,1}}, {"two",{4,1}} };
   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 < 10; 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}
              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'}

It is also possible to specify the name of the elements of each variable with

 {c++}
robometry::ChannelInfo var_one{ "one", {4,1}, {"A", "B", "C", "D"}};

Example matrix variable

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.

{c++}
   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 < 10; 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]

Example nested struct

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.

{c++}
   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 < 10; 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]

Example multiple types

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.

 {c++}
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 < 10; 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'}
              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}
              name: 'vector_channel'
        timestamps: [1.6512e+09 1.6512e+09 1.6512e+09]

Example additional callback

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

 {c++}
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);

Example configuration file

It is possible to load the configuration of a BufferManager from a json file

{c++}
  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"
    },
    {
      "dimensions": [1,1],
      "elements_names": ["element_0"],
      "name": "two"
    }
  ],
  "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

{c++}
   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");

TelemetryDeviceDumper

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.

Export the env variables

  • Add ${CMAKE_INSTALL_PREFIX}/share/yarp (where ${CMAKE_INSTALL_PREFIX} needs to be substituted to the directory that you choose as the CMAKE_INSTALL_PREFIX) to your YARP_DATA_DIRS enviromental variable (for more on the YARP_DATA_DIRS env variable, see YARP documentation on data directories ).
  • Once you do that, you should be able to find the telemetryDeviceDumper device compiled by this repo using the command yarp 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.

Parameters

Parameter nameTypeUnitsDefaultRequiredDescription
axesNamesList of strings--YesThe axes contained in the axesNames parameter are then mapped to the wrapped controlboard in the attachAll method, using controlBoardRemapper class.
logJointVelocity (DEPRECATED)bool-falseNoEnable the log of joint velocities.
logJointAcceleration (DEPRECATED)bool-falseNoEnable the log of joint accelerations.
logIEncodersbool-trueNoEnable the log of joints_state::positions, joints_state::velocities and joints_state::accelerations (http://yarp.it/git-master/classyarp_1_1dev_1_1IEncoders.html)
logITorqueControlbool-falseNoEnable the log of joints_state::torques(http://yarp.it/git-master/classyarp_1_1dev_1_1ITorqueControl.html).
logIMotorEncodersbool-falseNoEnable the log of motors_state::positions, motors_state::velocities and motors_state::accelerations (http://yarp.it/git-master/classyarp_1_1dev_1_1IMotorEncoders.html).
logIControlModebool-falseNoEnable the log of joints_state::control_mode (http://yarp.it/git-master/classyarp_1_1dev_1_1IControlMode.html.
logIInteractionModebool-falseNoEnable the log of joints_state::interaction_mode (http://yarp.it/git-master/classyarp_1_1dev_1_1IInteractionMode.html.
logIPidControlbool-falseNoEnable the log of PIDs::position_error, PIDs::position_reference, PIDs::torque_error, PIDs::torque_reference(http://yarp.it/git-master/classyarp_1_1dev_1_1IPidControl.html).
logIAmplifierControlbool-falseNoEnable the log of motors_state::pwm and motors_state::currents (http://yarp.it/git-master/classyarp_1_1dev_1_1IAmplifierControl.html).
logAllQuantities (DEPRECATED)bool-falseNoEnable the log all quantities described above.
logControlBoardQuantitiesbool-falseNoEnable the log of all the quantities that requires the attach to a control board (logIEncoders, logITorqueControl, logIMotorEncoders, logIControlMode, logIInteractionMode, logIPidControl, logIAmplifierControl).
logILocalization2Dbool-falseNoEnable the log of odometry_data (http://yarp.it/git-master/classyarp_1_1dev_1_1Nav2D_1_1ILocalization2D.html).
saveBufferManagerConfigurationbool-falseNoEnable the save of the configuration of the BufferManager into path+ "bufferConfig" + experimentName + ".json"
json_filestring--NoConfigure the robometry::BufferManagers reading from a json file like in Example configuration file. Note that this configuration will overwrite the parameter-by-parameter configuration
experimentNamestring--YesPrefix of the files that will be saved. The files will be named: experimentName+timestamp+ ".mat".
pathstring--NoPath of the folder where the data will be saved.
n_samplessize_t--YesThe max number of samples contained in the circular buffer/s
save_periodicallybool-falseNo(but it has to be set to true if auto_save is set to false)The flag for enabling the periodic save thread.
save_perioddoubleseconds-Yes(if save_periodically is set to true)The period in seconds of the save thread
data_thresholdsize_t-0NoThe save thread saves to a file if there are at least data_threshold samples
auto_savebool-falseNo(but it has to be set to true if save_periodically is set to false)the flag for enabling the save in the destructor of the robometry::BufferManager
yarp_robot_namestring-""NoName of the robot used during the experiment.

Mapping .mat variables -> YARP interfaces

Variable nameYARP interface
encodersyarp::dev::IEncoders::getEncoders
velocityyarp::dev::IEncoders::getEncoderSpeeds
accelerationyarp::dev::IEncoders::getEncoderAccelerations
motor_encodersyarp::dev::IMotorEncoders::getMotorEncoders
motor_velocityyarp::dev::IMotorEncoders::getMotorEncoderSpeeds
motor_accelerationyarp::dev::IMotorEncoders::getMotorEncoderAccelerations
control_modeyarp::dev::IControlMode::getControlModes
interaction_modeyarp::dev::IInteractionMode::getInteractionModes
position_erroryarp::dev::IPidControl::getPidErrors
position_referenceyarp::dev::IPidControl::getPidReferences
torque_erroryarp::dev::IPidControl::getPidErrors
torque_referenceyarp::dev::IPidControl::getPidReferences
odometry_datayarp::dev::Nav2D::ILocalization2D::getEstimatedOdometry

Example of xml

Example of xml file for using it on the iCub robot:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE devices PUBLIC "-//YARP//DTD yarprobotinterface 3.0//EN" "http://www.yarp.it/DTD/yarprobotinterfaceV3.0.dtd">


    <device xmlns:xi="http://www.w3.org/2001/XInclude" name="telemetryDeviceDumper" type="telemetryDeviceDumper">
        <param name="axesNames">(torso_pitch,torso_roll,torso_yaw,neck_pitch, neck_roll,neck_yaw,l_shoulder_pitch,l_shoulder_roll,l_shoulder_yaw,l_elbow,l_wrist_prosup,l_wrist_pitch,l_wrist_yaw,r_shoulder_pitch,r_shoulder_roll,r_shoulder_yaw,r_elbow,r_wrist_prosup,r_wrist_pitch,r_wrist_yaw,l_hip_pitch,l_hip_roll,l_hip_yaw,l_knee,l_ankle_pitch,l_ankle_roll,r_hip_pitch,r_hip_roll,r_hip_yaw,r_knee,r_ankle_pitch,r_ankle_roll)</param>
        <param name="logIEncoders">true</param>
        <param name="logITorqueControl">true</param>
        <param name="logIMotorEncoders">true</param>
        <param name="logIControlMode">true</param>
        <param name="logIInteractionMode">true</param>
        <param name="logIPidControl">false</param>
        <param name="logIAmplifierControl">true</param>
        <param name="saveBufferManagerConfiguration">true</param>
        <param name="experimentName">test_telemetry</param>
        <param name="path">/home/icub/test_telemetry/</param>
        <param name="n_samples">100000</param>
        <param name="save_periodically">true</param>
        <param name="save_period">120.0</param>
        <param name="data_threshold">300</param>
        <param name="auto_save">true</param>

        <action phase="startup" level="15" type="attach">
            <paramlist name="networks">
                <!-- motorcontrol and virtual torque sensors -->
                <elem name="left_lower_leg">left_leg-eb7-j4_5-mc</elem>
                <elem name="right_lower_leg">right_leg-eb9-j4_5-mc</elem>
                <elem name="left_upper_leg">left_leg-eb6-j0_3-mc</elem>
                <elem name="right_upper_leg">right_leg-eb8-j0_3-mc</elem>
                <elem name="torso">torso-eb5-j0_2-mc</elem>
                <elem name="right_lower_arm">right_arm-eb27-j4_7-mc</elem>
                <elem name="left_lower_arm">left_arm-eb24-j4_7-mc</elem>
                <elem name="right_upper_arm">right_arm-eb3-j0_3-mc</elem>
                <elem name="left_upper_arm">left_arm-eb1-j0_3-mc</elem>
                <elem name="head-j0">head-eb20-j0_1-mc</elem>
                <elem name="head-j2">head-eb21-j2_5-mc</elem>
                <!-- ft -->
            </paramlist>
        </action>

        <action phase="shutdown" level="2" type="detach" />

    </device>

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

License

See License