Building Your Own External Application

This guide provides comprehensive instructions for building a GVA-compliant external application that integrates with the HMI, raises alarms, and interfaces with UACM.

Try It Yourself

All the example games (Drone Invaders, Shield Protocol, Defense Grid Run) are implemented as external applications. You can play with them by assigning them to the BMS, RWS, or STR function buttons in the HMI. This lets you experience how external applications integrate with the GVA system in real-time.

Overview

External applications in the GVA architecture communicate via DDS (Data Distribution Service) to:

  • Register with the platform registry to obtain a unique resource ID
  • Display content in the HMI control area via Display Extension
  • Receive user input via soft keys (bezel buttons)
  • Raise alarms to notify operators of events
  • Report status to the UACM service for health monitoring
graph TB subgraph "Your External Application" APP[Application Logic] REG[Registration Manager] DIS[Display Handler] ALM[Alarm Publisher] UCM[UACM Reporter] end subgraph "GVA Services" REGISTRY[Registry Service] HMI[HMI Application] ALARMS[Alarms Service] UACM[UACM Service] end REG -->|Register| REGISTRY DIS -->|Display Data| HMI HMI -->|Key Events| DIS ALM -->|Alarms| ALARMS UCM -->|Parameters| UACM

Prerequisites

  • Qt6 development environment
  • CycloneDDS middleware
  • LDM10 SDK library
  • C++17 or later compiler

Core Components

1. Application Entry Point

Every external application needs a main entry point that initializes Qt, DDS, and the registration process:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "RegistrationManager.h"
#include "GameController.h"  // Your app logic

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    // Get domain ID from environment or use default
    int domainId = qEnvironmentVariableIntValue("LDM_DOMAIN_ID");
    if (domainId == 0) domainId = 0;  // Default domain

    // Create registration manager
    RegistrationManager regManager(domainId);

    // Create your application controller
    MyAppController controller;

    // Connect registration signals
    QObject::connect(&regManager, &RegistrationManager::registered,
        [&](int32_t resourceId) {
            qDebug() << "Registered with resource ID:" << resourceId;
            controller.start();
        });

    QObject::connect(&regManager, &RegistrationManager::buttonPressed,
        &controller, &MyAppController::handleButton);

    // Start registration
    regManager.start();

    return app.exec();
}

Registration Integration

Registration is the first step for any external application. It allows the platform to identify and manage your application.

Registration Flow

sequenceDiagram participant App as Your App participant DDS as DDS Network participant Reg as Registry Service participant HMI as HMI App->>DDS: requestResourceId (with UUID) DDS->>Reg: Process request Reg->>DDS: supplyResourceId (assigned ID) DDS->>App: Receive resource ID App->>DDS: Publish Third_Party_Session DDS->>HMI: Register display session App->>DDS: Publish Hard_Button_Labels DDS->>HMI: Configure soft keys App->>DDS: setOperatingMode (ON) Note over App: Application now active

DDS Publishers and Subscribers

Create the necessary DDS entities for registration:

#include "QtWrapperCThirdPartySessionPubSub.h"
#include "QtWrapperCHardButtonLabelPubSub.h"
#include "QtWrapperCHardButtonEventPubSub.h"
#include "QtWrapperCConfiguredPlatformWithRegistryrequestResourceIdPubSub.h"
#include "QtWrapperCRegisteredPlatformResourcesupplyResourceIdPubSub.h"
#include "QtWrapperCConfiguredPlatformsetOperatingModePubSub.h"

class RegistrationManager : public QObject {
    Q_OBJECT
public:
    explicit RegistrationManager(int domainId, QObject* parent = nullptr);

    bool start();
    void stop();

signals:
    void registered(int32_t resourceId);
    void buttonPressed(const QString& keyName);
    void connectedToHmi();

private:
    void initializeDds();
    void sendRegistrationRequest();
    void publishSession();
    void publishButtonLabels();
    void setOperationalMode();

    int m_domainId;
    int32_t m_resourceId = 9999;  // Fallback ID
    QUuid m_uuid;
    QString m_sessionId;

