Use simulation datasets

Huxon applications can be simulated offline to check the expected behavior by providing custom datasets for every declared sensor. A simulation dataset can be declared using the HUX_DECLARE_SIMULATION_DATA construct:

/*
 * simulation_dataset    : the simulation_dataset label
 * data                  : a hux::tuple of std::vector containing the custom dataset
 */
HUX_DECLARE_SIMULATION_DATA(simulation_dataset, hux::tuple< std::vector<...>, ...> data);

The data parameter is expected to be a hux::tuple consisting of one or more std::vector. Each std::vector represents a data series of a given type.

This flexibility is required since every sensor needs a specific number and types of data series, (e.g. one for acceleration on X axis, one for acceleration on Y axis, etc..). As an example, for sensor IIS2DLPC we need to provide a hux::tuple containing, in order, the following vectors:

  • std::vector<float>: Acceleration on the X axis in g units

  • std::vector<float>: Acceleration on the Y axis in g units

  • std::vector<float>: Acceleration on the Z axis in g units

  • std::vector<hux::uint64_t>: Unix timestamp in milliseconds of the data generated by the sensor

Note

To know the simulation data type required by a specific sensor, check the Sensors documentation.

There are different ways to specify the actual simulation data. The first possibility is to construct the simulation data directly using hux::make_tuple as:

HUX_DECLARE_SIMULATION_DATA(simulation_dataset, hux::make_tuple(
    std::vector<float>{1.0, -0.5, 0.3}, /* Acceleration on X axis */
    std::vector<float>{2.1, -1.5, 1.2}, /* Acceleration on Y axis */
    std::vector<float>{1.1, 1.0, -0.9}, /* Acceleration on Z axis */
    std::vector<hux::uint64_t>{1656684224000, 1656684225000, 1656684226000} /* Timestamp */
));

This approach is useful for simple tests, however, for more complex applications it is usually more convenient to load simulation data from a CSV file using the hux::simulation::load_csv method:

/*
 * <...>             : template parameters indicating the type of each CSV file column
 * "./dataset.csv"   : the CSV file with its relative path
 * ";"               : the separator used inside the CSV file
 */
HUX_DECLARE_SIMULATION_DATA(simulation_dataset,
    hux::simulation::load_csv<float, float, float, hux::uint64_t>("./dataset.csv", ";")
);

The hux::simulation::load_csv returns a hux::tuple of std::vectors by loading the dataset from a CSV file (“./dataset.csv”) using a specific column separator (“;”). The template parameter of the hux::simulation::load_csv method specifies how many columns to read and what is the type of each column. Each column from the CSV file corresponds to a vector of the tuple in the same order. In the previous code snippet, the columns of the CSV file will be treated as:

  • column 1: std::vector< float >

  • column 2: std::vector< float >

  • column 3: std::vector< float >

  • column 4: std::vector< hux::uint64_t >

The first row of the CSV file is completely ignored and can be used by the programmer to specify column names for reference. An example of a valid CSV file that could be loaded by the previous code is:

X;      Y;      Z;      timestamp
1.0;    0.3;    0.5;    1656684224000
-0.5;   0.8;    0.4;    1656684225000
-1.1;   1.1;    0.6;    1656684226000

Acceleration norm example

The following example shows how to write a simple application that computes the squared norm of the acceleration from an inertial sensor with simulation data.

Note that in this example we have introduced two new channel logics: “zip_latest” and “on_change”:

  • A zip_latest channel outputs a hux::tuple that combines (in order) the most recent samples from each input only when ALL inputs have produced at least a new sample

  • An on_change channel forwards the input data as output only when it differs from its previous value

Notice also the use of the hux::get method to retrieve an element from a hux::tuple.

/* Import Huxon language headers */
#include <huxon/lang.hpp>
#include <huxon/sensors/STMicroelectronics.hpp>

/* Import other libraries */
#include <math.h>

/* Set a namespace alias to ease constants and methods retrieval from vendor's namespace. */
namespace STM = hux::sensors::STMicroelectronics;

/* Declare a dataset to simulate the algorithm with huxc.
 * The dataset is loaded from the CSV file "./dataset.csv" using semicolon separators.
 * The CSV file is expected to have 4 columns of types:
 *  float | float | float | hux::uint64_t
 * */
HUX_DECLARE_SIMULATION_DATA(simulation_dataset,
    hux::simulation::load_csv<float, float, float, hux::uint64_t>("./dataset.csv", ";")
);

/* Declare a new sensor called "my_sensor" */
HUX_DECLARE_SENSOR(my_sensor, STM::IIS2DLPC, simulation_dataset);

/* Declare a zip_latest channel with inputs:
 * X, Y and Z acceleration sources from "my_sensor".
 * A zip_latest channel outputs an hux::tuple that combines (in order) the most recent
 * samples from each input only when ALL inputs have produced at least a new sample.
 * Here, the output of the channel will be of type: hux::tuple<float, float, float>
 * */
HUX_DECLARE_CHANNEL(my_sensor_ch, zip_latest,
    my_sensor.get_accX(),
    my_sensor.get_accY(),
    my_sensor.get_accZ()
);

/* Declare a processing node to compute the squared norm of the acceleration.
 * The processing node will be called every time "my_sensor_data" produces an output.
 * The data from "my_sensor_data" is provided by the "hux_input" variable, that in this
 * case is of type: hux::tuple<float, float, float>.
 * To retrieve a specific field of the tuple we can use: hux::get<index>(hux_input)
 * */
HUX_DECLARE_PROCESSING(squared_norm, my_sensor_ch, {
    float acc_x = hux::get<0>(hux_input);
    float acc_y = hux::get<1>(hux_input);
    float acc_z = hux::get<2>(hux_input);
    return pow(acc_x, 2) + pow(acc_y, 2) + pow(acc_z, 2);
});

/* Declare a channel with "on_change" logic that forwards the norm_squared
 * data only when it changes from its previous value.
 * */
HUX_DECLARE_CHANNEL(squared_norm_changed_ch, on_change, squared_norm);

/* Declare a processing node to prepare the output data for the algorithm.
 * the output data of the algorithm must be declared using "HUX_DECLARE_OUTPUT_VALUE".
 * */
HUX_DECLARE_PROCESSING(app_output, squared_norm_changed_ch, {
    HUX_DECLARE_OUTPUT_VALUE(result, Float, "acceleration_norm", hux_input);
    return result;
});

/* Set the data produced by "app_output" as the result of the application */
HUX_REGISTER_OUTPUT(app_output);

Check simulation results

To check the result of the simulation, simply run the following command from the development container terminal:

huxc simulate -n YOUR_APP_NAME

The output of the simulation will look similar to the following:

[INFO] Capturing virtual sensor output:

{
    "acceleration_norm":1.07432234
}

{
    "acceleration_norm":1.42203546
}

[INFO] Simulation completed successfully!