Event Service

The RTC Toolkit provides an Event Service library that enables RTC Components to publish and subscribe customisable events over the non-deterministic control network (CND). The Event Service is based on CII MAL using FastDDS. It makes use of the pre-defined MAL topic type rtctkif::GenericEvent that carries a BLOB with a customisable JSON payload.

Design Assumptions

The event service is used to exchange notification events within the SRTC via a publish and subscribe messaging pattern. Events should be relatively small (few KB) and they should be emitted at relatively low rates (up to 1 Hz).

The main sources of events are Data Tasks and HRTC Supervisors. Such components emit events e.g. after performing computations or when applying new configurations to the HRTC.

The main consumers of events are GUIs, Telemetry Recorders, Metadata Collectors, as well as Data Tasks and HRTC Supervisors. While GUIs visualise emitted events (e.g. in an event screen), Recorders and Collectors dump interesting events to FITS files. Data Tasks and HRTC Supervisors ‘react’ on events (e.g. start computations or apply configurations to the HRTC).

To provide enough flexibility, the Event Service comes with an API that allows publishing and subscribing to JSON objects on specified topic names. Additionally, a hierarchy of common event types is provided. Those types can be further customised by instrument RTC developers to fit project specific needs.

Event Service API

To use the event service, include the following headers and namespaces:

#include <rtctk/componentFramework/eventServiceIf.hpp>
#include <rtctk/componentFramework/eventDefinitions.hpp>

using namespace rtctk::componentFramework;

The event service object can be retrieved from the service container using:

EventServiceIf& es = services.Get<EventServiceIf>();

To publish events, use:

// create a publisher object publishing to a specific topic
auto pub = es.MakePublisher("computation_topic");

// define a payload
JsonPayload payload = {
    {"origin", "data_task_1"},
    {"time", 12345628652},
    {"type", "computation_event"},
    {"item", "control_matrix_foo"},
    {"result", 42},
};

// publish the payload on the event channel
pub->Publish(payload);

To subscribe to events, use:

// create a subscriber object for a specific topic
auto sub = es.MakeSubscriber("computation_topic");

sub->Subscribe(
    [](const JsonPayload& payload) {
        // dump the entire event as a string
        std::cout << payload.dump() << std::endl;

        // or access only specific payload members
        std::cout << payload.at("item").get<std::string>() << std::endl;
    });

// ...

sub->Unsubscribe();

Predefined Event Types

The event service comes with a hierarchy of predefined event types:

Event Type

Topic Name

Description

AbstractEvent

none

abstract, common base for all event types

ComputationEvent

computation_topic

abstract, defines common event attributes

ComputationStartedEvent

computation_topic

signals that computation has started

ComputationFinishedEvent

computation_topic

signals that computation has finished

ConfigurationEvent

configuration_topic

abstract, defines common event attributes

ConfigurationUpdatedEvent

configuration_topic

signals that a configuration item was updated

ConfigurationRetrievedEvent

configuration_topic

signals that a configuration item was retrieved

HrtcConfigurationScheduledEvent

configuration_topic

signals that a configuration item was scheduled to be applied in HRTC

HrtcConfigurationAppliedEvent

configuration_topic

signals that a configuration item was applied in HRTC

CoordinationEvent

coordination_topic

abstract, defines common event attributes

StateChangedEvent

coordination_topic

signals that an entity changed its state

HrtcStateChangedEvent

coordination_topic

signals that an entity in HRTC changed its state

AlertStatusEvent

alert_status_topic

event published when alert status is changed

It is recommended that instrument RTC teams use these event types wherever applicable, however, for specific needs, custom event types can be created.

Publishing events from predefined types works as follows:

// create a publisher object publishing to computation_topic
auto pub = es.MakePublisher(ComputationEvent::TOPIC_NAME);

// publish the payload on the event channel
pub->Publish(ComputationStartedEvent{"data_task_1", "control_matrix"});
// ...
pub->Publish(ComputationFinishedEvent{"data_task_1", "control_matrix", "42"});

Subscribing to events from predefined types works as follows:

// create a subscriber object for computation_topic
auto pub = es.MakeSubscriber(ComputationEvent::TOPIC_NAME);

sub->Subscribe(
    [](const JsonPayload& payload) {
        // either use the event as a JSON object
        std::cout << payload.dump() << std::endl;

        // or convert it back to a specific event type
        if(auto ev = ConvertTo<ComputationStartedEvent>(payload); ev.has_value()) {
            std::cout << ev.value().item << std::endl;
            return;
        }

        if(auto ev = ConvertTo<ComputationFinishedEvent>(payload); ev.has_value()) {
            std::cout << ev.value().item << std::endl;
            return;
        }
    });

Creating Custom Event Types

Custom event types can be created by specialising toolkit-provided event types.

struct CustomComputationEvent : ComputationEvent
{
    CustomComputationEvent = default(); // must be default constructible

    CustomComputationEvent(std::string origin, std::string item, int attribute)
    : ComputationEvent(origin, item)
    , custom_attribute(attribute)
    { }

    int custom_attribute;
};

void to_json(JsonPayload& j, const CustomComputationEvent& e) {
    to_json(j, static_cast<const ComputationEvent&>(e));
    j["type"].push_back(PrettyTypeName<std::remove_cvref_t<decltype(e)>>());
    j["custom_attribute"] = e.custom_attribute;
}

void from_json(const JsonPayload& j, CustomComputationEvent& e) {
    from_json(j, static_cast<ComputationEvent&>(e));
    j.at("custom_attribute").get_to(e.custom_attribute);
}

Above, the event type CustomComputationEvent is defined by extending toolkit-provided event type ComputationEvent with the additional attribute custom_attribte. For serialisation and deserialisation, methods to_json and from_json must be implemented in the same namespace. Additionally, constructors to create new events from required input data may be defined as well.

Limitations and Known Issues

The EventPublisher and EventSubscriber objects should be created well in advance before actually sending and receiving events such that DDS has enough time to perform the connection, otherwise the first samples may be lost.

Keeping DDS history already starts with creation of the EventPublisher and EventSubscriber objects. This means, if a publisher already sends data to a subscriber that has not yet subscribed, the DDS queues may fill up to a point where DDS write errors occur. This may cause problems especially with the DDS history policy KEEP_ALL. Therefore, carefully consider the life-cycle of EventPublisher and EventSubscriber objects for each application.

Avoid the following operations, they may cause deadlocks due to limitations in the underlying FastDDS implementation: - publishing events from within a subscription callback - subscribing or unsubscribing events from within a subscription callback