    // DDS publishers
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCThirdPartySession> m_sessionPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCHardButtonLabel> m_buttonLabelPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsSubscriberCHardButtonEvent> m_buttonEventSubscriber;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCConfiguredPlatformWithRegistryrequestResourceId> m_requestPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsSubscriberCRegisteredPlatformResourcesupplyResourceId> m_responseSubscriber;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCConfiguredPlatformsetOperatingMode> m_operatingModePublisher;
};

Sending Registration Request

Request a unique resource ID from the registry:

void RegistrationManager::sendRegistrationRequest()
{
    // Generate a unique UUID for your application
    m_uuid = QUuid::createUuidV5(QUuid(), QString("MyApp-001"));

    P_Resource_ID_Allocation_PSM::C_Configured_Platform_With_Registry_requestResourceId request;

    // Set UUID (16 bytes)
    QByteArray uuidBytes = m_uuid.toRfc4122();
    std::vector<uint8_t> gvaUuid(16);
    std::memcpy(gvaUuid.data(), uuidBytes.constData(), 16);
    request.A_uuid(gvaUuid);

    // Set source ID (temporary before registration)
    P_GVA_Common_PSM::T_IdentifierType sourceId;
    sourceId.A_resourceId(9999);  // Temporary ID
    sourceId.A_instanceId(0);
    request.A_sourceID(sourceId);

    // Set recipient ID (registry service)
    P_GVA_Common_PSM::T_IdentifierType recipientId;
    recipientId.A_resourceId(1);  // Registry is always resource 1
    recipientId.A_instanceId(0);
    request.A_recipientID(recipientId);

    request.A_responseRequired(true);

    // Set resource descriptor
    P_Platform_Configuration_Common_PSM::T_ResourceInstanceDescriptorType resourceInstance;
    resourceInstance.A_serialNumber("MYAPP-2025-001");

    P_Platform_Configuration_Common_PSM::T_ResourceTypeDescriptorType resourceType;
    resourceType.A_manufacturerName("Your Company");
    resourceType.A_modelName("My-Application");
    resourceType.A_versionName("1.0.0");

    resourceInstance.A_resourceType(resourceType);
    request.A_resourceInstance(resourceInstance);

    m_requestPublisher->Publish(request);
}

Publishing Third Party Session

After registration, publish your session to the HMI:

void RegistrationManager::publishSession()
{
    P_Display_Extension_PSM::C_Third_Party_Session session;

    // Set source ID with allocated resource ID
    P_GVA_Common_PSM::T_IdentifierType sourceId;
    sourceId.A_resourceId(m_resourceId);
    sourceId.A_instanceId(0);
    session.A_sourceID(sourceId);

    // Set session UUID
    QByteArray uuidBytes = m_uuid.toRfc4122();
    std::vector<uint8_t> sessionUuid(16);
    std::memcpy(sessionUuid.data(), uuidBytes.constData(), 16);
    session.A_uuid(sessionUuid);

    // Set session name (displayed in HMI menus)
    session.A_name("My Application");

    m_sessionPublisher->Publish(session);
}

Configuring Soft Keys (Bezel Buttons)

Configure the HMI soft keys for your application:

void RegistrationManager::publishButtonLabel(int buttonIndex, 
                                              const QString& label,
                                              bool visible,
                                              bool enabled)
{
    P_Display_Extension_PSM::C_Hard_Button_Label buttonLabel;

    // Source ID identifies this button
    P_GVA_Common_PSM::T_IdentifierType sourceId;
    sourceId.A_resourceId(buttonIndex);  // Button number (F1=1, F2=2, etc.)
    sourceId.A_instanceId(0);
    buttonLabel.A_sourceID(sourceId);

    // Associate with your session
    buttonLabel.A_thirdPartySession_sourceID(m_sessionSourceId);

    // Set display properties
    buttonLabel.A_labelText(label.toStdString());
    buttonLabel.A_visible(visible);
    buttonLabel.A_enabled(enabled);
    buttonLabel.A_selected(false);

    m_buttonLabelPublisher->Publish(buttonLabel);
}

