Handle custom data via Remote Sensors

In Huxon Language, sources are usually taking data from physical sensors. However, it is possible to collect data from elements that are different than a physical sensor, such as a Docker container or other sources.

To receive data from any generic source, Huxon Language defines Remote Sensors, constructs that can be defined as any other sensor by specifying the generic::text type:

#include <huxon/lang.hpp>
#include <huxon/sensors/generic/text.hpp>

HUX_DECLARE_SENSOR(temp_sensor, hux::sensors::generic::text);

The remote sensor accepts inputs as text that can be read with:

/*
 * sensor_label : the label of a previously declared sensor
 */
remote_sensor_label.get_text();

The text source of a remote sensor can contain arbitrary data, and it is possible to encode at maximum 4096 characters. It is up to the user to properly parse the text string, knowing the output of the remote sensor (it can be for example a json string with sensor data).

Temperature Example

Consider a situation where a sensor is generating plain text temperature data as a float. We want to collect and parse the data everytime it changes, and convert it into a variable that will then be provided as output.

#include <huxon/lang.hpp>
#include <huxon/sensors/generic/text.hpp>

HUX_DECLARE_SENSOR(temp_sensor, hux::sensors::generic::text);

HUX_DECLARE_CHANNEL(temp_ch, on_change,
    temp_sensor.get_text()
);

HUX_DECLARE_PROCESSING(app_output, temp_ch, {
    float temperature = std::stof(hux_input.c_str());

    HUX_DECLARE_OUTPUT_VALUE(temperature_out, Float, "temperature", temperature);

    return temperature_out;
});

HUX_REGISTER_OUTPUT(app_output);

Parse JSON data

To simplify the data parsing step, you can output the data in the JSON format, and make use of the mjson library. The Huxelerate mjson library fork has been modified to allow it to be an header only library, so to be compatible with the huxon language and be easily included in your project.

To use the mjson library in your code you can download the mjson source header and include it in your project.

The following example shows a simple case where a generic text sensor is used to output JSON data representing a simple thermostat that checks the tempearature of multiple rooms.

#include <huxon/entities.hpp>
#include <huxon/lang.hpp>
#include <huxon/sensors/generic/text.hpp>
#include <string>

#include "mjson.h"

/*
 * Define a maximum length of the JSON string that the remote generic text sensor will output
 */
#define TEXT_LENGTH 128

/*
 * Define the simulation data, to verify that the algorithm is properly parsing the JSON string
 */
HUX_DECLARE_SIMULATION_DATA(remote_sensor_sim_dataset, hux::make_tuple(
    std::vector<hux::string<TEXT_LENGTH>> {
        "{\"desired_temperature\":18.0,\"rooms\": [{\"temperature\": 16.8}, {\"temperature\": 16.7}]}",
        "{\"desired_temperature\":18.0,\"rooms\": [{\"temperature\": 18.1}, {\"temperature\": 18.0}]}",
        "{\"desired_temperature\":18.5,\"rooms\": [{\"temperature\": 18.2}, {\"temperature\": 18.2}]}",
        "{\"desired_temperature\":18.5,\"rooms\": [{\"temperature\": 18.5}, {\"temperature\": 18.5}]}"
    }
));

/*
 * Declare the configuration for our generic text sensor, specifying the text max length
 */
HUX_DECLARE_SENSOR_CONFIGURATION(config, hux::sensors::generic::text,
    .max_length = TEXT_LENGTH
);

/*
 * Declare the actual generic text sensor, passing both the simulation data and the configuration
 */
HUX_DECLARE_SENSOR(remote_sensor, hux::sensors::generic::text, remote_sensor_sim_dataset, config);

HUX_DECLARE_CHANNEL(remote_chan, on_change,
    remote_sensor.get_text()
);

/*
 * Define a struct to conveniently store the parsed data
 */
struct t_remote_sensor_data {
    float desired_temperature;
    float actual_temperature;
};

/*
 * Parse the JSON string and populate the data structure with the temperature mean of all the rooms
 * and the desired temperature set on the thermostat
 */
HUX_DECLARE_PROCESSING(remote_proc, remote_chan, {
    const auto json_string = hux_input.c_str();
    double desired_temperature;

    if (!mjson_get_number(json_string, strlen(json_string), "$.desired_temperature", &desired_temperature)) {
        printf("Unable to parse the desired temperature field!");
    }

    double sum = 0;

    const char rooms[][TEXT_LENGTH] = {
        "$.rooms[0].temperature",
        "$.rooms[1].temperature"
    };

    double temperature;
    size_t rooms_size = (size_t) sizeof(rooms) / sizeof(rooms[0]);
    for (int i = 0; i < rooms_size; i++) {
        if (mjson_get_number(json_string, strlen(json_string), rooms[i], &temperature)) {
            sum += temperature;
        }
    }

    t_remote_sensor_data data;

    data.desired_temperature = (float) desired_temperature;
    data.actual_temperature = (float) sum / rooms_size;
    return data;
});

HUX_DECLARE_CHANNEL(proc_chan, on_change, remote_proc);

/*
 * Declare the output values of the processed data
 */
HUX_DECLARE_PROCESSING(output, proc_chan, {
    HUX_DECLARE_OUTPUT_VALUE(actual_temperature, Float, "actual_temperature", hux_input.actual_temperature);
    HUX_DECLARE_OUTPUT_VALUE(temperature_reached, Boolean, "temperature_reached", hux_input.actual_temperature >= hux_input.desired_temperature);

    HUX_DECLARE_OUTPUT_VALUE(output_data, Object, "thermostat", actual_temperature, temperature_reached);

    return output_data;
});

HUX_REGISTER_OUTPUT(output);