Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -115,7 +115,7 @@ option(QGC_ENABLE_QT_VIDEOSTREAMING "Enable QtMultimedia video backend" OFF)
# ============================================================================

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 "35cd26af3036b54205b0c1d25ac7bf6f5bdbe15a" 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
6 changes: 6 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
127 changes: 127 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjection.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/****************************************************************************
* FailureInjection.cc
****************************************************************************/
#include "FailureInjection.h"
#include "MAVLinkEnumsQml.h" // MAVLinkEnums::FAILURE_UNIT / FAILURE_TYPE, Q_ENUM_NS-reflected from the MAVLink dialect
#include "QGCMAVLink.h" // QGCMAVLink::mavResultToString() fallback for resolveResult()

#include "MAVLinkLib.h" // MAV_RESULT_* for resolveResult()

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

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();
}
56 changes: 56 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/****************************************************************************
* FailureInjection.h
*
* QML singleton backing the Failure Injection page. It serves two roles:
*
* 1. Static catalog: the MAVLink FAILURE_UNIT / FAILURE_TYPE enums. Enum
* values come from the MAVLink dialect headers, tracking common.xml;
* display names and per-unit instance maxima are defined in the .cc.
*
* 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, max }]
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);

signals:
void activityChanged(void);

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

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/subMenuButtonImage.png");
}

QUrl FailureInjectionComponent::setupSource(void) const
{
return QUrl::fromUserInput(QStringLiteral("qrc:/qml/QGroundControl/AutoPilotPlugins/PX4/FailureInjectionComponent.qml"));
}
29 changes: 29 additions & 0 deletions src/AutoPilotPlugins/PX4/FailureInjectionComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/****************************************************************************
* 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