// Example: Setup game control buttons
void RegistrationManager::setupButtons()
{
    publishButtonLabel(1, "PAUSE", true, true);     // F1
    publishButtonLabel(13, "FIRE", true, true);     // F13 (Up)
    publishButtonLabel(18, "START", true, true);    // F18 (Enter)
    publishButtonLabel(19, "LEFT", true, true);     // F19 (Up arrow)
    publishButtonLabel(20, "RIGHT", true, true);    // F20 (Down arrow)
}

Handling Button Events

Subscribe to and handle button press events:

void RegistrationManager::initializeButtonSubscriber()
{
    m_buttonEventSubscriber = std::make_unique<ldm::pubsub::QtDdsSubscriberCHardButtonEvent>(
        m_domainId,
        P_Display_Extension_PSM::KC_Hard_Button_Event_TopicName,
        ldm::pubsub::LdmMessagePattern::eState);

    connect(m_buttonEventSubscriber.get(),
            &ldm::pubsub::QtDdsSubscriberCHardButtonEvent::OnDataReceived,
            this, [this](const P_Display_Extension_PSM::C_Hard_Button_Event& event) {

        bool isPressed = (event.A_state() == 
            P_LDM_Common_PSM::T_ButtonStateType::L_ButtonState__Pressed);

        if (!isPressed) return;  // Only handle press events

        int keyNum = event.A_hardButtonLabel_sourceID().A_resourceId();
        QString keyName = QString("F%1").arg(keyNum);

        // Map to application actions
        if (keyNum == 19) emit buttonPressed("left");
        else if (keyNum == 20) emit buttonPressed("right");
        else if (keyNum == 13) emit buttonPressed("fire");
        else if (keyNum == 1) emit buttonPressed("pause");
        else if (keyNum == 18) emit buttonPressed("enter");
    });

    m_buttonEventSubscriber->Open();
}

Alarms Integration

External applications can raise alarms to notify operators of important events. The GVA Alarms Service Specification defines three categories:

Category Priority Use Case
Warning Highest Safety conditions, critical failures
Caution Medium Degraded functionality, needs attention
Advisory Lowest Informational events

Alarm Publishers Setup

#include "QtWrapperCAlarmPubSub.h"
#include "QtWrapperCAlarmConditionPubSub.h"
#include "QtWrapperCAlarmConditionSpecificationPubSub.h"
#include "QtWrapperCAlarmCategoryDefinitionPubSub.h"

class AlarmManager {
public:
    void initializeAlarmPublishers(int domainId)
    {
        m_alarmPublisher = std::make_unique<ldm::pubsub::QtDdsPublisherCAlarm>(
            domainId, "Alarms__Alarm", ldm::pubsub::LdmMessagePattern::eState);
        m_alarmPublisher->Open();

        m_alarmConditionPublisher = std::make_unique<ldm::pubsub::QtDdsPublisherCAlarmCondition>(
            domainId, "Alarms__Alarm_Condition", ldm::pubsub::LdmMessagePattern::eState);
        m_alarmConditionPublisher->Open();

        m_alarmSpecPublisher = std::make_unique<ldm::pubsub::QtDdsPublisherCAlarmConditionSpecification>(
            domainId, "Alarms__Alarm_Condition_Specification", ldm::pubsub::LdmMessagePattern::eState);
        m_alarmSpecPublisher->Open();

        m_alarmCategoryPublisher = std::make_unique<ldm::pubsub::QtDdsPublisherCAlarmCategoryDefinition>(
            domainId, "Alarms__Alarm_Category_Definition", ldm::pubsub::LdmMessagePattern::eState);
        m_alarmCategoryPublisher->Open();
    }

private:
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCAlarm> m_alarmPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCAlarmCondition> m_alarmConditionPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCAlarmConditionSpecification> m_alarmSpecPublisher;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCAlarmCategoryDefinition> m_alarmCategoryPublisher;
};

Publishing Alarm Category Definitions

Define alarm categories your application will use:

