Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the possibility to store channels of different types #168

Merged
merged 11 commits into from
May 2, 2022
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ set(CMAKE_C_FLAGS ${YARP_C_FLAGS})
set(CMAKE_CXX_FLAGS ${YARP_CXX_FLAGS})


#### INSERT find_package(MATIO) and other libraries here
find_package(matioCpp 0.1.1 REQUIRED)
#### Dependencies
find_package(matioCpp 0.2.0 REQUIRED)
find_package(Boost REQUIRED)
find_package(Threads REQUIRED)

Expand Down
112 changes: 103 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,18 @@ target_link_libraries(myApp YARP::YARP_telemetry)

### Example scalar variable

Here is the code snippet for dumping in a `.mat` file 3 samples of the scalar varibles `"one"` and `"two"`.
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++
yarp::telemetry::experimental::BufferConfig bufferConfig;

// We use the default config, setting only the number of samples (no auto/periodic saving)
bufferConfig.n_samples = n_samples;

yarp::telemetry::experimental::BufferManager<int32_t> bm(bufferConfig);
yarp::telemetry::experimental::BufferManager bm(bufferConfig);
bm.setFileName("buffer_manager_test");
yarp::telemetry::experimental::ChannelInfo var_one{ "one", {1,1} };
yarp::telemetry::experimental::ChannelInfo var_two{ "two", {1,1} };
yarp::telemetry::experimental::ChannelInfo var_one{ "one", {1} };
yarp::telemetry::experimental::ChannelInfo var_two{ "two", {1} };

bool ok = bm.addChannel(var_one);
ok = ok && bm.addChannel(var_two);
Expand All @@ -112,9 +112,9 @@ Here is the code snippet for dumping in a `.mat` file 3 samples of the scalar va
}

for (int i = 0; i < 10; i++) {
bm.push_back({ i }, "one");
bm.push_back(i , "one");
yarp::os::Time::delay(0.2);
bm.push_back({ i + 1 }, "two");
bm.push_back(i + 1.0, "two");
}

if (bm.saveToFile())
Expand All @@ -139,8 +139,8 @@ buffer_manager_test.one =

struct with fields:

data: [1×3 int32]
dimensions: [1 1 3]
data: [1×3 int32]
dimensions: [1 3]
elements_names: {'element_0'}
name: 'one'
timestamps: [1.6481e+09 1.6481e+09 1.6481e+09]
Expand All @@ -150,6 +150,7 @@ buffer_manager_test.one =

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"`.
If ``BufferManager`` is used with a template ``type`` (e.g. ``BufferManager<double>``), it expects all the inputs to be of type ``std::vector<type>``.

```c++
yarp::telemetry::experimental::BufferConfig bufferConfig;
Expand All @@ -158,7 +159,7 @@ Here is the code snippet for dumping in a `.mat` file 3 samples of the 4x1 vecto
bufferConfig.filename = "buffer_manager_test_vector";
bufferConfig.n_samples = 3;

yarp::telemetry::experimental::BufferManager<double> bm_v(bufferConfig);
yarp::telemetry::experimental::BufferManager<double> 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");
yarp::os::Time::delay(0.2);
Expand Down Expand Up @@ -211,6 +212,7 @@ yarp::telemetry::experimental::ChannelInfo var_one{ "one", {4,1}, {"A", "B", "C"
### 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"`.
If ``BufferManager`` is used with a template ``type`` (e.g. ``BufferManager<double>``), it expects all the inputs to be of type ``std::vector<type>``, but then input is remapped into a matrix of the specified type.

```c++
yarp::telemetry::experimental::BufferManager<int32_t> bm_m;
Expand Down Expand Up @@ -302,6 +304,98 @@ ans =
timestamps: [1.6415e+09 1.6415e+09 1.6415e+09]
```

### Example multiple types

