Get data from a sensor

In this tutorial, our goal is to write a simple Huxon application that reads data from a sensor and sends it as output.

Huxon applications are defined inside C++ header files. The usual structure of a Huxon algorithm consists of a main header file that defines a computation pipeline in terms of sensors, sources, channels and processing nodes. The main header file can include additional header files that can, as an example, define functions and utilities to be used within your processing nodes.

For the sake of simplicity, our first Huxon application will use a single header file. The first step in almost all Huxon applications is to include the Huxon language which is provided as a header-only library:

#include <huxon/lang.hpp>

Declare a sensor

Sensors are the basic elements that provide sources to develop an IoT application. They can be declared with the HUX_DECLARE_SENSOR construct as:

/*
 * my_sensor                                     : the sensor label
 * hux::sensors::STMicroelectronics::ISM330DHCX  : the sensor model namespace
 */
HUX_DECLARE_SENSOR(my_sensor, hux::sensors::STMicroelectronics::ISM330DHCX)

Each sensor namespace is available under its manufacturer namespace which is defined in a dedicated header file. In our example, we need to include the following header file:

#include <huxon/sensors/STMicroelectronics.hpp>

Note

For a more concise code, it is helpful to create namespace aliases such as:

namespace STM = hux::sensors::STMicroelectronics;

Once a sensor is declared, it is possible to reference to it using its label.

A sensor offers one or more sources that can be used to develop an application. Their usage can be mixed in order to retrieve a specific result. To retrieve one of the sensor sources, you can call the corresponding “get” method on the sensor object:

/*
 * my_sensor : the sensor label
 * get_accX() : method to retrieve the accX source (acceleration over the X axis)
 */
my_sensor.get_accX()

Note

For full documentation of all the available sensors and their sources refer to the Sensors documentation.

A source cannot be used directly as the output of a Huxon algorithm. Indeed, the output of a Huxon algorithm must be defined inside a processing node that receives data from a channel and produces a specific type of output.

So, to achieve our goal we also need to:
  1. create a channel that receives as input the sensor source

  2. create a processing node that receives as input the channel and generates the algorithm’s output

Declare a channel

A channel is an element that combines data generated by one or more elements which can be either:

  • Sources

  • Other channels

  • Processing nodes

For our purpose we need a simple channel that combines data from our single sensor source (basically it just wraps it). To create it, we use the HUX_DECLARE_CHANNEL macro:

/*
 * my_sensor_data : the channel label
 * merge          : the channel logic
 * ...            : the channel input elements
 */
HUX_DECLARE_CHANNEL(my_sensor_data, merge,
    my_sensor.get_accX()
);

The channel logic is a key concept, it specifies how to combine the data from the input elements and under which conditions to fire an output. With the merge logic a channel forwards as output data received from any of the input elements as soon as available. The data type produced by a merge channel is the same as the type of the input elements (for a merge channel the input elements must all generate data of the same type).

Note

For a review of all the available channel logics refer to HUX_DECLARE_CHANNEL.

Once a channel is declared, it is possible to reference it using its label.

At this point, we are ready to create a processing node that receives data from my_sensor_data channel and prepares the output of the algorithm.

Declare a processing node

A processing node is an element that performs a user-defined C++ computation on data from an input channel. The computation is triggered as soon as a new value from the input channel is available, such value can be accessed through the special hux_input variable whose type is automatically inferred from the data type produced by the input channel. The computation must always return an output value whose type must obey the following restrictions:

  • it must not be a pointer or contain pointers

  • it must be trivially copyable (such as a scalar, a struct, a hux::tuple, or array of such types)

To declare our processing node we use the HUX_DECLARE_PROCESSING macro:

/*
 * app_output             : the processing node label
 * my_sensor_data         : the input channel
 * {...}                  : the computation algorithm
 */
HUX_DECLARE_PROCESSING(app_output, my_sensor_data, {
    HUX_DECLARE_OUTPUT_VALUE(result, Float, "accelerometerX", hux_input);
    return result;
})

Once a processing_node has been declared, it is possible to reference it using its label.

As we can see, inside the app_output processing node, we return a special output value created with the HUX_DECLARE_OUTPUT_VALUE macro:

/*
 * result                  : the output value label
 * Float                   : the output value schema
 * "accelerometerX"        : the name of the output value
 * hux_input               : the actual value contained inside the output value
 */
HUX_DECLARE_OUTPUT_VALUE(result, Float, "accelerometerX", hux_input)

Specify the application’s output

We are almost done! The only missing part is to register the Huxon application output. This step informs the runtime environment about which output data to collect from the application. It is used both by the simulator, during development to verify the output correctness, and by the Huxon platform, in production to collect data from the application running on an IoT infrastructure.

To register the output we can use the HUX_DECLARE_OUTPUT_VALUE macro which takes as input a processing node (or channel) that generates a special output value type:

/*
 * app_output : the processing_node proving the output_value
 */
HUX_REGISTER_OUTPUT(app_output);

Putting it all together

The complete Huxon application will look as follows:

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

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

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

/* Declare a merge channel taking the X axes acceleration from my_sensor as input.
 * The merge channel logic outputs samples from the inputs as soon as they are available.
 * NOTE: the accX source generates data of type float.
 * */
HUX_DECLARE_CHANNEL(my_sensor_data, merge,
    my_sensor.get_accX()
);

/* Declare a processing node taking as input the my_sensor_data channel.
 * The data from the input channel can be accessed using the "hux_input" variable,
 * whose type is inferred from the input channel.
 * In this case "hux_input" is a float since my_sensor.get_accX()
 * generates float data and the my_sensor_data merge channel simply forwards it.
 * */
HUX_DECLARE_PROCESSING(app_output, my_sensor_data, {
    /* Create an output value with Float schema named "accelerometerX"
     * the actual content of this output value is taken from hux_input
     * */
    HUX_DECLARE_OUTPUT_VALUE(result, Float, "accelerometerX", hux_input);

    /* Return the "result" output value as output of this processing node */
    return result;
});

/* Configure the output of the Huxon application. The output must be a processing node
 * (or channel) that produces an output value created with HUX_DECLARE_OUTPUT_VALUE
 * */
HUX_REGISTER_OUTPUT(app_output);

Check for compilation errors

Even if we did not specify any data for simulating our application, we can run an empty simulation for the purpose to check compilation errors:

huxc simulate -n YOUR_APP_NAME

This step is highly recommended to check potential compilation errors before loading the application to Huxon.