void AlarmManager::publishAlarmCategories()
{
    // Warning category (highest priority)
    P_Alarms_PSM::C_Alarm_Category_Definition warning;
    warning.A_sourceID().A_instanceId(0);
    warning.A_sourceID().A_resourceId(1001);
    warning.A_alarmCategory(P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Warning);
    warning.A_annunciateSupported(true);
    warning.A_overrideSupported(true);
    warning.A_reannunciateTimeout().A_seconds(30);
    warning.A_reannunciateTimeout().A_nanoseconds(0);
    m_alarmCategoryPublisher->Publish(warning);

    // Caution category
    P_Alarms_PSM::C_Alarm_Category_Definition caution;
    caution.A_sourceID().A_instanceId(0);
    caution.A_sourceID().A_resourceId(1002);
    caution.A_alarmCategory(P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Caution);
    caution.A_annunciateSupported(true);
    caution.A_overrideSupported(true);
    caution.A_reannunciateTimeout().A_seconds(60);
    caution.A_reannunciateTimeout().A_nanoseconds(0);
    m_alarmCategoryPublisher->Publish(caution);

    // Advisory category (informational)
    P_Alarms_PSM::C_Alarm_Category_Definition advisory;
    advisory.A_sourceID().A_instanceId(0);
    advisory.A_sourceID().A_resourceId(1003);
    advisory.A_alarmCategory(P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Advisory);
    advisory.A_annunciateSupported(false);
    advisory.A_overrideSupported(false);
    m_alarmCategoryPublisher->Publish(advisory);
}

Raising an Alarm

To raise an alarm, publish both the alarm condition and the alarm itself:

void AlarmManager::raiseAlarm(uint32_t conditionId, 
                               uint32_t alarmId, 
                               const QString& text,
                               P_Alarms_PSM::T_AlarmCategoryType category)
{
    // Step 1: Publish alarm condition as active
    P_Alarms_PSM::C_Alarm_Condition condition;
    condition.A_sourceID().A_instanceId(0);
    condition.A_sourceID().A_resourceId(conditionId);
    condition.A_currentState(
        P_Alarms_PSM::T_Alarm_Condition_StateType::L_AlarmConditionState__Active);
    m_alarmConditionPublisher->Publish(condition);

    // Step 2: Publish the alarm
    P_Alarms_PSM::C_Alarm alarm;
    alarm.A_sourceID().A_instanceId(0);
    alarm.A_sourceID().A_resourceId(alarmId);
    alarm.A_currentState(
        P_Alarms_PSM::T_Alarm_StateType::L_AlarmState__Active_Unacknowledged);
    alarm.A_annunciationCount(1);
    alarm.A_causingCondition_sourceID().A_instanceId(0);
    alarm.A_causingCondition_sourceID().A_resourceId(conditionId);
    m_alarmPublisher->Publish(alarm);

    qDebug() << "Raised alarm:" << text << "ID:" << alarmId;
}

Clearing an Alarm

void AlarmManager::clearAlarm(uint32_t conditionId, uint32_t alarmId)
{
    // Clear the alarm condition
    P_Alarms_PSM::C_Alarm_Condition condition;
    condition.A_sourceID().A_instanceId(0);
    condition.A_sourceID().A_resourceId(conditionId);
    condition.A_currentState(
        P_Alarms_PSM::T_Alarm_Condition_StateType::L_AlarmConditionState__Inactive);
    m_alarmConditionPublisher->Publish(condition);

    // Clear the alarm
    P_Alarms_PSM::C_Alarm alarm;
    alarm.A_sourceID().A_instanceId(0);
    alarm.A_sourceID().A_resourceId(alarmId);
    alarm.A_currentState(
        P_Alarms_PSM::T_Alarm_StateType::L_AlarmState__Cleared);
    alarm.A_annunciationCount(0);
    m_alarmPublisher->Publish(alarm);
}

Example: Application-Specific Alarms

// Define alarm IDs unique to your application
static constexpr uint32_t ALARM_CONDITION_CRITICAL_ERROR = 5001;
static constexpr uint32_t ALARM_CONDITION_LOW_BATTERY = 5002;
static constexpr uint32_t ALARM_CONDITION_STATUS_UPDATE = 5003;

