A Recorder is a type of implementation that records a specific Dataflow upon query of the Heex Agent. A Recorder produces an Event Recording. Any Recorder you are using shall be a customized implementation based on the Recorder class.

Building your Recorder

To integrate recorders in your project you have to build them with Heex SDK and libraries. See HeexSDK integration for more details.

Recorder main APIs

Recorder(id, serverIp, serverPort)
  • Id : Recorder implementation ID, that can be retrieved on the Heex Cloud
  • serverIp : IP address to contact Heex Kernel
  • serverPort : Port to contact Heex Kernel
awaitReady() : To wait until the recorder is ready to receive data. Please ensure this function returns True before continuing. getSignalRecordingRange(triggerId="", signalName="") : Returns a pair of min and max recording ranges for a signal. Possible to filter by triggerId, and by signal name. getRecorderSignals(triggerId="", signalName="") : Returns all the RecordingSignal your recorder is recording. triggerId and signalName can be left empty or filled if you want to filter specific signals.

RecordingSignal structure

/// @brief Structure to hold a recording signal
///
/// @param id The ID of the signal.
/// @param name The name of the signal. For ROS topics, the format is '/topicName>subtopic1>subtopic...'. eg: '/imu>angular_velocity>x'
/// @param start The start time of the signal.
/// @param end The end time of the signal.
/// @param unit The unit of the signal. enum value (eg: 1 -> METER_PER_SECOND, 2 -> METER, 3 -> KILOMETER, etc.)
/// @param rosTopicType The ROS topic type of the signal. Only used for ROS topics, eg 'sensor_msgs/msg/NavSatFix'
/// @param datasourceId The ID of the datasource this signal is associated to.
/// @param datasourceName The name of the datasource this signal is associated to.
/// @param signalType The type of the signal. enum value (eg: 1 -> INTEGER, 2 -> NUMBER, 7 -> STRING, 6 -> BOOLEAN...)
struct RecordingSignal
{
  std::string id{};   ///< The ID of the signal.
  std::string name{}; ///< The name of the signal. For ROS topics, the format is '/topicName>subtopic1>subtopic...'. eg: '/imu>angular_velocity>x'
  int start{0};       ///< The start time of the signal.
  int end{0};         ///< The end time of the signal.
  int unit{static_cast<int>(apiinterface::Unit::UNIT_UNSPECIFIED)};           ///< The unit of the signal.
  std::string rosTopicType{};                                                 ///< The ROS topic type of the signal. Only used for ROS topics, eg 'sensor_msgs/msg/NavSatFix'
  std::string datasourceId{};                                                 ///< The ID of the datasource this signal is associated to.
  std::string datasourceName{};                                               ///< The name of the datasource this signal is associated to.
  int signalType{static_cast<int>(apiinterface::SignalType::ST_UNSPECIFIED)}; ///< The type of the signal. enum value (eg: 1 -> INTEGER, 2 -> NUMBER, 7 -> STRING, 6 -> BOOLEAN...)

  // Copy assignment operator
  RecordingSignal& operator=(const RecordingSignal& other) = default;

  bool operator==(const RecordingSignal& other) const
  {
    return (
        id == other.id && name == other.name && start == other.start && end == other.end && unit == other.unit && rosTopicType == other.rosTopicType &&
        datasourceId == other.datasourceId && datasourceName == other.datasourceName && signalType == other.signalType);
  }

  /// @brief Convert the unit to a stringified enum value for human readability (ie: 0 -> UNIT_UNSPECIFIED, 1 -> METER_PER_SECOND, etc.)
  /// @return the stringified enum value
  std::string unitToString() const
  {
    auto u = static_cast<apiinterface::Unit>(unit);
    return apiinterface::Unit_Name(u);
  }

  /// @brief Convert the signal type to a stringified enum value for human readability (ie: 0 -> ST_UNSPECIFIED, 1 -> INTEGER, etc.)
  /// @return the stringified enum value
  std::string signalTypeToString() const
  {
    auto t = static_cast<apiinterface::SignalType>(signalType);
    return apiinterface::SignalType_Name(t);
  }
};