``BufferManager`` can be used to store channels of different types, including ``struct``s. 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``](https://github.com/ami-iit/matio-cpp).
```c++
struct testStruct
{
int a;
double b;
};
VISITABLE_STRUCT(testStruct, a, b);

...

yarp::telemetry::experimental::BufferManager bm;
yarp::telemetry::experimental::BufferConfig bufferConfig;

yarp::telemetry::experimental::ChannelInfo var_int{ "int_channel", {1}};
yarp::telemetry::experimental::ChannelInfo var_double{ "double_channel", {1}};
yarp::telemetry::experimental::ChannelInfo var_string{ "string_channel", {1}};
yarp::telemetry::experimental::ChannelInfo var_vector{ "vector_channel", {4, 1}};
yarp::telemetry::experimental::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");

yarp::os::Time::delay(0.01);
}
}

```
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: {3×1 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 configuration file

It is possible to load the configuration of a BufferManager **from a json file**
Expand Down
31 changes: 16 additions & 15 deletions src/examples/CB_to_matfile_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ class storeData {
bool closing;
double period;
double wait_interval;
vector<Record<int > > local_collection; // stores on the read-thread the values from the buffer
std::shared_ptr<boost::circular_buffer<Record<int > > > cb; // shared pointer to circular buffer
vector<Record> local_collection; // stores on the read-thread the values from the buffer
std::shared_ptr<boost::circular_buffer<Record>> cb; // shared pointer to circular buffer
public:

// constructor of the read/save class. Initialized with the shared pointer and the read period
storeData(std::shared_ptr<boost::circular_buffer<Record<int > > > _cb, const double& _period) : cb(_cb), period(_period)
storeData(std::shared_ptr<boost::circular_buffer<Record>> _cb, const double& _period) : cb(_cb), period(_period)
{
closing = false;
}
Expand Down Expand Up @@ -66,10 +66,11 @@ class storeData {

// here we read and remove all elements. Each element we retrieve from the circular buffer should be removed (pop_back) to prevent reread
lock_mut.lock();
for (long unsigned int i=0; i < cb->size(); i++)
for (long unsigned int i=0; i < cb->size(); i++)
{
// print the elements stored in the vector (for confirmation)
for (auto f = cb->back().m_datum.begin(); f != cb->back().m_datum.end(); ++f)
auto castedDatum = any_cast<vector<int>>(cb->back().m_datum);
for (auto f = castedDatum.begin(); f != castedDatum.end(); ++f)
cout << *f << ' ';
cout << endl;
// store the elements into a local collection (independent of the circular buffer to allow reading more elements)
Expand All @@ -95,7 +96,7 @@ class storeData {

// create copy of the local collection (this acts as a second buffer)
lock_mut.lock();
vector<Record<int > > _collection_copy = local_collection;
vector<Record> _collection_copy = local_collection;
lock_mut.unlock();
cout << "local copy created " << endl;

Expand All @@ -111,14 +112,14 @@ class storeData {
return false;
}
// we assume the size of the data is the same for every timestep (is there any situation this would not be the case?)
int size_datum = _collection_copy[0].m_datum.size();
int size_datum = any_cast<vector<int>>(_collection_copy[0].m_datum).size();
cout << "size of datum: " << size_datum << endl;

// we first collapse the matrix of data into a single vector, in preparation for matioCpp convertion
cout << "linearizing matrix..." << endl;
for (auto& _cell : _collection_copy)
{
for (auto& _el : _cell.m_datum)
for (auto& _el : any_cast<vector<int>>(_cell.m_datum))
{
linear_matrix.push_back(_el);
}
Expand All @@ -131,7 +132,7 @@ class storeData {
// first create timestamps vector
matioCpp::Vector<double> timestamps("timestamps");
timestamps = timestamp_vector;

// and the structures for the actual data too
vector<matioCpp::Variable> test_data;

Expand All @@ -157,11 +158,11 @@ class storeData {

// and the matioCpp struct for these signals
matioCpp::Struct signals("signals", signalsVect);

// now we initialize the proto-timeseries structure
vector<matioCpp::Variable> timeSeries_data;

// the timestamps vector is stored in parallel to the signals
// the timestamps vector is stored in parallel to the signals
timeSeries_data.emplace_back(timestamps);
timeSeries_data.emplace_back(signals);

Expand All @@ -170,8 +171,8 @@ class storeData {
// and finally we write the file
matioCpp::File file = matioCpp::File::Create("test_cb.mat");
file.write(timeSeries);
return true;

return true;
}

bool close()
Expand Down Expand Up @@ -199,7 +200,7 @@ int main()
/**************************************************/

// Initialization of our Buffer (3 entries, type vector<int>)
Buffer<int > cb(3);
Buffer cb(3);
double period = 5; // period for the reading of the buffer

// Initialization of our reading and saving to file class - uses the shared-pointer for reading the circular buffer
Expand All @@ -215,7 +216,7 @@ int main()

// we lock before we populate the circular buffer to prevent conflicts with reading
lock_mut.lock();
cb.push_back(Record<int>(yarp::os::Time::now(), vec));
cb.push_back(Record({yarp::os::Time::now(), vec}));
lock_mut.unlock();

// user input -> say "no" to close the loop and generate the mat file
Expand Down
Loading