void MyApp::onCriticalError(const QString& errorMsg)
{
    m_alarmManager->raiseAlarm(
        ALARM_CONDITION_CRITICAL_ERROR,
        m_nextAlarmId++,
        QString("CRITICAL: %1").arg(errorMsg),
        P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Warning);
}

void MyApp::onLowBattery(int percentage)
{
    m_alarmManager->raiseAlarm(
        ALARM_CONDITION_LOW_BATTERY,
        m_nextAlarmId++,
        QString("Battery low: %1%").arg(percentage),
        P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Caution);
}

void MyApp::onStatusUpdate(const QString& status)
{
    m_alarmManager->raiseAlarm(
        ALARM_CONDITION_STATUS_UPDATE,
        m_nextAlarmId++,
        status,
        P_Alarms_PSM::T_AlarmCategoryType::L_AlarmCategory__Advisory);
}

UACM Integration

The Usage and Condition Monitoring (UACM) service collects health and usage data from platform resources. External applications can report their status to UACM.

UACM Concepts

  • Characteristic Values: Operating parameters (temperature, memory, CPU usage)
  • Fault Codes: Error conditions detected by the resource
  • Usage Data: Operational metrics (hours, cycles, events)
  • Health Status: Overall resource condition (GREEN/AMBER/RED)

Publishing UACM Parameters

#include "Usage_and_Condition_Monitoring_PSM.hpp"
#include "QtWrapperCCharacteristicValuePubSub.h"

class UacmReporter {
public:
    void initializeUacmPublisher(int domainId)
    {
        m_characteristicPublisher = 
            std::make_unique<ldm::pubsub::QtDdsPublisherCCharacteristicValue>(
                domainId, 
                P_Usage_and_Condition_Monitoring_PSM::KC_Characteristic_Value_TopicName,
                ldm::pubsub::LdmMessagePattern::eState);
        m_characteristicPublisher->Open();
    }

    void publishCharacteristic(uint32_t parameterId, 
                                double value, 
                                const QString& unit)
    {
        P_Usage_and_Condition_Monitoring_PSM::C_Characteristic_Value characteristic;

        characteristic.A_sourceID().A_resourceId(m_resourceId);
        characteristic.A_sourceID().A_instanceId(0);
        characteristic.A_characteristicId(parameterId);
        characteristic.A_value(value);
        characteristic.A_unit(unit.toStdString());

        // Set timestamp
        auto now = std::chrono::system_clock::now();
        auto epoch = now.time_since_epoch();
        auto seconds = std::chrono::duration_cast<std::chrono::seconds>(epoch);
        characteristic.A_timestamp().A_seconds(seconds.count());

        m_characteristicPublisher->Publish(characteristic);
    }

private:
    int32_t m_resourceId;
    std::unique_ptr<ldm::pubsub::QtDdsPublisherCCharacteristicValue> m_characteristicPublisher;
};

Example: Periodic Health Reporting

// Define characteristic IDs
static constexpr uint32_t CHAR_CPU_USAGE = 1001;
static constexpr uint32_t CHAR_MEMORY_USAGE = 1002;
static constexpr uint32_t CHAR_FRAME_RATE = 1003;
static constexpr uint32_t CHAR_UPTIME = 1004;

void MyApp::startHealthReporting()
{
    m_healthTimer = new QTimer(this);
    m_healthTimer->setInterval(10000);  // Every 10 seconds

    connect(m_healthTimer, &QTimer::timeout, this, [this]() {
        // Report CPU usage
        m_uacmReporter->publishCharacteristic(
            CHAR_CPU_USAGE, 
            getCurrentCpuUsage(), 
            "%");

        // Report memory usage
        m_uacmReporter->publishCharacteristic(
            CHAR_MEMORY_USAGE, 
            getCurrentMemoryUsage(), 
            "MB");

        // Report application-specific metrics
        m_uacmReporter->publishCharacteristic(
            CHAR_FRAME_RATE, 
            m_controller->currentFrameRate(), 
            "fps");

        m_uacmReporter->publishCharacteristic(
            CHAR_UPTIME, 
            m_uptimeSeconds, 
            "s");
    });

    m_healthTimer->start();
}

Display Extension (Per GVA Spec v1.1)