Recorder implementation

You need to create a class that inherits from the Recorder class. Don’t forget to include the required files as seen previously with the monitor implementation.
class myRecorder : public Recorder
The first thing needed is to call the Recorder class’ constructor :
Recorder(const std::string& id, const std::string& serverIp, const unsigned int& serverPort)
After that, it is necessary to override two methods of the Recorder class.
virtual bool generateRequestedLabels(const Heex::RecorderArgs::EventRecordingLabelsQuery& query, HeexMessages::RecorderArgs::RecorderLabels& labels)

virtual bool generateRequestedFilePaths(const Heex::RecorderArgs::RecorderEventRecordingPartArgs& query, std::string& filepath)

Overriding generateRequestedLabels method to provide labels

Labels are a set of customer defined keys and values picked by the recorders from the data flows to contextualize events. It takes the form of a short string that will be sent to be visible on the event page of the Heex Cloud. Label sample On this example, you can see we contextualized our event with the GNSS coordinates of where the event occured. But we could have added external temperature, speed, tire pressure, etc. Note that there is a special label key that trigger behaviors when received by our Heex Cloud:
  • position key with a value set to the latitude and longitude in decimal degrees, separated by a comma and space character, for example "48.8580644, 2.3850982".
    ℹ️ Info: This will add a map to the event page on the Heex Cloud pointing the event location.e.

Add Labels

In the generateRequestedLabels() method of your recorder’s implementation, you can use the structure labels to add labels as keys and values.
labels.addLabel(key, value);
↳ Is used to assign the value to the key. Key and value are added to the RecorderLabels structure.
labels.addLabel(HeexGps(latitude, longitude))
↳ Is an overload of the addLabel method that takes a HeexGps type as an input. It is used to quickly assign separate latitude and longitude to the key "position" key.

You have access to all info about the query such as Recorder id (query.id), Event id (query.eventId) and requested timestamp (query.timestamp)…
See the EventRecordingLabelsQuery structure in the Query content for Labels for more details.
Finally, you shall return true after adding all the labels.
⚠️ Warning: error Returning false signals an error and stops the entire event process.

Overriding generateRequestedFilePaths method to provide Event Recordings

An event recording is a file or directory produced by the recorder on demand of the Heex Agent, to add substance to the event. The query is timed around the timestamp provided by the monitor that created the event. It can be videos, log files, rosbags and more, and of any type or format. Every Event Recording Part will be available for download in the event page on the Heex Cloud.

Add Event Recording

In the generateRequestedFilePaths() method of your recorder, you need to access query informations:
  • Event id (query.eventId)
  • Requested timestamp (query.timestamp)
  • Event id and timestamp (query.timestampedEventUuid)
  • Interval of time before the timestamp (query.recordIntervalStart)
  • Interval of time after the timestamp (query.recordIntervalEnd)
See the RecorderEventRecordingPartArgs structure in the Query content for Event Recordings Parts for more details. With this information you can generate a file (or collection of files) that represents the data you want to retrieve for your events.
After that, you just need to fill the filepath variable with your filepath value, pointing to your generated event recording part (filepath variable being passed by reference to the function).
You have different options in how you provide the desired filepath, each of which has a specific synthax depending on the use case:
  1. Send only the file. Return the direct path to file.
  2. Send the folder and all its content. Return the direct path to folder.
  3. Send only the content of the folder. Return the path to the folder with /* as an extra suffix (wildcard at the end to include only files within the parent directory).
⚠️ Warning: The event recording parts should have different names. Otherwise, some files may be overridden by your recorder before being fully uploaded. You may use query.timestampedEventUuid for unique namings.
Your data generation may differ from what is resquested by the query. The SDK provides methods to run inside your generateRequestedFilePaths() implementation to integrate these changes per field.
ℹ️ Note: Returning false signals an error and stops the entire event process.