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
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(®Manager, &RegistrationManager::registered,
[&](int32_t resourceId) {
qDebug() << "Registered with resource ID:" << resourceId;
controller.start();
});
QObject::connect(®Manager, &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¶
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¶
Best Practices¶
Registration¶
- Generate unique UUIDs - Use deterministic UUID generation based on your app identity
- Handle timeouts - Don't block forever waiting for registration response
- Republish periodically - Session and button labels should be republished for late joiners
Alarms¶
- Use appropriate categories - Warning for critical, Caution for degraded, Advisory for info
- Clear alarms when resolved - Don't leave stale alarms
- Unique alarm IDs - Each alarm instance needs a unique ID
UACM¶
- Report regularly - Publish health data at consistent intervals
- Use meaningful units - Include unit strings with values
- Don't flood the network - 10-30 second intervals are typical
Display Extension¶
- Respect HMI boundaries - Stay within your allocated display area
- Handle button events promptly - Users expect responsive feedback
- 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:
- Drone Invaders - Full game with alarms
- Shield Protocol - Breakout game with DDS
- Defense Grid Run - Stealth game with all integrations
- BMS Application - Map display application