The GVA Display Extension Service Specification v1.1 defines how third-party applications can extend the HMI display. Key concepts include:

Third Party Session

A session represents your application's connection to the HMI. The session:

  • Has a unique UUID
  • Is associated with Third_Party_Pages
  • Controls Hard_Button_Labels
  • Receives Hard_Button_Events

Pages and Widgets

Third-party applications can create pages with widgets:

Widget Type Description
Text_Widget Single line of text
Multi_Line_Text_Widget Multiple lines of text
Image_Widget Still images
Video_Widget Platform video streams
Table_Widget Tabular data display
Plan_Position_Indicator_Widget Radar/map display

Static vs Dynamic Page Creation

  • Static: Pages pre-configured by system integrator at startup
  • Dynamic: Pages created on-demand by the third-party application

Widget Events

Widgets can fire events when the user interacts with them:

// Subscribe to widget interaction events
connect(m_widgetEventSubscriber.get(),
        &ldm::pubsub::QtDdsSubscriberCWidgetInteractionEvent::OnDataReceived,
        this, [this](const P_Display_Extension_PSM::C_Widget_Interaction_Event& event) {

    uint32_t widgetId = event.A_widget_sourceID().A_resourceId();
    int x = event.A_x();
    int y = event.A_y();

    qDebug() << "Widget" << widgetId << "clicked at" << x << "," << y;
});

Setting Operational Mode

Set your application's operational mode to indicate its state:

void RegistrationManager::setOperationalMode()
{
    P_Platform_Configuration_PSM::C_Configured_Platform_setOperatingMode mode;

    mode.A_sourceID().A_resourceId(m_resourceId);
    mode.A_sourceID().A_instanceId(0);
    mode.A_operatingMode(
        P_Platform_Configuration_Common_PSM::T_OperatingModeType::L_OperatingMode__On);

    m_operatingModePublisher->Publish(mode);
}

void RegistrationManager::setOffMode()
{
    P_Platform_Configuration_PSM::C_Configured_Platform_setOperatingMode mode;

    mode.A_sourceID().A_resourceId(m_resourceId);
    mode.A_sourceID().A_instanceId(0);
    mode.A_operatingMode(
        P_Platform_Configuration_Common_PSM::T_OperatingModeType::L_OperatingMode__Off);

    m_operatingModePublisher->Publish(mode);
}

Complete Application Lifecycle

stateDiagram-v2 [*] --> Initializing: App starts Initializing --> Registering: Initialize DDS Registering --> WaitingForId: Send request WaitingForId --> Registered: Receive ID WaitingForId --> Registered: Timeout (use fallback) Registered --> Active: Publish session Active --> Active: Handle events Active --> ShuttingDown: Stop requested ShuttingDown --> [*]: Set OFF mode, cleanup

Best Practices

Registration

  1. Generate unique UUIDs - Use deterministic UUID generation based on your app identity
  2. Handle timeouts - Don't block forever waiting for registration response
  3. Republish periodically - Session and button labels should be republished for late joiners

Alarms

  1. Use appropriate categories - Warning for critical, Caution for degraded, Advisory for info
  2. Clear alarms when resolved - Don't leave stale alarms
  3. Unique alarm IDs - Each alarm instance needs a unique ID

UACM

  1. Report regularly - Publish health data at consistent intervals
  2. Use meaningful units - Include unit strings with values
  3. Don't flood the network - 10-30 second intervals are typical

Display Extension

  1. Respect HMI boundaries - Stay within your allocated display area
  2. Handle button events promptly - Users expect responsive feedback
  3. Clean shutdown - Deregister and release resources properly

Troubleshooting

Application Not Appearing in HMI

  • Verify Third_Party_Session is being published
  • Check domain ID matches HMI
  • Ensure UUID is unique

Buttons Not Responding

  • Verify Hard_Button_Event subscriber is open
  • Check button indices match HMI configuration
  • Ensure Hard_Button_Labels are published

Alarms Not Appearing

  • Verify alarm publishers are initialized
  • Check alarm condition state is Active
  • Ensure alarm category definitions are published

Reference Implementation

See the following example applications for complete implementations: