Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmake/CustomOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ option(QGC_ENABLE_GST_VIDEOSTREAMING "Enable GStreamer video backend" ON)
# ============================================================================

set(QGC_MAVLINK_GIT_REPO "https://github.com/mavlink/mavlink.git" CACHE STRING "MAVLink repository URL")
set(QGC_MAVLINK_GIT_TAG "c409cf690454db6d3e004bd14173bc6c7ff1e0ff" CACHE STRING "MAVLink repository commit/tag")
set(QGC_MAVLINK_GIT_TAG "1fe1417edba14c178a20303c6914d3b301097a02" CACHE STRING "MAVLink repository commit/tag")
set(QGC_MAVLINK_DIALECT "all" CACHE STRING "MAVLink dialect")
set(QGC_MAVLINK_VERSION "2.0" CACHE STRING "MAVLink protocol version")

Expand Down
7 changes: 7 additions & 0 deletions src/AutoPilotPlugins/PX4/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ target_sources(${CMAKE_PROJECT_NAME}
AirframeComponentAirframes.h
AirframeComponentController.cc
AirframeComponentController.h
FailureInjection.cc
FailureInjection.h
FailureInjectionComponent.cc
FailureInjectionComponent.h
FlightModesComponent.cc
FlightModesComponent.h
PowerComponent.cc
Expand Down Expand Up @@ -108,6 +112,8 @@ qt_add_qml_module(AutoPilotPluginsPX4Module
CalcAmpsPerVoltDialog.qml
CalcVoltageDividerDialog.qml
ESCCalibrationDialog.qml
FailureInjectionComponent.qml
FailureInjectionInstances.js
FlightModesComponentSummary.qml
PowerComponentSummary.qml
PX4FlightBehaviorCopter.qml
Expand Down Expand Up @@ -182,4 +188,5 @@ qt_add_resources(${CMAKE_PROJECT_NAME} autopilot_plugin_px4_qmlimages
"${CMAKE_SOURCE_DIR}/src/AutoPilotPlugins/PX4/Images/VehicleTailDownRotate.png"
"${CMAKE_SOURCE_DIR}/src/AutoPilotPlugins/PX4/Images/VehicleUpsideDown.png"
"${CMAKE_SOURCE_DIR}/src/AutoPilotPlugins/PX4/Images/VehicleUpsideDownRotate.png"
"${CMAKE_SOURCE_DIR}/src/AutoPilotPlugins/PX4/Images/WarningEmergency.svg"
)
174 changes: 174 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjection.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/****************************************************************************
* FailureInjection.cc
****************************************************************************/
#include "FailureInjection.h"

#include <QtCore/QMetaEnum>
#include <QtCore/QVariantMap>

#include "MAVLinkEnumsQml.h" // MAVLinkEnums::FAILURE_UNIT / FAILURE_TYPE, Q_ENUM_NS-reflected from the MAVLink dialect
#include "MAVLinkLib.h" // MAV_RESULT_* for resolveResult()
#include "QGCMAVLink.h" // QGCMAVLink::mavResultToString() fallback for resolveResult()

namespace {

QVariantMap _makeUnit(const QString& name, int unit)
{
return QVariantMap{{"name", name}, {"unit", unit}};
}

QVariantMap _makeType(const QString& name, int type)
{
return QVariantMap{{"name", name}, {"type", type}};
}

/// Strip the longest matching MAVLink enum prefix, e.g. FAILURE_UNIT_SENSOR_GYRO -> GYRO.
QString _stripPrefix(const QString& key, const QStringList& prefixesLongestFirst)
{
for (const QString& prefix : prefixesLongestFirst) {
if (key.startsWith(prefix)) {
return key.mid(prefix.length());
}
}
return key;
}

/// Builds a {name, value} catalog from the Q_ENUM_NS-exposed enum on MAVLinkEnums::staticMetaObject,
/// looked up by name.
QVariantList _buildCatalog(const char* enumName, const QStringList& prefixesLongestFirst,
QVariantMap (*makeEntry)(const QString&, int))
{
QVariantList list;
const QMetaObject& enumsMetaObject = MAVLinkEnums::staticMetaObject;
const QMetaEnum me = enumsMetaObject.enumerator(enumsMetaObject.indexOfEnumerator(enumName));
for (int i = 0; i < me.keyCount(); ++i) {
const QString key = QString::fromLatin1(me.key(i));
if (key.endsWith(QStringLiteral("_ENUM_END"))) {
continue; // dialect sentinel, not a real value
}
list.append(makeEntry(_stripPrefix(key, prefixesLongestFirst), me.value(i)));
}
return list;
}

} // namespace

FailureInjection::FailureInjection(QObject* parent) : QObject(parent)
{
_units = _buildCatalog("FAILURE_UNIT",
{QStringLiteral("FAILURE_UNIT_SENSOR_"), QStringLiteral("FAILURE_UNIT_SYSTEM_"),
QStringLiteral("FAILURE_UNIT_")},
_makeUnit);
_types = _buildCatalog("FAILURE_TYPE", {QStringLiteral("FAILURE_TYPE_")}, _makeType);
}

void FailureInjection::logRow(const QString& unitName, const QString& typeName, const QString& instanceLabel,
const QString& time)
{
_activity.prepend(QVariantMap{{"time", time},
{"unitName", unitName},
{"typeName", typeName},
{"instance", instanceLabel},
{"result", QStringLiteral("pending")}});
emit activityChanged();
}

void FailureInjection::logInjection(const QString& unitName, const QString& typeName, int unitEnum,
const QString& instanceLabel, const QString& time)
{
logRow(unitName, typeName, instanceLabel, time);
if (!_injectedUnits.contains(unitEnum)) {
_injectedUnits.append(unitEnum);
}
}

void FailureInjection::resolveResult(int ackResult)
{
// ACKs for MAV_CMD_INJECT_FAILURE arrive in send order; resolve the oldest pending row (highest index, since newest
// is prepended).
int pendingIndex = -1;
for (int i = _activity.size() - 1; i >= 0; --i) {
if (_activity.at(i).toMap().value(QStringLiteral("result")).toString() == QStringLiteral("pending")) {
pendingIndex = i;
break;
}
}
if (pendingIndex < 0) {
return;
}

if (ackResult == MAV_RESULT_IN_PROGRESS) {
return; // not a terminal result; leave pending
}

QString reason;
switch (ackResult) {
// Not tr()'d: FailureInjectionComponent.qml matches this exact literal to style the row green.
case MAV_RESULT_ACCEPTED:
reason = QStringLiteral("accepted");
break;
case MAV_RESULT_TEMPORARILY_REJECTED:
reason = tr("Temporarily rejected");
break;
case MAV_RESULT_DENIED:
reason = tr("Denied");
break;
case MAV_RESULT_UNSUPPORTED:
reason = tr("Unsupported");
break;
case MAV_RESULT_FAILED:
reason = tr("Failed");
break;
case MAV_RESULT_CANCELLED:
reason = tr("Cancelled");
break;
default:
reason = QGCMAVLink::mavResultToString(static_cast<uint8_t>(ackResult));
break;
}

QVariantMap row = _activity.at(pendingIndex).toMap();
row[QStringLiteral("result")] = reason;
_activity[pendingIndex] = row;
emit activityChanged();
}

QVariantList FailureInjection::injectedUnits(void) const
{
QVariantList list;
for (int unitEnum : _injectedUnits) {
list.append(unitEnum);
}
return list;
}

void FailureInjection::clearInjectedUnits(void)
{
_injectedUnits.clear();
}

QVariantList FailureInjection::detailParams(int unitEnum, int typeEnum) const
{
// Vehicle parameters that refine how a failure manifests, keyed by (unit, type). The page shows
// an editor per entry, but only when the connected vehicle actually exposes the parameter.
// Adding a new combo is one more table line.
struct DetailParam
{
int unitEnum;
int typeEnum;
const char* param;
const char* label;
};

static const DetailParam table[] = {
{FAILURE_UNIT_SYSTEM_BATTERY, FAILURE_TYPE_WRONG, "SYS_FAIL_BAT_LVL", QT_TR_NOOP("Battery level")},
};

QVariantList list;
for (const DetailParam& entry : table) {
if (entry.unitEnum == unitEnum && entry.typeEnum == typeEnum) {
list.append(QVariantMap{{"param", QString::fromLatin1(entry.param)}, {"label", tr(entry.label)}});
}
}
return list;
}
64 changes: 64 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/****************************************************************************
* FailureInjection.h
*
* QML singleton backing the Failure Injection page. It serves two roles:
*
* 1. Static catalog: the MAVLink FAILURE_UNIT / FAILURE_TYPE enums. Values and
* names come from the MAVLink dialect headers (tracking common.xml); the .cc
* derives each display name by stripping the enum-name prefix.
*
* 2. Session state: the activity log and the set of units injected this
* session, persisted here so they survive navigating away from and back
* to the page, which destroys/recreates the page via its SetupPage Loader.
****************************************************************************/
#pragma once

#include <QtCore/QObject>
#include <QtCore/QVariantList>
#include <QtQmlIntegration/QtQmlIntegration>

class FailureInjection : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(FailureInjection)
QML_SINGLETON
Q_PROPERTY(QVariantList units READ units CONSTANT) ///< [{ name, unit }]
Q_PROPERTY(QVariantList types READ types CONSTANT) ///< [{ name, type }]
Q_PROPERTY(QVariantList activity READ activity NOTIFY
activityChanged) ///< newest first: [{ time, unitName, typeName, instance, result }]

public:
explicit FailureInjection(QObject* parent = nullptr);

QVariantList units(void) const { return _units; }

QVariantList types(void) const { return _types; }

QVariantList activity(void) const { return _activity; }

/// Add a log row (prepended, newest first, result "pending") without tracking the unit for Reset.
/// instanceLabel is a display descriptor for the affected instance(s), e.g. "all", "2", "1, 3, 5".
Q_INVOKABLE void logRow(const QString& unitName, const QString& typeName, const QString& instanceLabel,
const QString& time);
/// Record one injected failure: adds a log row and remembers the unit so Reset can restore it.
Q_INVOKABLE void logInjection(const QString& unitName, const QString& typeName, int unitEnum,
const QString& instanceLabel, const QString& time);
/// Resolve the oldest still-pending injection with a MAV_RESULT ack code; sets the row result.
Q_INVOKABLE void resolveResult(int ackResult);
/// Distinct FAILURE_UNIT values injected this session, so Reset restores only those.
Q_INVOKABLE QVariantList injectedUnits(void) const;
/// Forget the injected-units set (after a Reset reverted them); the activity log is left intact.
Q_INVOKABLE void clearInjectedUnits(void);
/// Vehicle parameters that refine how a (unit, type) failure manifests, e.g. BATTERY+WRONG ->
/// SYS_FAIL_BAT_LVL. Returns [{ param, label }]; empty when the combo has no detail parameters.
Q_INVOKABLE QVariantList detailParams(int unitEnum, int typeEnum) const;

signals:
void activityChanged(void);

private:
QVariantList _units;
QVariantList _types;
QVariantList _activity;
QList<int> _injectedUnits;
};
35 changes: 35 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjectionComponent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/****************************************************************************
* FailureInjectionComponent.cc
****************************************************************************/
#include "FailureInjectionComponent.h"

#include "AutoPilotPlugin.h"
#include "Vehicle.h"
Comment thread
Claudio-Chies marked this conversation as resolved.
Outdated

FailureInjectionComponent::FailureInjectionComponent(Vehicle* vehicle, AutoPilotPlugin* autopilot, QObject* parent)
: VehicleComponent(vehicle, autopilot, AutoPilotPlugin::UnknownVehicleComponent, parent),
_name(tr("Failure Injection"))
{}

QString FailureInjectionComponent::name(void) const
{
return _name;
}

QString FailureInjectionComponent::description(void) const
{
return tr(
"Failure Injection is used to simulate sensor and system failures (MAV_CMD_INJECT_FAILURE) "
"to validate failsafes. Requires SYS_FAILURE_EN = 1 and a vehicle reboot.");
}

QString FailureInjectionComponent::iconResource(void) const
{
return QStringLiteral("/qmlimages/WarningEmergency.svg");
}

QUrl FailureInjectionComponent::setupSource(void) const
{
return QUrl::fromUserInput(
QStringLiteral("qrc:/qml/QGroundControl/AutoPilotPlugins/PX4/FailureInjectionComponent.qml"));
}
37 changes: 37 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjectionComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/****************************************************************************
* FailureInjectionComponent.h
* Vehicle Setup component wrapper for the Failure Injection QML page.
****************************************************************************/
#pragma once

#include "VehicleComponent.h"

class FailureInjectionComponent : public VehicleComponent
{
Q_OBJECT
public:
FailureInjectionComponent(Vehicle* vehicle, AutoPilotPlugin* autopilot, QObject* parent = nullptr);

QString name(void) const override;
QString description(void) const override;
QString iconResource(void) const override;

bool requiresSetup(void) const override { return false; }

bool setupComplete(void) const override { return true; }

QStringList setupCompleteChangedTriggerList(void) const override { return QStringList(); }

QUrl setupSource(void) const override;

QUrl summaryQmlSource(void) const override { return QUrl(); }

// Failure injection is meant to be exercised in flight (validate failsafes/EKF), so keep the page usable while
// armed.
bool allowSetupWhileArmed(void) const override { return true; }

bool allowSetupWhileFlying(void) const override { return true; }

private:
const QString _name;
};
Loading
Loading