diff --git a/ChangeLog.md b/ChangeLog.md index 282b6a2a2fe..d4594f8932f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,7 @@ Note: This file only contains high level features or important fixes. * New combined compass and attitude instrument * You can select between multiple instruments by clicking on the instrument conntrol on a desktop build or press and hold on mobile builds +* Support for Fly View and Joystick custom mavlink actions has changed. Both the name and formation of the command file is different now. Go to QGC docs to understand how it works now. ## 4.1 diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index ec2c94abecf..c9b01ec1fe4 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -8,6 +8,7 @@ - [Fly](qgc-user-guide/fly_view/fly_view.md) - [Replay Flight Data](qgc-user-guide/fly_view/replay_flight_data.md) - [Video Overlay](qgc-user-guide/fly_view/video_overlay.md) +- [Custom Mavlink Actions]((qgc-user-guide/custom_actions/custom_action.md)) - [3D View](qgc-user-guide/viewer_3d/viewer_3d.md) - [Plan](qgc-user-guide/plan_view/plan_view.md) - [GeoFence](qgc-user-guide/plan_view/plan_geofence.md) diff --git a/docs/en/qgc-user-guide/custom_actions/custom_actions.md b/docs/en/qgc-user-guide/custom_actions/custom_actions.md new file mode 100644 index 00000000000..f97e3c7c695 --- /dev/null +++ b/docs/en/qgc-user-guide/custom_actions/custom_actions.md @@ -0,0 +1,46 @@ +# Custom Mavlink Action + +Both the Fly View and Joysticks support the ability execute arbitrary mavlink commands to the active vehicle. In the Fly View these will show up in the Toolstrip Action list. With Joysticks you can assign then to button presses. + +## Custom Actions File + +The custom actions available are defined in a JSON file. The format of that file is as follows: + +''' +{ + "version": 1, + "fileType": "CustomActions", + "actions": +[ +{ + "label": "First Command", + "description": "This is the first command", + "mavCmd": 10, + "compId": 100, + "param1": 1, + "param2": 2, + ... +}, +{ + "label": "Second Command", + "description": "This is the second command", + "mavCmd": 20, + ... +} +] +} +''' + +Fields: +* actions (required) - An array of json objects, one for each command +* label (required) - The user visible short description for the command. This is used as the button text for the Fly View - Actions command list. For Joysticks, this is the command you select from the dropdown. For Joysticks, make sure your name doesn't conflict with the built in names. +* description (required) - This is a longer description of the command used in the Fly View - Action list. This is not used by joysticks. +* mavCmd (required) - The command id of the mavlink command you want to send. +* compId (options) - The component id for where you want to send the command to. If not specified `MAV_COMP_ID_AUTOPILOT1` is used. +* param1 thru param7 (optional) - The parameters for the command. Parameters which are not specified will default to 0.0 + +Custom action files should be located in the CustomActions directory of the QGC save location. For example on Linux that would be `~/Documents/QGroundControl/CustomActions` or `~/Documents/QGroundControl Daily/CustomActions`. The Fly View and Joysticks each have there own custom actions files: +* Fly View - FlyViewCustomActions.json +* Joystick - JoystickCustomActions.json + +When you start up QGC it will load these files if they exist and make the commands available for use. diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index e1fbb386763..6c9e2b2aa8c 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -440,7 +440,6 @@ HEADERS += \ src/FollowMe/FollowMe.h \ src/Joystick/Joystick.h \ src/Joystick/JoystickManager.h \ - src/Joystick/JoystickMavCommand.h \ src/Utilities/JsonHelper.h \ src/MissionManager/KMLDomDocument.h \ src/MissionManager/KMLHelper.h \ @@ -531,6 +530,7 @@ HEADERS += \ src/Settings/AutoConnectSettings.h \ src/Settings/BatteryIndicatorSettings.h \ src/Settings/BrandImageSettings.h \ + src/Settings/CustomMavlinkActionsSettings.h \ src/Settings/RemoteIDSettings.h \ src/Settings/FirmwareUpgradeSettings.h \ src/Settings/FlightMapSettings.h \ @@ -702,7 +702,6 @@ SOURCES += \ src/FollowMe/FollowMe.cc \ src/Joystick/Joystick.cc \ src/Joystick/JoystickManager.cc \ - src/Joystick/JoystickMavCommand.cc \ src/Utilities/JsonHelper.cc \ src/MissionManager/KMLDomDocument.cc \ src/MissionManager/KMLHelper.cc \ @@ -790,6 +789,7 @@ SOURCES += \ src/Settings/AutoConnectSettings.cc \ src/Settings/BatteryIndicatorSettings.cc \ src/Settings/BrandImageSettings.cc \ + src/Settings/CustomMavlinkActionsSettings.cc \ src/Settings/RemoteIDSettings.cc \ src/Settings/FirmwareUpgradeSettings.cc \ src/Settings/FlightMapSettings.cc \ diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 1a0a1c3e24a..8339af69f19 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -361,6 +361,7 @@ src/MissionManager/CameraSection.FactMetaData.json src/MissionManager/CameraSpec.FactMetaData.json src/MissionManager/CorridorScan.SettingsGroup.json + src/Settings/CustomMavlinkActions.SettingsGroup.json src/Settings/RemoteID.SettingsGroup.json src/QmlControls/EditPositionDialog.FactMetaData.json src/Settings/FirmwareUpgrade.SettingsGroup.json diff --git a/src/FlightDisplay/GuidedActionList.qml b/src/FlightDisplay/GuidedActionList.qml index ce9e9e30c96..12195a5720c 100644 --- a/src/FlightDisplay/GuidedActionList.qml +++ b/src/FlightDisplay/GuidedActionList.qml @@ -38,6 +38,7 @@ Rectangle { property real _margins: Math.round(ScreenTools.defaultFontPixelHeight * 0.66) property real _actionWidth: ScreenTools.defaultFontPixelWidth * 25 property real _actionHorizSpacing: ScreenTools.defaultFontPixelHeight * 2 + property var _flyViewSettings: QGroundControl.settingsManager.flyViewSettings property var _model: [ { @@ -79,9 +80,10 @@ Rectangle { ] property var _customManager: CustomActionManager { - id: customManager + id: customManager + actionFileNameFact: QGroundControl.settingsManager.customMavlinkActionsSettings.flyViewActionsFile } - readonly property bool hasCustomActions: QGroundControl.settingsManager.flyViewSettings.enableCustomActions.rawValue && customManager.hasActions + readonly property bool hasCustomActions: customManager.actions.count > 0 QGCPalette { id: qgcPal } @@ -154,12 +156,11 @@ Rectangle { ColumnLayout { spacing: ScreenTools.defaultFontPixelHeight / 2 - visible: _root.hasCustomActions Layout.fillHeight: true QGCLabel { id: customMessage - text: "Custom Action #" + (index + 1) + text: object.description horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap Layout.minimumWidth: _actionWidth diff --git a/src/Joystick/CMakeLists.txt b/src/Joystick/CMakeLists.txt index 247855b6261..ee77083de30 100644 --- a/src/Joystick/CMakeLists.txt +++ b/src/Joystick/CMakeLists.txt @@ -5,8 +5,6 @@ qt_add_library(Joystick STATIC Joystick.h JoystickManager.cc JoystickManager.h - JoystickMavCommand.cc - JoystickMavCommand.h ) if(ANDROID) diff --git a/src/Joystick/Joystick.cc b/src/Joystick/Joystick.cc index 7f19582aa5d..0b35d5b8353 100644 --- a/src/Joystick/Joystick.cc +++ b/src/Joystick/Joystick.cc @@ -15,6 +15,9 @@ #include "VideoManager.h" #include "QGCCameraManager.h" #include "VehicleCameraControl.h" +#include "CustomAction.h" +#include "SettingsManager.h" +#include "CustomMavlinkActionsSettings.h" #include @@ -101,13 +104,14 @@ AssignableButtonAction::AssignableButtonAction(QObject* parent, QString action_, } Joystick::Joystick(const QString& name, int axisCount, int buttonCount, int hatCount, MultiVehicleManager* multiVehicleManager) - : _name(name) - , _axisCount(axisCount) - , _buttonCount(buttonCount) - , _hatCount(hatCount) - , _hatButtonCount(4 * hatCount) - , _totalButtonCount(_buttonCount+_hatButtonCount) - , _multiVehicleManager(multiVehicleManager) + : _name (name) + , _axisCount (axisCount) + , _buttonCount (buttonCount) + , _hatCount (hatCount) + , _hatButtonCount (4 * hatCount) + , _totalButtonCount (_buttonCount+_hatButtonCount) + , _multiVehicleManager (multiVehicleManager) + , _customActionManager (qgcApp()->toolbox()->settingsManager()->customMavlinkActionsSettings()->joystickActionsFile()) { qRegisterMetaType(); @@ -126,8 +130,6 @@ Joystick::Joystick(const QString& name, int axisCount, int buttonCount, int hatC _loadSettings(); connect(_multiVehicleManager, &MultiVehicleManager::activeVehicleChanged, this, &Joystick::_activeVehicleChanged); connect(qgcApp()->toolbox()->multiVehicleManager()->vehicles(), &QmlObjectListModel::countChanged, this, &Joystick::_vehicleCountChanged); - - _customMavCommands = JoystickMavCommand::load("JoystickMavCommands.json"); } void Joystick::stop() @@ -491,7 +493,6 @@ float Joystick::_adjustRange(int value, Calibration_t calibration, bool withDead return std::max(-1.0f, std::min(correctedValue, 1.0f)); } - void Joystick::run() { //-- Joystick thread @@ -1077,9 +1078,10 @@ void Joystick::_executeButtonAction(const QString& action, bool buttonDown) if (buttonDown) emit landingGearRetract(); } else { if (buttonDown && _activeVehicle) { - for (auto& item : _customMavCommands) { - if (action == item.name()) { - item.send(_activeVehicle); + for (int i=0; i<_customActionManager.actions()->count(); i++) { + auto customAction = _customActionManager.actions()->value(i); + if (action == customAction->label()) { + customAction->sendTo(_activeVehicle); return; } } @@ -1172,8 +1174,9 @@ void Joystick::_buildActionList(Vehicle* activeVehicle) _assignableButtonActions.append(new AssignableButtonAction(this, _buttonActionLandingGearDeploy)); _assignableButtonActions.append(new AssignableButtonAction(this, _buttonActionLandingGearRetract)); - for (auto& item : _customMavCommands) { - _assignableButtonActions.append(new AssignableButtonAction(this, item.name())); + for (int i=0; i<_customActionManager.actions()->count(); i++) { + auto customAction = _customActionManager.actions()->value(i); + _assignableButtonActions.append(new AssignableButtonAction(this, customAction->label())); } for(int i = 0; i < _assignableButtonActions.count(); i++) { diff --git a/src/Joystick/Joystick.h b/src/Joystick/Joystick.h index 1f8e4968449..90a12371869 100644 --- a/src/Joystick/Joystick.h +++ b/src/Joystick/Joystick.h @@ -19,7 +19,7 @@ #include "QGCLoggingCategory.h" #include "Vehicle.h" #include "MultiVehicleManager.h" -#include "JoystickMavCommand.h" +#include "CustomActionManager.h" // JoystickLog Category declaration moved to QGCLoggingCategory.cc to allow access in Vehicle Q_DECLARE_LOGGING_CATEGORY(JoystickValuesLog) @@ -303,7 +303,7 @@ class Joystick : public QThread QStringList _availableActionTitles; MultiVehicleManager* _multiVehicleManager = nullptr; - QList _customMavCommands; + CustomActionManager _customActionManager; static const float _minAxisFrequencyHz; static const float _maxAxisFrequencyHz; diff --git a/src/Joystick/JoystickMavCommand.cc b/src/Joystick/JoystickMavCommand.cc deleted file mode 100644 index 66e72f46f38..00000000000 --- a/src/Joystick/JoystickMavCommand.cc +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2020 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "JoystickMavCommand.h" -#include "QGCLoggingCategory.h" -#include "Vehicle.h" -#include -#include -#include - -QGC_LOGGING_CATEGORY(JoystickMavCommandLog, "JoystickMavCommandLog") - -static void parseJsonValue(const QJsonObject& jsonObject, const QString& key, float& param) -{ - if (jsonObject.contains(key)) - param = static_cast(jsonObject.value(key).toDouble()); -} - -QList JoystickMavCommand::load(const QString& jsonFilename) -{ - qCDebug(JoystickMavCommandLog) << "Loading" << jsonFilename; - QList result; - - QFile jsonFile(jsonFilename); - if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCDebug(JoystickMavCommandLog) << "Could not open" << jsonFilename; - return result; - } - - QByteArray bytes = jsonFile.readAll(); - jsonFile.close(); - QJsonParseError jsonParseError; - QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError); - if (jsonParseError.error != QJsonParseError::NoError) { - qWarning() << jsonFilename << "Unable to open json document" << jsonParseError.errorString(); - return result; - } - - QJsonObject json = doc.object(); - - const int version = json.value("version").toInt(); - if (version != 1) { - qWarning() << jsonFilename << ": invalid version" << version; - return result; - } - - QJsonValue jsonValue = json.value("commands"); - if (!jsonValue.isArray()) { - qWarning() << jsonFilename << ": 'commands' is not an array"; - return result; - } - - QJsonArray jsonArray = jsonValue.toArray(); - for (QJsonValue info: jsonArray) { - if (!info.isObject()) { - qWarning() << jsonFilename << ": 'commands' should contain objects"; - return result; - } - - auto jsonObject = info.toObject(); - JoystickMavCommand item; - if (!jsonObject.contains("id")) { - qWarning() << jsonFilename << ": 'id' is required"; - continue; - } - item._id = jsonObject.value("id").toInt(); - if (!jsonObject.contains("name")) { - qWarning() << jsonFilename << ": 'name' is required"; - continue; - } - item._name = jsonObject.value("name").toString(); - item._showError = jsonObject.value("showError").toBool(); - parseJsonValue(jsonObject, "param1", item._param1); - parseJsonValue(jsonObject, "param2", item._param2); - parseJsonValue(jsonObject, "param3", item._param3); - parseJsonValue(jsonObject, "param4", item._param4); - parseJsonValue(jsonObject, "param5", item._param5); - parseJsonValue(jsonObject, "param6", item._param6); - parseJsonValue(jsonObject, "param7", item._param7); - - qCDebug(JoystickMavCommandLog) << jsonObject; - - result.append(item); - } - - return result; -} - -void JoystickMavCommand::send(Vehicle* vehicle) -{ - vehicle->sendMavCommand(vehicle->defaultComponentId(), - static_cast(_id), - _showError, - _param1, _param2, _param3, _param4, _param5, _param6, _param7); -} diff --git a/src/Joystick/JoystickMavCommand.h b/src/Joystick/JoystickMavCommand.h deleted file mode 100644 index 96cf9f4f791..00000000000 --- a/src/Joystick/JoystickMavCommand.h +++ /dev/null @@ -1,40 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2020 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -/// @file -/// @brief Custom Joystick MAV command - -#pragma once - -#include -#include - -class Vehicle; - -/// Custom MAV command -class JoystickMavCommand -{ -public: - static QList load(const QString& jsonFilename); - QString name() const { return _name; } - - void send(Vehicle* vehicle); -private: - QString _name; - int _id = 0; - bool _showError = false; - float _param1 = 0.0f; - float _param2 = 0.0f; - float _param3 = 0.0f; - float _param4 = 0.0f; - float _param5 = 0.0f; - float _param6 = 0.0f; - float _param7 = 0.0f; -}; - diff --git a/src/Joystick/JoystickMavCommands.json b/src/Joystick/JoystickMavCommands.json deleted file mode 100644 index ba28ed46f2b..00000000000 --- a/src/Joystick/JoystickMavCommands.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "comment": "Joystick MAV commands", - "version": 1, - - "commands": [ - { - "id": 31010, - "name": "MAV_CMD_USER_1", - "param1": 1.0 - }, - { - "id": 31011, - "name": "MAV_CMD_USER_2", - "param1": 0.0 - } - ] -} diff --git a/src/QmlControls/CustomAction.h b/src/QmlControls/CustomAction.h index f87883e758d..50be679d7a9 100644 --- a/src/QmlControls/CustomAction.h +++ b/src/QmlControls/CustomAction.h @@ -7,6 +7,8 @@ * ****************************************************************************/ +#pragma once + #include #include "MAVLinkProtocol.h" @@ -16,43 +18,47 @@ class CustomAction: public QObject { Q_OBJECT - Q_PROPERTY(QString label READ label CONSTANT) - + Q_PROPERTY(QString label READ label CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) public: - CustomAction() { CustomAction("", MAV_CMD(0)); } // this is required for QML reflection + CustomAction() { CustomAction(QString(), QString(), MAV_CMD(0), MAV_COMPONENT(0), 0, 0, 0, 0, 0, 0, 0); } // this is required for QML reflection CustomAction( - QString label, - MAV_CMD mavCmd, - MAV_COMPONENT compId = MAV_COMP_ID_AUTOPILOT1, - float param1 = 0.0f, - float param2 = 0.0f, - float param3 = 0.0f, - float param4 = 0.0f, - float param5 = 0.0f, - float param6 = 0.0f, - float param7 = 0.0f - ): - _label(label), - _mavCmd(mavCmd), - _compId(compId), - _params{ param1, param2, param3, param4, param5, param6, param7 } + QString label, + QString description, + MAV_CMD mavCmd, + MAV_COMPONENT compId, + float param1, + float param2, + float param3, + float param4, + float param5, + float param6, + float param7, + QObject* parent = nullptr) + : QObject (parent) + , _label (label) + , _description (description) + , _mavCmd (mavCmd) + , _compId (compId) + , _params { param1, param2, param3, param4, param5, param6, param7 } {}; - Q_INVOKABLE void sendTo(Vehicle* vehicle) { + Q_INVOKABLE void sendTo(Vehicle* vehicle) + { if (vehicle) { const bool showError = true; vehicle->sendMavCommand(_compId, _mavCmd, showError, _params[0], _params[1], _params[2], _params[3], _params[4], _params[5], _params[6]); } }; + QString label () const { return _label; } + QString description() const { return _description; } private: - QString label() const { return _label; } - - QString _label; - MAV_CMD _mavCmd; - MAV_COMPONENT _compId; - float _params[7]; - + QString _label; + QString _description; + MAV_CMD _mavCmd; + MAV_COMPONENT _compId; + float _params[7]; }; diff --git a/src/QmlControls/CustomActionManager.cc b/src/QmlControls/CustomActionManager.cc index bbf6074bb18..e1d6bd289c6 100644 --- a/src/QmlControls/CustomActionManager.cc +++ b/src/QmlControls/CustomActionManager.cc @@ -15,21 +15,45 @@ #include "QGCApplication.h" #include "SettingsManager.h" -CustomActionManager::CustomActionManager(void) { - auto flyViewSettings = qgcApp()->toolbox()->settingsManager()->flyViewSettings(); - Fact* customActionsFact = flyViewSettings->customActionDefinitions(); +CustomActionManager::CustomActionManager(QObject* parent) + : QObject(parent) +{ - connect(customActionsFact, &Fact::valueChanged, this, &CustomActionManager::_loadFromJson); +} - // On construction, we only load the Custom Actions if we have a path - // defined, to prevent spurious warnings. - if (!customActionsFact->rawValue().toString().isEmpty()) { - _loadFromJson(customActionsFact->rawValue()); - } +CustomActionManager::CustomActionManager(Fact* actionFileNameFact, QObject* parent) + : QObject(parent) +{ + setActionFileNameFact(actionFileNameFact); } -void CustomActionManager::_loadFromJson(QVariant fact) { - QString path = fact.toString(); +void CustomActionManager::setActionFileNameFact(Fact* actionFileNameFact) +{ + _actionFileNameFact = actionFileNameFact; + emit actionFileNameFactChanged(); + connect(_actionFileNameFact, &Fact::rawValueChanged, this, &CustomActionManager::_loadActionsFile); + + _loadActionsFile(); +} + +void CustomActionManager::_loadActionsFile() +{ + _actions.clearAndDeleteContents(); + QString actionFileName = _actionFileNameFact->rawValue().toString(); + if (actionFileName.isEmpty()) { + return; + } + + // Custom actions are always loaded from the custom actions save path + QString savePath = qgcApp()->toolbox()->settingsManager()->appSettings()->customActionsSavePath(); + QDir saveDir(savePath); + QString fullPath = saveDir.absoluteFilePath(actionFileName); + + // It's ok for the file to not exist + QFileInfo fileInfo(fullPath); + if (!fileInfo.exists()) { + return; + } const char* kQgcFileType = "CustomActions"; const char* kActionListKey = "actions"; @@ -38,10 +62,9 @@ void CustomActionManager::_loadFromJson(QVariant fact) { QString errorString; int version; - QJsonObject jsonObject = JsonHelper::openInternalQGCJsonFile(path, kQgcFileType, 1, 1, version, errorString); + QJsonObject jsonObject = JsonHelper::openInternalQGCJsonFile(fullPath, kQgcFileType, 1, 1, version, errorString); if (!errorString.isEmpty()) { - qCWarning(GuidedActionsControllerLog) << "Custom Actions Internal Error: " << errorString; - emit actionsChanged(); + qgcApp()->showAppMessage(tr("Failed to load custom actions file: `%1` error: `%2`").arg(fullPath).arg(errorString)); return; } @@ -49,39 +72,42 @@ void CustomActionManager::_loadFromJson(QVariant fact) { { kActionListKey, QJsonValue::Array, /* required= */ true }, }; if (!JsonHelper::validateKeys(jsonObject, keyInfoList, errorString)) { - qCWarning(GuidedActionsControllerLog) << "Custom Actions JSON document incorrect format:" << errorString; - emit actionsChanged(); + qgcApp()->showAppMessage(tr("Custom actions file - incorrect format: %1").arg(errorString)); return; } QJsonArray actionList = jsonObject[kActionListKey].toArray(); for (auto actionJson: actionList) { if (!actionJson.isObject()) { - qCWarning(GuidedActionsControllerLog) << "Custom Actions JsonValue not an object: " << actionJson; - continue; + qgcApp()->showAppMessage(tr("Custom actions file - incorrect format: JsonValue not an object")); + _actions.clearAndDeleteContents(); + return; } auto actionObj = actionJson.toObject(); QList actionKeyInfoList = { - { "label", QJsonValue::String, /* required= */ true }, - { "mavCmd", QJsonValue::Double, /* required= */ true }, - - { "compId", QJsonValue::Double, /* required= */ false }, - { "param1", QJsonValue::Double, /* required= */ false }, - { "param2", QJsonValue::Double, /* required= */ false }, - { "param3", QJsonValue::Double, /* required= */ false }, - { "param4", QJsonValue::Double, /* required= */ false }, - { "param5", QJsonValue::Double, /* required= */ false }, - { "param6", QJsonValue::Double, /* required= */ false }, - { "param7", QJsonValue::Double, /* required= */ false }, + { "label", QJsonValue::String, /* required= */ true }, + { "description", QJsonValue::String, /* required= */ true }, + { "mavCmd", QJsonValue::Double, /* required= */ true }, + + { "compId", QJsonValue::Double, /* required= */ false }, + { "param1", QJsonValue::Double, /* required= */ false }, + { "param2", QJsonValue::Double, /* required= */ false }, + { "param3", QJsonValue::Double, /* required= */ false }, + { "param4", QJsonValue::Double, /* required= */ false }, + { "param5", QJsonValue::Double, /* required= */ false }, + { "param6", QJsonValue::Double, /* required= */ false }, + { "param7", QJsonValue::Double, /* required= */ false }, }; if (!JsonHelper::validateKeys(actionObj, actionKeyInfoList, errorString)) { - qCWarning(GuidedActionsControllerLog) << "Custom Actions JSON document incorrect format:" << errorString; - continue; + qgcApp()->showAppMessage(tr("Custom actions file - incorrect format: %1").arg(errorString)); + _actions.clearAndDeleteContents(); + return; } auto label = actionObj["label"].toString(); + auto description = actionObj["description"].toString(); auto mavCmd = (MAV_CMD)actionObj["mavCmd"].toInt(); auto compId = (MAV_COMPONENT)actionObj["compId"].toInt(MAV_COMP_ID_AUTOPILOT1); auto param1 = actionObj["param1"].toDouble(0.0); @@ -92,10 +118,8 @@ void CustomActionManager::_loadFromJson(QVariant fact) { auto param6 = actionObj["param6"].toDouble(0.0); auto param7 = actionObj["param7"].toDouble(0.0); - CustomAction* action = new CustomAction(label, mavCmd, compId, param1, param2, param3, param4, param5, param6, param7); + CustomAction* action = new CustomAction(label, description, mavCmd, compId, param1, param2, param3, param4, param5, param6, param7, this); QQmlEngine::setObjectOwnership(action, QQmlEngine::CppOwnership); _actions.append(action); } - - emit actionsChanged(); } diff --git a/src/QmlControls/CustomActionManager.h b/src/QmlControls/CustomActionManager.h index e7f895696d0..5c2e8daf269 100644 --- a/src/QmlControls/CustomActionManager.h +++ b/src/QmlControls/CustomActionManager.h @@ -7,31 +7,38 @@ * ****************************************************************************/ +#pragma once + #include #include +#include "Fact.h" +/// Loads the specified action file and provides access to the actions it contains. +/// Action files are loaded from the default CustomActions directory. +/// The actions file name is filename only, no path. class CustomActionManager : public QObject { Q_OBJECT - Q_PROPERTY(QmlObjectListModel* actions READ actions NOTIFY actionsChanged) - Q_PROPERTY(bool hasActions READ hasActions NOTIFY actionsChanged) + Q_PROPERTY(Fact* actionFileNameFact READ actionFileNameFact WRITE setActionFileNameFact NOTIFY actionFileNameFactChanged) + Q_PROPERTY(QmlObjectListModel* actions READ actions CONSTANT) public: - CustomActionManager(void); + CustomActionManager(QObject* parent = nullptr); + CustomActionManager(Fact* actionFileNameFact, QObject* parent = nullptr); - QmlObjectListModel* actions(void) { return &_actions; } - bool hasActions(void) { return _actions.count() > 0; } + Fact* actionFileNameFact (void) { return _actionFileNameFact; } + void setActionFileNameFact (Fact* actionFileNameFact); + QmlObjectListModel* actions (void) { return &_actions; } signals: - void actionsChanged(); + void actionFileNameFactChanged(); private slots: - void _loadFromJson(QVariant path); + void _loadActionsFile(void); private: + Fact* _actionFileNameFact; QmlObjectListModel _actions; - bool _hasActions; - }; diff --git a/src/Settings/CMakeLists.txt b/src/Settings/CMakeLists.txt index 54322a0a695..fd9ecd21768 100644 --- a/src/Settings/CMakeLists.txt +++ b/src/Settings/CMakeLists.txt @@ -12,6 +12,8 @@ qt_add_library(Settings STATIC BatteryIndicatorSettings.h BrandImageSettings.cc BrandImageSettings.h + CustomMavlinkActionsSettings.cc + CustomMavlinkActionsSettings.h FirmwareUpgradeSettings.cc FirmwareUpgradeSettings.h FlightMapSettings.cc diff --git a/src/Settings/CustomMavlinkActions.SettingsGroup.json b/src/Settings/CustomMavlinkActions.SettingsGroup.json new file mode 100644 index 00000000000..906abec364b --- /dev/null +++ b/src/Settings/CustomMavlinkActions.SettingsGroup.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "fileType": "FactMetaData", + "QGC.MetaData.Facts": +[ +{ + "name": "flyViewActionsFile", + "shortDesc": "Name of JSON custom actions file for Fly View", + "type": "string", + "default": "" +}, +{ + "name": "joystickActionsFile", + "shortDesc": "Name of JSON custom actions file for Joysticks", + "type": "string", + "default": "" +} +] +} diff --git a/src/Settings/CustomMavlinkActionsSettings.cc b/src/Settings/CustomMavlinkActionsSettings.cc new file mode 100644 index 00000000000..44b9025e281 --- /dev/null +++ b/src/Settings/CustomMavlinkActionsSettings.cc @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * (c) 2009-2020 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "CustomMavlinkActionsSettings.h" +#include "QGCApplication.h" + +#include +#include +#include + +DECLARE_SETTINGGROUP(CustomMavlinkActions, "CustomMavlinkActions") +{ + qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "CustomMavlinkActionsSettings", "Reference only"); + + // Notify the user of new Fly View custom actions support + QSettings deprecatedSettings; + static const char* deprecatedKey1 = "enableCustomActions"; + static const char* deprecatedKey2 = "customActionsDefinitions"; + deprecatedSettings.beginGroup("FlyView"); + if (deprecatedSettings.contains(deprecatedKey1) || deprecatedSettings.contains(deprecatedKey2)) { + deprecatedSettings.remove(deprecatedKey1); + deprecatedSettings.remove(deprecatedKey2); + qgcApp()->showAppMessage(tr("Support for Fly View custom actions has changed. The location of the files has changed. You will need to setup up your settings again from Fly View Settings.")); + } + + // Notify the user of new Joystick custom actions support + static const char* joystickFileName = "JoystickMavCommands.json"; + if (QFile(joystickFileName).exists()) { + qgcApp()->showAppMessage(tr("Support for Joystick custom actions has changed. The format and location of the files has changed. New setting is available from Fly View Settings. File format is documented in user guide. Delete the %1 file to disable this warning").arg(joystickFileName)); + } +} + +DECLARE_SETTINGSFACT(CustomMavlinkActionsSettings, flyViewActionsFile) +DECLARE_SETTINGSFACT(CustomMavlinkActionsSettings, joystickActionsFile) diff --git a/src/Settings/CustomMavlinkActionsSettings.h b/src/Settings/CustomMavlinkActionsSettings.h new file mode 100644 index 00000000000..9bfb4725a0e --- /dev/null +++ b/src/Settings/CustomMavlinkActionsSettings.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * + * (c) 2009-2020 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "SettingsGroup.h" + +class CustomMavlinkActionsSettings : public SettingsGroup +{ + Q_OBJECT +public: + CustomMavlinkActionsSettings(QObject* parent = nullptr); + + DEFINE_SETTING_NAME_GROUP() + + DEFINE_SETTINGFACT(flyViewActionsFile) + DEFINE_SETTINGFACT(joystickActionsFile) +}; diff --git a/src/Settings/FlyView.SettingsGroup.json b/src/Settings/FlyView.SettingsGroup.json index a197a087928..fa3025cc658 100644 --- a/src/Settings/FlyView.SettingsGroup.json +++ b/src/Settings/FlyView.SettingsGroup.json @@ -67,19 +67,6 @@ "type": "bool", "default": false }, -{ - "name": "enableCustomActions", - "shortDesc": "Enable Custom Actions", - "type": "bool", - "default": false -}, -{ - "name": "customActionDefinitions", - "shortDesc": "Custom Action Definitions", - "longDesc": "File that defines custom actions to send connected vehicle", - "type": "string", - "default": "" -}, { "name": "instrumentQmlFile", "shortDesc": "Qml file for instrument panel", diff --git a/src/Settings/FlyViewSettings.cc b/src/Settings/FlyViewSettings.cc index d4584c85d05..dd3d7ac61d4 100644 --- a/src/Settings/FlyViewSettings.cc +++ b/src/Settings/FlyViewSettings.cc @@ -27,6 +27,4 @@ DECLARE_SETTINGSFACT(FlyViewSettings, keepMapCenteredOnVehicle) DECLARE_SETTINGSFACT(FlyViewSettings, showSimpleCameraControl) DECLARE_SETTINGSFACT(FlyViewSettings, showObstacleDistanceOverlay) DECLARE_SETTINGSFACT(FlyViewSettings, updateHomePosition) -DECLARE_SETTINGSFACT(FlyViewSettings, enableCustomActions) -DECLARE_SETTINGSFACT(FlyViewSettings, customActionDefinitions) DECLARE_SETTINGSFACT(FlyViewSettings, instrumentQmlFile) diff --git a/src/Settings/FlyViewSettings.h b/src/Settings/FlyViewSettings.h index 1de26f8d8e6..a4223f65bcf 100644 --- a/src/Settings/FlyViewSettings.h +++ b/src/Settings/FlyViewSettings.h @@ -29,7 +29,5 @@ class FlyViewSettings : public SettingsGroup DEFINE_SETTINGFACT(showSimpleCameraControl) DEFINE_SETTINGFACT(showObstacleDistanceOverlay) DEFINE_SETTINGFACT(updateHomePosition) - DEFINE_SETTINGFACT(enableCustomActions) - DEFINE_SETTINGFACT(customActionDefinitions) DEFINE_SETTINGFACT(instrumentQmlFile) }; diff --git a/src/Settings/SettingsManager.cc b/src/Settings/SettingsManager.cc index 488913e832d..42d0d82c8f5 100644 --- a/src/Settings/SettingsManager.cc +++ b/src/Settings/SettingsManager.cc @@ -34,6 +34,7 @@ SettingsManager::SettingsManager(QGCApplication* app, QGCToolbox* toolbox) , _apmMavlinkStreamRateSettings (nullptr) #endif , _remoteIDSettings (nullptr) + , _customMavlinkActionsSettings (nullptr) { } @@ -64,4 +65,5 @@ void SettingsManager::setToolbox(QGCToolbox *toolbox) _apmMavlinkStreamRateSettings = new APMMavlinkStreamRateSettings(this); #endif _remoteIDSettings = new RemoteIDSettings (this); + _customMavlinkActionsSettings = new CustomMavlinkActionsSettings(this); } diff --git a/src/Settings/SettingsManager.h b/src/Settings/SettingsManager.h index fc089213452..7c2fd8156df 100644 --- a/src/Settings/SettingsManager.h +++ b/src/Settings/SettingsManager.h @@ -33,6 +33,7 @@ #include #include "RemoteIDSettings.h" #include "Viewer3DSettings.h" +#include "CustomMavlinkActionsSettings.h" /// Provides access to all app settings class SettingsManager : public QGCTool @@ -56,12 +57,15 @@ class SettingsManager : public QGCTool Q_PROPERTY(QObject* firmwareUpgradeSettings READ firmwareUpgradeSettings CONSTANT) Q_PROPERTY(QObject* adsbVehicleManagerSettings READ adsbVehicleManagerSettings CONSTANT) Q_PROPERTY(QObject* batteryIndicatorSettings READ batteryIndicatorSettings CONSTANT) - Q_PROPERTY(QObject* mapsSettings READ mapsSettings CONSTANT) - Q_PROPERTY(QObject* viewer3DSettings READ viewer3DSettings CONSTANT) + Q_PROPERTY(QObject* mapsSettings READ mapsSettings CONSTANT) + Q_PROPERTY(QObject* viewer3DSettings READ viewer3DSettings CONSTANT) #if !defined(NO_ARDUPILOT_DIALECT) Q_PROPERTY(QObject* apmMavlinkStreamRateSettings READ apmMavlinkStreamRateSettings CONSTANT) #endif Q_PROPERTY(QObject* remoteIDSettings READ remoteIDSettings CONSTANT) + Q_PROPERTY(QObject* customMavlinkActionsSettings READ customMavlinkActionsSettings CONSTANT) + + // Override from QGCTool virtual void setToolbox(QGCToolbox *toolbox); @@ -85,6 +89,8 @@ class SettingsManager : public QGCTool APMMavlinkStreamRateSettings* apmMavlinkStreamRateSettings(void) { return _apmMavlinkStreamRateSettings; } #endif RemoteIDSettings* remoteIDSettings (void) { return _remoteIDSettings; } + CustomMavlinkActionsSettings* customMavlinkActionsSettings(void) { return _customMavlinkActionsSettings; } + private: AppSettings* _appSettings; UnitsSettings* _unitsSettings; @@ -106,6 +112,7 @@ class SettingsManager : public QGCTool APMMavlinkStreamRateSettings* _apmMavlinkStreamRateSettings; #endif RemoteIDSettings* _remoteIDSettings; + CustomMavlinkActionsSettings* _customMavlinkActionsSettings; }; #endif diff --git a/src/ui/preferences/FlyViewSettings.qml b/src/ui/preferences/FlyViewSettings.qml index 29515b31a6e..a0e2e9d8a6b 100644 --- a/src/ui/preferences/FlyViewSettings.qml +++ b/src/ui/preferences/FlyViewSettings.qml @@ -20,21 +20,31 @@ import QGroundControl.Controls import QGroundControl.ScreenTools import QGroundControl.MultiVehicleManager import QGroundControl.Palette +import QGroundControl.Controllers SettingsPage { property var _settingsManager: QGroundControl.settingsManager + property var _flyViewSettings: _settingsManager.flyViewSettings + property var _customMavlinkActionsSettings: _settingsManager.customMavlinkActionsSettings property Fact _virtualJoystick: _settingsManager.appSettings.virtualJoystick property Fact _virtualJoystickAutoCenterThrottle: _settingsManager.appSettings.virtualJoystickAutoCenterThrottle - property Fact _showAdditionalIndicatorsCompass: _settingsManager.flyViewSettings.showAdditionalIndicatorsCompass - property Fact _lockNoseUpCompass: _settingsManager.flyViewSettings.lockNoseUpCompass - property Fact _guidedMinimumAltitude: _settingsManager.flyViewSettings.guidedMinimumAltitude - property Fact _guidedMaximumAltitude: _settingsManager.flyViewSettings.guidedMaximumAltitude - property Fact _maxGoToLocationDistance: _settingsManager.flyViewSettings.maxGoToLocationDistance + property Fact _showAdditionalIndicatorsCompass: _flyViewSettings.showAdditionalIndicatorsCompass + property Fact _lockNoseUpCompass: _flyViewSettings.lockNoseUpCompass + property Fact _guidedMinimumAltitude: _flyViewSettings.guidedMinimumAltitude + property Fact _guidedMaximumAltitude: _flyViewSettings.guidedMaximumAltitude + property Fact _maxGoToLocationDistance: _flyViewSettings.maxGoToLocationDistance property Fact _viewer3DEnabled: _settingsManager.viewer3DSettings.enabled property Fact _viewer3DOsmFilePath: _settingsManager.viewer3DSettings.osmFilePath property Fact _viewer3DBuildingLevelHeight: _settingsManager.viewer3DSettings.buildingLevelHeight property Fact _viewer3DAltitudeBias: _settingsManager.viewer3DSettings.altitudeBias + QGCFileDialogController { id: fileController } + + function customActionList() { + var fileModel = fileController.getFiles(_settingsManager.appSettings.customActionsSavePath, "*.json") + fileModel.unshift(qsTr("")) + return fileModel + } SettingsGroupLayout { Layout.fillWidth: true @@ -63,7 +73,7 @@ SettingsPage { text: qsTr("Keep Map Centered On Vehicle") fact: _keepMapCenteredOnVehicle visible: _keepMapCenteredOnVehicle.visible - property Fact _keepMapCenteredOnVehicle: _settingsManager.flyViewSettings.keepMapCenteredOnVehicle + property Fact _keepMapCenteredOnVehicle: _flyViewSettings.keepMapCenteredOnVehicle } FactCheckBoxSlider { @@ -71,7 +81,7 @@ SettingsPage { text: qsTr("Show Telemetry Log Replay Status Bar") fact: _showLogReplayStatusBar visible: _showLogReplayStatusBar.visible - property Fact _showLogReplayStatusBar: _settingsManager.flyViewSettings.showLogReplayStatusBar + property Fact _showLogReplayStatusBar: _flyViewSettings.showLogReplayStatusBar } FactCheckBoxSlider { @@ -80,7 +90,7 @@ SettingsPage { visible: _showDumbCameraControl.visible fact: _showDumbCameraControl - property Fact _showDumbCameraControl: _settingsManager.flyViewSettings.showSimpleCameraControl + property Fact _showDumbCameraControl: _flyViewSettings.showSimpleCameraControl } FactCheckBoxSlider { @@ -88,7 +98,7 @@ SettingsPage { text: qsTr("Update return to home position based on device location.") fact: _updateHomePosition visible: _updateHomePosition.visible - property Fact _updateHomePosition: _settingsManager.flyViewSettings.updateHomePosition + property Fact _updateHomePosition: _flyViewSettings.updateHomePosition } } @@ -119,6 +129,37 @@ SettingsPage { } } + SettingsGroupLayout { + Layout.fillWidth: true + Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 35 + heading: qsTr("Custom MAVLink Actions") + headingDescription: qsTr("Custom action JSON files should be created in the '%1' folder.").arg(QGroundControl.settingsManager.appSettings.customActionsSavePath) + + LabelledComboBox { + Layout.fillWidth: true + label: qsTr("Fly View Custom Actions") + model: customActionList() + onActivated: (index) => index == 0 ? _customMavlinkActionsSettings.flyViewActionsFile.rawValue = "" : _customMavlinkActionsSettings.flyViewActionsFile.rawValue = comboBox.currentText + + Component.onCompleted: { + var index = comboBox.find(_customMavlinkActionsSettings.flyViewActionsFile.valueString) + comboBox.currentIndex = index == -1 ? 0 : index + } + } + + LabelledComboBox { + Layout.fillWidth: true + label: qsTr("Joystick Custom Actions") + model: customActionList() + onActivated: (index) => index == 0 ? _customMavlinkActionsSettings.joystickActionsFile.rawValue = "" : _customMavlinkActionsSettings.joystickActionsFile.rawValue = comboBox.currentText + + Component.onCompleted: { + var index = comboBox.find(_customMavlinkActionsSettings.joystickActionsFile.valueString) + comboBox.currentIndex = index == -1 ? 0 : index + } + } + } + SettingsGroupLayout { Layout.fillWidth: true heading: qsTr("Virtual Joystick")