Commit da5d7f9b authored by Gus Grubba's avatar Gus Grubba

Merge branch 'master' of https://github.com/mavlink/qgroundcontrol into Airmap

parents d77583a0 8c7e5780
......@@ -2,9 +2,103 @@ pipeline {
agent any
stages {
stage('build') {
steps {
sh 'git status'
parallel {
stage('Linux Debug') {
environment {
CCACHE_BASEDIR = "${env.WORKSPACE}"
QGC_CONFIG = 'debug'
QMAKE_VER = "5.9.2/gcc_64/bin/qmake"
}
agent {
docker {
image 'mavlink/qgc-build-linux'
args '-v ${CCACHE_DIR}:${CCACHE_DIR}:rw'
}
}
steps {
sh 'export'
sh 'ccache -z'
sh 'git submodule deinit -f .'
sh 'git clean -ff -x -d .'
sh 'git submodule update --init --recursive --force'
sh 'mkdir build; cd build; ${QT_PATH}/${QMAKE_VER} -r ${WORKSPACE}/qgroundcontrol.pro CONFIG+=${QGC_CONFIG} CONFIG+=WarningsAsErrorsOn'
sh 'cd build; make -j`nproc --all`'
sh 'ccache -s'
}
}
stage('Linux Release') {
environment {
CCACHE_BASEDIR = "${env.WORKSPACE}"
QGC_CONFIG = 'release'
QMAKE_VER = "5.9.2/gcc_64/bin/qmake"
}
agent {
docker {
image 'mavlink/qgc-build-linux'
args '-v ${CCACHE_DIR}:${CCACHE_DIR}:rw'
}
}
steps {
sh 'export'
sh 'ccache -z'
sh 'git submodule deinit -f .'
sh 'git clean -ff -x -d .'
sh 'git submodule update --init --recursive --force'
sh 'mkdir build; cd build; ${QT_PATH}/${QMAKE_VER} -r ${WORKSPACE}/qgroundcontrol.pro CONFIG+=${QGC_CONFIG} CONFIG+=WarningsAsErrorsOn'
sh 'cd build; make -j`nproc --all`'
sh 'ccache -s'
}
}
stage('OSX Debug') {
agent {
node {
label 'mac'
}
}
environment {
CCACHE_BASEDIR = "${env.WORKSPACE}"
QGC_CONFIG = 'debug'
QMAKE_VER = "5.9.3/clang_64/bin/qmake"
}
steps {
sh 'export'
sh 'ccache -z'
sh 'git submodule deinit -f .'
sh 'git clean -ff -x -d .'
sh 'git submodule update --init --recursive --force'
sh 'mkdir build; cd build; ${QT_PATH}/${QMAKE_VER} -r ${WORKSPACE}/qgroundcontrol.pro CONFIG+=${QGC_CONFIG} CONFIG+=WarningsAsErrorsOn'
sh 'cd build; make -j`sysctl -n hw.ncpu`'
sh 'ccache -s'
}
}
stage('OSX Release') {
agent {
node {
label 'mac'
}
}
environment {
CCACHE_BASEDIR = "${env.WORKSPACE}"
QGC_CONFIG = 'release'
QMAKE_VER = "5.9.3/clang_64/bin/qmake"
}
steps {
sh 'export'
sh 'ccache -z'
sh 'git submodule deinit -f .'
sh 'git clean -ff -x -d .'
sh 'git submodule update --init --recursive --force'
sh 'mkdir build; cd build; ${QT_PATH}/${QMAKE_VER} -r ${WORKSPACE}/qgroundcontrol.pro CONFIG+=${QGC_CONFIG} CONFIG+=WarningsAsErrorsOn'
sh 'cd build; make -j`sysctl -n hw.ncpu`'
sh 'ccache -s'
}
}
}
}
}
}
\ No newline at end of file
environment {
CCACHE_CPP2 = '1'
CCACHE_DIR = '/tmp/ccache'
QT_FATAL_WARNINGS = '1'
}
}
......@@ -9,9 +9,10 @@
<file alias="MavCmdInfoVTOL.json">src/MissionManager/UnitTest/MavCmdInfoVTOL.json</file>
<file alias="MissionPlanner.waypoints">src/MissionManager/UnitTest/MissionPlanner.waypoints</file>
<file alias="OldFileFormat.mission">src/MissionManager/UnitTest/OldFileFormat.mission</file>
<file alias="GoodPolygon.kml">src/MissionManager/UnitTest/GoodPolygon.kml</file>
<file alias="MissingPolygonNode.kml">src/MissionManager/UnitTest/MissingPolygonNode.kml</file>
<file alias="BadXml.kml">src/MissionManager/UnitTest/BadXml.kml</file>
<file alias="BadCoordinatesNode.kml">src/MissionManager/UnitTest/BadCoordinatesNode.kml</file>
<file alias="PolygonAreaTest.kml">src/MissionManager/UnitTest/PolygonAreaTest.kml</file>
<file alias="PolygonGood.kml">src/MissionManager/UnitTest/PolygonGood.kml</file>
<file alias="PolygonMissingNode.kml">src/MissionManager/UnitTest/PolygonMissingNode.kml</file>
<file alias="PolygonBadXml.kml">src/MissionManager/UnitTest/PolygonBadXml.kml</file>
<file alias="PolygonBadCoordinatesNode.kml">src/MissionManager/UnitTest/PolygonBadCoordinatesNode.kml</file>
</qresource>
</RCC>
Subproject commit a31f7d989dffc2e554c26ad2c22c2a432a48fa74
Subproject commit f36b9c4c5c0c9b6d33621779469de0c1e7eea457
......@@ -7,14 +7,16 @@
# License terms set in COPYING.md
# -------------------------------------------------
QMAKE_PROJECT_DEPTH = 0 # undocumented qmake flag to force absolute paths in make files
exists($${OUT_PWD}/qgroundcontrol.pro) {
error("You must use shadow build (e.g. mkdir build; cd build; qmake ../qgroundcontrol.pro).")
}
message(Qt version $$[QT_VERSION])
!equals(QT_MAJOR_VERSION, 5) | !greaterThan(QT_MINOR_VERSION, 6) {
error("Unsupported Qt version, 5.7+ is required")
!equals(QT_MAJOR_VERSION, 5) | !greaterThan(QT_MINOR_VERSION, 8) {
error("Unsupported Qt version, 5.9+ is required")
}
include(QGCCommon.pri)
......@@ -207,8 +209,7 @@ contains(DEFINES, ENABLE_VERBOSE_OUTPUT) {
} else:exists(user_config.pri):infile(user_config.pri, DEFINES, ENABLE_VERBOSE_OUTPUT) {
message("Enable verbose compiler output (manual override from user_config.pri)")
} else {
CONFIG += \
silent
CONFIG += silent
}
QT += \
......@@ -414,7 +415,9 @@ DebugBuild { PX4FirmwarePlugin { PX4FirmwarePluginFactory { APMFirmwarePlugin {
src/FactSystem/FactSystemTestGeneric.h \
src/FactSystem/FactSystemTestPX4.h \
src/FactSystem/ParameterManagerTest.h \
src/MissionManager/CameraCalcTest.h \
src/MissionManager/CameraSectionTest.h \
src/MissionManager/CorridorScanComplexItemTest.h \
src/MissionManager/MissionCommandTreeTest.h \
src/MissionManager/MissionControllerManagerTest.h \
src/MissionManager/MissionControllerTest.h \
......@@ -423,11 +426,13 @@ DebugBuild { PX4FirmwarePlugin { PX4FirmwarePluginFactory { APMFirmwarePlugin {
src/MissionManager/MissionSettingsTest.h \
src/MissionManager/PlanMasterControllerTest.h \
src/MissionManager/QGCMapPolygonTest.h \
src/MissionManager/QGCMapPolylineTest.h \
src/MissionManager/SectionTest.h \
src/MissionManager/SimpleMissionItemTest.h \
src/MissionManager/SpeedSectionTest.h \
src/MissionManager/StructureScanComplexItemTest.h \
src/MissionManager/SurveyMissionItemTest.h \
src/MissionManager/TransectStyleComplexItemTest.h \
src/MissionManager/VisualMissionItemTest.h \
src/qgcunittest/FileDialogTest.h \
src/qgcunittest/FileManagerTest.h \
......@@ -451,7 +456,9 @@ DebugBuild { PX4FirmwarePlugin { PX4FirmwarePluginFactory { APMFirmwarePlugin {
src/FactSystem/FactSystemTestGeneric.cc \
src/FactSystem/FactSystemTestPX4.cc \
src/FactSystem/ParameterManagerTest.cc \
src/MissionManager/CameraCalcTest.cc \
src/MissionManager/CameraSectionTest.cc \
src/MissionManager/CorridorScanComplexItemTest.cc \
src/MissionManager/MissionCommandTreeTest.cc \
src/MissionManager/MissionControllerManagerTest.cc \
src/MissionManager/MissionControllerTest.cc \
......@@ -460,11 +467,13 @@ DebugBuild { PX4FirmwarePlugin { PX4FirmwarePluginFactory { APMFirmwarePlugin {
src/MissionManager/MissionSettingsTest.cc \
src/MissionManager/PlanMasterControllerTest.cc \
src/MissionManager/QGCMapPolygonTest.cc \
src/MissionManager/QGCMapPolylineTest.cc \
src/MissionManager/SectionTest.cc \
src/MissionManager/SimpleMissionItemTest.cc \
src/MissionManager/SpeedSectionTest.cc \
src/MissionManager/StructureScanComplexItemTest.cc \
src/MissionManager/SurveyMissionItemTest.cc \
src/MissionManager/TransectStyleComplexItemTest.cc \
src/MissionManager/VisualMissionItemTest.cc \
src/qgcunittest/FileDialogTest.cc \
src/qgcunittest/FileManagerTest.cc \
......@@ -508,6 +517,7 @@ HEADERS += \
src/MissionManager/CameraSection.h \
src/MissionManager/CameraSpec.h \
src/MissionManager/ComplexMissionItem.h \
src/MissionManager/CorridorScanComplexItem.h \
src/MissionManager/FixedWingLandingComplexItem.h \
src/MissionManager/GeoFenceController.h \
src/MissionManager/GeoFenceManager.h \
......@@ -526,6 +536,7 @@ HEADERS += \
src/MissionManager/QGCFencePolygon.h \
src/MissionManager/QGCMapCircle.h \
src/MissionManager/QGCMapPolygon.h \
src/MissionManager/QGCMapPolyline.h \
src/MissionManager/RallyPoint.h \
src/MissionManager/RallyPointController.h \
src/MissionManager/RallyPointManager.h \
......@@ -534,6 +545,7 @@ HEADERS += \
src/MissionManager/SpeedSection.h \
src/MissionManager/StructureScanComplexItem.h \
src/MissionManager/SurveyMissionItem.h \
src/MissionManager/TransectStyleComplexItem.h \
src/MissionManager/VisualMissionItem.h \
src/PositionManager/PositionManager.h \
src/PositionManager/SimulatedPosition.h \
......@@ -701,6 +713,7 @@ SOURCES += \
src/MissionManager/CameraSection.cc \
src/MissionManager/CameraSpec.cc \
src/MissionManager/ComplexMissionItem.cc \
src/MissionManager/CorridorScanComplexItem.cc \
src/MissionManager/FixedWingLandingComplexItem.cc \
src/MissionManager/GeoFenceController.cc \
src/MissionManager/GeoFenceManager.cc \
......@@ -719,6 +732,7 @@ SOURCES += \
src/MissionManager/QGCFencePolygon.cc \
src/MissionManager/QGCMapCircle.cc \
src/MissionManager/QGCMapPolygon.cc \
src/MissionManager/QGCMapPolyline.cc \
src/MissionManager/RallyPoint.cc \
src/MissionManager/RallyPointController.cc \
src/MissionManager/RallyPointManager.cc \
......@@ -726,6 +740,7 @@ SOURCES += \
src/MissionManager/SpeedSection.cc \
src/MissionManager/StructureScanComplexItem.cc \
src/MissionManager/SurveyMissionItem.cc \
src/MissionManager/TransectStyleComplexItem.cc \
src/MissionManager/VisualMissionItem.cc \
src/PositionManager/PositionManager.cpp \
src/PositionManager/SimulatedPosition.cc \
......
......@@ -18,6 +18,7 @@
<file alias="AnalyzeView.qml">src/AnalyzeView/AnalyzeView.qml</file>
<file alias="AppSettings.qml">src/ui/AppSettings.qml</file>
<file alias="BluetoothSettings.qml">src/ui/preferences/BluetoothSettings.qml</file>
<file alias="CorridorScanEditor.qml">src/PlanView/CorridorScanEditor.qml</file>
<file alias="CustomCommandWidget.qml">src/ViewWidgets/CustomCommandWidget.qml</file>
<file alias="DebugWindow.qml">src/ui/preferences/DebugWindow.qml</file>
<file alias="ESP8266Component.qml">src/AutoPilotPlugins/Common/ESP8266Component.qml</file>
......@@ -48,6 +49,7 @@
<file alias="QGroundControl/Controls/CameraCalc.qml">src/PlanView/CameraCalc.qml</file>
<file alias="QGroundControl/Controls/CameraSection.qml">src/PlanView/CameraSection.qml</file>
<file alias="QGroundControl/Controls/ClickableColor.qml">src/QmlControls/ClickableColor.qml</file>
<file alias="QGroundControl/Controls/CorridorScanMapVisual.qml">src/PlanView/CorridorScanMapVisual.qml</file>
<file alias="QGroundControl/Controls/DropButton.qml">src/QmlControls/DropButton.qml</file>
<file alias="QGroundControl/Controls/EditPositionDialog.qml">src/QmlControls/EditPositionDialog.qml</file>
<file alias="QGroundControl/Controls/ExclusiveGroupItem.qml">src/QmlControls/ExclusiveGroupItem.qml</file>
......@@ -89,6 +91,7 @@
<file alias="QGroundControl/Controls/QGCMapLabel.qml">src/QmlControls/QGCMapLabel.qml</file>
<file alias="QGroundControl/Controls/QGCMapCircleVisuals.qml">src/MissionManager/QGCMapCircleVisuals.qml</file>
<file alias="QGroundControl/Controls/QGCMapPolygonVisuals.qml">src/MissionManager/QGCMapPolygonVisuals.qml</file>
<file alias="QGroundControl/Controls/QGCMapPolylineVisuals.qml">src/MissionManager/QGCMapPolylineVisuals.qml</file>
<file alias="QGroundControl/Controls/QGCMouseArea.qml">src/QmlControls/QGCMouseArea.qml</file>
<file alias="QGroundControl/Controls/QGCMovableItem.qml">src/QmlControls/QGCMovableItem.qml</file>
<file alias="QGroundControl/Controls/QGCPipable.qml">src/QmlControls/QGCPipable.qml</file>
......@@ -201,7 +204,9 @@
<file alias="Guided.SettingsGroup.json">src/Settings/Guided.SettingsGroup.json</file>
<file alias="QGCMapCircle.Facts.json">src/MissionManager/QGCMapCircle.Facts.json</file>
<file alias="RTK.SettingsGroup.json">src/Settings/RTK.SettingsGroup.json</file>
<file alias="TransectStyle.SettingsGroup.json">src/MissionManager/TransectStyle.SettingsGroup.json</file>
<file alias="Survey.SettingsGroup.json">src/MissionManager/Survey.SettingsGroup.json</file>
<file alias="CorridorScan.SettingsGroup.json">src/MissionManager/CorridorScan.SettingsGroup.json</file>
<file alias="StructureScan.SettingsGroup.json">src/MissionManager/StructureScan.SettingsGroup.json</file>
<file alias="Units.SettingsGroup.json">src/Settings/Units.SettingsGroup.json</file>
<file alias="Video.SettingsGroup.json">src/Settings/Video.SettingsGroup.json</file>
......
......@@ -123,23 +123,23 @@ RadioComponentController::~RadioComponentController()
/// @brief Returns the state machine entry for the specified state.
const RadioComponentController::stateMachineEntry* RadioComponentController::_getStateMachineEntry(int step) const
{
static const char* msgBeginPX4 = "Lower the Throttle stick all the way down as shown in diagram.\n\n"
static const char* msgBeginPX4 = QT_TR_NOOP("Lower the Throttle stick all the way down as shown in diagram.\n\n"
"It is recommended to disconnect all motors for additional safety, however, the system is designed to not arm during the calibration.\n\n"
"Click Next to continue";
static const char* msgBeginAPM = "Lower the Throttle stick all the way down as shown in diagram.\nReset all transmitter trims to center.\n\n"
"Click Next to continue");
static const char* msgBeginAPM = QT_TR_NOOP("Lower the Throttle stick all the way down as shown in diagram.\nReset all transmitter trims to center.\n\n"
"Please ensure all motor power is disconnected AND all props are removed from the vehicle.\n\n"
"Click Next to continue";
static const char* msgThrottleUp = "Move the Throttle stick all the way up and hold it there...";
static const char* msgThrottleDown = "Move the Throttle stick all the way down and leave it there...";
static const char* msgYawLeft = "Move the Yaw stick all the way to the left and hold it there...";
static const char* msgYawRight = "Move the Yaw stick all the way to the right and hold it there...";
static const char* msgRollLeft = "Move the Roll stick all the way to the left and hold it there...";
static const char* msgRollRight = "Move the Roll stick all the way to the right and hold it there...";
static const char* msgPitchDown = "Move the Pitch stick all the way down and hold it there...";
static const char* msgPitchUp = "Move the Pitch stick all the way up and hold it there...";
static const char* msgPitchCenter = "Allow the Pitch stick to move back to center...";
static const char* msgSwitchMinMax = "Move all the transmitter switches and/or dials back and forth to their extreme positions.";
static const char* msgComplete = "All settings have been captured. Click Next to write the new parameters to your board.";
"Click Next to continue");
static const char* msgThrottleUp = QT_TR_NOOP("Move the Throttle stick all the way up and hold it there...");
static const char* msgThrottleDown = QT_TR_NOOP("Move the Throttle stick all the way down and leave it there...");
static const char* msgYawLeft = QT_TR_NOOP("Move the Yaw stick all the way to the left and hold it there...");
static const char* msgYawRight = QT_TR_NOOP("Move the Yaw stick all the way to the right and hold it there...");
static const char* msgRollLeft = QT_TR_NOOP("Move the Roll stick all the way to the left and hold it there...");
static const char* msgRollRight = QT_TR_NOOP("Move the Roll stick all the way to the right and hold it there...");
static const char* msgPitchDown = QT_TR_NOOP("Move the Pitch stick all the way down and hold it there...");
static const char* msgPitchUp = QT_TR_NOOP("Move the Pitch stick all the way up and hold it there...");
static const char* msgPitchCenter = QT_TR_NOOP("Allow the Pitch stick to move back to center...");
static const char* msgSwitchMinMax = QT_TR_NOOP("Move all the transmitter switches and/or dials back and forth to their extreme positions.");
static const char* msgComplete = QT_TR_NOOP("All settings have been captured. Click Next to write the new parameters to your board.");
static const stateMachineEntry rgStateMachinePX4[] = {
//Function
......@@ -206,9 +206,9 @@ void RadioComponentController::_advanceState(void)
/// @brief Sets up the state machine according to the current step from _currentStep.
void RadioComponentController::_setupCurrentState(void)
{
static const char* msgBeginAPMRover = "Center the Throttle stick as shown in diagram.\nReset all transmitter trims to center.\n\n"
static const char* msgBeginAPMRover = QT_TR_NOOP("Center the Throttle stick as shown in diagram.\nReset all transmitter trims to center.\n\n"
"Please ensure all motor power is disconnected from the vehicle.\n\n"
"Click Next to continue";
"Click Next to continue");
const stateMachineEntry* state = _getStateMachineEntry(_currentStep);
const char* instructions = state->instructions;
......
......@@ -81,7 +81,17 @@ ParameterManager::ParameterManager(Vehicle* vehicle)
// Ensure the cache directory exists
QFileInfo(QSettings().fileName()).dir().mkdir("ParamCache");
refreshAllParameters();
if (_vehicle->highLatencyLink()) {
// High latency links don't load parameters
_parametersReady = true;
_missingParameters = true;
_initialLoadComplete = true;
_waitingForDefaultComponent = false;
emit parametersReadyChanged(_parametersReady);
emit missingParametersChanged(_missingParameters);
} else {
refreshAllParameters();
}
}
ParameterManager::~ParameterManager()
......@@ -133,12 +143,12 @@ void ParameterManager::_parameterUpdate(int vehicleId, int componentId, QString
}
#endif
if (_vehicle->px4Firmware() && parameterName == "_HASH_CHECK") {
if (!_initialLoadComplete && !_logReplay) {
/* we received a cache hash, potentially load from cache */
_tryCacheHashLoad(vehicleId, componentId, value);
}
return;
if (_vehicle->px4Firmware() && parameterName == "_HASH_CHECK") {
if (!_initialLoadComplete && !_logReplay) {
/* we received a cache hash, potentially load from cache */
_tryCacheHashLoad(vehicleId, componentId, value);
}
return;
}
_initialRequestTimeoutTimer.stop();
......
......@@ -620,4 +620,9 @@ QGCCameraControl* FirmwarePlugin::createCameraControl(const mavlink_camera_infor
return NULL;
}
uint32_t FirmwarePlugin::highLatencyCustomModeTo32Bits(uint16_t hlCustomMode)
{
// Standard implementation assumes no special handling. Upper part of 32 bit value is not used.
return hlCustomMode;
}
......@@ -304,6 +304,9 @@ public:
/// Returns true if the vehicle is a VTOL
virtual bool isVtol(const Vehicle* vehicle) const;
/// Convert from HIGH_LATENCY2.custom_mode value to correct 32 bit value.
virtual uint32_t highLatencyCustomModeTo32Bits(uint16_t hlCustomMode);
// FIXME: Hack workaround for non pluginize FollowMe support
static const QString px4FollowMeFlightMode;
......
......@@ -589,3 +589,12 @@ QGCCameraControl* PX4FirmwarePlugin::createCameraControl(const mavlink_camera_in
{
return new QGCCameraControl(info, vehicle, compID, parent);
}
uint32_t PX4FirmwarePlugin::highLatencyCustomModeTo32Bits(uint16_t hlCustomMode)
{
union px4_custom_mode px4_cm;
px4_cm.data = 0;
px4_cm.custom_mode_hl = hlCustomMode;
return px4_cm.data;
}
......@@ -70,6 +70,7 @@ public:
QString autoDisarmParameter (Vehicle* vehicle) override { Q_UNUSED(vehicle); return QStringLiteral("COM_DISARM_LAND"); }
QGCCameraManager* createCameraManager (Vehicle* vehicle) override;
QGCCameraControl* createCameraControl (const mavlink_camera_information_t* info, Vehicle* vehicle, int compID, QObject* parent = NULL) override;
uint32_t highLatencyCustomModeTo32Bits (uint16_t hlCustomMode) override;
protected:
typedef struct {
......
......@@ -1119,7 +1119,9 @@ This parameter controls the time constant of the decay</short_desc>
</parameter>
<parameter default="0.0" name="EKF2_ARSP_THR" type="FLOAT">
<short_desc>Airspeed fusion threshold. A value of zero will deactivate airspeed fusion. Any other positive
value will determine the minimum airspeed which will still be fused</short_desc>
value will determine the minimum airspeed which will still be fused. Set to about 90% of the vehicles stall speed.
Both airspeed fusion and sideslip fusion must be active for the EKF to continue navigating after loss of GPS.
Use EKF2_FUSE_BETA to activate sideslip fusion</short_desc>
<min>0.0</min>
<unit>m/s</unit>
<decimal>1</decimal>
......@@ -1291,7 +1293,7 @@ Increasing it makes the multi-rotor wind estimates adjust more slowly</short_des
</parameter>
<parameter default="0" name="EKF2_FUSE_BETA" type="INT32">
<short_desc>Boolean determining if synthetic sideslip measurements should fused</short_desc>
<long_desc>A value of 1 indicates that fusion is active</long_desc>
<long_desc>A value of 1 indicates that fusion is active Both sideslip fusion and airspeed fusion must be active for the EKF to continue navigating after loss of GPS. Use EKF2_ARSP_THR to activate airspeed fusion.</long_desc>
<boolean />
<scope>modules/ekf2</scope>
</parameter>
......@@ -1653,7 +1655,8 @@ Assumes measurement is timestamped at trailing edge of integration period</short
<scope>modules/ekf2</scope>
</parameter>
<parameter default="2.5" name="EKF2_OF_RMAX" type="FLOAT">
<short_desc>Optical Flow data will not fused if the magnitude of the flow rate &gt; EKF2_OF_RMAX</short_desc>
<short_desc>Optical Flow data will not fused if the magnitude of the flow rate &gt; EKF2_OF_RMAX.
Control loops will be instructed to limit ground speed such that the flow rate produced by movement over ground is less than 50% of EKF2_OF_RMAX</short_desc>
<min>1.0</min>
<unit>rad/s</unit>
<decimal>2</decimal>
......@@ -2480,7 +2483,7 @@ Set to 0 to disable heading hold</short_desc>
<parameter default="0" name="LAUN_ALL_ON" type="INT32">
<short_desc>Launch detection</short_desc>
<boolean />
<scope>lib/launchdetection</scope>
<scope>modules/fw_pos_control_l1/launchdetection</scope>
</parameter>
<parameter default="30.0" name="LAUN_CAT_A" type="FLOAT">
<short_desc>Catapult accelerometer threshold</short_desc>
......@@ -2489,7 +2492,7 @@ Set to 0 to disable heading hold</short_desc>
<unit>m/s/s</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/launchdetection</scope>
<scope>modules/fw_pos_control_l1/launchdetection</scope>
</parameter>
<parameter default="0.0" name="LAUN_CAT_MDEL" type="FLOAT">
<short_desc>Motor delay</short_desc>
......@@ -2499,7 +2502,7 @@ Set to 0 to disable heading hold</short_desc>
<unit>s</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/launchdetection</scope>
<scope>modules/fw_pos_control_l1/launchdetection</scope>
</parameter>
<parameter default="30.0" name="LAUN_CAT_PMAX" type="FLOAT">
<short_desc>Maximum pitch before the throttle is powered up (during motor delay phase)</short_desc>
......@@ -2509,7 +2512,7 @@ Set to 0 to disable heading hold</short_desc>
<unit>deg</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/launchdetection</scope>
<scope>modules/fw_pos_control_l1/launchdetection</scope>
</parameter>
<parameter default="0.05" name="LAUN_CAT_T" type="FLOAT">
<short_desc>Catapult time threshold</short_desc>
......@@ -2519,7 +2522,7 @@ Set to 0 to disable heading hold</short_desc>
<unit>s</unit>
<decimal>2</decimal>
<increment>0.05</increment>
<scope>lib/launchdetection</scope>
<scope>modules/fw_pos_control_l1/launchdetection</scope>
</parameter>
</group>
<group name="FW TECS">
......@@ -4456,6 +4459,17 @@ default 1.5 turns per second</short_desc>
<increment>1</increment>
<scope>modules/mc_pos_control</scope>
</parameter>
<parameter default="0.5" name="MPC_ACC_HOR_FLOW" type="FLOAT">
<short_desc>Horizontal acceleration in manual modes when optical flow ground speed limit is removed.
If full stick is being applied and the EKF starts using GPS whilst using optical flow,
the vehicle will accelerate at this rate until the normal position control speed is achieved</short_desc>
<min>0.2</min>
<max>2.0</max>
<unit>m/s/s</unit>
<decimal>1</decimal>
<increment>0.1</increment>
<scope>modules/mc_pos_control</scope>
</parameter>
<parameter default="10.0" name="MPC_ACC_HOR_MAX" type="FLOAT">
<short_desc>Maximum horizontal acceleration for auto mode and maximum deceleration for manual mode</short_desc>
<min>2.0</min>
......@@ -7253,14 +7267,14 @@ FW_AIRSPD_MIN * RWTO_AIRSPD_SCL</short_desc>
<unit>norm</unit>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="0" name="RWTO_HDG" type="INT32">
<short_desc>Specifies which heading should be held during runnway takeoff</short_desc>
<long_desc>0: airframe heading, 1: heading towards takeoff waypoint</long_desc>
<min>0</min>
<max>1</max>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
<values>
<value code="0">Airframe</value>
<value code="1">Waypoint</value>
......@@ -7275,7 +7289,7 @@ pitch of 10 degrees during takeoff, so this must be larger if set</short_desc>
<unit>deg</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="25.0" name="RWTO_MAX_ROLL" type="FLOAT">
<short_desc>Max roll during climbout.
......@@ -7286,7 +7300,7 @@ navigation before we're on a safe height</short_desc>
<unit>deg</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="1.0" name="RWTO_MAX_THR" type="FLOAT">
<short_desc>Max throttle during runway takeoff.
......@@ -7296,7 +7310,7 @@ navigation before we're on a safe height</short_desc>
<unit>norm</unit>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="5.0" name="RWTO_NAV_ALT" type="FLOAT">
<short_desc>Altitude AGL at which we have enough ground clearance to allow some roll.
......@@ -7308,7 +7322,7 @@ FW_CLMBOUT_DIFF if FW_CLMBOUT_DIFF &gt; 0</short_desc>
<unit>m</unit>
<decimal>1</decimal>
<increment>1</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="0.0" name="RWTO_PSP" type="FLOAT">
<short_desc>Pitch setpoint during taxi / before takeoff airspeed is reached.
......@@ -7320,12 +7334,12 @@ to takeoff is reached</short_desc>
<unit>deg</unit>
<decimal>1</decimal>
<increment>0.5</increment>
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
<parameter default="0" name="RWTO_TKOFF" type="INT32">
<short_desc>Runway takeoff with landing gear</short_desc>
<boolean />
<scope>lib/runway_takeoff</scope>
<scope>modules/fw_pos_control_l1/runway_takeoff</scope>
</parameter>
</group>
<group name="SD Logging">
......@@ -7966,6 +7980,11 @@ This is used for gathering replay logs for the ekf2 module</short_desc>
<long_desc>The offset (zero-reading) in Pascal</long_desc>
<scope>modules/sensors</scope>
</parameter>
<parameter default="0.7" name="SENS_FLOW_MINRNG" type="FLOAT">
<short_desc>Optical Flow minimum focus distance</short_desc>
<long_desc>This parameter defines the minimum distance from ground required for the optical flow sensor to operate reliably. The sensor may be usable below this height, but accuracy will progressively reduce to loss of focus. *</long_desc>
<scope>modules/sensors</scope>
</parameter>
</group>
<group name="Sensors">
<parameter default="63" name="CAL_MAG_SIDES" type="INT32">
......@@ -8057,6 +8076,12 @@ This is used for gathering replay logs for the ekf2 module</short_desc>
<unit>deg</unit>
<scope>modules/sensors</scope>
</parameter>
<parameter default="0" name="SENS_EN_LEDDAR1" type="INT32">
<short_desc>LeddarOne rangefinder</short_desc>
<boolean />
<reboot_required>true</reboot_required>
<scope>modules/sensors</scope>
</parameter>
<parameter default="0" name="SENS_EN_LL40LS" type="INT32">
<short_desc>Lidar-Lite (LL40LS)</short_desc>
<min>0</min>
......@@ -8384,6 +8409,10 @@ This is used for gathering replay logs for the ekf2 module</short_desc>
<short_desc>TEST_2</short_desc>
<scope>systemcmds/tests</scope>
</parameter>
<parameter default="5.0" name="TEST_3" type="FLOAT">
<short_desc>TEST_3</short_desc>
<scope>systemcmds/tests</scope>
</parameter>
<parameter default="0.01" name="TEST_D" type="FLOAT">
<short_desc>TEST_D</short_desc>
<scope>lib/controllib/controllib_test</scope>
......
......@@ -71,7 +71,11 @@ union px4_custom_mode {
uint8_t main_mode;
uint8_t sub_mode;
};
uint32_t data;
struct {
uint16_t reserved_hl;
uint16_t custom_mode_hl;
};
uint32_t data;
float data_float;
};
......
......@@ -26,23 +26,26 @@ import QGroundControl.FactControls 1.0
/// Camera page for Instrument Panel PageView
Column {
width: pageWidth
spacing: ScreenTools.defaultFontPixelHeight
spacing: ScreenTools.defaultFontPixelHeight * 0.25
property bool showSettingsIcon: _camera !== null
property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
property var _dynamicCameras: _activeVehicle ? _activeVehicle.dynamicCameras : null
property bool _isCamera: _dynamicCameras ? _dynamicCameras.cameras.count > 0 : false
property var _camera: _isCamera ? _dynamicCameras.cameras.get(0) : null // Single camera support for the time being
property bool _cameraModeUndefined: _isCamera ? _dynamicCameras.cameras.get(0).cameraMode === QGCCameraControl.CAMERA_MODE_UNDEFINED : true
property bool _cameraVideoMode: _isCamera ? _dynamicCameras.cameras.get(0).cameraMode === 1 : false
property bool _cameraPhotoMode: _isCamera ? _dynamicCameras.cameras.get(0).cameraMode === 0 : false
property var _camera: _isCamera ? _dynamicCameras.cameras.get(0) : null // Single camera support for the time being
property bool _cameraPhotoIdle: _isCamera && _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_IDLE
property bool _cameraElapsedMode: _isCamera && _camera.cameraMode === QGCCameraControl.CAM_MODE_PHOTO && _camera.photoMode === QGCCameraControl.PHOTO_CAPTURE_TIMELAPSE
property real _spacers: ScreenTools.defaultFontPixelHeight * 0.5
property real _labelFieldWidth: ScreenTools.defaultFontPixelWidth * 30
property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 30
property bool _communicationLost: _activeVehicle ? _activeVehicle.connectionLost : false
property bool _hasModes: _isCamera && _camera && _camera.hasModes
property bool _videoRecording: _camera && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING
property bool _noStorage: _camera && _camera.storageTotal === 0
function showSettings() {
qgcView.showDialog(cameraSettings, _cameraVideoMode ? qsTr("Video Settings") : qsTr("Camera Settings"), 70, StandardButton.Ok)
......@@ -60,13 +63,21 @@ Column {
//-- Actual controller
QGCLabel {
id: cameraLabel
text: _isCamera ? _dynamicCameras.cameras.get(0).modelName : qsTr("Camera")
text: _isCamera ? _camera.modelName : qsTr("Camera")
visible: _isCamera
font.pointSize: ScreenTools.smallFontPointSize
anchors.horizontalCenter: parent.horizontalCenter
}
QGCLabel {
text: _camera ? qsTr("Free Space: ") + _camera.storageFreeStr : ""
font.pointSize: ScreenTools.smallFontPointSize
anchors.horizontalCenter: parent.horizontalCenter
visible: _camera && !_noStorage
}
//-- Camera Mode (visible only if camera has modes)
Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.75; visible: camMode.visible; }
Rectangle {
id: camMode
width: _hasModes ? ScreenTools.defaultFontPixelWidth * 8 : 0
height: _hasModes ? ScreenTools.defaultFontPixelWidth * 4 : 0
color: qgcPal.button
......@@ -129,19 +140,21 @@ Column {
}
}
//-- Shutter
Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.75; visible: camShutter.visible; }
Rectangle {
id: camShutter
color: Qt.rgba(0,0,0,0)
width: ScreenTools.defaultFontPixelWidth * 6
height: width
radius: width * 0.5
visible: _isCamera
visible: _isCamera
border.color: qgcPal.buttonText
border.width: 3
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: parent.width * (_videoRecording ? 0.5 : 0.75)
width: parent.width * (_videoRecording || (_cameraPhotoMode && !_cameraPhotoIdle && _cameraElapsedMode) ? 0.5 : 0.75)
height: width
radius: _videoRecording ? 0 : width * 0.5
radius: _videoRecording || (_cameraPhotoMode && !_cameraPhotoIdle && _cameraElapsedMode) ? 0 : width * 0.5
color: _cameraModeUndefined ? qgcPal.colorGrey : qgcPal.colorRed
anchors.centerIn: parent
}
......@@ -152,11 +165,28 @@ Column {
if(_cameraVideoMode) {
_camera.toggleVideo()
} else {
_camera.takePhoto()
if(_cameraPhotoMode && !_cameraPhotoIdle && _cameraElapsedMode) {
_camera.stopTakePhoto()
} else {
_camera.takePhoto()
}
}
}
}
}
Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.75; visible: _isCamera; }
QGCLabel {
text: (_cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) ? _camera.recordTimeStr : "00:00:00"
font.pointSize: ScreenTools.smallFontPointSize
visible: _cameraVideoMode
anchors.horizontalCenter: parent.horizontalCenter
}
QGCLabel {
text: _activeVehicle && _cameraPhotoMode ? ('00000' + _activeVehicle.cameraTriggerPoints.count).slice(-5) : "00000"
font.pointSize: ScreenTools.smallFontPointSize
visible: _cameraPhotoMode
anchors.horizontalCenter: parent.horizontalCenter
}
Item { width: 1; height: ScreenTools.defaultFontPixelHeight; visible: _isCamera; }
Component {
id: cameraSettings
......@@ -232,6 +262,54 @@ Column {
}
}
//-------------------------------------------
//-- Time Lapse
Row {
spacing: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
visible: _cameraPhotoMode
property var photoModes: [qsTr("Single"), qsTr("Time Lapse")]
QGCLabel {
text: qsTr("Photo Mode")
width: _labelFieldWidth
anchors.verticalCenter: parent.verticalCenter
}
QGCComboBox {
id: photoModeCombo
width: _editFieldWidth
model: parent.photoModes
currentIndex: _camera ? _camera.photoMode : 0
onActivated: _camera.photoMode = index
}
}
//-------------------------------------------
//-- Time Lapse Interval
Row {
spacing: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
visible: _cameraPhotoMode && _camera.photoMode === QGCCameraControl.PHOTO_CAPTURE_TIMELAPSE
QGCLabel {
text: qsTr("Photo Interval (seconds)")
width: _labelFieldWidth
anchors.verticalCenter: parent.verticalCenter
}
Item {
height: photoModeCombo.height
width: _editFieldWidth
QGCSlider {
maximumValue: 60
minimumValue: 1
stepSize: 1
value: _camera ? _camera.photoLapse : 5
displayValue: true
updateValueWhileDragging: true
anchors.fill: parent
onValueChanged: {
_camera.photoLapse = value
}
}
}
}
//-------------------------------------------
//-- Reset Camera
Row {
spacing: ScreenTools.defaultFontPixelWidth
......
......@@ -14,15 +14,16 @@
#include <QQmlEngine>
const char* CameraCalc::_valueSetIsDistanceName = "ValueSetIsDistance";
const char* CameraCalc::_distanceToSurfaceName = "DistanceToSurface";
const char* CameraCalc::_imageDensityName = "ImageDensity";
const char* CameraCalc::_frontalOverlapName = "FrontalOverlap";
const char* CameraCalc::_sideOverlapName = "SideOverlap";
const char* CameraCalc::_adjustedFootprintFrontalName = "AdjustedFootprintFrontal";
const char* CameraCalc::_adjustedFootprintSideName = "AdjustedFootprintSide";
const char* CameraCalc::_jsonCameraNameKey = "CameraName";
const char* CameraCalc::_jsonCameraSpecTypeKey = "CameraSpecType";
const char* CameraCalc::_valueSetIsDistanceName = "ValueSetIsDistance";
const char* CameraCalc::_distanceToSurfaceName = "DistanceToSurface";
const char* CameraCalc::_distanceToSurfaceRelativeName = "DistanceToSurfaceRelative";
const char* CameraCalc::_imageDensityName = "ImageDensity";
const char* CameraCalc::_frontalOverlapName = "FrontalOverlap";
const char* CameraCalc::_sideOverlapName = "SideOverlap";
const char* CameraCalc::_adjustedFootprintFrontalName = "AdjustedFootprintFrontal";
const char* CameraCalc::_adjustedFootprintSideName = "AdjustedFootprintSide";
const char* CameraCalc::_jsonCameraNameKey = "CameraName";
const char* CameraCalc::_jsonCameraSpecTypeKey = "CameraSpecType";
CameraCalc::CameraCalc(Vehicle* vehicle, QObject* parent)
: CameraSpec (parent)
......@@ -30,6 +31,7 @@ CameraCalc::CameraCalc(Vehicle* vehicle, QObject* parent)
, _dirty (false)
, _cameraName (manualCameraName())
, _disableRecalc (false)
, _distanceToSurfaceRelative (true)
, _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CameraCalc.FactMetaData.json"), this))
, _valueSetIsDistanceFact (0, _valueSetIsDistanceName, FactMetaData::valueTypeBool)
, _distanceToSurfaceFact (0, _distanceToSurfaceName, FactMetaData::valueTypeDouble)
......@@ -52,7 +54,18 @@ CameraCalc::CameraCalc(Vehicle* vehicle, QObject* parent)
_adjustedFootprintSideFact.setMetaData (_metaDataMap[_adjustedFootprintSideName], true);
_adjustedFootprintFrontalFact.setMetaData (_metaDataMap[_adjustedFootprintFrontalName], true);
connect(&_valueSetIsDistanceFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_distanceToSurfaceFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_imageDensityFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_frontalOverlapFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_sideOverlapFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_adjustedFootprintSideFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(&_adjustedFootprintFrontalFact, &Fact::valueChanged, this, &CameraCalc::_setDirty);
connect(this, &CameraCalc::cameraNameChanged, this, &CameraCalc::_setDirty);
connect(this, &CameraCalc::distanceToSurfaceRelativeChanged, this, &CameraCalc::_setDirty);
connect(this, &CameraCalc::cameraNameChanged, this, &CameraCalc::_recalcTriggerDistance);
connect(this, &CameraCalc::cameraNameChanged, this, &CameraCalc::_adjustDistanceToSurfaceRelative);
connect(&_distanceToSurfaceFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance);
connect(&_imageDensityFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance);
......@@ -167,8 +180,9 @@ void CameraCalc::save(QJsonObject& json) const
json[JsonHelper::jsonVersionKey] = 1;
json[_adjustedFootprintSideName] = _adjustedFootprintSideFact.rawValue().toDouble();
json[_adjustedFootprintFrontalName] = _adjustedFootprintFrontalFact.rawValue().toDouble();
json[_distanceToSurfaceName] = _distanceToSurfaceFact.rawValue().toDouble();
json[_jsonCameraNameKey] = _cameraName;
json[_distanceToSurfaceName] = _distanceToSurfaceFact.rawValue().toDouble();
json[_distanceToSurfaceRelativeName] = _distanceToSurfaceRelative;
json[_jsonCameraNameKey] = _cameraName;
if (_cameraName != manualCameraName()) {
CameraSpec::save(json);
......@@ -209,6 +223,7 @@ bool CameraCalc::load(const QJsonObject& json, QString& errorString)
{ _adjustedFootprintSideName, QJsonValue::Double, true },
{ _adjustedFootprintFrontalName, QJsonValue::Double, true },
{ _distanceToSurfaceName, QJsonValue::Double, true },
{ _distanceToSurfaceRelativeName, QJsonValue::Bool, true },
};
if (!JsonHelper::validateKeys(v1Json, keyInfoList1, errorString)) {
return false;
......@@ -221,6 +236,8 @@ bool CameraCalc::load(const QJsonObject& json, QString& errorString)
_adjustedFootprintFrontalFact.setRawValue (v1Json[_adjustedFootprintFrontalName].toDouble());
_distanceToSurfaceFact.setRawValue (v1Json[_distanceToSurfaceName].toDouble());
_distanceToSurfaceRelative = v1Json[_distanceToSurfaceRelativeName].toBool();
if (_cameraName != manualCameraName()) {
QList<JsonHelper::KeyValidateInfo> keyInfoList2 = {
{ _valueSetIsDistanceName, QJsonValue::Bool, true },
......@@ -257,3 +274,23 @@ QString CameraCalc::manualCameraName(void)
{
return tr("Manual (no camera specs)");
}
void CameraCalc::_adjustDistanceToSurfaceRelative(void)
{
if (!isManualCamera()) {
setDistanceToSurfaceRelative(true);
}
}
void CameraCalc::setDistanceToSurfaceRelative(bool distanceToSurfaceRelative)
{
if (distanceToSurfaceRelative != _distanceToSurfaceRelative) {
_distanceToSurfaceRelative = distanceToSurfaceRelative;
emit distanceToSurfaceRelativeChanged(distanceToSurfaceRelative);
}
}
void CameraCalc::_setDirty(void)
{
setDirty(true);
}
......@@ -20,17 +20,18 @@ class CameraCalc : public CameraSpec
public:
CameraCalc(Vehicle* vehicle, QObject* parent = NULL);
Q_PROPERTY(QString cameraName READ cameraName WRITE setCameraName NOTIFY cameraNameChanged)
Q_PROPERTY(QString customCameraName READ customCameraName CONSTANT) ///< Camera name for custom camera setting
Q_PROPERTY(QString manualCameraName READ manualCameraName CONSTANT) ///< Camera name for manual camera setting
Q_PROPERTY(bool isManualCamera READ isManualCamera NOTIFY cameraNameChanged) ///< true: using manual camera
Q_PROPERTY(Fact* valueSetIsDistance READ valueSetIsDistance CONSTANT) ///< true: distance specified, resolution calculated
Q_PROPERTY(Fact* distanceToSurface READ distanceToSurface CONSTANT) ///< Distance to surface for image foot print calculation
Q_PROPERTY(Fact* imageDensity READ imageDensity CONSTANT) ///< Image density on surface (cm/px)
Q_PROPERTY(Fact* frontalOverlap READ frontalOverlap CONSTANT)
Q_PROPERTY(Fact* sideOverlap READ sideOverlap CONSTANT)
Q_PROPERTY(Fact* adjustedFootprintSide READ adjustedFootprintSide CONSTANT) ///< Side footprint adjusted down for overlap
Q_PROPERTY(Fact* adjustedFootprintFrontal READ adjustedFootprintFrontal CONSTANT) ///< Frontal footprint adjusted down for overlap
Q_PROPERTY(QString cameraName READ cameraName WRITE setCameraName NOTIFY cameraNameChanged)
Q_PROPERTY(QString customCameraName READ customCameraName CONSTANT) ///< Camera name for custom camera setting
Q_PROPERTY(QString manualCameraName READ manualCameraName CONSTANT) ///< Camera name for manual camera setting
Q_PROPERTY(bool isManualCamera READ isManualCamera NOTIFY cameraNameChanged) ///< true: using manual camera
Q_PROPERTY(Fact* valueSetIsDistance READ valueSetIsDistance CONSTANT) ///< true: distance specified, resolution calculated
Q_PROPERTY(Fact* distanceToSurface READ distanceToSurface CONSTANT) ///< Distance to surface for image foot print calculation
Q_PROPERTY(Fact* imageDensity READ imageDensity CONSTANT) ///< Image density on surface (cm/px)
Q_PROPERTY(Fact* frontalOverlap READ frontalOverlap CONSTANT)
Q_PROPERTY(Fact* sideOverlap READ sideOverlap CONSTANT)
Q_PROPERTY(Fact* adjustedFootprintSide READ adjustedFootprintSide CONSTANT) ///< Side footprint adjusted down for overlap
Q_PROPERTY(Fact* adjustedFootprintFrontal READ adjustedFootprintFrontal CONSTANT) ///< Frontal footprint adjusted down for overlap
Q_PROPERTY(bool distanceToSurfaceRelative READ distanceToSurfaceRelative WRITE setDistanceToSurfaceRelative NOTIFY distanceToSurfaceRelativeChanged)
// The following values are calculated from the camera properties
Q_PROPERTY(double imageFootprintSide READ imageFootprintSide NOTIFY imageFootprintSideChanged) ///< Size of image size side in meters
......@@ -49,30 +50,44 @@ public:
Fact* adjustedFootprintSide (void) { return &_adjustedFootprintSideFact; }
Fact* adjustedFootprintFrontal (void) { return &_adjustedFootprintFrontalFact; }
bool isManualCamera (void) { return cameraName() == manualCameraName(); }
double imageFootprintSide (void) const { return _imageFootprintSide; }
double imageFootprintFrontal (void) const { return _imageFootprintFrontal; }
const Fact* valueSetIsDistance (void) const { return &_valueSetIsDistanceFact; }
const Fact* distanceToSurface (void) const { return &_distanceToSurfaceFact; }
const Fact* imageDensity (void) const { return &_imageDensityFact; }
const Fact* frontalOverlap (void) const { return &_frontalOverlapFact; }
const Fact* sideOverlap (void) const { return &_sideOverlapFact; }
const Fact* adjustedFootprintSide (void) const { return &_adjustedFootprintSideFact; }
const Fact* adjustedFootprintFrontal (void) const { return &_adjustedFootprintFrontalFact; }
bool dirty (void) const { return _dirty; }
void setDirty (bool dirty);
bool dirty (void) const { return _dirty; }
bool isManualCamera (void) { return cameraName() == manualCameraName(); }
double imageFootprintSide (void) const { return _imageFootprintSide; }
double imageFootprintFrontal (void) const { return _imageFootprintFrontal; }
bool distanceToSurfaceRelative (void) const { return _distanceToSurfaceRelative; }
void setDirty (bool dirty);
void setDistanceToSurfaceRelative (bool distanceToSurfaceRelative);
void save(QJsonObject& json) const;
bool load(const QJsonObject& json, QString& errorString);
signals:
void cameraNameChanged (QString cameraName);
void dirtyChanged (bool dirty);
void imageFootprintSideChanged (double imageFootprintSide);
void imageFootprintFrontalChanged (double imageFootprintFrontal);
void cameraNameChanged (QString cameraName);
void dirtyChanged (bool dirty);
void imageFootprintSideChanged (double imageFootprintSide);
void imageFootprintFrontalChanged (double imageFootprintFrontal);
void distanceToSurfaceRelativeChanged (bool distanceToSurfaceRelative);
private slots:
void _recalcTriggerDistance(void);
void _recalcTriggerDistance (void);
void _adjustDistanceToSurfaceRelative (void);
void _setDirty (void);
private:
Vehicle* _vehicle;
bool _dirty;
QString _cameraName;
bool _disableRecalc;
bool _distanceToSurfaceRelative;
QMap<QString, FactMetaData*> _metaDataMap;
......@@ -91,6 +106,7 @@ private:
static const char* _valueSetIsDistanceName;
static const char* _distanceToSurfaceName;
static const char* _distanceToSurfaceRelativeName;
static const char* _imageDensityName;
static const char* _frontalOverlapName;
static const char* _sideOverlapName;
......
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "CameraCalcTest.h"
#include "QGCApplication.h"
CameraCalcTest::CameraCalcTest(void)
: _offlineVehicle(NULL)
{
}
void CameraCalcTest::init(void)
{
UnitTest::init();
_offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this);
_cameraCalc = new CameraCalc(_offlineVehicle, this);
_rgSignals[cameraNameChangedIndex] = SIGNAL(cameraNameChanged(QString));
_rgSignals[dirtyChangedIndex] = SIGNAL(dirtyChanged(bool));
_rgSignals[imageFootprintSideChangedIndex] = SIGNAL(imageFootprintSideChanged(double));
_rgSignals[imageFootprintFrontalChangedIndex] = SIGNAL(imageFootprintFrontalChanged(double));
_rgSignals[distanceToSurfaceRelativeChangedIndex] = SIGNAL(distanceToSurfaceRelativeChanged(bool));
_multiSpy = new MultiSignalSpy();
QCOMPARE(_multiSpy->init(_cameraCalc, _rgSignals, _cSignals), true);
}
void CameraCalcTest::cleanup(void)
{
delete _cameraCalc;
delete _offlineVehicle;
delete _multiSpy;
}
void CameraCalcTest::_testDirty(void)
{
QVERIFY(!_cameraCalc->dirty());
_cameraCalc->setDirty(false);
QVERIFY(!_cameraCalc->dirty());
QVERIFY(_multiSpy->checkNoSignals());
_cameraCalc->setDirty(true);
QVERIFY(_cameraCalc->dirty());
QVERIFY(_multiSpy->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(_multiSpy->pullBoolFromSignalIndex(dirtyChangedIndex));
_multiSpy->clearAllSignals();
_cameraCalc->setDirty(false);
QVERIFY(!_cameraCalc->dirty());
QVERIFY(_multiSpy->checkOnlySignalByMask(dirtyChangedMask));
_multiSpy->clearAllSignals();
// These facts should set dirty when changed
QList<Fact*> rgFacts;
rgFacts << _cameraCalc->valueSetIsDistance()
<< _cameraCalc->distanceToSurface()
<< _cameraCalc->imageDensity()
<< _cameraCalc->frontalOverlap ()
<< _cameraCalc->sideOverlap ()
<< _cameraCalc->adjustedFootprintSide()
<< _cameraCalc->adjustedFootprintFrontal();
foreach(Fact* fact, rgFacts) {
qDebug() << fact->name();
QVERIFY(!_cameraCalc->dirty());
if (fact->typeIsBool()) {
fact->setRawValue(!fact->rawValue().toBool());
} else {
fact->setRawValue(fact->rawValue().toDouble() + 1);
}
QVERIFY(_multiSpy->checkSignalByMask(dirtyChangedMask));
_cameraCalc->setDirty(false);
_multiSpy->clearAllSignals();
}
rgFacts.clear();
_cameraCalc->setCameraName(_cameraCalc->customCameraName());
QVERIFY(_cameraCalc->dirty());
_multiSpy->clearAllSignals();
_cameraCalc->setDistanceToSurfaceRelative(!_cameraCalc->distanceToSurfaceRelative());
QVERIFY(_cameraCalc->dirty());
_multiSpy->clearAllSignals();
}
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "UnitTest.h"
#include "MultiSignalSpy.h"
#include "CameraCalc.h"
#include <QGeoCoordinate>
class CameraCalcTest : public UnitTest
{
Q_OBJECT
public:
CameraCalcTest(void);
protected:
void init(void) final;
void cleanup(void) final;
private slots:
void _testDirty(void);
private:
enum {
cameraNameChangedIndex = 0,
dirtyChangedIndex,
imageFootprintSideChangedIndex,
imageFootprintFrontalChangedIndex,
distanceToSurfaceRelativeChangedIndex,
maxSignalIndex
};
enum {
cameraNameChangedMask = 1 << cameraNameChangedIndex,
dirtyChangedMask = 1 << dirtyChangedIndex,
imageFootprintSideChangedMask = 1 << imageFootprintSideChangedIndex,
imageFootprintFrontalChangedMask = 1 << imageFootprintFrontalChangedIndex,
distanceToSurfaceRelativeChangedMask = 1 << distanceToSurfaceRelativeChangedIndex,
};
static const size_t _cSignals = maxSignalIndex;
const char* _rgSignals[_cSignals];
Vehicle* _offlineVehicle;
MultiSignalSpy* _multiSpy;
CameraCalc* _cameraCalc;
};
......@@ -47,9 +47,9 @@ public:
static const char* jsonComplexItemTypeKey;
signals:
void complexDistanceChanged (double complexDistance);
void complexDistanceChanged (void);
void greatestDistanceToChanged (void);
void additionalTimeDelayChanged (double additionalTimeDelay);
void additionalTimeDelayChanged (void);
};
#endif
[
{
"name": "Altitude",
"shortDescription": "Altitude for the bottom layer of the structure scan.",
"type": "double",
"units": "m",
"decimalPlaces": 1,
"defaultValue": 50
},
{
"name": "CorridorWidth",
"shortDescription": "Corridor width. Specify 0 width for a single pass scan.",
"type": "double",
"units": "m",
"min": 0,
"decimalPlaces": 1,
"defaultValue": 50
},
{
"name": "Trigger distance",
"shortDescription": "Distance between each triggering of the camera. 0 specifies not camera trigger.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m",
"defaultValue": 25
},
{
"name": "GridSpacing",
"shortDescription": "Amount of spacing in between parallel grid lines.",
"type": "double",
"decimalPlaces": 2,
"min": 0.1,
"units": "m",
"defaultValue": 30
},
{
"name": "TurnaroundDistance",
"shortDescription": "Amount of additional distance to add outside the survey area for vehicle turnaround.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m",
"defaultValue": 30
}
]
This diff is collapsed.
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "TransectStyleComplexItem.h"
#include "MissionItem.h"
#include "SettingsFact.h"
#include "QGCLoggingCategory.h"
#include "QGCMapPolyline.h"
#include "QGCMapPolygon.h"
#include "CameraCalc.h"
Q_DECLARE_LOGGING_CATEGORY(CorridorScanComplexItemLog)
class CorridorScanComplexItem : public TransectStyleComplexItem
{
Q_OBJECT
public:
CorridorScanComplexItem(Vehicle* vehicle, QObject* parent = NULL);
Q_PROPERTY(CameraCalc* cameraCalc READ cameraCalc CONSTANT)
Q_PROPERTY(QGCMapPolyline* corridorPolyline READ corridorPolyline CONSTANT)
Q_PROPERTY(Fact* corridorWidth READ corridorWidth CONSTANT)
Fact* corridorWidth (void) { return &_corridorWidthFact; }
QGCMapPolyline* corridorPolyline(void) { return &_corridorPolyline; }
Q_INVOKABLE void rotateEntryPoint(void);
// Overrides from ComplexMissionItem
int lastSequenceNumber (void) const final;
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final;
QString mapVisualQML (void) const final { return QStringLiteral("CorridorScanMapVisual.qml"); }
// Overrides from TransectStyleComplexItem
void save (QJsonArray& missionItems) final;
bool specifiesCoordinate (void) const final;
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) final;
void applyNewAltitude (double newAltitude) final;
static const char* jsonComplexItemTypeValue;
static const char* settingsGroup;
static const char* corridorWidthName;
private slots:
void _polylineDirtyChanged (bool dirty);
void _polylineCountChanged (int count);
void _rebuildCorridor (void);
// Overrides from TransectStyleComplexItem
virtual void _rebuildTransects (void) final;
private:
int _transectCount (void) const;
void _rebuildCorridorPolygon(void);
QGCMapPolyline _corridorPolyline;
QList<QList<QGeoCoordinate>> _transectSegments; ///< Internal transect segments including grid exit, turnaround and internal camera points
bool _ignoreRecalc;
int _entryPoint;
QMap<QString, FactMetaData*> _metaDataMap;
SettingsFact _corridorWidthFact;
static const char* _entryPointName;
};
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "CorridorScanComplexItemTest.h"
#include "QGCApplication.h"
CorridorScanComplexItemTest::CorridorScanComplexItemTest(void)
: _offlineVehicle(NULL)
{
_linePoints << QGeoCoordinate(47.633550640000003, -122.08982199)
<< QGeoCoordinate(47.634129020000003, -122.08887249)
<< QGeoCoordinate(47.633619320000001, -122.08811074);
}
void CorridorScanComplexItemTest::init(void)
{
UnitTest::init();
_offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this);
_corridorItem = new CorridorScanComplexItem(_offlineVehicle, this);
// _corridorItem->setTurnaroundDist(0); // Unit test written for no turnaround distance
_setPolyline();
_corridorItem->setDirty(false);
_rgCorridorPolygonSignals[corridorPolygonPathChangedIndex] = SIGNAL(pathChanged());
_multiSpyCorridorPolygon = new MultiSignalSpy();
QCOMPARE(_multiSpyCorridorPolygon->init(_corridorItem->surveyAreaPolygon(), _rgCorridorPolygonSignals, _cCorridorPolygonSignals), true);
}
void CorridorScanComplexItemTest::cleanup(void)
{
delete _corridorItem;
delete _offlineVehicle;
}
void CorridorScanComplexItemTest::_testDirty(void)
{
Fact* fact = _corridorItem->corridorWidth();
fact->setRawValue(fact->rawValue().toDouble() + 1);
QVERIFY(_corridorItem->dirty());
_corridorItem->setDirty(false);
changeFactValue(_corridorItem->cameraCalc()->distanceToSurface());
QVERIFY(_corridorItem->dirty());
_corridorItem->setDirty(false);
QGeoCoordinate coord = _corridorItem->corridorPolyline()->vertexCoordinate(0);
coord.setLatitude(coord.latitude() + 1);
_corridorItem->corridorPolyline()->adjustVertex(1, coord);
QVERIFY(_corridorItem->dirty());
_corridorItem->setDirty(false);
}
void CorridorScanComplexItemTest::_testCameraTrigger(void)
{
#if 0
QCOMPARE(_corridorItem->property("cameraTrigger").toBool(), true);
// Set up a grid
for (int i=0; i<3; i++) {
_mapPolyline->appendVertex(_linePoints[i]);
}
_corridorItem->setDirty(false);
_multiSpy->clearAllSignals();
int lastSeq = _corridorItem->lastSequenceNumber();
QVERIFY(lastSeq > 0);
// Turning off camera triggering should remove two camera trigger mission items, this should trigger:
// lastSequenceNumberChanged
// dirtyChanged
_corridorItem->setProperty("cameraTrigger", false);
QVERIFY(_multiSpy->checkOnlySignalByMask(lastSequenceNumberChangedMask | dirtyChangedMask | cameraTriggerChangedMask));
QCOMPARE(_multiSpy->pullIntFromSignalIndex(lastSequenceNumberChangedIndex), lastSeq - 2);
_corridorItem->setDirty(false);
_multiSpy->clearAllSignals();
// Turn on camera triggering and make sure things go back to previous count
_corridorItem->setProperty("cameraTrigger", true);
QVERIFY(_multiSpy->checkOnlySignalByMask(lastSequenceNumberChangedMask | dirtyChangedMask | cameraTriggerChangedMask));
QCOMPARE(_multiSpy->pullIntFromSignalIndex(lastSequenceNumberChangedIndex), lastSeq);
#endif
}
void CorridorScanComplexItemTest::_setPolyline(void)
{
for (int i=0; i<_linePoints.count(); i++) {
QGeoCoordinate& vertex = _linePoints[i];
_corridorItem->corridorPolyline()->appendVertex(vertex);
}
}
#if 0
void CorridorScanComplexItemTest::_testEntryLocation(void)
{
_setPolygon();
for (double gridAngle=-360.0; gridAngle<=360.0; gridAngle++) {
_corridorItem->gridAngle()->setRawValue(gridAngle);
QList<QGeoCoordinate> rgSeenEntryCoords;
QList<int> rgEntryLocation;
rgEntryLocation << SurveyMissionItem::EntryLocationTopLeft
<< SurveyMissionItem::EntryLocationTopRight
<< SurveyMissionItem::EntryLocationBottomLeft
<< SurveyMissionItem::EntryLocationBottomRight;
// Validate that each entry location is unique
for (int i=0; i<rgEntryLocation.count(); i++) {
int entryLocation = rgEntryLocation[i];
_corridorItem->gridEntryLocation()->setRawValue(entryLocation);
QVERIFY(!rgSeenEntryCoords.contains(_corridorItem->coordinate()));
rgSeenEntryCoords << _corridorItem->coordinate();
}
rgSeenEntryCoords.clear();
}
}
#endif
void CorridorScanComplexItemTest::_testItemCount(void)
{
QList<MissionItem*> items;
_corridorItem->turnAroundDistance()->setRawValue(20);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(false);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->cameraTriggerInTurnAround()->setRawValue(true);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
}
void CorridorScanComplexItemTest::_testPathChanges(void)
{
QGeoCoordinate vertex = _corridorItem->corridorPolyline()->vertexCoordinate(1);
vertex.setLatitude(vertex.latitude() + 0.01);
_corridorItem->corridorPolyline()->adjustVertex(1, vertex);
QVERIFY(_multiSpyCorridorPolygon->checkSignalsByMask(corridorPolygonPathChangedMask));
}
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "UnitTest.h"
#include "TCPLink.h"
#include "MultiSignalSpy.h"
#include "CorridorScanComplexItem.h"
#include <QGeoCoordinate>
class CorridorScanComplexItemTest : public UnitTest
{
Q_OBJECT
public:
CorridorScanComplexItemTest(void);
protected:
void init(void) final;
void cleanup(void) final;
private slots:
void _testDirty(void);
void _testCameraTrigger(void);
// void _testEntryLocation(void);
void _testItemCount(void);
void _testPathChanges(void);
private:
void _setPolyline(void);
enum {
corridorPolygonPathChangedIndex = 0,
maxCorridorPolygonSignalIndex
};
enum {
corridorPolygonPathChangedMask = 1 << corridorPolygonPathChangedIndex,
};
static const size_t _cCorridorPolygonSignals = maxCorridorPolygonSignalIndex;
const char* _rgCorridorPolygonSignals[_cCorridorPolygonSignals];
Vehicle* _offlineVehicle;
MultiSignalSpy* _multiSpyCorridorPolygon;
CorridorScanComplexItem* _corridorItem;
QList<QGeoCoordinate> _linePoints;
};
......@@ -19,7 +19,7 @@
#include "SurveyMissionItem.h"
#include "FixedWingLandingComplexItem.h"
#include "StructureScanComplexItem.h"
#include "StructureScanComplexItem.h"
#include "CorridorScanComplexItem.h"
#include "JsonHelper.h"
#include "ParameterManager.h"
#include "QGroundControlQmlGlobal.h"
......@@ -63,6 +63,7 @@ MissionController::MissionController(PlanMasterController* masterController, QOb
, _surveyMissionItemName(tr("Survey"))
, _fwLandingMissionItemName(tr("Fixed Wing Landing"))
, _structureScanMissionItemName(tr("Structure Scan"))
, _corridorScanMissionItemName(tr("Corridor Scan"))
, _appSettings(qgcApp()->toolbox()->settingsManager()->appSettings())
, _progressPct(0)
, _currentPlanViewIndex(-1)
......@@ -388,22 +389,41 @@ int MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int i)
int MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int i)
{
ComplexMissionItem* newItem;
bool surveyStyleItem = false;
int sequenceNumber = _nextSequenceNumber();
if (itemName == _surveyMissionItemName) {
newItem = new SurveyMissionItem(_controllerVehicle, _visualItems);
newItem->setCoordinate(mapCenterCoordinate);
// If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set
surveyStyleItem = true;
} else if (itemName == _fwLandingMissionItemName) {
newItem = new FixedWingLandingComplexItem(_controllerVehicle, _visualItems);
} else if (itemName == _structureScanMissionItemName) {
newItem = new StructureScanComplexItem(_controllerVehicle, _visualItems);
} else if (itemName == _corridorScanMissionItemName) {
newItem = new CorridorScanComplexItem(_controllerVehicle, _visualItems);
surveyStyleItem = true;
} else {
qWarning() << "Internal error: Unknown complex item:" << itemName;
return sequenceNumber;
}
if (surveyStyleItem) {
bool rollSupported = false;
bool pitchSupported = false;
bool yawSupported = false;
// If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set
MissionSettingsItem* settingsItem = _visualItems->value<MissionSettingsItem*>(0);
CameraSection* cameraSection = settingsItem->cameraSection();
// Set camera to photo mode (leave alone if user already specified)
if (cameraSection->cameraModeSupported() && !cameraSection->specifyCameraMode()) {
cameraSection->setSpecifyCameraMode(true);
cameraSection->cameraMode()->setRawValue(CAMERA_MODE_IMAGE_SURVEY);
}
// Point gimbal straight down
if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) {
// If the user already specified a gimbal angle leave it alone
......@@ -412,14 +432,8 @@ int MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate
cameraSection->gimbalPitch()->setRawValue(-90.0);
}
}
} else if (itemName == _fwLandingMissionItemName) {
newItem = new FixedWingLandingComplexItem(_controllerVehicle, _visualItems);
} else if (itemName == _structureScanMissionItemName) {
newItem = new StructureScanComplexItem(_controllerVehicle, _visualItems);
} else {
qWarning() << "Internal error: Unknown complex item:" << itemName;
return sequenceNumber;
}
newItem->setSequenceNumber(sequenceNumber);
_initVisualItem(newItem);
......@@ -437,17 +451,17 @@ void MissionController::removeMissionItem(int index)
return;
}
bool surveyRemoved = _visualItems->value<SurveyMissionItem*>(index);
bool removeSurveyStyle = _visualItems->value<SurveyMissionItem*>(index) || _visualItems->value<CorridorScanComplexItem*>(index);
VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->removeAt(index));
_deinitVisualItem(item);
item->deleteLater();
if (surveyRemoved) {
// Determine if the mission still has another survey in it
if (removeSurveyStyle) {
// Determine if the mission still has another survey style item in it
bool foundSurvey = false;
for (int i=1; i<_visualItems->count(); i++) {
if (_visualItems->value<SurveyMissionItem*>(i)) {
if (_visualItems->value<SurveyMissionItem*>(i) || _visualItems->value<CorridorScanComplexItem*>(index)) {
foundSurvey = true;
break;
}
......@@ -698,6 +712,15 @@ bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjec
nextSequenceNumber = structureItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Structure Scan load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(structureItem);
} else if (complexItemType == CorridorScanComplexItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Corridor Scan: nextSequenceNumber" << nextSequenceNumber;
CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_controllerVehicle, visualItems);
if (!corridorItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = corridorItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Corridor Scan load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(corridorItem);
} else if (complexItemType == MissionSettingsItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Mission Settings: nextSequenceNumber" << nextSequenceNumber;
MissionSettingsItem* settingsItem = new MissionSettingsItem(_controllerVehicle, visualItems);
......@@ -1842,6 +1865,7 @@ QStringList MissionController::complexMissionItemNames(void) const
QStringList complexItems;
complexItems.append(_surveyMissionItemName);
complexItems.append(_corridorScanMissionItemName);
if (_controllerVehicle->fixedWing()) {
complexItems.append(_fwLandingMissionItemName);
}
......
......@@ -253,6 +253,7 @@ private:
QString _surveyMissionItemName;
QString _fwLandingMissionItemName;
QString _structureScanMissionItemName;
QString _corridorScanMissionItemName;
AppSettings* _appSettings;
double _progressPct;
int _currentPlanViewIndex;
......
......@@ -30,13 +30,13 @@ PlanManager::PlanManager(Vehicle* vehicle, MAV_MISSION_TYPE planType)
, _transactionInProgress(TransactionNone)
, _resumeMission(false)
, _lastMissionRequest(-1)
, _missionItemCountToRead(-1)
, _currentMissionIndex(-1)
, _lastCurrentIndex(-1)
{
_ackTimeoutTimer = new QTimer(this);
_ackTimeoutTimer->setSingleShot(true);
_ackTimeoutTimer->setInterval(_ackTimeoutMilliseconds);
connect(_ackTimeoutTimer, &QTimer::timeout, this, &PlanManager::_ackTimeout);
}
......@@ -254,6 +254,24 @@ void PlanManager::_ackTimeout(void)
void PlanManager::_startAckTimeout(AckType_t ack)
{
switch (ack) {
case AckMissionItem:
// We are actively trying to get the mission item, so we don't want to wait as long.
_ackTimeoutTimer->setInterval(_retryTimeoutMilliseconds);
break;
case AckNone:
// FALLTHROUGH
case AckMissionCount:
// FALLTHROUGH
case AckMissionRequest:
// FALLTHROUGH
case AckMissionClearAll:
// FALLTHROUGH
case AckGuidedItem:
_ackTimeoutTimer->setInterval(_ackTimeoutMilliseconds);
break;
}
_expectedAck = ack;
_ackTimeoutTimer->start();
}
......@@ -327,6 +345,7 @@ void PlanManager::_handleMissionCount(const mavlink_message_t& message)
for (int i=0; i<missionCount.count; i++) {
_itemIndicesToRead << i;
}
_missionItemCountToRead = missionCount.count;
_requestNextMissionItem();
}
}
......@@ -470,7 +489,7 @@ void PlanManager::_handleMissionItem(const mavlink_message_t& message, bool miss
return;
}
emit progressPct((double)seq / (double)_missionItems.count());
emit progressPct((double)seq / (double)_missionItemCountToRead);
_retryCount = 0;
if (_itemIndicesToRead.count() == 0) {
......
......@@ -28,11 +28,11 @@ Q_DECLARE_LOGGING_CATEGORY(PlanManagerLog)
class PlanManager : public QObject
{
Q_OBJECT
public:
PlanManager(Vehicle* vehicle, MAV_MISSION_TYPE planType);
~PlanManager();
bool inProgress(void) const;
const QList<MissionItem*>& missionItems(void) { return _missionItems; }
......@@ -41,11 +41,11 @@ public:
/// Last current mission item reported while in Mission flight mode
int lastCurrentIndex(void) const { return _lastCurrentIndex; }
/// Load the mission items from the vehicle
/// Signals newMissionItemsAvailable when done
void loadFromVehicle(void);
/// Writes the specified set of mission items to the vehicle
/// IMPORTANT NOTE: PlanManager will take control of the MissionItem objects with the missionItems list. It will free them when done.
/// @param missionItems Items to send to vehicle
......@@ -70,9 +70,12 @@ public:
} ErrorCode_t;
// These values are public so the unit test can set appropriate signal wait times
// When passively waiting for a mission process, use a longer timeout.
static const int _ackTimeoutMilliseconds = 1000;
// When actively retrying to request mission items, use a shorter timeout instead.
static const int _retryTimeoutMilliseconds = 250;
static const int _maxRetryCount = 5;
signals:
void newMissionItemsAvailable (bool removeAllRequested);
void inProgressChanged (bool inProgress);
......@@ -88,7 +91,7 @@ signals:
private slots:
void _mavlinkMessageReceived(const mavlink_message_t& message);
void _ackTimeout(void);
protected:
typedef enum {
AckNone, ///< State machine is idle
......@@ -134,17 +137,18 @@ protected:
Vehicle* _vehicle;
MAV_MISSION_TYPE _planType;
LinkInterface* _dedicatedLink;
QTimer* _ackTimeoutTimer;
AckType_t _expectedAck;
int _retryCount;
TransactionType_t _transactionInProgress;
bool _resumeMission;
QList<int> _itemIndicesToWrite; ///< List of mission items which still need to be written to vehicle
QList<int> _itemIndicesToRead; ///< List of mission items which still need to be requested from vehicle
int _lastMissionRequest; ///< Index of item last requested by MISSION_REQUEST
int _missionItemCountToRead;///< Count of all mission items to read
QList<MissionItem*> _missionItems; ///< Set of mission items on vehicle
QList<MissionItem*> _writeMissionItems; ///< Set of mission items currently being written to vehicle
int _currentMissionIndex;
......
......@@ -157,6 +157,11 @@ void PlanMasterController::_activeVehicleChanged(Vehicle* activeVehicle)
void PlanMasterController::loadFromVehicle(void)
{
if (_managerVehicle->highLatencyLink()) {
qgcApp()->showMessage(tr("Download not supported on high latency links."));
return;
}
if (offline()) {
qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while offline";
} else if (!_editMode) {
......@@ -256,6 +261,11 @@ void PlanMasterController::_sendRallyPointsComplete(void)
void PlanMasterController::sendToVehicle(void)
{
if (_managerVehicle->highLatencyLink()) {
qgcApp()->showMessage(tr("Upload not supported on high latency links."));
return;
}
if (offline()) {
qCWarning(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle called while offline";
} else if (syncInProgress()) {
......
......@@ -362,7 +362,7 @@ QGeoCoordinate QGCMapPolygon::vertexCoordinate(int vertex) const
}
}
QList<QPointF> QGCMapPolygon::nedPolygon(void)
QList<QPointF> QGCMapPolygon::nedPolygon(void) const
{
QList<QPointF> nedPolygon;
......@@ -515,3 +515,19 @@ bool QGCMapPolygon::loadKMLFile(const QString& kmlFile)
return true;
}
double QGCMapPolygon::area(void) const
{
// https://www.mathopenref.com/coordpolygonarea2.html
double coveredArea = 0.0;
QList<QPointF> nedVertices = nedPolygon();
for (int i=0; i<nedVertices.count(); i++) {
if (i != 0) {
coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices[i -1].y();
} else {
coveredArea += nedVertices.last().x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices.last().y();
}
}
return 0.5 * fabs(coveredArea);
}
......@@ -77,7 +77,10 @@ public:
bool loadFromJson(const QJsonObject& json, bool required, QString& errorString);
/// Convert polygon to NED and return (D is ignored)
QList<QPointF> nedPolygon(void);
QList<QPointF> nedPolygon(void) const;
/// Returns the area of the polygon in meters squared
double area(void) const;
// Property methods
......
......@@ -205,17 +205,17 @@ void QGCMapPolygonTest::_testVertexManipulation(void)
void QGCMapPolygonTest::_testKMLLoad(void)
{
QVERIFY(_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/GoodPolygon.kml")));
QVERIFY(_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/PolygonGood.kml")));
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/BadXml.kml")));
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/PolygonBadXml.kml")));
checkExpectedMessageBox();
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/MissingPolygonNode.kml")));
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/PolygonMissingNode.kml")));
checkExpectedMessageBox();
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/BadCoordinatesNode.kml")));
QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/PolygonBadCoordinatesNode.kml")));
checkExpectedMessageBox();
}
This diff is collapsed.
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include <QObject>
#include <QGeoCoordinate>
#include <QVariantList>
#include "QmlObjectListModel.h"
class QGCMapPolyline : public QObject
{
Q_OBJECT
public:
QGCMapPolyline(QObject* parent = NULL);
QGCMapPolyline(const QGCMapPolyline& other, QObject* parent = NULL);
const QGCMapPolyline& operator=(const QGCMapPolyline& other);
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QVariantList path READ path NOTIFY pathChanged)
Q_PROPERTY(QmlObjectListModel* pathModel READ qmlPathModel CONSTANT)
Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged)
Q_PROPERTY(bool interactive READ interactive WRITE setInteractive NOTIFY interactiveChanged)
Q_INVOKABLE void clear(void);
Q_INVOKABLE void appendVertex(const QGeoCoordinate& coordinate);
Q_INVOKABLE void removeVertex(int vertexIndex);
/// Adjust the value for the specified coordinate
/// @param vertexIndex Polygon point index to modify (0-based)
/// @param coordinate New coordinate for point
Q_INVOKABLE void adjustVertex(int vertexIndex, const QGeoCoordinate coordinate);
/// Splits the line segment comprised of vertexIndex -> vertexIndex + 1
Q_INVOKABLE void splitSegment(int vertexIndex);
/// Offsets the current polyline edges by the specified distance in meters
/// @return Offset set of vertices
QList<QGeoCoordinate> offsetPolyline(double distance);
/// Loads a polyline from a KML file
/// @return true: success
Q_INVOKABLE bool loadKMLFile(const QString& kmlFile);
/// Returns the path in a list of QGeoCoordinate's format
QList<QGeoCoordinate> coordinateList(void) const;
/// Returns the QGeoCoordinate for the vertex specified
QGeoCoordinate vertexCoordinate(int vertex) const;
/// Saves the polyline to the json object.
/// @param json Json object to save to
void saveToJson(QJsonObject& json);
/// Load a polyline from json
/// @param json Json object to load from
/// @param required true: no polygon in object will generate error
/// @param errorString Error string if return is false
/// @return true: success, false: failure (errorString set)
bool loadFromJson(const QJsonObject& json, bool required, QString& errorString);
/// Convert polyline to NED and return (D is ignored)
QList<QPointF> nedPolyline(void);
/// Returns the length of the polyline in meters
double length(void) const;
// Property methods
int count (void) const { return _polylinePath.count(); }
bool dirty (void) const { return _dirty; }
void setDirty (bool dirty);
bool interactive (void) const { return _interactive; }
QVariantList path (void) const { return _polylinePath; }
QmlObjectListModel* qmlPathModel(void) { return &_polylineModel; }
QmlObjectListModel& pathModel (void) { return _polylineModel; }
void setPath (const QList<QGeoCoordinate>& path);
void setPath (const QVariantList& path);
void setInteractive (bool interactive);
static const char* jsonPolylineKey;
signals:
void countChanged (int count);
void pathChanged (void);
void dirtyChanged (bool dirty);
void cleared (void);
void interactiveChanged (bool interactive);
private slots:
void _polylineModelCountChanged(int count);
void _polylineModelDirtyChanged(bool dirty);
private:
void _init(void);
QGeoCoordinate _coordFromPointF(const QPointF& point) const;
QPointF _pointFFromCoord(const QGeoCoordinate& coordinate) const;
QVariantList _polylinePath;
QmlObjectListModel _polylineModel;
bool _dirty;
bool _interactive;
};
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "QGCMapPolylineTest.h"
#include "QGCApplication.h"
#include "QGCQGeoCoordinate.h"
QGCMapPolylineTest::QGCMapPolylineTest(void)
{
_linePoints << QGeoCoordinate(47.635638361473475, -122.09269407980834 ) <<
QGeoCoordinate(47.635638361473475, -122.08545246602667) <<
QGeoCoordinate(47.63057923872075, -122.08545246602667) <<
QGeoCoordinate(47.63057923872075, -122.09269407980834);
}
void QGCMapPolylineTest::init(void)
{
UnitTest::init();
_rgSignals[countChangedIndex] = SIGNAL(countChanged(int));
_rgSignals[pathChangedIndex] = SIGNAL(pathChanged());
_rgSignals[dirtyChangedIndex] = SIGNAL(dirtyChanged(bool));
_rgSignals[clearedIndex] = SIGNAL(cleared());
_rgModelSignals[modelCountChangedIndex] = SIGNAL(countChanged(int));
_rgModelSignals[modelDirtyChangedIndex] = SIGNAL(dirtyChanged(bool));
_mapPolyline = new QGCMapPolyline(this);
_pathModel = _mapPolyline->qmlPathModel();
QVERIFY(_pathModel);
_multiSpyPolyline = new MultiSignalSpy();
QCOMPARE(_multiSpyPolyline->init(_mapPolyline, _rgSignals, _cSignals), true);
_multiSpyModel = new MultiSignalSpy();
QCOMPARE(_multiSpyModel->init(_pathModel, _rgModelSignals, _cModelSignals), true);
}
void QGCMapPolylineTest::cleanup(void)
{
delete _mapPolyline;
delete _multiSpyPolyline;
delete _multiSpyModel;
}
void QGCMapPolylineTest::_testDirty(void)
{
// Check basic dirty bit set/get
QVERIFY(!_mapPolyline->dirty());
QVERIFY(!_pathModel->dirty());
_mapPolyline->setDirty(false);
QVERIFY(!_mapPolyline->dirty());
QVERIFY(!_pathModel->dirty());
QVERIFY(_multiSpyPolyline->checkNoSignals());
QVERIFY(_multiSpyModel->checkNoSignals());
_mapPolyline->setDirty(true);
QVERIFY(_mapPolyline->dirty());
QVERIFY(!_pathModel->dirty());
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(_multiSpyPolyline->pullBoolFromSignalIndex(dirtyChangedIndex));
QVERIFY(_multiSpyModel->checkNoSignals());
_multiSpyPolyline->clearAllSignals();
_mapPolyline->setDirty(false);
QVERIFY(!_mapPolyline->dirty());
QVERIFY(!_pathModel->dirty());
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(!_multiSpyPolyline->pullBoolFromSignalIndex(dirtyChangedIndex));
QVERIFY(_multiSpyModel->checkNoSignals());
_multiSpyPolyline->clearAllSignals();
_pathModel->setDirty(true);
QVERIFY(_pathModel->dirty());
QVERIFY(_mapPolyline->dirty());
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(_multiSpyPolyline->pullBoolFromSignalIndex(dirtyChangedIndex));
QVERIFY(_multiSpyModel->checkOnlySignalByMask(modelDirtyChangedMask));
QVERIFY(_multiSpyModel->pullBoolFromSignalIndex(modelDirtyChangedIndex));
_multiSpyPolyline->clearAllSignals();
_multiSpyModel->clearAllSignals();
_mapPolyline->setDirty(false);
QVERIFY(!_mapPolyline->dirty());
QVERIFY(!_pathModel->dirty());
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(!_multiSpyPolyline->pullBoolFromSignalIndex(dirtyChangedIndex));
QVERIFY(_multiSpyModel->checkOnlySignalByMask(modelDirtyChangedMask));
QVERIFY(!_multiSpyModel->pullBoolFromSignalIndex(modelDirtyChangedIndex));
_multiSpyPolyline->clearAllSignals();
_multiSpyModel->clearAllSignals();
}
void QGCMapPolylineTest::_testVertexManipulation(void)
{
// Vertex addition testing
for (int i=0; i<_linePoints.count(); i++) {
QCOMPARE(_mapPolyline->count(), i);
_mapPolyline->appendVertex(_linePoints[i]);
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(pathChangedMask | dirtyChangedMask | countChangedMask));
QVERIFY(_multiSpyModel->checkOnlySignalByMask(modelDirtyChangedMask | modelCountChangedMask));
QCOMPARE(_multiSpyPolyline->pullIntFromSignalIndex(countChangedIndex), i+1);
QCOMPARE(_multiSpyModel->pullIntFromSignalIndex(modelCountChangedIndex), i+1);
QVERIFY(_mapPolyline->dirty());
QVERIFY(_pathModel->dirty());
QCOMPARE(_mapPolyline->count(), i+1);
QVariantList vertexList = _mapPolyline->path();
QCOMPARE(vertexList.count(), i+1);
QCOMPARE(vertexList[i].value<QGeoCoordinate>(), _linePoints[i]);
QCOMPARE(_pathModel->count(), i+1);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(i)->coordinate(), _linePoints[i]);
_mapPolyline->setDirty(false);
_multiSpyPolyline->clearAllSignals();
_multiSpyModel->clearAllSignals();
}
// Vertex adjustment testing
QGCQGeoCoordinate* geoCoord = _pathModel->value<QGCQGeoCoordinate*>(1);
QSignalSpy coordSpy(geoCoord, SIGNAL(coordinateChanged(QGeoCoordinate)));
QSignalSpy coordDirtySpy(geoCoord, SIGNAL(dirtyChanged(bool)));
QGeoCoordinate adjustCoord(_linePoints[1].latitude() + 1, _linePoints[1].longitude() + 1);
_mapPolyline->adjustVertex(1, adjustCoord);
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(pathChangedMask | dirtyChangedMask));
QVERIFY(_multiSpyModel->checkOnlySignalByMask(modelDirtyChangedMask));
QCOMPARE(coordSpy.count(), 1);
QCOMPARE(coordDirtySpy.count(), 1);
QCOMPARE(geoCoord->coordinate(), adjustCoord);
QVariantList vertexList = _mapPolyline->path();
QCOMPARE(vertexList[0].value<QGeoCoordinate>(), _linePoints[0]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(0)->coordinate(), _linePoints[0]);
QCOMPARE(vertexList[2].value<QGeoCoordinate>(), _linePoints[2]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(2)->coordinate(), _linePoints[2]);
QCOMPARE(vertexList[3].value<QGeoCoordinate>(), _linePoints[3]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(3)->coordinate(), _linePoints[3]);
_mapPolyline->setDirty(false);
_multiSpyPolyline->clearAllSignals();
_multiSpyModel->clearAllSignals();
// Vertex removal testing
_mapPolyline->removeVertex(1);
QVERIFY(_multiSpyPolyline->checkOnlySignalByMask(pathChangedMask | dirtyChangedMask | countChangedMask));
QVERIFY(_multiSpyModel->checkOnlySignalByMask(modelDirtyChangedMask | modelCountChangedMask));
QCOMPARE(_mapPolyline->count(), 3);
vertexList = _mapPolyline->path();
QCOMPARE(vertexList.count(), 3);
QCOMPARE(_pathModel->count(), 3);
QCOMPARE(vertexList[0].value<QGeoCoordinate>(), _linePoints[0]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(0)->coordinate(), _linePoints[0]);
QCOMPARE(vertexList[1].value<QGeoCoordinate>(), _linePoints[2]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(1)->coordinate(), _linePoints[2]);
QCOMPARE(vertexList[2].value<QGeoCoordinate>(), _linePoints[3]);
QCOMPARE(_pathModel->value<QGCQGeoCoordinate*>(2)->coordinate(), _linePoints[3]);
// Clear testing
_mapPolyline->clear();
QVERIFY(_multiSpyPolyline->checkOnlySignalsByMask(pathChangedMask | dirtyChangedMask | countChangedMask | clearedMask));
QVERIFY(_multiSpyModel->checkOnlySignalsByMask(modelDirtyChangedMask | modelCountChangedMask));
QVERIFY(_mapPolyline->dirty());
QVERIFY(_pathModel->dirty());
QCOMPARE(_mapPolyline->count(), 0);
vertexList = _mapPolyline->path();
QCOMPARE(vertexList.count(), 0);
QCOMPARE(_pathModel->count(), 0);
}
#if 0
void QGCMapPolylineTest::_testKMLLoad(void)
{
QVERIFY(_mapPolyline->loadKMLFile(QStringLiteral(":/unittest/PolygonGood.kml")));
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolyline->loadKMLFile(QStringLiteral(":/unittest/BadXml.kml")));
checkExpectedMessageBox();
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolyline->loadKMLFile(QStringLiteral(":/unittest/MissingPolygonNode.kml")));
checkExpectedMessageBox();
setExpectedMessageBox(QMessageBox::Ok);
QVERIFY(!_mapPolyline->loadKMLFile(QStringLiteral(":/unittest/BadCoordinatesNode.kml")));
checkExpectedMessageBox();
}
#endif
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "UnitTest.h"
#include "MultiSignalSpy.h"
#include "QGCMapPolyline.h"
#include "QmlObjectListModel.h"
class QGCMapPolylineTest : public UnitTest
{
Q_OBJECT
public:
QGCMapPolylineTest(void);
protected:
void init(void) final;
void cleanup(void) final;
private slots:
void _testDirty(void);
void _testVertexManipulation(void);
// void _testKMLLoad(void);
private:
enum {
countChangedIndex = 0,
pathChangedIndex,
dirtyChangedIndex,
clearedIndex,
maxSignalIndex
};
enum {
countChangedMask = 1 << countChangedIndex,
pathChangedMask = 1 << pathChangedIndex,
dirtyChangedMask = 1 << dirtyChangedIndex,
clearedMask = 1 << clearedIndex,
};
static const size_t _cSignals = maxSignalIndex;
const char* _rgSignals[_cSignals];
enum {
modelCountChangedIndex = 0,
modelDirtyChangedIndex,
maxModelSignalIndex
};
enum {
modelCountChangedMask = 1 << modelCountChangedIndex,
modelDirtyChangedMask = 1 << modelDirtyChangedIndex,
};
static const size_t _cModelSignals = maxModelSignalIndex;
const char* _rgModelSignals[_cModelSignals];
MultiSignalSpy* _multiSpyPolyline;
MultiSignalSpy* _multiSpyModel;
QGCMapPolyline* _mapPolyline;
QmlObjectListModel* _pathModel;
QList<QGeoCoordinate> _linePoints;
};
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtLocation 5.3
import QtPositioning 5.3
import QtQuick.Dialogs 1.2
import QGroundControl 1.0
import QGroundControl.ScreenTools 1.0
import QGroundControl.Palette 1.0
import QGroundControl.Controls 1.0
import QGroundControl.FlightMap 1.0
/// QGCmapPolyline map visuals
Item {
id: _root
property var qgcView ///< QGCView for popping dialogs
property var mapControl ///< Map control to place item in
property var mapPolyline ///< QGCMapPolyline object
property bool interactive: mapPolyline.interactive
property int lineWidth: 3
property color lineColor: "#be781c"
property var _polylineComponent
property var _dragHandlesComponent
property var _splitHandlesComponent
property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap
property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2
function addVisuals() {
_polylineComponent = polylineComponent.createObject(mapControl)
mapControl.addMapItem(_polylineComponent)
}
function removeVisuals() {
_polylineComponent.destroy()
}
function addHandles() {
if (!_dragHandlesComponent) {
_dragHandlesComponent = dragHandlesComponent.createObject(mapControl)
_splitHandlesComponent = splitHandlesComponent.createObject(mapControl)
}
}
function removeHandles() {
if (_dragHandlesComponent) {
_dragHandlesComponent.destroy()
_dragHandlesComponent = undefined
}
if (_splitHandlesComponent) {
_splitHandlesComponent.destroy()
_splitHandlesComponent = undefined
}
}
/// Calculate the default/initial polyline
function defaultPolylineVertices() {
var x = map.centerViewport.x + (map.centerViewport.width / 2)
var yInset = map.centerViewport.height / 4
var topPointCoord = map.toCoordinate(Qt.point(x, map.centerViewport.y + yInset), false /* clipToViewPort */)
var bottomPointCoord = map.toCoordinate(Qt.point(x, map.centerViewport.y + map.centerViewport.height - yInset), false /* clipToViewPort */)
return [ topPointCoord, bottomPointCoord ]
}
/// Add an initial 2 point polyline
function addInitialPolyline() {
if (mapPolyline.count < 2) {
mapPolyline.clear()
var initialVertices = defaultPolylineVertices()
mapPolyline.appendVertex(initialVertices[0])
mapPolyline.appendVertex(initialVertices[1])
}
}
/// Reset polyline back to initial default
function resetPolyline() {
mapPolyline.clear()
addInitialPolyline()
}
onInteractiveChanged: {
if (interactive) {
addHandles()
} else {
removeHandles()
}
}
Component.onCompleted: {
addVisuals()
if (interactive) {
addHandles()
}
}
Component.onDestruction: {
removeVisuals()
removeHandles()
}
QGCPalette { id: qgcPal }
QGCFileDialog {
id: kmlLoadDialog
qgcView: _root.qgcView
folder: QGroundControl.settingsManager.appSettings.missionSavePath
title: qsTr("Select KML File")
selectExisting: true
nameFilters: [ qsTr("KML files (*.kml)") ]
onAcceptedForLoad: {
mapPolyline.loadKMLFile(file)
close()
}
}
Component {
id: polylineComponent
MapPolyline {
line.width: lineWidth
line.color: lineColor
path: mapPolyline.path
}
}
Component {
id: splitHandleComponent
MapQuickItem {
id: mapQuickItem
anchorPoint.x: splitHandle.width / 2
anchorPoint.y: splitHandle.height / 2
property int vertexIndex
sourceItem: Rectangle {
id: splitHandle
width: ScreenTools.defaultFontPixelHeight * 1.5
height: width
radius: width / 2
border.color: "white"
color: "transparent"
opacity: .50
z: _zorderSplitHandle
QGCLabel {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: "+"
}
QGCMouseArea {
fillItem: parent
onClicked: mapPolyline.splitSegment(mapQuickItem.vertexIndex)
}
}
}
}
Component {
id: splitHandlesComponent
Repeater {
model: mapPolyline.path
delegate: Item {
property var _splitHandle
property var _vertices: mapPolyline.path
function _setHandlePosition() {
var nextIndex = index + 1
var distance = _vertices[index].distanceTo(_vertices[nextIndex])
var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
_splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
}
Component.onCompleted: {
if (index + 1 <= _vertices.length - 1) {
_splitHandle = splitHandleComponent.createObject(mapControl)
_splitHandle.vertexIndex = index
_setHandlePosition()
mapControl.addMapItem(_splitHandle)
}
}
Component.onDestruction: {
if (_splitHandle) {
_splitHandle.destroy()
}
}
}
}
}
// Control which is used to drag polygon vertices
Component {
id: dragAreaComponent
MissionItemIndicatorDrag {
id: dragArea
z: _zorderDragHandle
property int polylineVertex
property bool _creationComplete: false
Component.onCompleted: _creationComplete = true
onItemCoordinateChanged: {
if (_creationComplete) {
// During component creation some bad coordinate values got through which screws up draw
mapPolyline.adjustVertex(polylineVertex, itemCoordinate)
}
}
onClicked: mapPolyline.removeVertex(polylineVertex)
}
}
Component {
id: dragHandleComponent
MapQuickItem {
id: mapQuickItem
anchorPoint.x: dragHandle.width / 2
anchorPoint.y: dragHandle.height / 2
z: _zorderDragHandle
property int polylineVertex
sourceItem: Rectangle {
id: dragHandle
width: ScreenTools.defaultFontPixelHeight * 1.5
height: width
radius: width / 2
color: "white"
opacity: .90
}
}
}
// Add all polygon vertex drag handles to the map
Component {
id: dragHandlesComponent
Repeater {
model: mapPolyline.pathModel
delegate: Item {
property var _visuals: [ ]
Component.onCompleted: {
var dragHandle = dragHandleComponent.createObject(mapControl)
dragHandle.coordinate = Qt.binding(function() { return object.coordinate })
dragHandle.polylineVertex = Qt.binding(function() { return index })
mapControl.addMapItem(dragHandle)
var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate })
dragArea.polylineVertex = Qt.binding(function() { return index })
_visuals.push(dragHandle)
_visuals.push(dragArea)
}
Component.onDestruction: {
for (var i=0; i<_visuals.length; i++) {
_visuals[i].destroy()
}
_visuals = [ ]
}
}
}
}
}
......@@ -91,7 +91,7 @@ void StructureScanComplexItem::_setScanDistance(double scanDistance)
{
if (!qFuzzyCompare(_scanDistance, scanDistance)) {
_scanDistance = scanDistance;
emit complexDistanceChanged(_scanDistance);
emit complexDistanceChanged();
}
}
......
......@@ -159,7 +159,7 @@ void SurveyMissionItem::_setSurveyDistance(double surveyDistance)
{
if (!qFuzzyCompare(_surveyDistance, surveyDistance)) {
_surveyDistance = surveyDistance;
emit complexDistanceChanged(_surveyDistance);
emit complexDistanceChanged();
}
}
......@@ -742,7 +742,7 @@ void SurveyMissionItem::_generateGrid(void)
if (_hoverAndCaptureEnabled()) {
_additionalFlightDelaySeconds = cameraShots * _hoverAndCaptureDelaySeconds;
}
emit additionalTimeDelayChanged(_additionalFlightDelaySeconds);
emit additionalTimeDelayChanged();
emit gridPointsChanged();
......
[
{
"name": "TurnAroundDistance",
"shortDescription": "Amount of additional distance to add outside the survey area for vehicle turn around.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m",
"defaultValue": 30
},
{
"name": "TurnAroundDistanceMultiRotor",
"shortDescription": "Amount of additional distance to add outside the survey area for vehicle turn around.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m",
"defaultValue": 10
},
{
"name": "CameraTriggerInTurnAround",
"shortDescription": "Camera continues taking images in turn arounds.",
"type": "bool",
"defaultValue": true
},
{
"name": "HoverAndCapture",
"shortDescription": "Stop and Hover at each image point before taking image",
"type": "bool",
"defaultValue": false
},
{
"name": "Refly90Degrees",
"shortDescription": "Refly the pattern at a 90 degree angle",
"type": "bool",
"defaultValue": false
}
]
This diff is collapsed.
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "ComplexMissionItem.h"
#include "MissionItem.h"
#include "SettingsFact.h"
#include "QGCLoggingCategory.h"
#include "QGCMapPolyline.h"
#include "QGCMapPolygon.h"
#include "CameraCalc.h"
Q_DECLARE_LOGGING_CATEGORY(TransectStyleComplexItemLog)
class TransectStyleComplexItem : public ComplexMissionItem
{
Q_OBJECT
public:
TransectStyleComplexItem(Vehicle* vehicle, QString settignsGroup, QObject* parent = NULL);
Q_PROPERTY(QGCMapPolygon* surveyAreaPolygon READ surveyAreaPolygon CONSTANT)
Q_PROPERTY(CameraCalc* cameraCalc READ cameraCalc CONSTANT)
Q_PROPERTY(Fact* turnAroundDistance READ turnAroundDistance CONSTANT)
Q_PROPERTY(Fact* cameraTriggerInTurnAround READ cameraTriggerInTurnAround CONSTANT)
Q_PROPERTY(Fact* hoverAndCapture READ hoverAndCapture CONSTANT)
Q_PROPERTY(Fact* refly90Degrees READ refly90Degrees CONSTANT)
Q_PROPERTY(int cameraShots READ cameraShots NOTIFY cameraShotsChanged)
Q_PROPERTY(double timeBetweenShots READ timeBetweenShots NOTIFY timeBetweenShotsChanged)
Q_PROPERTY(double coveredArea READ coveredArea NOTIFY coveredAreaChanged)
Q_PROPERTY(double cameraMinTriggerInterval READ cameraMinTriggerInterval NOTIFY cameraMinTriggerIntervalChanged)
Q_PROPERTY(bool hoverAndCaptureAllowed READ hoverAndCaptureAllowed CONSTANT)
Q_PROPERTY(QVariantList transectPoints READ transectPoints NOTIFY transectPointsChanged)
QGCMapPolygon* surveyAreaPolygon (void) { return &_surveyAreaPolygon; }
CameraCalc* cameraCalc (void) { return &_cameraCalc; }
QVariantList transectPoints (void) { return _transectPoints; }
Fact* turnAroundDistance (void) { return &_turnAroundDistanceFact; }
Fact* cameraTriggerInTurnAround (void) { return &_cameraTriggerInTurnAroundFact; }
Fact* hoverAndCapture (void) { return &_hoverAndCaptureFact; }
Fact* refly90Degrees (void) { return &_refly90DegreesFact; }
int cameraShots (void) const { return _cameraShots; }
double timeBetweenShots (void);
double coveredArea (void) const;
double cameraMinTriggerInterval(void) const { return _cameraMinTriggerInterval; }
bool hoverAndCaptureAllowed (void) const;
// Overrides from ComplexMissionItem
int lastSequenceNumber (void) const override = 0;
QString mapVisualQML (void) const override = 0;
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) override = 0;
double complexDistance (void) const final { return _complexDistance; }
double greatestDistanceTo (const QGeoCoordinate &other) const final;
// Overrides from VisualMissionItem
void save (QJsonArray& missionItems) override = 0;
bool specifiesCoordinate (void) const override = 0;
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) override = 0;
void applyNewAltitude (double newAltitude) override = 0;
bool dirty (void) const final { return _dirty; }
bool isSimpleItem (void) const final { return false; }
bool isStandaloneCoordinate (void) const final { return false; }
bool specifiesAltitudeOnly (void) const final { return false; }
QString commandDescription (void) const final { return tr("Corridor Scan"); }
QString commandName (void) const final { return tr("Corridor Scan"); }
QString abbreviation (void) const final { return "S"; }
QGeoCoordinate coordinate (void) const final { return _coordinate; }
QGeoCoordinate exitCoordinate (void) const final { return _exitCoordinate; }
int sequenceNumber (void) const final { return _sequenceNumber; }
double specifiedFlightSpeed (void) final { return std::numeric_limits<double>::quiet_NaN(); }
double specifiedGimbalYaw (void) final { return std::numeric_limits<double>::quiet_NaN(); }
double specifiedGimbalPitch (void) final { return std::numeric_limits<double>::quiet_NaN(); }
void setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) final;
bool coordinateHasRelativeAltitude (void) const final { return true /*_altitudeRelative*/; }
bool exitCoordinateHasRelativeAltitude (void) const final { return true /*_altitudeRelative*/; }
bool exitCoordinateSameAsEntry (void) const final { return false; }
void setDirty (bool dirty) final;
void setCoordinate (const QGeoCoordinate& coordinate) final { Q_UNUSED(coordinate); }
void setSequenceNumber (int sequenceNumber) final;
static const char* turnAroundDistanceName;
static const char* turnAroundDistanceMultiRotorName;
static const char* cameraTriggerInTurnAroundName;
static const char* hoverAndCaptureName;
static const char* refly90DegreesName;
signals:
void cameraShotsChanged (void);
void timeBetweenShotsChanged (void);
void cameraMinTriggerIntervalChanged(double cameraMinTriggerInterval);
void transectPointsChanged (void);
void coveredAreaChanged (void);
protected slots:
virtual void _rebuildTransects (void) = 0;
void _setDirty (void);
void _setIfDirty (bool dirty);
void _updateCoordinateAltitudes (void);
void _signalLastSequenceNumberChanged (void);
protected:
void _save (QJsonObject& saveObject);
bool _load (const QJsonObject& complexObject, QString& errorString);
void _setExitCoordinate (const QGeoCoordinate& coordinate);
void _setCameraShots (int cameraShots);
double _triggerDistance (void) const;
int _transectCount (void) const;
bool _hasTurnaround (void) const;
double _turnaroundDistance (void) const;
QString _settingsGroup;
int _sequenceNumber;
bool _dirty;
QGeoCoordinate _coordinate;
QGeoCoordinate _exitCoordinate;
QVariantList _transectPoints;
QGCMapPolygon _surveyAreaPolygon;
bool _ignoreRecalc;
double _complexDistance;
int _cameraShots;
double _timeBetweenShots;
double _cameraMinTriggerInterval;
double _cruiseSpeed;
CameraCalc _cameraCalc;
QMap<QString, FactMetaData*> _metaDataMap;
SettingsFact _turnAroundDistanceFact;
SettingsFact _cameraTriggerInTurnAroundFact;
SettingsFact _hoverAndCaptureFact;
SettingsFact _refly90DegreesFact;
static const char* _jsonCameraCalcKey;
};
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "TransectStyleComplexItemTest.h"
#include "QGCApplication.h"
TransectStyleComplexItemTest::TransectStyleComplexItemTest(void)
: _offlineVehicle(NULL)
{
_polygonVertices << QGeoCoordinate(47.633550640000003, -122.08982199)
<< QGeoCoordinate(47.634129020000003, -122.08887249)
<< QGeoCoordinate(47.633619320000001, -122.08811074)
<< QGeoCoordinate(47.633189139999999, -122.08900124);
}
void TransectStyleComplexItemTest::init(void)
{
UnitTest::init();
_offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this);
_transectStyleItem = new TransectStyleItem(_offlineVehicle, this);
_setSurveyAreaPolygon();
_transectStyleItem->setDirty(false);
_rgSignals[cameraShotsChangedIndex] = SIGNAL(cameraShotsChanged());
_rgSignals[timeBetweenShotsChangedIndex] = SIGNAL(timeBetweenShotsChanged());
_rgSignals[cameraMinTriggerIntervalChangedIndex] = SIGNAL(cameraMinTriggerIntervalChanged(double));
_rgSignals[transectPointsChangedIndex] = SIGNAL(transectPointsChanged());
_rgSignals[coveredAreaChangedIndex] = SIGNAL(coveredAreaChanged());
_rgSignals[dirtyChangedIndex] = SIGNAL(dirtyChanged(bool));
_rgSignals[complexDistanceChangedIndex] = SIGNAL(complexDistanceChanged());
_rgSignals[greatestDistanceToChangedIndex] = SIGNAL(greatestDistanceToChanged());
_rgSignals[additionalTimeDelayChangedIndex] = SIGNAL(additionalTimeDelayChanged());
_rgSignals[lastSequenceNumberChangedIndex] = SIGNAL(lastSequenceNumberChanged(int));
_multiSpy = new MultiSignalSpy();
QCOMPARE(_multiSpy->init(_transectStyleItem, _rgSignals, _cSignals), true);
}
void TransectStyleComplexItemTest::cleanup(void)
{
delete _transectStyleItem;
delete _offlineVehicle;
delete _multiSpy;
}
void TransectStyleComplexItemTest::_testDirty(void)
{
QVERIFY(!_transectStyleItem->dirty());
_transectStyleItem->setDirty(false);
QVERIFY(!_transectStyleItem->dirty());
QVERIFY(_multiSpy->checkNoSignals());
_transectStyleItem->setDirty(true);
QVERIFY(_transectStyleItem->dirty());
QVERIFY(_multiSpy->checkOnlySignalByMask(dirtyChangedMask));
QVERIFY(_multiSpy->pullBoolFromSignalIndex(dirtyChangedIndex));
_multiSpy->clearAllSignals();
_transectStyleItem->setDirty(false);
QVERIFY(!_transectStyleItem->dirty());
QVERIFY(_multiSpy->checkOnlySignalByMask(dirtyChangedMask));
_multiSpy->clearAllSignals();
// These facts should set dirty when changed
QList<Fact*> rgFacts;
rgFacts << _transectStyleItem->turnAroundDistance()
<< _transectStyleItem->cameraTriggerInTurnAround()
<< _transectStyleItem->hoverAndCapture()
<< _transectStyleItem->refly90Degrees();
foreach(Fact* fact, rgFacts) {
qDebug() << fact->name();
QVERIFY(!_transectStyleItem->dirty());
changeFactValue(fact);
QVERIFY(_multiSpy->checkSignalByMask(dirtyChangedMask));
_transectStyleItem->setDirty(false);
_multiSpy->clearAllSignals();
}
rgFacts.clear();
_adjustSurveAreaPolygon();
QVERIFY(_transectStyleItem->dirty());
_transectStyleItem->setDirty(false);
QVERIFY(!_transectStyleItem->surveyAreaPolygon()->dirty());
_multiSpy->clearAllSignals();
changeFactValue(_transectStyleItem->cameraCalc()->distanceToSurface());
QVERIFY(_transectStyleItem->dirty());
_transectStyleItem->setDirty(false);
QVERIFY(!_transectStyleItem->cameraCalc()->dirty());
_multiSpy->clearAllSignals();
}
void TransectStyleComplexItemTest::_setSurveyAreaPolygon(void)
{
foreach (const QGeoCoordinate vertex, _polygonVertices) {
_transectStyleItem->surveyAreaPolygon()->appendVertex(vertex);
}
}
void TransectStyleComplexItemTest::_testRebuildTransects(void)
{
// Changing the survey polygon should trigger:
// _rebuildTransects call
// coveredAreaChanged signal
// lastSequenceNumberChanged signal
_adjustSurveAreaPolygon();
QVERIFY(_transectStyleItem->rebuildTransectsCalled);
QVERIFY(_multiSpy->checkSignalsByMask(coveredAreaChangedMask | lastSequenceNumberChangedMask));
_transectStyleItem->rebuildTransectsCalled = false;
_transectStyleItem->setDirty(false);
_multiSpy->clearAllSignals();
// Changes to these facts should trigger:
// _rebuildTransects call
// lastSequenceNumberChanged signal
QList<Fact*> rgFacts;
rgFacts << _transectStyleItem->turnAroundDistance()
<< _transectStyleItem->cameraTriggerInTurnAround()
<< _transectStyleItem->hoverAndCapture()
<< _transectStyleItem->refly90Degrees()
<< _transectStyleItem->cameraCalc()->adjustedFootprintSide()
<< _transectStyleItem->cameraCalc()->adjustedFootprintFrontal();
foreach(Fact* fact, rgFacts) {
qDebug() << fact->name();
changeFactValue(fact);
QVERIFY(_transectStyleItem->rebuildTransectsCalled);
QVERIFY(_multiSpy->checkSignalsByMask(lastSequenceNumberChangedMask));
_transectStyleItem->setDirty(false);
_multiSpy->clearAllSignals();
_transectStyleItem->rebuildTransectsCalled = false;
}
rgFacts.clear();
}
void TransectStyleComplexItemTest::_testDistanceSignalling(void)
{
_adjustSurveAreaPolygon();
QVERIFY(_multiSpy->checkSignalsByMask(complexDistanceChangedMask | greatestDistanceToChangedMask));
_transectStyleItem->setDirty(false);
_multiSpy->clearAllSignals();
QList<Fact*> rgFacts;
rgFacts << _transectStyleItem->turnAroundDistance()
<< _transectStyleItem->hoverAndCapture()
<< _transectStyleItem->refly90Degrees();
foreach(Fact* fact, rgFacts) {
qDebug() << fact->name();
changeFactValue(fact);
QVERIFY(_multiSpy->checkSignalsByMask(complexDistanceChangedMask | greatestDistanceToChangedMask));
_transectStyleItem->setDirty(false);
_multiSpy->clearAllSignals();
}
rgFacts.clear();
}
void TransectStyleComplexItemTest::_adjustSurveAreaPolygon(void)
{
QGeoCoordinate vertex = _transectStyleItem->surveyAreaPolygon()->vertexCoordinate(0);
vertex.setLatitude(vertex.latitude() + 1);
_transectStyleItem->surveyAreaPolygon()->adjustVertex(0, vertex);
}
TransectStyleItem::TransectStyleItem(Vehicle* vehicle, QObject* parent)
: TransectStyleComplexItem (vehicle, QStringLiteral("UnitTestTransect"), parent)
, rebuildTransectsCalled (false)
{
}
void TransectStyleItem::_rebuildTransects(void)
{
rebuildTransectsCalled = true;
}
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include "UnitTest.h"
#include "MultiSignalSpy.h"
#include "CorridorScanComplexItem.h"
#include <QGeoCoordinate>
class TransectStyleItem;
class TransectStyleComplexItemTest : public UnitTest
{
Q_OBJECT
public:
TransectStyleComplexItemTest(void);
protected:
void init(void) final;
void cleanup(void) final;
private slots:
void _testDirty (void);
void _testRebuildTransects (void);
void _testDistanceSignalling (void);
private:
void _setSurveyAreaPolygon (void);
void _adjustSurveAreaPolygon(void);
enum {
// These signals are from TransectStyleComplexItem
cameraShotsChangedIndex = 0,
timeBetweenShotsChangedIndex,
cameraMinTriggerIntervalChangedIndex,
transectPointsChangedIndex,
coveredAreaChangedIndex,
// These signals are from ComplexItem
dirtyChangedIndex,
complexDistanceChangedIndex,
greatestDistanceToChangedIndex,
additionalTimeDelayChangedIndex,
// These signals are from VisualMissionItem
lastSequenceNumberChangedIndex,
maxSignalIndex
};
enum {
// These signals are from TransectStyleComplexItem
cameraShotsChangedMask = 1 << cameraShotsChangedIndex,
timeBetweenShotsChangedMask = 1 << timeBetweenShotsChangedIndex,
cameraMinTriggerIntervalChangedMask = 1 << cameraMinTriggerIntervalChangedIndex,
transectPointsChangedMask = 1 << transectPointsChangedIndex,
coveredAreaChangedMask = 1 << coveredAreaChangedIndex,
// These signals are from ComplexItem
dirtyChangedMask = 1 << dirtyChangedIndex,
complexDistanceChangedMask = 1 << complexDistanceChangedIndex,
greatestDistanceToChangedMask = 1 << greatestDistanceToChangedIndex,
additionalTimeDelayChangedMask = 1 << additionalTimeDelayChangedIndex,
// These signals are from VisualMissionItem
lastSequenceNumberChangedMask = 1 << lastSequenceNumberChangedIndex,
};
static const size_t _cSignals = maxSignalIndex;
const char* _rgSignals[_cSignals];
Vehicle* _offlineVehicle;
MultiSignalSpy* _multiSpy;
QList<QGeoCoordinate> _polygonVertices;
TransectStyleItem* _transectStyleItem;
};
class TransectStyleItem : public TransectStyleComplexItem
{
Q_OBJECT
public:
TransectStyleItem(Vehicle* vehicle, QObject* parent = NULL);
// Overrides from ComplexMissionItem
int lastSequenceNumber (void) const final { return _sequenceNumber; }
QString mapVisualQML (void) const final { return QString(); }
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final { Q_UNUSED(complexObject); Q_UNUSED(sequenceNumber); Q_UNUSED(errorString); return false; }
// Overrides from VisualMissionItem
void save (QJsonArray& missionItems) final { Q_UNUSED(missionItems); }
bool specifiesCoordinate (void) const final { return true; }
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) final { Q_UNUSED(items); Q_UNUSED(missionItemParent); }
void applyNewAltitude (double newAltitude) final { Q_UNUSED(newAltitude); }
bool rebuildTransectsCalled;
private slots:
// Overrides from TransectStyleComplexItem
void _rebuildTransects (void) final;
};
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>AreaTestPolygon.kmz</name>
<StyleMap id="m_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#s_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#s_ylw-pushpin_hl</styleUrl>
</Pair>
</StyleMap>
<Style id="s_ylw-pushpin">
<IconStyle>
<scale>1.1</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
</IconStyle>
</Style>
<Style id="s_ylw-pushpin_hl">
<IconStyle>
<scale>1.3</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
</IconStyle>
</Style>
<Placemark>
<name>Untitled Polygon</name>
<styleUrl>#m_ylw-pushpin</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-122.1059149362712,47.65965281788451,0 -122.1044593196253,47.66002598220988,0 -122.1047336695092,47.66034166158975,0 -122.1061470943783,47.6599810708829,0 -122.1059149362712,47.65965281788451,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Document>
</kml>
......@@ -63,7 +63,7 @@ VisualMissionItem::VisualMissionItem(const VisualMissionItem& other, QObject* pa
{
*this = other;
// Don't get terrain altitude information for submarines or boards
// Don't get terrain altitude information for submarines or boats
if (_vehicle->vehicleType() != MAV_TYPE_SUBMARINE && _vehicle->vehicleType() != MAV_TYPE_SURFACE_BOAT) {
connect(this, &VisualMissionItem::coordinateChanged, this, &VisualMissionItem::_updateTerrainAltitude);
}
......
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.2
import QGroundControl 1.0
import QGroundControl.ScreenTools 1.0
import QGroundControl.Vehicle 1.0
import QGroundControl.Controls 1.0
import QGroundControl.FactControls 1.0
import QGroundControl.Palette 1.0
import QGroundControl.FlightMap 1.0
// Editor for Survery mission items
Rectangle {
id: _root
height: visible ? (editorColumn.height + (_margin * 2)) : 0
width: availableWidth
color: qgcPal.windowShadeDark
radius: _radius
// The following properties must be available up the hierarchy chain
//property real availableWidth ///< Width for control
//property var missionItem ///< Mission Item for editor
property real _margin: ScreenTools.defaultFontPixelWidth / 2
property real _fieldWidth: ScreenTools.defaultFontPixelWidth * 10.5
property var _vehicle: QGroundControl.multiVehicleManager.activeVehicle ? QGroundControl.multiVehicleManager.activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle
function polygonCaptureStarted() {
missionItem.clearPolygon()
}
function polygonCaptureFinished(coordinates) {
for (var i=0; i<coordinates.length; i++) {
missionItem.addPolygonCoordinate(coordinates[i])
}
}
function polygonAdjustVertex(vertexIndex, vertexCoordinate) {
missionItem.adjustPolygonCoordinate(vertexIndex, vertexCoordinate)
}
function polygonAdjustStarted() { }
function polygonAdjustFinished() { }
QGCPalette { id: qgcPal; colorGroupEnabled: true }
Column {
id: editorColumn
anchors.margins: _margin
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: _margin
QGCLabel {
anchors.left: parent.left
anchors.right: parent.right
text: qsTr("WARNING: Photo interval is below minimum interval (%1 secs) supported by camera.").arg(missionItem.cameraMinTriggerInterval.toFixed(1))
wrapMode: Text.WordWrap
color: qgcPal.warningText
visible: missionItem.cameraShots > 0 && missionItem.cameraMinTriggerInterval !== 0 && missionItem.cameraMinTriggerInterval > missionItem.timeBetweenShots
}
CameraCalc {
cameraCalc: missionItem.cameraCalc
vehicleFlightIsFrontal: true
distanceToSurfaceLabel: qsTr("Altitude")
frontalDistanceLabel: qsTr("Trigger Distance")
sideDistanceLabel: qsTr("Spacing")
}
SectionHeader {
id: corridorHeader
text: qsTr("Corridor")
}
GridLayout {
anchors.left: parent.left
anchors.right: parent.right
columnSpacing: _margin
rowSpacing: _margin
columns: 2
visible: corridorHeader.checked
QGCLabel { text: qsTr("Width") }
FactTextField {
fact: missionItem.corridorWidth
Layout.fillWidth: true
}
QGCLabel { text: qsTr("Turnaround dist") }
FactTextField {
fact: missionItem.turnAroundDistance
Layout.fillWidth: true
}
FactCheckBox {
text: qsTr("Take images in turnarounds")
fact: missionItem.cameraTriggerInTurnAround
enabled: missionItem.hoverAndCaptureAllowed ? !missionItem.hoverAndCapture.rawValue : true
Layout.columnSpan: 2
}
QGCCheckBox {
id: relAlt
anchors.left: parent.left
text: qsTr("Relative altitude")
checked: missionItem.cameraCalc.distanceToSurfaceRelative
enabled: missionItem.cameraCalc.isManualCamera
Layout.columnSpan: 2
onClicked: missionItem.cameraCalc.distanceToSurfaceRelative = checked
Connections {
target: missionItem.cameraCalc
onDistanceToSurfaceRelativeChanged: relAlt.checked = missionItem.cameraCalc.distanceToSurfaceRelative
}
}
}
QGCButton {
text: qsTr("Rotate Entry Point")
onClicked: missionItem.rotateEntryPoint()
}
SectionHeader {
id: statsHeader
text: qsTr("Statistics")
}
Grid {
columns: 2
columnSpacing: ScreenTools.defaultFontPixelWidth
visible: statsHeader.checked
QGCLabel { text: qsTr("Photo count") }
QGCLabel { text: missionItem.cameraShots }
QGCLabel { text: qsTr("Photo interval") }
QGCLabel { text: missionItem.timeBetweenShots.toFixed(1) + " " + qsTr("secs") }
}
} // Column
} // Rectangle
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtLocation 5.3
import QtPositioning 5.3
import QGroundControl 1.0
import QGroundControl.Controls 1.0
/// Corridor Scan Complex Mission Item visuals
Item {
id: _root
property var map ///< Map control to place item in
property var qgcView ///< QGCView to use for popping dialogs
property var _missionItem: object
property var _entryCoordinate
property var _exitCoordinate
property var _transectLines
signal clicked(int sequenceNumber)
function _addVisualElements() {
_entryCoordinate = entryPointComponent.createObject(map)
_exitCoordinate = exitPointComponent.createObject(map)
_transectLines = transectsComponent.createObject(map)
map.addMapItem(_entryCoordinate)
map.addMapItem(_exitCoordinate)
map.addMapItem(_transectLines)
}
function _destroyVisualElements() {
_entryCoordinate.destroy()
_exitCoordinate.destroy()
_transectLines.destroy()
}
Component.onCompleted: {
mapPolylineVisuals.addInitialPolyline()
_addVisualElements()
}
Component.onDestruction: {
_destroyVisualElements()
}
QGCMapPolygonVisuals {
qgcView: _root.qgcView
mapControl: map
mapPolygon: object.surveyAreaPolygon
interactive: false
interiorColor: "green"
interiorOpacity: 0.25
}
QGCMapPolylineVisuals {
id: mapPolylineVisuals
qgcView: _root.qgcView
mapControl: map
mapPolyline: object.corridorPolyline
interactive: _missionItem.isCurrentItem
lineWidth: 3
lineColor: "#be781c"
}
// Entry point
Component {
id: entryPointComponent
MapQuickItem {
anchorPoint.x: sourceItem.anchorPointX
anchorPoint.y: sourceItem.anchorPointY
z: QGroundControl.zOrderMapItems
coordinate: _missionItem.coordinate
visible: _missionItem.coordinate.isValid
sourceItem: MissionItemIndexLabel {
index: _missionItem.sequenceNumber
label: "Entry"
checked: _missionItem.isCurrentItem
onClicked: _root.clicked(_missionItem.sequenceNumber)
}
}
}
// Exit point
Component {
id: exitPointComponent
MapQuickItem {
anchorPoint.x: sourceItem.anchorPointX
anchorPoint.y: sourceItem.anchorPointY
z: QGroundControl.zOrderMapItems
coordinate: _missionItem.exitCoordinate
visible: _missionItem.exitCoordinate.isValid
sourceItem: MissionItemIndexLabel {
index: _missionItem.lastSequenceNumber
label: "Exit"
checked: _missionItem.isCurrentItem
onClicked: _root.clicked(_missionItem.sequenceNumber)
}
}
}
// Transect lines
Component {
id: transectsComponent
MapPolyline {
line.color: "white"
line.width: 2
path: _missionItem.transectPoints
}
}
}
......@@ -95,7 +95,8 @@ void QGCFileDownload::_downloadFinished(void)
// When an error occurs or the user cancels the download, we still end up here. So bail out in
// those cases.
if (reply->error() != QNetworkReply::NoError) {
if (reply->error() != QNetworkReply::NoError) {
reply->deleteLater();
return;
}
......@@ -128,6 +129,8 @@ void QGCFileDownload::_downloadFinished(void)
qWarning() << errorMsg;
emit error(errorMsg);
}
reply->deleteLater();
}
/// @brief Called when an error occurs during download
......
......@@ -20,6 +20,8 @@ Rectangle {
border.width: _showBorder ? 1: 0
border.color: qgcPal.buttonText
property var tileSet: null
property var currentSet: null
property bool checked: false
property bool complete: false
property alias text: nameLabel.text
......@@ -37,6 +39,19 @@ Rectangle {
signal clicked()
property ExclusiveGroup exclusiveGroup: null
onExclusiveGroupChanged: {
if (exclusiveGroup) {
checked = false
exclusiveGroup.bindCheckable(mapButton)
}
}
onCheckedChanged: {
if(checked) {
currentSet = tileSet
}
}
QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
Row {
......@@ -85,26 +100,53 @@ Rectangle {
MouseArea {
anchors.fill: parent
hoverEnabled: true
hoverEnabled: !ScreenTools.isMobile
onMouseXChanged: {
_lastGlobalMouseX = ScreenTools.mouseX()
_lastGlobalMouseY = ScreenTools.mouseY()
if(!ScreenTools.isMobile) {
_lastGlobalMouseX = ScreenTools.mouseX()
_lastGlobalMouseY = ScreenTools.mouseY()
}
}
onMouseYChanged: {
_lastGlobalMouseX = ScreenTools.mouseX()
_lastGlobalMouseY = ScreenTools.mouseY()
}
onEntered: { _hovered = true; _forceHoverOff = false; hoverTimer.start() }
onExited: { _hovered = false; _forceHoverOff = false; hoverTimer.stop() }
onPressed: { _pressed = true; }
onReleased: { _pressed = false; }
onClicked: mapButton.clicked()
if(!ScreenTools.isMobile) {
_lastGlobalMouseX = ScreenTools.mouseX()
_lastGlobalMouseY = ScreenTools.mouseY()
}
}
onEntered: {
if(!ScreenTools.isMobile) {
_hovered = true
_forceHoverOff = false
hoverTimer.start()
}
}
onExited: {
if(!ScreenTools.isMobile) {
_hovered = false
_forceHoverOff = false
hoverTimer.stop()
}
}
onPressed: {
if(!ScreenTools.isMobile) {
_pressed = true
}
}
onReleased: {
if(!ScreenTools.isMobile) {
_pressed = false
}
}
onClicked: {
checked = true
mapButton.clicked()
}
}
Timer {
id: hoverTimer
interval: 250
repeat: true
repeat: !ScreenTools.isMobile
onTriggered: {
if (_lastGlobalMouseX !== ScreenTools.mouseX() || _lastGlobalMouseY !== ScreenTools.mouseY()) {
_forceHoverOff = true
......
......@@ -21,6 +21,7 @@ Slider {
// Value indicator starts display from zero instead of min value
property bool zeroCentered: false
property bool displayValue: false
QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
......@@ -67,8 +68,15 @@ Slider {
implicitWidth: _radius * 2
implicitHeight: _radius * 2
radius: _radius
property real _radius: Math.round(ScreenTools.defaultFontPixelHeight * 0.75)
Label {
text: _root.value.toFixed(0)
visible: _root.displayValue
anchors.centerIn: parent
font.family: ScreenTools.normalFontFamily
font.pointSize: ScreenTools.smallFontPointSize
color: qgcPal.buttonText
}
}
}
}
......@@ -48,6 +48,7 @@ QGCListView 1.0 QGCListView.qml
QGCMapLabel 1.0 QGCMapLabel.qml
QGCMapCircleVisuals 1.0 QGCMapCircleVisuals.qml
QGCMapPolygonVisuals 1.0 QGCMapPolygonVisuals.qml
QGCMapPolylineVisuals 1.0 QGCMapPolylineVisuals.qml
QGCMouseArea 1.0 QGCMouseArea.qml
QGCMovableItem 1.0 QGCMovableItem.qml
QGCPipable 1.0 QGCPipable.qml
......
......@@ -918,11 +918,14 @@ QGCView {
width: Math.min(_tileSetList.width, (ScreenTools.defaultFontPixelWidth * 50).toFixed(0))
spacing: ScreenTools.defaultFontPixelHeight * 0.5
anchors.horizontalCenter: parent.horizontalCenter
ExclusiveGroup { id: selectionGroup }
OfflineMapButton {
id: firstButton
text: qsTr("Add New Set")
width: _cacheList.width
height: ScreenTools.defaultFontPixelHeight * 2
height: ScreenTools.defaultFontPixelHeight * (ScreenTools.isMobile ? 3 : 2)
currentSet: _currentSelection
exclusiveGroup: selectionGroup
onClicked: {
offlineMapView._currentSelection = null
addNewSet()
......@@ -936,7 +939,10 @@ QGCView {
tiles: object.totalTileCount
complete: object.complete
width: firstButton.width
height: ScreenTools.defaultFontPixelHeight * 2
height: ScreenTools.defaultFontPixelHeight * (ScreenTools.isMobile ? 3 : 2)
exclusiveGroup: selectionGroup
currentSet: _currentSelection
tileSet: object
onClicked: {
offlineMapView._currentSelection = object
showInfo()
......
......@@ -72,7 +72,7 @@ void MultiVehicleManager::setToolbox(QGCToolbox *toolbox)
this);
}
void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int componentId, int vehicleMavlinkVersion, int vehicleFirmwareType, int vehicleType)
void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType)
{
if (componentId != MAV_COMP_ID_AUTOPILOT1) {
// Special case for PX4 Flow
......@@ -82,7 +82,6 @@ void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicle
<< link->getName()
<< vehicleId
<< componentId
<< vehicleMavlinkVersion
<< vehicleFirmwareType
<< vehicleType;
return;
......@@ -108,11 +107,10 @@ void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicle
break;
}
qCDebug(MultiVehicleManagerLog()) << "Adding new vehicle link:vehicleId:componentId:vehicleMavlinkVersion:vehicleFirmwareType:vehicleType "
qCDebug(MultiVehicleManagerLog()) << "Adding new vehicle link:vehicleId:componentId:vehicleFirmwareType:vehicleType "
<< link->getName()
<< vehicleId
<< componentId
<< vehicleMavlinkVersion
<< vehicleFirmwareType
<< vehicleType;
......@@ -120,16 +118,6 @@ void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicle
_app->showMessage(tr("Warning: A vehicle is using the same system id as %1: %2").arg(qgcApp()->applicationName()).arg(vehicleId));
}
// QSettings settings;
// bool mavlinkVersionCheck = settings.value("VERSION_CHECK_ENABLED", true).toBool();
// if (mavlinkVersionCheck && vehicleMavlinkVersion != MAVLINK_VERSION) {
// _ignoreVehicleIds += vehicleId;
// _app->showMessage(QString("The MAVLink protocol version on vehicle #%1 and %2 differ! "
// "It is unsafe to use different MAVLink versions. "
// "%2 therefore refuses to connect to vehicle #%1, which sends MAVLink version %3 (%2 uses version %4).").arg(vehicleId).arg(qgcApp()->applicationName()).arg(vehicleMavlinkVersion).arg(MAVLINK_VERSION));
// return;
// }
Vehicle* vehicle = new Vehicle(link, vehicleId, componentId, (MAV_AUTOPILOT)vehicleFirmwareType, (MAV_TYPE)vehicleType, _firmwarePluginManager, _joystickManager);
connect(vehicle, &Vehicle::allLinksInactive, this, &MultiVehicleManager::_deleteVehiclePhase1);
connect(vehicle, &Vehicle::requestProtocolVersion, this, &MultiVehicleManager::_requestProtocolVersion);
......@@ -371,7 +359,7 @@ void MultiVehicleManager::_sendGCSHeartbeat(void)
LinkManager* linkMgr = _toolbox->linkManager();
for (int i=0; i<linkMgr->links().count(); i++) {
LinkInterface* link = linkMgr->links()[i];
if (link->isConnected()) {
if (link->isConnected() && !link->highLatency()) {
mavlink_message_t message;
mavlink_msg_heartbeat_pack_chan(_mavlinkProtocol->getSystemId(),
_mavlinkProtocol->getComponentId(),
......
......@@ -94,7 +94,7 @@ private slots:
void _setActiveVehiclePhase2(void);
void _vehicleParametersReadyChanged(bool parametersReady);
void _sendGCSHeartbeat(void);
void _vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int componentId, int vehicleMavlinkVersion, int vehicleFirmwareType, int vehicleType);
void _vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType);
void _requestProtocolVersion(unsigned version);
private:
......
This diff is collapsed.
......@@ -941,6 +941,7 @@ private:
void _handleScaledPressure(mavlink_message_t& message);
void _handleScaledPressure2(mavlink_message_t& message);
void _handleScaledPressure3(mavlink_message_t& message);
void _handleHighLatency2(mavlink_message_t& message);
// ArduPilot dialect messages
#if !defined(NO_ARDUPILOT_DIALECT)
void _handleCameraFeedback(const mavlink_message_t& message);
......@@ -1041,6 +1042,7 @@ private:
int _mavCommandRetryCount;
static const int _mavCommandMaxRetryCount = 3;
static const int _mavCommandAckTimeoutMSecs = 3000;
static const int _mavCommandAckTimeoutMSecsHighLatency = 120000;
QString _prearmError;
QTimer _prearmErrorTimer;
......
......@@ -85,6 +85,8 @@ SetupPage {
Item {
property int axisValue: 0
property int deadbandValue: 0
property bool narrowIndicator: false
property color deadbandColor: "#8c161a"
property color __barColor: qgcPal.windowShade
......@@ -104,7 +106,7 @@ SetupPage {
x: _deadbandPosition
width: _deadbandWidth
height: parent.height / 2
color: "#8c161a"
color: deadbandColor
visible: controller.deadbandToggle
property real _percentDeadband: ((2 * deadbandValue) / (32768.0 * 2))
......@@ -123,8 +125,8 @@ SetupPage {
// Indicator
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.height * 0.75
height: width
width: parent.narrowIndicator ? height/6 : height
height: parent.height * 0.75
x: (reversed ? (parent.width - _indicatorPosition) : _indicatorPosition) - (width / 2)
radius: width / 2
color: qgcPal.text
......@@ -535,6 +537,21 @@ SetupPage {
onClicked: controller.deadbandToggle = checked
}
}
Row{
width: parent.width
spacing: ScreenTools.defaultFontPixelWidth
visible: advancedSettings.checked
QGCLabel{
width: parent.width * 0.85
font.pointSize: ScreenTools.smallFontPointSize