地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4205 lines
158 KiB

/****************************************************************************
*
* (c) 2009-2020 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 <QTime>
#include <QDateTime>
#include <QLocale>
#include <QQuaternion>
#include <Eigen/Eigen>
#include "Vehicle.h"
#include "MAVLinkProtocol.h"
#include "FirmwarePluginManager.h"
#include "LinkManager.h"
#include "FirmwarePlugin.h"
#include "UAS.h"
#include "JoystickManager.h"
#include "MissionManager.h"
#include "MissionController.h"
#include "PlanMasterController.h"
#include "GeoFenceManager.h"
#include "RallyPointManager.h"
#include "FlightPathSegment.h"
#include "QGCApplication.h"
#include "QGCImageProvider.h"
#include "MissionCommandTree.h"
#include "SettingsManager.h"
#include "QGCQGeoCoordinate.h"
#include "QGCCorePlugin.h"
#include "QGCOptions.h"
#include "ADSBVehicleManager.h"
#include "QGCCameraManager.h"
#include "VideoReceiver.h"
#include "VideoManager.h"
#include "VideoSettings.h"
#include "PositionManager.h"
#include "VehicleObjectAvoidance.h"
#include "TrajectoryPoints.h"
#include "QGCGeo.h"
#include "TerrainProtocolHandler.h"
#include "ParameterManager.h"
#include "FTPManager.h"
#include "ComponentInformationManager.h"
#include "InitialConnectStateMachine.h"
#include "VehicleBatteryFactGroup.h"
#include "EventHandler.h"
#include "Actuators/Actuators.h"
#ifdef QT_DEBUG
#include "MockLink.h"
#endif
#include "Autotune.h"
#if defined(QGC_AIRMAP_ENABLED)
#include "AirspaceVehicleManager.h"
#endif
QGC_LOGGING_CATEGORY(VehicleLog, "VehicleLog")
#define UPDATE_TIMER 50
#define DEFAULT_LAT 38.965767f
#define DEFAULT_LON -120.083923f
const QString guided_mode_not_supported_by_vehicle = QObject::tr("Guided mode not supported by Vehicle.");
const char* Vehicle::_settingsGroup = "Vehicle%1"; // %1 replaced with mavlink system id
const char* Vehicle::_joystickEnabledSettingsKey = "JoystickEnabled";
const char* Vehicle::_rollFactName = "roll";
const char* Vehicle::_pitchFactName = "pitch";
const char* Vehicle::_headingFactName = "heading";
const char* Vehicle::_rollRateFactName = "rollRate";
const char* Vehicle::_pitchRateFactName = "pitchRate";
const char* Vehicle::_yawRateFactName = "yawRate";
const char* Vehicle::_airSpeedFactName = "airSpeed";
const char* Vehicle::_airSpeedSetpointFactName = "airSpeedSetpoint";
const char* Vehicle::_xTrackErrorFactName = "xTrackError";
const char* Vehicle::_rangeFinderDistFactName = "rangeFinderDist";
const char* Vehicle::_groundSpeedFactName = "groundSpeed";
const char* Vehicle::_climbRateFactName = "climbRate";
const char* Vehicle::_altitudeRelativeFactName = "altitudeRelative";
const char* Vehicle::_altitudeAMSLFactName = "altitudeAMSL";
const char* Vehicle::_altitudeTuningFactName = "altitudeTuning";
const char* Vehicle::_altitudeTuningSetpointFactName = "altitudeTuningSetpoint";
const char* Vehicle::_flightDistanceFactName = "flightDistance";
const char* Vehicle::_flightTimeFactName = "flightTime";
const char* Vehicle::_distanceToHomeFactName = "distanceToHome";
const char* Vehicle::_missionItemIndexFactName = "missionItemIndex";
const char* Vehicle::_headingToNextWPFactName = "headingToNextWP";
const char* Vehicle::_headingToHomeFactName = "headingToHome";
const char* Vehicle::_distanceToGCSFactName = "distanceToGCS";
const char* Vehicle::_hobbsFactName = "hobbs";
const char* Vehicle::_throttlePctFactName = "throttlePct";
const char* Vehicle::_gpsFactGroupName = "gps";
const char* Vehicle::_gps2FactGroupName = "gps2";
const char* Vehicle::_windFactGroupName = "wind";
const char* Vehicle::_vibrationFactGroupName = "vibration";
const char* Vehicle::_temperatureFactGroupName = "temperature";
const char* Vehicle::_clockFactGroupName = "clock";
const char* Vehicle::_setpointFactGroupName = "setpoint";
const char* Vehicle::_distanceSensorFactGroupName = "distanceSensor";
const char* Vehicle::_localPositionFactGroupName = "localPosition";
const char* Vehicle::_localPositionSetpointFactGroupName ="localPositionSetpoint";
const char* Vehicle::_escStatusFactGroupName = "escStatus";
const char* Vehicle::_estimatorStatusFactGroupName = "estimatorStatus";
const char* Vehicle::_terrainFactGroupName = "terrain";
const char* Vehicle::_hygrometerFactGroupName = "hygrometer";
// Standard connected vehicle
Vehicle::Vehicle(LinkInterface* link,
int vehicleId,
int defaultComponentId,
MAV_AUTOPILOT firmwareType,
MAV_TYPE vehicleType,
FirmwarePluginManager* firmwarePluginManager,
JoystickManager* joystickManager)
: FactGroup (_vehicleUIUpdateRateMSecs, ":/json/Vehicle/VehicleFact.json")
, _id (vehicleId)
, _defaultComponentId (defaultComponentId)
, _firmwareType (firmwareType)
, _vehicleType (vehicleType)
, _toolbox (qgcApp()->toolbox())
, _settingsManager (_toolbox->settingsManager())
, _defaultCruiseSpeed (_settingsManager->appSettings()->offlineEditingCruiseSpeed()->rawValue().toDouble())
, _defaultHoverSpeed (_settingsManager->appSettings()->offlineEditingHoverSpeed()->rawValue().toDouble())
, _firmwarePluginManager (firmwarePluginManager)
, _joystickManager (joystickManager)
, _trajectoryPoints (new TrajectoryPoints(this, this))
, _mavlinkStreamConfig (std::bind(&Vehicle::_setMessageInterval, this, std::placeholders::_1, std::placeholders::_2))
, _rollFact (0, _rollFactName, FactMetaData::valueTypeDouble)
, _pitchFact (0, _pitchFactName, FactMetaData::valueTypeDouble)
, _headingFact (0, _headingFactName, FactMetaData::valueTypeDouble)
, _rollRateFact (0, _rollRateFactName, FactMetaData::valueTypeDouble)
, _pitchRateFact (0, _pitchRateFactName, FactMetaData::valueTypeDouble)
, _yawRateFact (0, _yawRateFactName, FactMetaData::valueTypeDouble)
, _groundSpeedFact (0, _groundSpeedFactName, FactMetaData::valueTypeDouble)
, _airSpeedFact (0, _airSpeedFactName, FactMetaData::valueTypeDouble)
, _airSpeedSetpointFact (0, _airSpeedSetpointFactName, FactMetaData::valueTypeDouble)
, _climbRateFact (0, _climbRateFactName, FactMetaData::valueTypeDouble)
, _altitudeRelativeFact (0, _altitudeRelativeFactName, FactMetaData::valueTypeDouble)
, _altitudeAMSLFact (0, _altitudeAMSLFactName, FactMetaData::valueTypeDouble)
, _altitudeTuningFact (0, _altitudeTuningFactName, FactMetaData::valueTypeDouble)
, _altitudeTuningSetpointFact (0, _altitudeTuningSetpointFactName, FactMetaData::valueTypeDouble)
, _xTrackErrorFact (0, _xTrackErrorFactName, FactMetaData::valueTypeDouble)
, _rangeFinderDistFact (0, _rangeFinderDistFactName, FactMetaData::valueTypeFloat)
, _flightDistanceFact (0, _flightDistanceFactName, FactMetaData::valueTypeDouble)
, _flightTimeFact (0, _flightTimeFactName, FactMetaData::valueTypeElapsedTimeInSeconds)
, _distanceToHomeFact (0, _distanceToHomeFactName, FactMetaData::valueTypeDouble)
, _missionItemIndexFact (0, _missionItemIndexFactName, FactMetaData::valueTypeUint16)
, _headingToNextWPFact (0, _headingToNextWPFactName, FactMetaData::valueTypeDouble)
, _headingToHomeFact (0, _headingToHomeFactName, FactMetaData::valueTypeDouble)
, _distanceToGCSFact (0, _distanceToGCSFactName, FactMetaData::valueTypeDouble)
, _hobbsFact (0, _hobbsFactName, FactMetaData::valueTypeString)
, _throttlePctFact (0, _throttlePctFactName, FactMetaData::valueTypeUint16)
, _gpsFactGroup (this)
, _gps2FactGroup (this)
, _windFactGroup (this)
, _vibrationFactGroup (this)
, _temperatureFactGroup (this)
, _clockFactGroup (this)
, _setpointFactGroup (this)
, _distanceSensorFactGroup (this)
, _localPositionFactGroup (this)
, _localPositionSetpointFactGroup(this)
, _escStatusFactGroup (this)
, _estimatorStatusFactGroup (this)
, _hygrometerFactGroup (this)
, _terrainFactGroup (this)
, _terrainProtocolHandler (new TerrainProtocolHandler(this, &_terrainFactGroup, this))
{
_linkManager = _toolbox->linkManager();
connect(_joystickManager, &JoystickManager::activeJoystickChanged, this, &Vehicle::_loadSettings);
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::activeVehicleAvailableChanged, this, &Vehicle::_loadSettings);
_mavlink = _toolbox->mavlinkProtocol();
qCDebug(VehicleLog) << "Link started with Mavlink " << (_mavlink->getCurrentVersion() >= 200 ? "V2" : "V1");
connect(_mavlink, &MAVLinkProtocol::messageReceived, this, &Vehicle::_mavlinkMessageReceived);
connect(_mavlink, &MAVLinkProtocol::mavlinkMessageStatus, this, &Vehicle::_mavlinkMessageStatus);
connect(this, &Vehicle::flightModeChanged, this, &Vehicle::_handleFlightModeChanged);
connect(this, &Vehicle::armedChanged, this, &Vehicle::_announceArmedChanged);
connect(_toolbox->multiVehicleManager(), &MultiVehicleManager::parameterReadyVehicleAvailableChanged, this, &Vehicle::_vehicleParamLoaded);
_uas = new UAS(_mavlink, this, _firmwarePluginManager);
_uas->setParent(this);
connect(this, &Vehicle::remoteControlRSSIChanged, this, &Vehicle::_remoteControlRSSIChanged);
_commonInit();
_vehicleLinkManager->_addLink(link);
// Set video stream to udp if running ArduSub and Video is disabled
if (sub() && _settingsManager->videoSettings()->videoSource()->rawValue() == VideoSettings::videoDisabled) {
_settingsManager->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceUDPH264);
_settingsManager->videoSettings()->lowLatencyMode()->setRawValue(true);
}
//-- Airspace Management
#if defined(QGC_AIRMAP_ENABLED)
AirspaceManager* airspaceManager = _toolbox->airspaceManager();
if (airspaceManager) {
_airspaceVehicleManager = airspaceManager->instantiateVehicle(*this);
if (_airspaceVehicleManager) {
connect(_airspaceVehicleManager, &AirspaceVehicleManager::trafficUpdate, this, &Vehicle::_trafficUpdate);
}
}
#endif
_autopilotPlugin = _firmwarePlugin->autopilotPlugin(this);
_autopilotPlugin->setParent(this);
// PreArm Error self-destruct timer
connect(&_prearmErrorTimer, &QTimer::timeout, this, &Vehicle::_prearmErrorTimeout);
_prearmErrorTimer.setInterval(_prearmErrorTimeoutMSecs);
_prearmErrorTimer.setSingleShot(true);
// Send MAV_CMD ack timer
_mavCommandResponseCheckTimer.setSingleShot(false);
_mavCommandResponseCheckTimer.setInterval(_mavCommandResponseCheckTimeoutMSecs);
_mavCommandResponseCheckTimer.start();
connect(&_mavCommandResponseCheckTimer, &QTimer::timeout, this, &Vehicle::_sendMavCommandResponseTimeoutCheck);
// Chunked status text timeout timer
_chunkedStatusTextTimer.setSingleShot(true);
_chunkedStatusTextTimer.setInterval(1000);
connect(&_chunkedStatusTextTimer, &QTimer::timeout, this, &Vehicle::_chunkedStatusTextTimeout);
_mav = uas();
// Listen for system messages
connect(_toolbox->uasMessageHandler(), &UASMessageHandler::textMessageCountChanged, this, &Vehicle::_handleTextMessage);
connect(_toolbox->uasMessageHandler(), &UASMessageHandler::textMessageReceived, this, &Vehicle::_handletextMessageReceived);
// MAV_TYPE_GENERIC is used by unit test for creating a vehicle which doesn't do the connect sequence. This
// way we can test the methods that are used within the connect sequence.
if (!qgcApp()->runningUnitTests() || _vehicleType != MAV_TYPE_GENERIC) {
_initialConnectStateMachine->start();
}
_firmwarePlugin->initializeVehicle(this);
for(auto& factName: factNames()) {
_firmwarePlugin->adjustMetaData(vehicleType, getFact(factName)->metaData());
}
_sendMultipleTimer.start(_sendMessageMultipleIntraMessageDelay);
connect(&_sendMultipleTimer, &QTimer::timeout, this, &Vehicle::_sendMessageMultipleNext);
connect(&_orbitTelemetryTimer, &QTimer::timeout, this, &Vehicle::_orbitTelemetryTimeout);
// Create camera manager instance
_cameraManager = _firmwarePlugin->createCameraManager(this);
emit cameraManagerChanged();
// Start csv logger
connect(&_csvLogTimer, &QTimer::timeout, this, &Vehicle::_writeCsvLine);
_csvLogTimer.start(1000);
}
// Disconnected Vehicle for offline editing
Vehicle::Vehicle(MAV_AUTOPILOT firmwareType,
MAV_TYPE vehicleType,
FirmwarePluginManager* firmwarePluginManager,
QObject* parent)
: FactGroup (_vehicleUIUpdateRateMSecs, ":/json/Vehicle/VehicleFact.json", parent)
, _id (0)
, _defaultComponentId (MAV_COMP_ID_ALL)
, _offlineEditingVehicle (true)
, _firmwareType (firmwareType)
, _vehicleType (vehicleType)
, _toolbox (qgcApp()->toolbox())
, _settingsManager (_toolbox->settingsManager())
, _defaultCruiseSpeed (_settingsManager->appSettings()->offlineEditingCruiseSpeed()->rawValue().toDouble())
, _defaultHoverSpeed (_settingsManager->appSettings()->offlineEditingHoverSpeed()->rawValue().toDouble())
, _mavlinkProtocolRequestComplete (true)
, _maxProtoVersion (200)
, _capabilityBitsKnown (true)
, _capabilityBits (MAV_PROTOCOL_CAPABILITY_MISSION_FENCE | MAV_PROTOCOL_CAPABILITY_MISSION_RALLY)
, _firmwarePluginManager (firmwarePluginManager)
, _trajectoryPoints (new TrajectoryPoints(this, this))
, _mavlinkStreamConfig (std::bind(&Vehicle::_setMessageInterval, this, std::placeholders::_1, std::placeholders::_2))
, _rollFact (0, _rollFactName, FactMetaData::valueTypeDouble)
, _pitchFact (0, _pitchFactName, FactMetaData::valueTypeDouble)
, _headingFact (0, _headingFactName, FactMetaData::valueTypeDouble)
, _rollRateFact (0, _rollRateFactName, FactMetaData::valueTypeDouble)
, _pitchRateFact (0, _pitchRateFactName, FactMetaData::valueTypeDouble)
, _yawRateFact (0, _yawRateFactName, FactMetaData::valueTypeDouble)
, _groundSpeedFact (0, _groundSpeedFactName, FactMetaData::valueTypeDouble)
, _airSpeedFact (0, _airSpeedFactName, FactMetaData::valueTypeDouble)
, _airSpeedSetpointFact (0, _airSpeedSetpointFactName, FactMetaData::valueTypeDouble)
, _climbRateFact (0, _climbRateFactName, FactMetaData::valueTypeDouble)
, _altitudeRelativeFact (0, _altitudeRelativeFactName, FactMetaData::valueTypeDouble)
, _altitudeAMSLFact (0, _altitudeAMSLFactName, FactMetaData::valueTypeDouble)
, _altitudeTuningFact (0, _altitudeTuningFactName, FactMetaData::valueTypeDouble)
, _altitudeTuningSetpointFact (0, _altitudeTuningSetpointFactName, FactMetaData::valueTypeDouble)
, _xTrackErrorFact (0, _xTrackErrorFactName, FactMetaData::valueTypeDouble)
, _rangeFinderDistFact (0, _rangeFinderDistFactName, FactMetaData::valueTypeFloat)
, _flightDistanceFact (0, _flightDistanceFactName, FactMetaData::valueTypeDouble)
, _flightTimeFact (0, _flightTimeFactName, FactMetaData::valueTypeElapsedTimeInSeconds)
, _distanceToHomeFact (0, _distanceToHomeFactName, FactMetaData::valueTypeDouble)
, _missionItemIndexFact (0, _missionItemIndexFactName, FactMetaData::valueTypeUint16)
, _headingToNextWPFact (0, _headingToNextWPFactName, FactMetaData::valueTypeDouble)
, _headingToHomeFact (0, _headingToHomeFactName, FactMetaData::valueTypeDouble)
, _distanceToGCSFact (0, _distanceToGCSFactName, FactMetaData::valueTypeDouble)
, _hobbsFact (0, _hobbsFactName, FactMetaData::valueTypeString)
, _throttlePctFact (0, _throttlePctFactName, FactMetaData::valueTypeUint16)
, _gpsFactGroup (this)
, _gps2FactGroup (this)
, _windFactGroup (this)
, _vibrationFactGroup (this)
, _clockFactGroup (this)
, _distanceSensorFactGroup (this)
, _localPositionFactGroup (this)
, _localPositionSetpointFactGroup (this)
{
_linkManager = _toolbox->linkManager();
// This will also set the settings based firmware/vehicle types. So it needs to happen first.
if (_firmwareType == MAV_AUTOPILOT_TRACK) {
trackFirmwareVehicleTypeChanges();
}
_commonInit();
connect(_settingsManager->appSettings()->offlineEditingCruiseSpeed(), &Fact::rawValueChanged, this, &Vehicle::_offlineCruiseSpeedSettingChanged);
connect(_settingsManager->appSettings()->offlineEditingHoverSpeed(), &Fact::rawValueChanged, this, &Vehicle::_offlineHoverSpeedSettingChanged);
_offlineFirmwareTypeSettingChanged(_firmwareType); // This adds correct terrain capability bit
_firmwarePlugin->initializeVehicle(this);
}
void Vehicle::trackFirmwareVehicleTypeChanges(void)
{
connect(_settingsManager->appSettings()->offlineEditingFirmwareClass(), &Fact::rawValueChanged, this, &Vehicle::_offlineFirmwareTypeSettingChanged);
connect(_settingsManager->appSettings()->offlineEditingVehicleClass(), &Fact::rawValueChanged, this, &Vehicle::_offlineVehicleTypeSettingChanged);
_offlineFirmwareTypeSettingChanged(_settingsManager->appSettings()->offlineEditingFirmwareClass()->rawValue());
_offlineVehicleTypeSettingChanged(_settingsManager->appSettings()->offlineEditingVehicleClass()->rawValue());
}
void Vehicle::stopTrackingFirmwareVehicleTypeChanges(void)
{
disconnect(_settingsManager->appSettings()->offlineEditingFirmwareClass(), &Fact::rawValueChanged, this, &Vehicle::_offlineFirmwareTypeSettingChanged);
disconnect(_settingsManager->appSettings()->offlineEditingVehicleClass(), &Fact::rawValueChanged, this, &Vehicle::_offlineVehicleTypeSettingChanged);
}
void Vehicle::_commonInit()
{
_firmwarePlugin = _firmwarePluginManager->firmwarePluginForAutopilot(_firmwareType, _vehicleType);
connect(_firmwarePlugin, &FirmwarePlugin::toolIndicatorsChanged, this, &Vehicle::toolIndicatorsChanged);
connect(_firmwarePlugin, &FirmwarePlugin::modeIndicatorsChanged, this, &Vehicle::modeIndicatorsChanged);
connect(this, &Vehicle::coordinateChanged, this, &Vehicle::_updateDistanceHeadingToHome);
connect(this, &Vehicle::coordinateChanged, this, &Vehicle::_updateDistanceToGCS);
connect(this, &Vehicle::homePositionChanged, this, &Vehicle::_updateDistanceHeadingToHome);
connect(this, &Vehicle::hobbsMeterChanged, this, &Vehicle::_updateHobbsMeter);
connect(_toolbox->qgcPositionManager(), &QGCPositionManager::gcsPositionChanged, this, &Vehicle::_updateDistanceToGCS);
connect(_toolbox->qgcPositionManager(), &QGCPositionManager::gcsPositionChanged, this, &Vehicle::_updateHomepoint);
_missionManager = new MissionManager(this);
connect(_missionManager, &MissionManager::error, this, &Vehicle::_missionManagerError);
connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_firstMissionLoadComplete);
connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_clearCameraTriggerPoints);
connect(_missionManager, &MissionManager::sendComplete, this, &Vehicle::_clearCameraTriggerPoints);
connect(_missionManager, &MissionManager::currentIndexChanged, this, &Vehicle::_updateHeadingToNextWP);
connect(_missionManager, &MissionManager::currentIndexChanged, this, &Vehicle::_updateMissionItemIndex);
connect(_missionManager, &MissionManager::sendComplete, _trajectoryPoints, &TrajectoryPoints::clear);
connect(_missionManager, &MissionManager::newMissionItemsAvailable, _trajectoryPoints, &TrajectoryPoints::clear);
_componentInformationManager = new ComponentInformationManager (this);
_initialConnectStateMachine = new InitialConnectStateMachine (this);
_ftpManager = new FTPManager (this);
_imageProtocolManager = new ImageProtocolManager ();
_vehicleLinkManager = new VehicleLinkManager (this);
_parameterManager = new ParameterManager(this);
connect(_parameterManager, &ParameterManager::parametersReadyChanged, this, &Vehicle::_parametersReady);
connect(_initialConnectStateMachine, &InitialConnectStateMachine::progressUpdate,
this, &Vehicle::_gotProgressUpdate);
connect(_parameterManager, &ParameterManager::loadProgressChanged, this, &Vehicle::_gotProgressUpdate);
_objectAvoidance = new VehicleObjectAvoidance(this, this);
_autotune = _firmwarePlugin->createAutotune(this);
// GeoFenceManager needs to access ParameterManager so make sure to create after
_geoFenceManager = new GeoFenceManager(this);
connect(_geoFenceManager, &GeoFenceManager::error, this, &Vehicle::_geoFenceManagerError);
connect(_geoFenceManager, &GeoFenceManager::loadComplete, this, &Vehicle::_firstGeoFenceLoadComplete);
_rallyPointManager = new RallyPointManager(this);
connect(_rallyPointManager, &RallyPointManager::error, this, &Vehicle::_rallyPointManagerError);
connect(_rallyPointManager, &RallyPointManager::loadComplete, this, &Vehicle::_firstRallyPointLoadComplete);
// Flight modes can differ based on advanced mode
connect(_toolbox->corePlugin(), &QGCCorePlugin::showAdvancedUIChanged, this, &Vehicle::flightModesChanged);
connect(_imageProtocolManager, &ImageProtocolManager::imageReady, this, &Vehicle::_imageProtocolImageReady);
// Build FactGroup object model
_addFact(&_rollFact, _rollFactName);
_addFact(&_pitchFact, _pitchFactName);
_addFact(&_headingFact, _headingFactName);
_addFact(&_rollRateFact, _rollRateFactName);
_addFact(&_pitchRateFact, _pitchRateFactName);
_addFact(&_yawRateFact, _yawRateFactName);
_addFact(&_groundSpeedFact, _groundSpeedFactName);
_addFact(&_airSpeedFact, _airSpeedFactName);
_addFact(&_airSpeedSetpointFact, _airSpeedSetpointFactName);
_addFact(&_climbRateFact, _climbRateFactName);
_addFact(&_altitudeRelativeFact, _altitudeRelativeFactName);
_addFact(&_altitudeAMSLFact, _altitudeAMSLFactName);
_addFact(&_altitudeTuningFact, _altitudeTuningFactName);
_addFact(&_altitudeTuningSetpointFact, _altitudeTuningSetpointFactName);
_addFact(&_xTrackErrorFact, _xTrackErrorFactName);
_addFact(&_rangeFinderDistFact, _rangeFinderDistFactName);
_addFact(&_flightDistanceFact, _flightDistanceFactName);
_addFact(&_flightTimeFact, _flightTimeFactName);
_addFact(&_distanceToHomeFact, _distanceToHomeFactName);
_addFact(&_missionItemIndexFact, _missionItemIndexFactName);
_addFact(&_headingToNextWPFact, _headingToNextWPFactName);
_addFact(&_headingToHomeFact, _headingToHomeFactName);
_addFact(&_distanceToGCSFact, _distanceToGCSFactName);
_addFact(&_throttlePctFact, _throttlePctFactName);
_hobbsFact.setRawValue(QVariant(QString("0000:00:00")));
_addFact(&_hobbsFact, _hobbsFactName);
_addFactGroup(&_gpsFactGroup, _gpsFactGroupName);
_addFactGroup(&_gps2FactGroup, _gps2FactGroupName);
_addFactGroup(&_windFactGroup, _windFactGroupName);
_addFactGroup(&_vibrationFactGroup, _vibrationFactGroupName);
_addFactGroup(&_temperatureFactGroup, _temperatureFactGroupName);
_addFactGroup(&_clockFactGroup, _clockFactGroupName);
_addFactGroup(&_setpointFactGroup, _setpointFactGroupName);
_addFactGroup(&_distanceSensorFactGroup, _distanceSensorFactGroupName);
_addFactGroup(&_localPositionFactGroup, _localPositionFactGroupName);
_addFactGroup(&_localPositionSetpointFactGroup,_localPositionSetpointFactGroupName);
_addFactGroup(&_escStatusFactGroup, _escStatusFactGroupName);
_addFactGroup(&_estimatorStatusFactGroup, _estimatorStatusFactGroupName);
_addFactGroup(&_hygrometerFactGroup, _hygrometerFactGroupName);
_addFactGroup(&_terrainFactGroup, _terrainFactGroupName);
// Add firmware-specific fact groups, if provided
QMap<QString, FactGroup*>* fwFactGroups = _firmwarePlugin->factGroups();
if (fwFactGroups) {
QMapIterator<QString, FactGroup*> i(*fwFactGroups);
while(i.hasNext()) {
i.next();
_addFactGroup(i.value(), i.key());
}
}
_flightDistanceFact.setRawValue(0);
_flightTimeFact.setRawValue(0);
_flightTimeUpdater.setInterval(1000);
_flightTimeUpdater.setSingleShot(false);
connect(&_flightTimeUpdater, &QTimer::timeout, this, &Vehicle::_updateFlightTime);
// Set video stream to udp if running ArduSub and Video is disabled
if (sub() && _settingsManager->videoSettings()->videoSource()->rawValue() == VideoSettings::videoDisabled) {
_settingsManager->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceUDPH264);
_settingsManager->videoSettings()->lowLatencyMode()->setRawValue(true);
}
//-- Airspace Management
#if defined(QGC_AIRMAP_ENABLED)
AirspaceManager* airspaceManager = _toolbox->airspaceManager();
if (airspaceManager) {
_airspaceVehicleManager = airspaceManager->instantiateVehicle(*this);
if (_airspaceVehicleManager) {
connect(_airspaceVehicleManager, &AirspaceVehicleManager::trafficUpdate, this, &Vehicle::_trafficUpdate);
}
}
#endif
}
Vehicle::~Vehicle()
{
qCDebug(VehicleLog) << "~Vehicle" << this;
delete _missionManager;
_missionManager = nullptr;
delete _autopilotPlugin;
_autopilotPlugin = nullptr;
delete _mav;
_mav = nullptr;
if (_joystickManager) {
_startJoystick(false);
}
#if defined(QGC_AIRMAP_ENABLED)
if (_airspaceVehicleManager) {
delete _airspaceVehicleManager;
}
#endif
}
void Vehicle::prepareDelete()
{
if(_cameraManager) {
// because of _cameraManager QML bindings check for nullptr won't work in the binding pipeline
// the dangling pointer access will cause a runtime fault
auto tmpCameras = _cameraManager;
_cameraManager = nullptr;
delete tmpCameras;
emit cameraManagerChanged();
qApp->processEvents();
}
}
void Vehicle::_offlineFirmwareTypeSettingChanged(QVariant varFirmwareType)
{
_firmwareType = static_cast<MAV_AUTOPILOT>(varFirmwareType.toInt());
_firmwarePlugin = _firmwarePluginManager->firmwarePluginForAutopilot(_firmwareType, _vehicleType);
if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
_capabilityBits |= MAV_PROTOCOL_CAPABILITY_TERRAIN;
} else {
_capabilityBits &= ~MAV_PROTOCOL_CAPABILITY_TERRAIN;
}
emit firmwareTypeChanged();
emit capabilityBitsChanged(_capabilityBits);
}
void Vehicle::_offlineVehicleTypeSettingChanged(QVariant varVehicleType)
{
_vehicleType = static_cast<MAV_TYPE>(varVehicleType.toInt());
emit vehicleTypeChanged();
}
void Vehicle::_offlineCruiseSpeedSettingChanged(QVariant value)
{
_defaultCruiseSpeed = value.toDouble();
emit defaultCruiseSpeedChanged(_defaultCruiseSpeed);
}
void Vehicle::_offlineHoverSpeedSettingChanged(QVariant value)
{
_defaultHoverSpeed = value.toDouble();
emit defaultHoverSpeedChanged(_defaultHoverSpeed);
}
QString Vehicle::firmwareTypeString() const
{
if (px4Firmware()) {
return QStringLiteral("PX4 Pro");
} else if (apmFirmware()) {
return QStringLiteral("ArduPilot");
} else {
return tr("MAVLink Generic");
}
}
QString Vehicle::vehicleTypeString() const
{
if (airship()) {
return tr("Airship");
} else if (fixedWing()) {
return tr("Fixed Wing");
} else if (multiRotor()) {
return tr("Multi-Rotor");
} else if (vtol()) {
return tr("VTOL");
} else if (rover()) {
return tr("Rover");
} else if (sub()) {
return tr("Sub");
} else {
return tr("Unknown");
}
}
void Vehicle::resetCounters()
{
_messagesReceived = 0;
_messagesSent = 0;
_messagesLost = 0;
_messageSeq = 0;
_heardFrom = false;
}
void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
{
// If the link is already running at Mavlink V2 set our max proto version to it.
unsigned mavlinkVersion = _mavlink->getCurrentVersion();
if (_maxProtoVersion != mavlinkVersion && mavlinkVersion >= 200) {
_maxProtoVersion = mavlinkVersion;
qCDebug(VehicleLog) << "_mavlinkMessageReceived Link already running Mavlink v2. Setting _maxProtoVersion" << _maxProtoVersion;
}
if (message.sysid != _id && message.sysid != 0) {
// We allow RADIO_STATUS messages which come from a link the vehicle is using to pass through and be handled
if (!(message.msgid == MAVLINK_MSG_ID_RADIO_STATUS && _vehicleLinkManager->containsLink(link))) {
return;
}
}
// We give the link manager first whack since it it reponsible for adding new links
_vehicleLinkManager->mavlinkMessageReceived(link, message);
//-- Check link status
_messagesReceived++;
emit messagesReceivedChanged();
if(!_heardFrom) {
if(message.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
_heardFrom = true;
_compID = message.compid;
_messageSeq = message.seq + 1;
}
} else {
if(_compID == message.compid) {
uint16_t seq_received = static_cast<uint16_t>(message.seq);
uint16_t packet_lost_count = 0;
//-- Account for overflow during packet loss
if(seq_received < _messageSeq) {
packet_lost_count = (seq_received + 255) - _messageSeq;
} else {
packet_lost_count = seq_received - _messageSeq;
}
_messageSeq = message.seq + 1;
_messagesLost += packet_lost_count;
if(packet_lost_count)
emit messagesLostChanged();
}
}
// Give the plugin a change to adjust the message contents
if (!_firmwarePlugin->adjustIncomingMavlinkMessage(this, &message)) {
return;
}
// Give the Core Plugin access to all mavlink traffic
if (!_toolbox->corePlugin()->mavlinkMessage(this, link, message)) {
return;
}
if (!_terrainProtocolHandler->mavlinkMessageReceived(message)) {
return;
}
_ftpManager->_mavlinkMessageReceived(message);
_parameterManager->mavlinkMessageReceived(message);
_imageProtocolManager->mavlinkMessageReceived(message);
_waitForMavlinkMessageMessageReceived(message);
// Battery fact groups are created dynamically as new batteries are discovered
VehicleBatteryFactGroup::handleMessageForFactGroupCreation(this, message);
// Let the fact groups take a whack at the mavlink traffic
for (FactGroup* factGroup : factGroups()) {
factGroup->handleMessage(this, message);
}
switch (message.msgid) {
case MAVLINK_MSG_ID_HOME_POSITION:
_handleHomePosition(message);
break;
case MAVLINK_MSG_ID_HEARTBEAT:
_handleHeartbeat(message);
break;
case MAVLINK_MSG_ID_RADIO_STATUS:
_handleRadioStatus(message);
break;
case MAVLINK_MSG_ID_RC_CHANNELS:
_handleRCChannels(message);
break;
case MAVLINK_MSG_ID_BATTERY_STATUS:
_handleBatteryStatus(message);
break;
case MAVLINK_MSG_ID_SYS_STATUS:
_handleSysStatus(message);
break;
case MAVLINK_MSG_ID_RAW_IMU:
emit mavlinkRawImu(message);
break;
case MAVLINK_MSG_ID_SCALED_IMU:
emit mavlinkScaledImu1(message);
break;
case MAVLINK_MSG_ID_SCALED_IMU2:
emit mavlinkScaledImu2(message);
break;
case MAVLINK_MSG_ID_SCALED_IMU3:
emit mavlinkScaledImu3(message);
break;
case MAVLINK_MSG_ID_EXTENDED_SYS_STATE:
_handleExtendedSysState(message);
break;
case MAVLINK_MSG_ID_COMMAND_ACK:
_handleCommandAck(message);
break;
case MAVLINK_MSG_ID_LOGGING_DATA:
_handleMavlinkLoggingData(message);
break;
case MAVLINK_MSG_ID_LOGGING_DATA_ACKED:
_handleMavlinkLoggingDataAcked(message);
break;
case MAVLINK_MSG_ID_GPS_RAW_INT:
_handleGpsRawInt(message);
break;
case MAVLINK_MSG_ID_GLOBAL_POSITION_INT:
_handleGlobalPositionInt(message);
break;
case MAVLINK_MSG_ID_ALTITUDE:
_handleAltitude(message);
break;
case MAVLINK_MSG_ID_VFR_HUD:
_handleVfrHud(message);
break;
case MAVLINK_MSG_ID_RANGEFINDER:
_handleRangefinder(message);
break;
case MAVLINK_MSG_ID_NAV_CONTROLLER_OUTPUT:
_handleNavControllerOutput(message);
break;
case MAVLINK_MSG_ID_CAMERA_IMAGE_CAPTURED:
_handleCameraImageCaptured(message);
break;
case MAVLINK_MSG_ID_ADSB_VEHICLE:
_handleADSBVehicle(message);
break;
case MAVLINK_MSG_ID_HIGH_LATENCY:
_handleHighLatency(message);
break;
case MAVLINK_MSG_ID_HIGH_LATENCY2:
_handleHighLatency2(message);
break;
case MAVLINK_MSG_ID_ATTITUDE:
_handleAttitude(message);
break;
case MAVLINK_MSG_ID_ATTITUDE_QUATERNION:
_handleAttitudeQuaternion(message);
break;
case MAVLINK_MSG_ID_STATUSTEXT:
_handleStatusText(message);
break;
case MAVLINK_MSG_ID_ORBIT_EXECUTION_STATUS:
_handleOrbitExecutionStatus(message);
break;
case MAVLINK_MSG_ID_PING:
_handlePing(link, message);
break;
case MAVLINK_MSG_ID_MOUNT_ORIENTATION:
_handleGimbalOrientation(message);
break;
case MAVLINK_MSG_ID_OBSTACLE_DISTANCE:
_handleObstacleDistance(message);
break;
case MAVLINK_MSG_ID_EVENT:
case MAVLINK_MSG_ID_CURRENT_EVENT_SEQUENCE:
case MAVLINK_MSG_ID_RESPONSE_EVENT_ERROR:
_eventHandler(message.compid).handleEvents(message);
break;
case MAVLINK_MSG_ID_SERIAL_CONTROL:
{
mavlink_serial_control_t ser;
mavlink_msg_serial_control_decode(&message, &ser);
if (static_cast<size_t>(ser.count) > sizeof(ser.data)) {
qWarning() << "Invalid count for SERIAL_CONTROL, discarding." << ser.count;
} else {
emit mavlinkSerialControl(ser.device, ser.flags, ser.timeout, ser.baudrate,
QByteArray(reinterpret_cast<const char*>(ser.data), ser.count));
}
}
break;
// Following are ArduPilot dialect messages
#if !defined(NO_ARDUPILOT_DIALECT)
case MAVLINK_MSG_ID_CAMERA_FEEDBACK:
_handleCameraFeedback(message);
break;
#endif
}
// This must be emitted after the vehicle processes the message. This way the vehicle state is up to date when anyone else
// does processing.
emit mavlinkMessageReceived(message);
_uas->receiveMessage(message);
}
#if !defined(NO_ARDUPILOT_DIALECT)
void Vehicle::_handleCameraFeedback(const mavlink_message_t& message)
{
mavlink_camera_feedback_t feedback;
mavlink_msg_camera_feedback_decode(&message, &feedback);
QGeoCoordinate imageCoordinate((double)feedback.lat / qPow(10.0, 7.0), (double)feedback.lng / qPow(10.0, 7.0), feedback.alt_msl);
qCDebug(VehicleLog) << "_handleCameraFeedback coord:index" << imageCoordinate << feedback.img_idx;
_cameraTriggerPoints.append(new QGCQGeoCoordinate(imageCoordinate, this));
}
#endif
void Vehicle::_handleOrbitExecutionStatus(const mavlink_message_t& message)
{
mavlink_orbit_execution_status_t orbitStatus;
mavlink_msg_orbit_execution_status_decode(&message, &orbitStatus);
double newRadius = qAbs(static_cast<double>(orbitStatus.radius));
if (!QGC::fuzzyCompare(_orbitMapCircle.radius()->rawValue().toDouble(), newRadius)) {
_orbitMapCircle.radius()->setRawValue(newRadius);
}
bool newOrbitClockwise = orbitStatus.radius > 0 ? true : false;
if (_orbitMapCircle.clockwiseRotation() != newOrbitClockwise) {
_orbitMapCircle.setClockwiseRotation(newOrbitClockwise);
}
QGeoCoordinate newCenter(static_cast<double>(orbitStatus.x) / qPow(10.0, 7.0), static_cast<double>(orbitStatus.y) / qPow(10.0, 7.0));
if (_orbitMapCircle.center() != newCenter) {
_orbitMapCircle.setCenter(newCenter);
}
if (!_orbitActive) {
_orbitActive = true;
_orbitMapCircle.setShowRotation(true);
emit orbitActiveChanged(true);
}
_orbitTelemetryTimer.start(_orbitTelemetryTimeoutMsecs);
}
void Vehicle::_orbitTelemetryTimeout()
{
_orbitActive = false;
emit orbitActiveChanged(false);
}
void Vehicle::_handleCameraImageCaptured(const mavlink_message_t& message)
{
mavlink_camera_image_captured_t feedback;
mavlink_msg_camera_image_captured_decode(&message, &feedback);
QGeoCoordinate imageCoordinate((double)feedback.lat / qPow(10.0, 7.0), (double)feedback.lon / qPow(10.0, 7.0), feedback.alt);
qCDebug(VehicleLog) << "_handleCameraFeedback coord:index" << imageCoordinate << feedback.image_index << feedback.capture_result;
if (feedback.capture_result == 1) {
_cameraTriggerPoints.append(new QGCQGeoCoordinate(imageCoordinate, this));
}
}
void Vehicle::_chunkedStatusTextTimeout(void)
{
// Spit out all incomplete chunks
QList<uint8_t> rgCompId = _chunkedStatusTextInfoMap.keys();
for (uint8_t compId : rgCompId) {
_chunkedStatusTextInfoMap[compId].rgMessageChunks.append(QString());
_chunkedStatusTextCompleted(compId);
}
}
void Vehicle::_chunkedStatusTextCompleted(uint8_t compId)
{
ChunkedStatusTextInfo_t& chunkedInfo = _chunkedStatusTextInfoMap[compId];
uint8_t severity = chunkedInfo.severity;
QStringList& rgChunks = chunkedInfo.rgMessageChunks;
// Build up message from chunks
QString messageText;
for (const QString& chunk : rgChunks) {
if (chunk.isEmpty()) {
// Indicates missing chunk
messageText += tr(" ... ", "Indicates missing chunk from chunked STATUS_TEXT");
} else {
messageText += chunk;
}
}
_chunkedStatusTextInfoMap.remove(compId);
// PX4 backwards compatibility: messages sent out ending with a tab are also sent as event
if (messageText.endsWith('\t') && px4Firmware()) {
qCDebug(VehicleLog) << "Dropping message (expected as event):" << messageText;
return;
}
bool skipSpoken = false;
bool ardupilotPrearm = messageText.startsWith(QStringLiteral("PreArm"));
bool px4Prearm = messageText.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive) && severity >= MAV_SEVERITY_CRITICAL;
if (ardupilotPrearm || px4Prearm) {
// check if expected as event
auto eventData = _events.find(compId);
if (eventData != _events.end()) {
if (eventData->data()->healthAndArmingChecksSupported()) {
qCDebug(VehicleLog) << "Dropping preflight message (expected as event):" << messageText;
return;
}
}
// Limit repeated PreArm message to once every 10 seconds
if (_noisySpokenPrearmMap.contains(messageText) && _noisySpokenPrearmMap[messageText].msecsTo(QTime::currentTime()) < (10 * 1000)) {
skipSpoken = true;
} else {
_noisySpokenPrearmMap[messageText] = QTime::currentTime();
setPrearmError(messageText);
}
}
// If the message is NOTIFY or higher severity, or starts with a '#',
// then read it aloud.
bool readAloud = false;
if (messageText.startsWith("#")) {
messageText.remove(0, 1);
readAloud = true;
}
else if (severity <= MAV_SEVERITY_NOTICE) {
readAloud = true;
}
if (readAloud) {
if (!skipSpoken) {
qgcApp()->toolbox()->audioOutput()->say(messageText);
}
}
emit textMessageReceived(id(), compId, severity, messageText);
}
void Vehicle::_handleStatusText(mavlink_message_t& message)
{
QByteArray b;
QString messageText;
mavlink_statustext_t statustext;
mavlink_msg_statustext_decode(&message, &statustext);
uint8_t compId = message.compid;
b.resize(MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1);
strncpy(b.data(), statustext.text, MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN);
b[b.length()-1] = '\0';
messageText = QString(b);
bool includesNullTerminator = messageText.length() < MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN;
if (_chunkedStatusTextInfoMap.contains(compId) && _chunkedStatusTextInfoMap[compId].chunkId != statustext.id) {
// We have an incomplete chunked status still pending
_chunkedStatusTextInfoMap[compId].rgMessageChunks.append(QString());
_chunkedStatusTextCompleted(compId);
}
if (statustext.id == 0) {
// Non-chunked status text. We still use common chunked text output mechanism.
ChunkedStatusTextInfo_t chunkedInfo;
chunkedInfo.chunkId = 0;
chunkedInfo.severity = statustext.severity;
chunkedInfo.rgMessageChunks.append(messageText);
_chunkedStatusTextInfoMap[compId] = chunkedInfo;
} else {
if (_chunkedStatusTextInfoMap.contains(compId)) {
// A chunk sequence is in progress
QStringList& chunks = _chunkedStatusTextInfoMap[compId].rgMessageChunks;
if (statustext.chunk_seq > chunks.size()) {
// We are missing some chunks in between, fill them in as missing
for (int i=chunks.size(); i<statustext.chunk_seq; i++) {
chunks.append(QString());
}
}
chunks.append(messageText);
} else {
// Starting a new chunk sequence
ChunkedStatusTextInfo_t chunkedInfo;
chunkedInfo.chunkId = statustext.id;
chunkedInfo.severity = statustext.severity;
chunkedInfo.rgMessageChunks.append(messageText);
_chunkedStatusTextInfoMap[compId] = chunkedInfo;
}
_chunkedStatusTextTimer.start();
}
if (statustext.id == 0 || includesNullTerminator) {
_chunkedStatusTextTimer.stop();
_chunkedStatusTextCompleted(message.compid);
}
}
void Vehicle::_handleVfrHud(mavlink_message_t& message)
{
mavlink_vfr_hud_t vfrHud;
mavlink_msg_vfr_hud_decode(&message, &vfrHud);
_airSpeedFact.setRawValue(qIsNaN(vfrHud.airspeed) ? 0 : vfrHud.airspeed);
_groundSpeedFact.setRawValue(qIsNaN(vfrHud.groundspeed) ? 0 : vfrHud.groundspeed);
_climbRateFact.setRawValue(qIsNaN(vfrHud.climb) ? 0 : vfrHud.climb);
_throttlePctFact.setRawValue(static_cast<int16_t>(vfrHud.throttle));
if (qIsNaN(_altitudeTuningOffset)) {
_altitudeTuningOffset = vfrHud.alt;
}
_altitudeTuningFact.setRawValue(vfrHud.alt - _altitudeTuningOffset);
}
void Vehicle::_handleRangefinder(mavlink_message_t& message)
{
mavlink_rangefinder_t rangefinder;
mavlink_msg_rangefinder_decode(&message, &rangefinder);
_rangeFinderDistFact.setRawValue(qIsNaN(rangefinder.distance) ? 0 : rangefinder.distance);
}
void Vehicle::_handleNavControllerOutput(mavlink_message_t& message)
{
mavlink_nav_controller_output_t navControllerOutput;
mavlink_msg_nav_controller_output_decode(&message, &navControllerOutput);
_altitudeTuningSetpointFact.setRawValue(_altitudeTuningFact.rawValue().toDouble() - navControllerOutput.alt_error);
_xTrackErrorFact.setRawValue(navControllerOutput.xtrack_error);
_airSpeedSetpointFact.setRawValue(_airSpeedFact.rawValue().toDouble() - navControllerOutput.aspd_error);
}
// Ignore warnings from mavlink headers for both GCC/Clang and MSVC
#ifdef __GNUC__
#if __GNUC__ > 8
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Waddress-of-packed-member"
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#endif
#else
#pragma warning(push, 0)
#endif
void Vehicle::_handleAttitudeWorker(double rollRadians, double pitchRadians, double yawRadians)
{
double roll, pitch, yaw;
roll = QGC::limitAngleToPMPIf(rollRadians);
pitch = QGC::limitAngleToPMPIf(pitchRadians);
yaw = QGC::limitAngleToPMPIf(yawRadians);
roll = qRadiansToDegrees(roll);
pitch = qRadiansToDegrees(pitch);
yaw = qRadiansToDegrees(yaw);
if (yaw < 0.0) {
yaw += 360.0;
}
// truncate to integer so widget never displays 360
yaw = trunc(yaw);
_rollFact.setRawValue(roll);
_pitchFact.setRawValue(pitch);
_headingFact.setRawValue(yaw);
}
void Vehicle::_handleAttitude(mavlink_message_t& message)
{
// only accept the attitude message from the vehicle's flight controller
if (message.sysid != _id || message.compid != _compID) {
return;
}
if (_receivingAttitudeQuaternion) {
return;
}
mavlink_attitude_t attitude;
mavlink_msg_attitude_decode(&message, &attitude);
_handleAttitudeWorker(attitude.roll, attitude.pitch, attitude.yaw);
}
void Vehicle::_handleAttitudeQuaternion(mavlink_message_t& message)
{
// only accept the attitude message from the vehicle's flight controller
if (message.sysid != _id || message.compid != _compID) {
return;
}
_receivingAttitudeQuaternion = true;
mavlink_attitude_quaternion_t attitudeQuaternion;
mavlink_msg_attitude_quaternion_decode(&message, &attitudeQuaternion);
Eigen::Quaternionf quat(attitudeQuaternion.q1, attitudeQuaternion.q2, attitudeQuaternion.q3, attitudeQuaternion.q4);
Eigen::Vector3f rates(attitudeQuaternion.rollspeed, attitudeQuaternion.pitchspeed, attitudeQuaternion.yawspeed);
Eigen::Quaternionf repr_offset(attitudeQuaternion.repr_offset_q[0], attitudeQuaternion.repr_offset_q[1], attitudeQuaternion.repr_offset_q[2], attitudeQuaternion.repr_offset_q[3]);
// if repr_offset is valid, rotate attitude and rates
if (repr_offset.norm() >= 0.5f) {
quat = quat * repr_offset;
rates = repr_offset * rates;
}
float roll, pitch, yaw;
float q[] = { quat.w(), quat.x(), quat.y(), quat.z() };
mavlink_quaternion_to_euler(q, &roll, &pitch, &yaw);
_handleAttitudeWorker(roll, pitch, yaw);
rollRate()->setRawValue(qRadiansToDegrees(rates[0]));
pitchRate()->setRawValue(qRadiansToDegrees(rates[1]));
yawRate()->setRawValue(qRadiansToDegrees(rates[2]));
}
void Vehicle::_handleGpsRawInt(mavlink_message_t& message)
{
mavlink_gps_raw_int_t gpsRawInt;
mavlink_msg_gps_raw_int_decode(&message, &gpsRawInt);
_gpsRawIntMessageAvailable = true;
if (gpsRawInt.fix_type >= GPS_FIX_TYPE_3D_FIX) {
if (!_globalPositionIntMessageAvailable) {
QGeoCoordinate newPosition(gpsRawInt.lat / (double)1E7, gpsRawInt.lon / (double)1E7, gpsRawInt.alt / 1000.0);
if (newPosition != _coordinate) {
_coordinate = newPosition;
emit coordinateChanged(_coordinate);
}
if (!_altitudeMessageAvailable) {
_altitudeAMSLFact.setRawValue(gpsRawInt.alt / 1000.0);
}
}
}
}
void Vehicle::_handleGlobalPositionInt(mavlink_message_t& message)
{
mavlink_global_position_int_t globalPositionInt;
mavlink_msg_global_position_int_decode(&message, &globalPositionInt);
if (!_altitudeMessageAvailable) {
_altitudeRelativeFact.setRawValue(globalPositionInt.relative_alt / 1000.0);
_altitudeAMSLFact.setRawValue(globalPositionInt.alt / 1000.0);
}
// ArduPilot sends bogus GLOBAL_POSITION_INT messages with lat/lat 0/0 even when it has no gps signal
// Apparently, this is in order to transport relative altitude information.
if (globalPositionInt.lat == 0 && globalPositionInt.lon == 0) {
return;
}
_globalPositionIntMessageAvailable = true;
QGeoCoordinate newPosition(globalPositionInt.lat / (double)1E7, globalPositionInt.lon / (double)1E7, globalPositionInt.alt / 1000.0);
if (newPosition != _coordinate) {
_coordinate = newPosition;
emit coordinateChanged(_coordinate);
}
}
void Vehicle::_handleHighLatency(mavlink_message_t& message)
{
mavlink_high_latency_t highLatency;
mavlink_msg_high_latency_decode(&message, &highLatency);
QString previousFlightMode;
if (_base_mode != 0 || _custom_mode != 0){
// Vehicle is initialized with _base_mode=0 and _custom_mode=0. Don't pass this to flightMode() since it will complain about
// bad modes while unit testing.
previousFlightMode = flightMode();
}
_base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
_custom_mode = _firmwarePlugin->highLatencyCustomModeTo32Bits(highLatency.custom_mode);
if (previousFlightMode != flightMode()) {
emit flightModeChanged(flightMode());
}
// Assume armed since we don't know
if (_armed != true) {
_armed = true;
emit armedChanged(_armed);
}
struct {
const double latitude;
const double longitude;
const double altitude;
} coordinate {
highLatency.latitude / (double)1E7,
highLatency.longitude / (double)1E7,
static_cast<double>(highLatency.altitude_amsl)
};
_coordinate.setLatitude(coordinate.latitude);
_coordinate.setLongitude(coordinate.longitude);
_coordinate.setAltitude(coordinate.altitude);
emit coordinateChanged(_coordinate);
_airSpeedFact.setRawValue((double)highLatency.airspeed / 5.0);
_groundSpeedFact.setRawValue((double)highLatency.groundspeed / 5.0);
_climbRateFact.setRawValue((double)highLatency.climb_rate / 10.0);
_headingFact.setRawValue((double)highLatency.heading * 2.0);
_altitudeRelativeFact.setRawValue(qQNaN());
_altitudeAMSLFact.setRawValue(coordinate.altitude);
}
void Vehicle::_handleHighLatency2(mavlink_message_t& message)
{
mavlink_high_latency2_t highLatency2;
mavlink_msg_high_latency2_decode(&message, &highLatency2);
QString previousFlightMode;
if (_base_mode != 0 || _custom_mode != 0){
// Vehicle is initialized with _base_mode=0 and _custom_mode=0. Don't pass this to flightMode() since it will complain about
// bad modes while unit testing.
previousFlightMode = flightMode();
}
_base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
_custom_mode = _firmwarePlugin->highLatencyCustomModeTo32Bits(highLatency2.custom_mode);
if (previousFlightMode != flightMode()) {
emit flightModeChanged(flightMode());
}
// Assume armed since we don't know
if (_armed != true) {
_armed = true;
emit armedChanged(_armed);
}
_coordinate.setLatitude(highLatency2.latitude / (double)1E7);
_coordinate.setLongitude(highLatency2.longitude / (double)1E7);
_coordinate.setAltitude(highLatency2.altitude);
emit coordinateChanged(_coordinate);
_airSpeedFact.setRawValue((double)highLatency2.airspeed / 5.0);
_groundSpeedFact.setRawValue((double)highLatency2.groundspeed / 5.0);
_climbRateFact.setRawValue((double)highLatency2.climb_rate / 10.0);
_headingFact.setRawValue((double)highLatency2.heading * 2.0);
_altitudeRelativeFact.setRawValue(qQNaN());
_altitudeAMSLFact.setRawValue(highLatency2.altitude);
struct failure2Sensor_s {
HL_FAILURE_FLAG failureBit;
MAV_SYS_STATUS_SENSOR sensorBit;
};
static const failure2Sensor_s rgFailure2Sensor[] = {
{ HL_FAILURE_FLAG_GPS, MAV_SYS_STATUS_SENSOR_GPS },
{ HL_FAILURE_FLAG_DIFFERENTIAL_PRESSURE, MAV_SYS_STATUS_SENSOR_DIFFERENTIAL_PRESSURE },
{ HL_FAILURE_FLAG_ABSOLUTE_PRESSURE, MAV_SYS_STATUS_SENSOR_ABSOLUTE_PRESSURE },
{ HL_FAILURE_FLAG_3D_ACCEL, MAV_SYS_STATUS_SENSOR_3D_ACCEL },
{ HL_FAILURE_FLAG_3D_GYRO, MAV_SYS_STATUS_SENSOR_3D_GYRO },
{ HL_FAILURE_FLAG_3D_MAG, MAV_SYS_STATUS_SENSOR_3D_MAG },
};
// Map from MAV_FAILURE bits to standard SYS_STATUS message handling
uint32_t newOnboardControlSensorsEnabled = 0;
for (size_t i=0; i<sizeof(rgFailure2Sensor)/sizeof(failure2Sensor_s); i++) {
const failure2Sensor_s* pFailure2Sensor = &rgFailure2Sensor[i];
if (highLatency2.failure_flags & pFailure2Sensor->failureBit) {
// Assume if reporting as unhealthy that is it present and enabled
newOnboardControlSensorsEnabled |= pFailure2Sensor->sensorBit;
}
}
if (newOnboardControlSensorsEnabled != _onboardControlSensorsEnabled) {
_onboardControlSensorsEnabled = newOnboardControlSensorsEnabled;
_onboardControlSensorsPresent = newOnboardControlSensorsEnabled;
_onboardControlSensorsUnhealthy = 0;
}
}
void Vehicle::_handleAltitude(mavlink_message_t& message)
{
mavlink_altitude_t altitude;
mavlink_msg_altitude_decode(&message, &altitude);
// Data from ALTITUDE message takes precedence over gps messages
_altitudeMessageAvailable = true;
_altitudeRelativeFact.setRawValue(altitude.altitude_relative);
_altitudeAMSLFact.setRawValue(altitude.altitude_amsl);
}
void Vehicle::_setCapabilities(uint64_t capabilityBits)
{
_capabilityBits = capabilityBits;
_capabilityBitsKnown = true;
emit capabilitiesKnownChanged(true);
emit capabilityBitsChanged(_capabilityBits);
QString supports("supports");
QString doesNotSupport("does not support");
qCDebug(VehicleLog) << QString("Vehicle %1 Mavlink 2.0").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_MAVLINK2 ? supports : doesNotSupport);
qCDebug(VehicleLog) << QString("Vehicle %1 MISSION_ITEM_INT").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_MISSION_INT ? supports : doesNotSupport);
qCDebug(VehicleLog) << QString("Vehicle %1 MISSION_COMMAND_INT").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_COMMAND_INT ? supports : doesNotSupport);
qCDebug(VehicleLog) << QString("Vehicle %1 GeoFence").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_MISSION_FENCE ? supports : doesNotSupport);
qCDebug(VehicleLog) << QString("Vehicle %1 RallyPoints").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_MISSION_RALLY ? supports : doesNotSupport);
qCDebug(VehicleLog) << QString("Vehicle %1 Terrain").arg(_capabilityBits & MAV_PROTOCOL_CAPABILITY_TERRAIN ? supports : doesNotSupport);
_setMaxProtoVersionFromBothSources();
}
void Vehicle::_setMaxProtoVersion(unsigned version) {
// Set only once or if we need to reduce the max version
if (_maxProtoVersion == 0 || version < _maxProtoVersion) {
qCDebug(VehicleLog) << "_setMaxProtoVersion before:after" << _maxProtoVersion << version;
_maxProtoVersion = version;
emit requestProtocolVersion(_maxProtoVersion);
}
}
void Vehicle::_setMaxProtoVersionFromBothSources()
{
if (_mavlinkProtocolRequestComplete && _capabilityBitsKnown) {
if (_mavlinkProtocolRequestMaxProtoVersion != 0) {
qCDebug(VehicleLog) << "_setMaxProtoVersionFromBothSources using protocol version message";
_setMaxProtoVersion(_mavlinkProtocolRequestMaxProtoVersion);
} else {
qCDebug(VehicleLog) << "_setMaxProtoVersionFromBothSources using capability bits";
_setMaxProtoVersion(capabilityBits() & MAV_PROTOCOL_CAPABILITY_MAVLINK2 ? 200 : 100);
}
}
}
QString Vehicle::vehicleUIDStr()
{
QString uid;
uint8_t* pUid = (uint8_t*)(void*)&_uid;
uid = uid.asprintf("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
pUid[0] & 0xff,
pUid[1] & 0xff,
pUid[2] & 0xff,
pUid[3] & 0xff,
pUid[4] & 0xff,
pUid[5] & 0xff,
pUid[6] & 0xff,
pUid[7] & 0xff);
return uid;
}
void Vehicle::_handleExtendedSysState(mavlink_message_t& message)
{
mavlink_extended_sys_state_t extendedState;
mavlink_msg_extended_sys_state_decode(&message, &extendedState);
switch (extendedState.landed_state) {
case MAV_LANDED_STATE_ON_GROUND:
_setFlying(false);
_setLanding(false);
break;
case MAV_LANDED_STATE_TAKEOFF:
case MAV_LANDED_STATE_IN_AIR:
_setFlying(true);
_setLanding(false);
break;
case MAV_LANDED_STATE_LANDING:
_setFlying(true);
_setLanding(true);
break;
default:
break;
}
if (vtol()) {
bool vtolInFwdFlight = extendedState.vtol_state == MAV_VTOL_STATE_FW;
if (vtolInFwdFlight != _vtolInFwdFlight) {
_vtolInFwdFlight = vtolInFwdFlight;
emit vtolInFwdFlightChanged(vtolInFwdFlight);
}
}
}
bool Vehicle::_apmArmingNotRequired()
{
QString armingRequireParam("ARMING_REQUIRE");
return _parameterManager->parameterExists(FactSystem::defaultComponentId, armingRequireParam) &&
_parameterManager->getParameter(FactSystem::defaultComponentId, armingRequireParam)->rawValue().toInt() == 0;
}
void Vehicle::_handleSysStatus(mavlink_message_t& message)
{
mavlink_sys_status_t sysStatus;
mavlink_msg_sys_status_decode(&message, &sysStatus);
_sysStatusSensorInfo.update(sysStatus);
if (sysStatus.onboard_control_sensors_enabled & MAV_SYS_STATUS_PREARM_CHECK) {
if (!_readyToFlyAvailable) {
_readyToFlyAvailable = true;
emit readyToFlyAvailableChanged(true);
}
bool newReadyToFly = sysStatus.onboard_control_sensors_health & MAV_SYS_STATUS_PREARM_CHECK;
if (newReadyToFly != _readyToFly) {
_readyToFly = newReadyToFly;
emit readyToFlyChanged(_readyToFly);
}
}
bool newAllSensorsHealthy = (sysStatus.onboard_control_sensors_enabled & sysStatus.onboard_control_sensors_health) == sysStatus.onboard_control_sensors_enabled;
if (newAllSensorsHealthy != _allSensorsHealthy) {
_allSensorsHealthy = newAllSensorsHealthy;
emit allSensorsHealthyChanged(_allSensorsHealthy);
}
if (_onboardControlSensorsPresent != sysStatus.onboard_control_sensors_present) {
_onboardControlSensorsPresent = sysStatus.onboard_control_sensors_present;
emit sensorsPresentBitsChanged(_onboardControlSensorsPresent);
emit requiresGpsFixChanged();
}
if (_onboardControlSensorsEnabled != sysStatus.onboard_control_sensors_enabled) {
_onboardControlSensorsEnabled = sysStatus.onboard_control_sensors_enabled;
emit sensorsEnabledBitsChanged(_onboardControlSensorsEnabled);
}
if (_onboardControlSensorsHealth != sysStatus.onboard_control_sensors_health) {
_onboardControlSensorsHealth = sysStatus.onboard_control_sensors_health;
emit sensorsHealthBitsChanged(_onboardControlSensorsHealth);
}
// ArduPilot firmare has a strange case when ARMING_REQUIRE=0. This means the vehicle is always armed but the motors are not
// really powered up until the safety button is pressed. Because of this we can't depend on the heartbeat to tell us the true
// armed (and dangerous) state. We must instead rely on SYS_STATUS telling us that the motors are enabled.
if (apmFirmware() && _apmArmingNotRequired()) {
_updateArmed(_onboardControlSensorsEnabled & MAV_SYS_STATUS_SENSOR_MOTOR_OUTPUTS);
}
uint32_t newSensorsUnhealthy = _onboardControlSensorsEnabled & ~_onboardControlSensorsHealth;
if (newSensorsUnhealthy != _onboardControlSensorsUnhealthy) {
_onboardControlSensorsUnhealthy = newSensorsUnhealthy;
emit sensorsUnhealthyBitsChanged(_onboardControlSensorsUnhealthy);
}
}
void Vehicle::_handleBatteryStatus(mavlink_message_t& message)
{
mavlink_battery_status_t batteryStatus;
mavlink_msg_battery_status_decode(&message, &batteryStatus);
if (!_lowestBatteryChargeStateAnnouncedMap.contains(batteryStatus.id)) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
}
QString batteryMessage;
switch (batteryStatus.charge_state) {
case MAV_BATTERY_CHARGE_STATE_OK:
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
break;
case MAV_BATTERY_CHARGE_STATE_LOW:
if (batteryStatus.charge_state > _lowestBatteryChargeStateAnnouncedMap[batteryStatus.id]) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
batteryMessage = tr("battery %1 level low");
}
break;
case MAV_BATTERY_CHARGE_STATE_CRITICAL:
if (batteryStatus.charge_state > _lowestBatteryChargeStateAnnouncedMap[batteryStatus.id]) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
batteryMessage = tr("battery %1 level is critical");
}
break;
case MAV_BATTERY_CHARGE_STATE_EMERGENCY:
if (batteryStatus.charge_state > _lowestBatteryChargeStateAnnouncedMap[batteryStatus.id]) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
batteryMessage = tr("battery %1 level emergency");
}
break;
case MAV_BATTERY_CHARGE_STATE_FAILED:
if (batteryStatus.charge_state > _lowestBatteryChargeStateAnnouncedMap[batteryStatus.id]) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
batteryMessage = tr("battery %1 failed");
}
break;
case MAV_BATTERY_CHARGE_STATE_UNHEALTHY:
if (batteryStatus.charge_state > _lowestBatteryChargeStateAnnouncedMap[batteryStatus.id]) {
_lowestBatteryChargeStateAnnouncedMap[batteryStatus.id] = batteryStatus.charge_state;
batteryMessage = tr("battery %1 unhealthy");
}
break;
}
if (!batteryMessage.isEmpty()) {
QString batteryIdStr("%1");
if (_batteryFactGroupListModel.count() > 1) {
batteryIdStr = batteryIdStr.arg(batteryStatus.id);
} else {
batteryIdStr = batteryIdStr.arg("");
}
_say(tr("warning"));
_say(QStringLiteral("%1 %2 ").arg(_vehicleIdSpeech()).arg(batteryMessage.arg(batteryIdStr)));
}
}
void Vehicle::_setHomePosition(QGeoCoordinate& homeCoord)
{
if (homeCoord != _homePosition) {
_homePosition = homeCoord;
emit homePositionChanged(_homePosition);
}
}
void Vehicle::_handleHomePosition(mavlink_message_t& message)
{
mavlink_home_position_t homePos;
mavlink_msg_home_position_decode(&message, &homePos);
QGeoCoordinate newHomePosition (homePos.latitude / 10000000.0,
homePos.longitude / 10000000.0,
homePos.altitude / 1000.0);
_setHomePosition(newHomePosition);
}
void Vehicle::_updateArmed(bool armed)
{
if (_armed != armed) {
_armed = armed;
emit armedChanged(_armed);
// We are transitioning to the armed state, begin tracking trajectory points for the map
if (_armed) {
_trajectoryPoints->start();
_flightTimerStart();
_clearCameraTriggerPoints();
// Reset battery warning
_lowestBatteryChargeStateAnnouncedMap.clear();
} else {
_trajectoryPoints->stop();
_flightTimerStop();
// Also handle Video Streaming
if(qgcApp()->toolbox()->videoManager()->videoReceiver()) {
if(_settingsManager->videoSettings()->disableWhenDisarmed()->rawValue().toBool()) {
_settingsManager->videoSettings()->streamEnabled()->setRawValue(false);
qgcApp()->toolbox()->videoManager()->videoReceiver()->stop();
}
}
}
}
}
void Vehicle::_handlePing(LinkInterface* link, mavlink_message_t& message)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "_handlePing: primary link gone!";
return;
}
mavlink_ping_t ping;
mavlink_message_t msg;
mavlink_msg_ping_decode(&message, &ping);
if ((ping.target_system == 0) && (ping.target_component == 0)) {
// Mavlink defines a ping request as a MSG_ID_PING which contains target_system = 0 and target_component = 0
// So only send a ping response when you receive a valid ping request
mavlink_msg_ping_pack_chan(static_cast<uint8_t>(_mavlink->getSystemId()),
static_cast<uint8_t>(_mavlink->getComponentId()),
sharedLink->mavlinkChannel(),
&msg,
ping.time_usec,
ping.seq,
message.sysid,
message.compid);
sendMessageOnLinkThreadSafe(link, msg);
}
}
void Vehicle::_handleEvent(uint8_t comp_id, std::unique_ptr<events::parser::ParsedEvent> event)
{
int severity = -1;
switch (events::externalLogLevel(event->eventData().log_levels)) {
case events::Log::Emergency: severity = MAV_SEVERITY_EMERGENCY; break;
case events::Log::Alert: severity = MAV_SEVERITY_ALERT; break;
case events::Log::Critical: severity = MAV_SEVERITY_CRITICAL; break;
case events::Log::Error: severity = MAV_SEVERITY_ERROR; break;
case events::Log::Warning: severity = MAV_SEVERITY_WARNING; break;
case events::Log::Notice: severity = MAV_SEVERITY_NOTICE; break;
case events::Log::Info: severity = MAV_SEVERITY_INFO; break;
default: break;
}
// handle special groups & protocols
if (event->group() == "health" || event->group() == "arming_check") {
// these are displayed separately
return;
}
if (event->group() == "calibration") {
emit calibrationEventReceived(id(), comp_id, severity,
QSharedPointer<events::parser::ParsedEvent>{new events::parser::ParsedEvent{*event}});
// these are displayed separately
return;
}
// show message according to the log level, don't show unknown event groups (might be part of a new protocol)
if (event->group() == "default" && severity != -1) {
std::string message = event->message();
std::string description = event->description();
if (event->type() == "append_health_and_arming_messages" && event->numArguments() > 0) {
uint32_t customMode = event->argumentValue(0).value.val_uint32_t;
const QSharedPointer<EventHandler>& eventHandler = _events[comp_id];
int modeGroup = eventHandler->getModeGroup(customMode);
std::vector<events::HealthAndArmingChecks::Check> checks = eventHandler->healthAndArmingCheckResults().checks(modeGroup);
QList<std::string> messageChecks;
for (const auto& check : checks) {
if (events::externalLogLevel(check.log_levels) <= events::Log::Warning) {
messageChecks.append(check.message);
}
}
if (messageChecks.empty()) {
// Add all
for (const auto& check : checks) {
messageChecks.append(check.message);
}
}
if (!message.empty() && !messageChecks.empty()) {
message += "<br/>";
}
if (messageChecks.size() == 1) {
message += messageChecks[0];
} else {
for (const auto& messageCheck : messageChecks) {
message += "- " + messageCheck + "<br/>";
}
}
}
if (message.size() > 0) {
// TODO: handle this properly in the UI (e.g. with an expand button to display the description, clickable URL's + params)...
QString msg = QString::fromStdString(message);
if (description.size() > 0) {
msg += "<br/><small><small>" + QString::fromStdString(description).replace("\n", "<br/>") + "</small></small>";
}
emit textMessageReceived(id(), comp_id, severity, msg);
}
}
}
EventHandler& Vehicle::_eventHandler(uint8_t compid)
{
auto eventData = _events.find(compid);
if (eventData == _events.end()) {
// add new component
auto sendRequestEventMessageCB = [this](const mavlink_request_event_t& msg) {
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (sharedLink) {
mavlink_message_t message;
mavlink_msg_request_event_encode_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&message,
&msg);
sendMessageOnLinkThreadSafe(sharedLink.get(), message);
}
};
QString profile = "dev"; // TODO: should be configurable
QSharedPointer<EventHandler> eventHandler{new EventHandler(this, profile,
std::bind(&Vehicle::_handleEvent, this, compid, std::placeholders::_1),
sendRequestEventMessageCB,
_mavlink->getSystemId(), _mavlink->getComponentId(), _id, compid)};
eventData = _events.insert(compid, eventHandler);
// connect health and arming check updates
connect(eventHandler.data(), &EventHandler::healthAndArmingChecksUpdated, this, [compid, this]() {
// TODO: use user-intended mode instead of currently set mode
const QSharedPointer<EventHandler>& eventHandler = _events[compid];
_healthAndArmingCheckReport.update(compid, eventHandler->healthAndArmingCheckResults(),
eventHandler->getModeGroup(_custom_mode));
});
connect(this, &Vehicle::flightModeChanged, this, [compid, this]() {
const QSharedPointer<EventHandler>& eventHandler = _events[compid];
if (eventHandler->healthAndArmingCheckResultsValid()) {
_healthAndArmingCheckReport.update(compid, eventHandler->healthAndArmingCheckResults(),
eventHandler->getModeGroup(_custom_mode));
}
});
}
return *eventData->data();
}
void Vehicle::setEventsMetadata(uint8_t compid, const QString& metadataJsonFileName, const QString& translationJsonFileName)
{
_eventHandler(compid).setMetadata(metadataJsonFileName);
// get the mode group for some well-known flight modes
int modeGroups[2]{-1, -1};
const QString modes[2]{"Takeoff", "Mission"};
for (size_t i = 0; i < sizeof(modeGroups)/sizeof(modeGroups[0]); ++i) {
uint8_t base_mode;
uint32_t custom_mode;
if (_firmwarePlugin->setFlightMode(modes[i], &base_mode, &custom_mode)) {
modeGroups[i] = _eventHandler(compid).getModeGroup(custom_mode);
if (modeGroups[i] == -1) {
qCDebug(VehicleLog) << "Failed to get mode group for mode" << modes[i] << "(Might not be in metadata)";
}
}
}
_healthAndArmingCheckReport.setModeGroups(modeGroups[0], modeGroups[1]);
// Request arming checks to be reported
sendMavCommand(_defaultComponentId,
MAV_CMD_RUN_PREARM_CHECKS,
false);
}
void Vehicle::setActuatorsMetadata(uint8_t compid, const QString& metadataJsonFileName, const QString& translationJsonFileName)
{
if (!_actuators) {
_actuators = new Actuators(this, this);
}
_actuators->load(metadataJsonFileName);
}
void Vehicle::_handleHeartbeat(mavlink_message_t& message)
{
if (message.compid != _defaultComponentId) {
return;
}
mavlink_heartbeat_t heartbeat;
mavlink_msg_heartbeat_decode(&message, &heartbeat);
bool newArmed = heartbeat.base_mode & MAV_MODE_FLAG_DECODE_POSITION_SAFETY;
// ArduPilot firmare has a strange case when ARMING_REQUIRE=0. This means the vehicle is always armed but the motors are not
// really powered up until the safety button is pressed. Because of this we can't depend on the heartbeat to tell us the true
// armed (and dangerous) state. We must instead rely on SYS_STATUS telling us that the motors are enabled.
if (apmFirmware()) {
if (!_apmArmingNotRequired() || !(_onboardControlSensorsPresent & MAV_SYS_STATUS_SENSOR_MOTOR_OUTPUTS)) {
// If ARMING_REQUIRE!=0 or we haven't seen motor output status yet we use the hearbeat info for armed
_updateArmed(newArmed);
}
} else {
// Non-ArduPilot always updates from armed state in heartbeat
_updateArmed(newArmed);
}
if (heartbeat.base_mode != _base_mode || heartbeat.custom_mode != _custom_mode) {
QString previousFlightMode;
if (_base_mode != 0 || _custom_mode != 0){
// Vehicle is initialized with _base_mode=0 and _custom_mode=0. Don't pass this to flightMode() since it will complain about
// bad modes while unit testing.
previousFlightMode = flightMode();
}
_base_mode = heartbeat.base_mode;
_custom_mode = heartbeat.custom_mode;
if (previousFlightMode != flightMode()) {
emit flightModeChanged(flightMode());
}
}
}
void Vehicle::_handleRadioStatus(mavlink_message_t& message)
{
//-- Process telemetry status message
mavlink_radio_status_t rstatus;
mavlink_msg_radio_status_decode(&message, &rstatus);
int rssi = rstatus.rssi;
int remrssi = rstatus.remrssi;
int lnoise = (int)(int8_t)rstatus.noise;
int rnoise = (int)(int8_t)rstatus.remnoise;
//-- 3DR Si1k radio needs rssi fields to be converted to dBm
if (message.sysid == '3' && message.compid == 'D') {
/* Per the Si1K datasheet figure 23.25 and SI AN474 code
* samples the relationship between the RSSI register
* and received power is as follows:
*
* 10
* inputPower = rssi * ------ 127
* 19
*
* Additionally limit to the only realistic range [-120,0] dBm
*/
rssi = qMin(qMax(qRound(static_cast<qreal>(rssi) / 1.9 - 127.0), - 120), 0);
remrssi = qMin(qMax(qRound(static_cast<qreal>(remrssi) / 1.9 - 127.0), - 120), 0);
} else {
rssi = (int)(int8_t)rstatus.rssi;
remrssi = (int)(int8_t)rstatus.remrssi;
}
//-- Check for changes
if(_telemetryLRSSI != rssi) {
_telemetryLRSSI = rssi;
emit telemetryLRSSIChanged(_telemetryLRSSI);
}
if(_telemetryRRSSI != remrssi) {
_telemetryRRSSI = remrssi;
emit telemetryRRSSIChanged(_telemetryRRSSI);
}
if(_telemetryRXErrors != rstatus.rxerrors) {
_telemetryRXErrors = rstatus.rxerrors;
emit telemetryRXErrorsChanged(_telemetryRXErrors);
}
if(_telemetryFixed != rstatus.fixed) {
_telemetryFixed = rstatus.fixed;
emit telemetryFixedChanged(_telemetryFixed);
}
if(_telemetryTXBuffer != rstatus.txbuf) {
_telemetryTXBuffer = rstatus.txbuf;
emit telemetryTXBufferChanged(_telemetryTXBuffer);
}
if(_telemetryLNoise != lnoise) {
_telemetryLNoise = lnoise;
emit telemetryLNoiseChanged(_telemetryLNoise);
}
if(_telemetryRNoise != rnoise) {
_telemetryRNoise = rnoise;
emit telemetryRNoiseChanged(_telemetryRNoise);
}
}
void Vehicle::_handleRCChannels(mavlink_message_t& message)
{
mavlink_rc_channels_t channels;
mavlink_msg_rc_channels_decode(&message, &channels);
uint16_t* _rgChannelvalues[cMaxRcChannels] = {
&channels.chan1_raw,
&channels.chan2_raw,
&channels.chan3_raw,
&channels.chan4_raw,
&channels.chan5_raw,
&channels.chan6_raw,
&channels.chan7_raw,
&channels.chan8_raw,
&channels.chan9_raw,
&channels.chan10_raw,
&channels.chan11_raw,
&channels.chan12_raw,
&channels.chan13_raw,
&channels.chan14_raw,
&channels.chan15_raw,
&channels.chan16_raw,
&channels.chan17_raw,
&channels.chan18_raw,
};
int pwmValues[cMaxRcChannels];
for (int i=0; i<cMaxRcChannels; i++) {
uint16_t channelValue = *_rgChannelvalues[i];
if (i < channels.chancount) {
pwmValues[i] = channelValue == UINT16_MAX ? -1 : channelValue;
} else {
pwmValues[i] = -1;
}
}
emit remoteControlRSSIChanged(channels.rssi);
emit rcChannelsChanged(channels.chancount, pwmValues);
}
// Pop warnings ignoring for mavlink headers for both GCC/Clang and MSVC
#ifdef __GNUC__
#if defined(__clang__)
#pragma clang diagnostic pop
#else
#pragma GCC diagnostic pop
#endif
#else
#pragma warning(pop, 0)
#endif
bool Vehicle::sendMessageOnLinkThreadSafe(LinkInterface* link, mavlink_message_t message)
{
if (!link->isConnected()) {
qCDebug(VehicleLog) << "sendMessageOnLinkThreadSafe" << link << "not connected!";
return false;
}
// Give the plugin a chance to adjust
_firmwarePlugin->adjustOutgoingMavlinkMessageThreadSafe(this, link, &message);
// Write message into buffer, prepending start sign
uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
int len = mavlink_msg_to_send_buffer(buffer, &message);
link->writeBytesThreadSafe((const char*)buffer, len);
_messagesSent++;
emit messagesSentChanged();
return true;
}
int Vehicle::motorCount()
{
switch (_vehicleType) {
case MAV_TYPE_HELICOPTER:
return 1;
case MAV_TYPE_VTOL_TAILSITTER_DUOROTOR:
return 2;
case MAV_TYPE_TRICOPTER:
return 3;
case MAV_TYPE_QUADROTOR:
case MAV_TYPE_VTOL_TAILSITTER_QUADROTOR:
return 4;
case MAV_TYPE_HEXAROTOR:
return 6;
case MAV_TYPE_OCTOROTOR:
return 8;
case MAV_TYPE_SUBMARINE:
{
// Supported frame types
enum {
SUB_FRAME_BLUEROV1,
SUB_FRAME_VECTORED,
SUB_FRAME_VECTORED_6DOF,
SUB_FRAME_VECTORED_6DOF_90DEG,
SUB_FRAME_SIMPLEROV_3,
SUB_FRAME_SIMPLEROV_4,
SUB_FRAME_SIMPLEROV_5,
SUB_FRAME_CUSTOM
};
uint8_t frameType = parameterManager()->getParameter(_compID, "FRAME_CONFIG")->rawValue().toInt();
switch (frameType) { // ardupilot/libraries/AP_Motors/AP_Motors6DOF.h sub_frame_t
case SUB_FRAME_BLUEROV1:
case SUB_FRAME_VECTORED:
return 6;
case SUB_FRAME_SIMPLEROV_3:
return 3;
case SUB_FRAME_SIMPLEROV_4:
return 4;
case SUB_FRAME_SIMPLEROV_5:
return 5;
case SUB_FRAME_VECTORED_6DOF:
case SUB_FRAME_VECTORED_6DOF_90DEG:
case SUB_FRAME_CUSTOM:
return 8;
default:
return -1;
}
}
default:
return -1;
}
}
bool Vehicle::coaxialMotors()
{
return _firmwarePlugin->multiRotorCoaxialMotors(this);
}
bool Vehicle::xConfigMotors()
{
return _firmwarePlugin->multiRotorXConfig(this);
}
QString Vehicle::formattedMessages()
{
QString messages;
for(UASMessage* message: _toolbox->uasMessageHandler()->messages()) {
messages += message->getFormatedText();
}
return messages;
}
void Vehicle::clearMessages()
{
_toolbox->uasMessageHandler()->clearMessages();
}
void Vehicle::_handletextMessageReceived(UASMessage* message)
{
if (message) {
emit newFormattedMessage(message->getFormatedText());
}
}
void Vehicle::_handleTextMessage(int newCount)
{
// Reset?
if(!newCount) {
_currentMessageCount = 0;
_currentNormalCount = 0;
_currentWarningCount = 0;
_currentErrorCount = 0;
_messageCount = 0;
_currentMessageType = MessageNone;
emit newMessageCountChanged();
emit messageTypeChanged();
emit messageCountChanged();
return;
}
UASMessageHandler* pMh = _toolbox->uasMessageHandler();
MessageType_t type = newCount ? _currentMessageType : MessageNone;
int errorCount = _currentErrorCount;
int warnCount = _currentWarningCount;
int normalCount = _currentNormalCount;
//-- Add current message counts
errorCount += pMh->getErrorCount();
warnCount += pMh->getWarningCount();
normalCount += pMh->getNormalCount();
//-- See if we have a higher level
if(errorCount != _currentErrorCount) {
_currentErrorCount = errorCount;
type = MessageError;
}
if(warnCount != _currentWarningCount) {
_currentWarningCount = warnCount;
if(_currentMessageType != MessageError) {
type = MessageWarning;
}
}
if(normalCount != _currentNormalCount) {
_currentNormalCount = normalCount;
if(_currentMessageType != MessageError && _currentMessageType != MessageWarning) {
type = MessageNormal;
}
}
int count = _currentErrorCount + _currentWarningCount + _currentNormalCount;
if(count != _currentMessageCount) {
_currentMessageCount = count;
// Display current total new messages count
emit newMessageCountChanged();
}
if(type != _currentMessageType) {
_currentMessageType = type;
// Update message level
emit messageTypeChanged();
}
// Update message count (all messages)
if(newCount != _messageCount) {
_messageCount = newCount;
emit messageCountChanged();
}
QString errMsg = pMh->getLatestError();
if(errMsg != _latestError) {
_latestError = errMsg;
emit latestErrorChanged();
}
}
void Vehicle::resetMessages()
{
// Reset Counts
int count = _currentMessageCount;
MessageType_t type = _currentMessageType;
_currentErrorCount = 0;
_currentWarningCount = 0;
_currentNormalCount = 0;
_currentMessageCount = 0;
_currentMessageType = MessageNone;
if(count != _currentMessageCount) {
emit newMessageCountChanged();
}
if(type != _currentMessageType) {
emit messageTypeChanged();
}
}
void Vehicle::_loadSettings()
{
QSettings settings;
settings.beginGroup(QString(_settingsGroup).arg(_id));
// Joystick enabled is a global setting so first make sure there are any joysticks connected
if (_toolbox->joystickManager()->joysticks().count()) {
setJoystickEnabled(settings.value(_joystickEnabledSettingsKey, false).toBool());
_startJoystick(true);
}
}
void Vehicle::_saveSettings()
{
QSettings settings;
settings.beginGroup(QString(_settingsGroup).arg(_id));
// The joystick enabled setting should only be changed if a joystick is present
// since the checkbox can only be clicked if one is present
if (_toolbox->joystickManager()->joysticks().count()) {
settings.setValue(_joystickEnabledSettingsKey, _joystickEnabled);
}
}
bool Vehicle::joystickEnabled() const
{
return _joystickEnabled;
}
void Vehicle::setJoystickEnabled(bool enabled)
{
_joystickEnabled = enabled;
_startJoystick(_joystickEnabled);
_saveSettings();
emit joystickEnabledChanged(_joystickEnabled);
}
void Vehicle::_startJoystick(bool start)
{
Joystick* joystick = _joystickManager->activeJoystick();
if (joystick) {
if (start) {
joystick->startPolling(this);
} else {
joystick->stopPolling();
joystick->wait(500);
}
}
}
QGeoCoordinate Vehicle::homePosition()
{
return _homePosition;
}
void Vehicle::setArmed(bool armed, bool showError)
{
// We specifically use COMMAND_LONG:MAV_CMD_COMPONENT_ARM_DISARM since it is supported by more flight stacks.
sendMavCommand(_defaultComponentId,
MAV_CMD_COMPONENT_ARM_DISARM,
showError,
armed ? 1.0f : 0.0f);
}
void Vehicle::forceArm(void)
{
sendMavCommand(_defaultComponentId,
MAV_CMD_COMPONENT_ARM_DISARM,
true, // show error if fails
1.0f, // arm
2989); // force arm
}
bool Vehicle::flightModeSetAvailable()
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::SetFlightModeCapability);
}
QStringList Vehicle::flightModes()
{
return _firmwarePlugin->flightModes(this);
}
QStringList Vehicle::extraJoystickFlightModes()
{
return _firmwarePlugin->extraJoystickFlightModes(this);
}
QString Vehicle::flightMode() const
{
return _firmwarePlugin->flightMode(_base_mode, _custom_mode);
}
void Vehicle::setFlightMode(const QString& flightMode)
{
uint8_t base_mode;
uint32_t custom_mode;
if (_firmwarePlugin->setFlightMode(flightMode, &base_mode, &custom_mode)) {
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "setFlightMode: primary link gone!";
return;
}
uint8_t newBaseMode = _base_mode & ~MAV_MODE_FLAG_DECODE_POSITION_CUSTOM_MODE;
// setFlightMode will only set MAV_MODE_FLAG_CUSTOM_MODE_ENABLED in base_mode, we need to move back in the existing
// states.
newBaseMode |= base_mode;
mavlink_message_t msg;
mavlink_msg_set_mode_pack_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
id(),
newBaseMode,
custom_mode);
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
} else {
qCWarning(VehicleLog) << "FirmwarePlugin::setFlightMode failed, flightMode:" << flightMode;
}
}
#if 0
QVariantList Vehicle::links() const {
QVariantList ret;
for( const auto &item: _links )
ret << QVariant::fromValue(item);
return ret;
}
#endif
void Vehicle::requestDataStream(MAV_DATA_STREAM stream, uint16_t rate, bool sendMultiple)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "requestDataStream: primary link gone!";
return;
}
mavlink_message_t msg;
mavlink_request_data_stream_t dataStream;
memset(&dataStream, 0, sizeof(dataStream));
dataStream.req_stream_id = stream;
dataStream.req_message_rate = rate;
dataStream.start_stop = 1; // start
dataStream.target_system = id();
dataStream.target_component = _defaultComponentId;
mavlink_msg_request_data_stream_encode_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
&dataStream);
if (sendMultiple) {
// We use sendMessageMultiple since we really want these to make it to the vehicle
sendMessageMultiple(msg);
} else {
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
}
void Vehicle::_sendMessageMultipleNext()
{
if (_nextSendMessageMultipleIndex < _sendMessageMultipleList.count()) {
qCDebug(VehicleLog) << "_sendMessageMultipleNext:" << _sendMessageMultipleList[_nextSendMessageMultipleIndex].message.msgid;
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (sharedLink) {
sendMessageOnLinkThreadSafe(sharedLink.get(), _sendMessageMultipleList[_nextSendMessageMultipleIndex].message);
}
if (--_sendMessageMultipleList[_nextSendMessageMultipleIndex].retryCount <= 0) {
_sendMessageMultipleList.removeAt(_nextSendMessageMultipleIndex);
} else {
_nextSendMessageMultipleIndex++;
}
}
if (_nextSendMessageMultipleIndex >= _sendMessageMultipleList.count()) {
_nextSendMessageMultipleIndex = 0;
}
}
void Vehicle::sendMessageMultiple(mavlink_message_t message)
{
SendMessageMultipleInfo_t info;
info.message = message;
info.retryCount = _sendMessageMultipleRetries;
_sendMessageMultipleList.append(info);
}
void Vehicle::_missionManagerError(int errorCode, const QString& errorMsg)
{
Q_UNUSED(errorCode);
qgcApp()->showAppMessage(tr("Mission transfer failed. Error: %1").arg(errorMsg));
}
void Vehicle::_geoFenceManagerError(int errorCode, const QString& errorMsg)
{
Q_UNUSED(errorCode);
qgcApp()->showAppMessage(tr("GeoFence transfer failed. Error: %1").arg(errorMsg));
}
void Vehicle::_rallyPointManagerError(int errorCode, const QString& errorMsg)
{
Q_UNUSED(errorCode);
qgcApp()->showAppMessage(tr("Rally Point transfer failed. Error: %1").arg(errorMsg));
}
void Vehicle::_clearCameraTriggerPoints()
{
_cameraTriggerPoints.clearAndDeleteContents();
}
void Vehicle::_flightTimerStart()
{
_flightTimer.start();
_flightTimeUpdater.start();
_flightDistanceFact.setRawValue(0);
_flightTimeFact.setRawValue(0);
}
void Vehicle::_flightTimerStop()
{
_flightTimeUpdater.stop();
}
void Vehicle::_updateFlightTime()
{
_flightTimeFact.setRawValue((double)_flightTimer.elapsed() / 1000.0);
}
void Vehicle::_gotProgressUpdate(float progressValue)
{
if (sender() != _initialConnectStateMachine && _initialConnectStateMachine->active()) {
return;
}
if (sender() == _initialConnectStateMachine && !_initialConnectStateMachine->active()) {
progressValue = 0.f;
}
_loadProgress = progressValue;
emit loadProgressChanged(progressValue);
}
void Vehicle::_firstMissionLoadComplete()
{
disconnect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_firstMissionLoadComplete);
_initialConnectStateMachine->advance();
}
void Vehicle::_firstGeoFenceLoadComplete()
{
disconnect(_geoFenceManager, &GeoFenceManager::loadComplete, this, &Vehicle::_firstGeoFenceLoadComplete);
_initialConnectStateMachine->advance();
}
void Vehicle::_firstRallyPointLoadComplete()
{
disconnect(_rallyPointManager, &RallyPointManager::loadComplete, this, &Vehicle::_firstRallyPointLoadComplete);
_initialPlanRequestComplete = true;
emit initialPlanRequestCompleteChanged(true);
_initialConnectStateMachine->advance();
}
void Vehicle::_parametersReady(bool parametersReady)
{
qCDebug(VehicleLog) << "_parametersReady" << parametersReady;
// Try to set current unix time to the vehicle
_sendQGCTimeToVehicle();
// Send time twice, more likely to get to the vehicle on a noisy link
_sendQGCTimeToVehicle();
if (parametersReady) {
disconnect(_parameterManager, &ParameterManager::parametersReadyChanged, this, &Vehicle::_parametersReady);
_setupAutoDisarmSignalling();
_initialConnectStateMachine->advance();
}
_multirotor_speed_limits_available = _firmwarePlugin->mulirotorSpeedLimitsAvailable(this);
_fixed_wing_airspeed_limits_available = _firmwarePlugin->fixedWingAirSpeedLimitsAvailable(this);
emit haveMRSpeedLimChanged();
emit haveFWSpeedLimChanged();
}
void Vehicle::_sendQGCTimeToVehicle()
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "_sendQGCTimeToVehicle: primary link gone!";
return;
}
mavlink_message_t msg;
mavlink_system_time_t cmd;
// Timestamp of the master clock in microseconds since UNIX epoch.
cmd.time_unix_usec = QDateTime::currentDateTime().currentMSecsSinceEpoch()*1000;
// Timestamp of the component clock since boot time in milliseconds (Not necessary).
cmd.time_boot_ms = 0;
mavlink_msg_system_time_encode_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
&cmd);
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
void Vehicle::_imageProtocolImageReady(void)
{
QImage img = _imageProtocolManager->getImage();
_toolbox->imageProvider()->setImage(&img, _id);
_flowImageIndex++;
emit flowImageIndexChanged();
}
void Vehicle::_remoteControlRSSIChanged(uint8_t rssi)
{
//-- 0 <= rssi <= 100 - 255 means "invalid/unknown"
if(rssi > 100) { // Anything over 100 doesn't make sense
if(_rcRSSI != 255) {
_rcRSSI = 255;
emit rcRSSIChanged(_rcRSSI);
}
return;
}
//-- Initialize it
if(_rcRSSIstore == 255.) {
_rcRSSIstore = (double)rssi;
}
// Low pass to git rid of jitter
_rcRSSIstore = (_rcRSSIstore * 0.9f) + ((float)rssi * 0.1);
uint8_t filteredRSSI = (uint8_t)ceil(_rcRSSIstore);
if(_rcRSSIstore < 0.1) {
filteredRSSI = 0;
}
if(_rcRSSI != filteredRSSI) {
_rcRSSI = filteredRSSI;
emit rcRSSIChanged(_rcRSSI);
}
}
void Vehicle::virtualTabletJoystickValue(double roll, double pitch, double yaw, double thrust)
{
// The following if statement prevents the virtualTabletJoystick from sending values if the standard joystick is enabled
if (!_joystickEnabled) {
sendJoystickDataThreadSafe(
static_cast<float>(roll),
static_cast<float>(pitch),
static_cast<float>(yaw),
static_cast<float>(thrust),
0);
}
}
void Vehicle::_say(const QString& text)
{
_toolbox->audioOutput()->say(text.toLower());
}
bool Vehicle::airship() const
{
return QGCMAVLink::isAirship(vehicleType());
}
bool Vehicle::fixedWing() const
{
return QGCMAVLink::isFixedWing(vehicleType());
}
bool Vehicle::rover() const
{
return QGCMAVLink::isRoverBoat(vehicleType());
}
bool Vehicle::sub() const
{
return QGCMAVLink::isSub(vehicleType());
}
bool Vehicle::multiRotor() const
{
return QGCMAVLink::isMultiRotor(vehicleType());
}
bool Vehicle::vtol() const
{
return QGCMAVLink::isVTOL(vehicleType());
}
bool Vehicle::supportsThrottleModeCenterZero() const
{
return _firmwarePlugin->supportsThrottleModeCenterZero();
}
bool Vehicle::supportsNegativeThrust()
{
return _firmwarePlugin->supportsNegativeThrust(this);
}
bool Vehicle::supportsRadio() const
{
return _firmwarePlugin->supportsRadio();
}
bool Vehicle::supportsJSButton() const
{
return _firmwarePlugin->supportsJSButton();
}
bool Vehicle::supportsMotorInterference() const
{
return _firmwarePlugin->supportsMotorInterference();
}
bool Vehicle::supportsTerrainFrame() const
{
return !px4Firmware();
}
QString Vehicle::vehicleTypeName() const {
static QMap<int, QString> typeNames = {
{ MAV_TYPE_GENERIC, tr("Generic micro air vehicle" )},
{ MAV_TYPE_FIXED_WING, tr("Fixed wing aircraft")},
{ MAV_TYPE_QUADROTOR, tr("Quadrotor")},
{ MAV_TYPE_COAXIAL, tr("Coaxial helicopter")},
{ MAV_TYPE_HELICOPTER, tr("Normal helicopter with tail rotor.")},
{ MAV_TYPE_ANTENNA_TRACKER, tr("Ground installation")},
{ MAV_TYPE_GCS, tr("Operator control unit / ground control station")},
{ MAV_TYPE_AIRSHIP, tr("Airship, controlled")},
{ MAV_TYPE_FREE_BALLOON, tr("Free balloon, uncontrolled")},
{ MAV_TYPE_ROCKET, tr("Rocket")},
{ MAV_TYPE_GROUND_ROVER, tr("Ground rover")},
{ MAV_TYPE_SURFACE_BOAT, tr("Surface vessel, boat, ship")},
{ MAV_TYPE_SUBMARINE, tr("Submarine")},
{ MAV_TYPE_HEXAROTOR, tr("Hexarotor")},
{ MAV_TYPE_OCTOROTOR, tr("Octorotor")},
{ MAV_TYPE_TRICOPTER, tr("Octorotor")},
{ MAV_TYPE_FLAPPING_WING, tr("Flapping wing")},
{ MAV_TYPE_KITE, tr("Flapping wing")},
{ MAV_TYPE_ONBOARD_CONTROLLER, tr("Onboard companion controller")},
{ MAV_TYPE_VTOL_TAILSITTER_DUOROTOR, tr("Two-rotor VTOL using control surfaces in vertical operation in addition. Tailsitter")},
{ MAV_TYPE_VTOL_TAILSITTER_QUADROTOR, tr("Quad-rotor VTOL using a V-shaped quad config in vertical operation. Tailsitter")},
{ MAV_TYPE_VTOL_TILTROTOR, tr("Tiltrotor VTOL")},
{ MAV_TYPE_VTOL_FIXEDROTOR, tr("VTOL Fixedrotor")},
{ MAV_TYPE_VTOL_TAILSITTER, tr("VTOL Tailsitter")},
{ MAV_TYPE_VTOL_RESERVED4, tr("VTOL reserved 4")},
{ MAV_TYPE_VTOL_RESERVED5, tr("VTOL reserved 5")},
{ MAV_TYPE_GIMBAL, tr("Onboard gimbal")},
{ MAV_TYPE_ADSB, tr("Onboard ADSB peripheral")},
};
return typeNames[_vehicleType];
}
/// Returns the string to speak to identify the vehicle
QString Vehicle::_vehicleIdSpeech()
{
if (_toolbox->multiVehicleManager()->vehicles()->count() > 1) {
return tr("Vehicle %1 ").arg(id());
} else {
return QString();
}
}
void Vehicle::_handleFlightModeChanged(const QString& flightMode)
{
_say(tr("%1 %2 flight mode").arg(_vehicleIdSpeech()).arg(flightMode));
emit guidedModeChanged(_firmwarePlugin->isGuidedMode(this));
}
void Vehicle::_announceArmedChanged(bool armed)
{
_say(QString("%1 %2").arg(_vehicleIdSpeech()).arg(armed ? tr("armed") : tr("disarmed")));
if(armed) {
//-- Keep track of armed coordinates
_armedPosition = _coordinate;
emit armedPositionChanged();
}
}
void Vehicle::_setFlying(bool flying)
{
if (_flying != flying) {
_flying = flying;
emit flyingChanged(flying);
}
}
void Vehicle::_setLanding(bool landing)
{
if (armed() && _landing != landing) {
_landing = landing;
emit landingChanged(landing);
}
}
bool Vehicle::guidedModeSupported() const
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::GuidedModeCapability);
}
bool Vehicle::pauseVehicleSupported() const
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::PauseVehicleCapability);
}
bool Vehicle::orbitModeSupported() const
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::OrbitModeCapability);
}
bool Vehicle::roiModeSupported() const
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::ROIModeCapability);
}
bool Vehicle::takeoffVehicleSupported() const
{
return _firmwarePlugin->isCapable(this, FirmwarePlugin::TakeoffVehicleCapability);
}
QString Vehicle::gotoFlightMode() const
{
return _firmwarePlugin->gotoFlightMode();
}
void Vehicle::guidedModeRTL(bool smartRTL)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeRTL(this, smartRTL);
}
void Vehicle::guidedModeLand()
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeLand(this);
}
void Vehicle::guidedModeTakeoff(double altitudeRelative)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeTakeoff(this, altitudeRelative);
}
double Vehicle::minimumTakeoffAltitude()
{
return _firmwarePlugin->minimumTakeoffAltitude(this);
}
double Vehicle::maximumHorizontalSpeedMultirotor()
{
return _firmwarePlugin->maximumHorizontalSpeedMultirotor(this);
}
double Vehicle::maximumEquivalentAirspeed()
{
return _firmwarePlugin->maximumEquivalentAirspeed(this);
}
double Vehicle::minimumEquivalentAirspeed()
{
return _firmwarePlugin->minimumEquivalentAirspeed(this);
}
void Vehicle::startMission()
{
_firmwarePlugin->startMission(this);
}
void Vehicle::guidedModeGotoLocation(const QGeoCoordinate& gotoCoord)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
if (!coordinate().isValid()) {
return;
}
double maxDistance = _settingsManager->flyViewSettings()->maxGoToLocationDistance()->rawValue().toDouble();
if (coordinate().distanceTo(gotoCoord) > maxDistance) {
qgcApp()->showAppMessage(QString("New location is too far. Must be less than %1 %2.").arg(qRound(FactMetaData::metersToAppSettingsHorizontalDistanceUnits(maxDistance).toDouble())).arg(FactMetaData::appSettingsHorizontalDistanceUnitsString()));
return;
}
_firmwarePlugin->guidedModeGotoLocation(this, gotoCoord);
}
void Vehicle::guidedModeChangeAltitude(double altitudeChange, bool pauseVehicle)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeChangeAltitude(this, altitudeChange, pauseVehicle);
}
void
Vehicle::guidedModeChangeGroundSpeed(double groundspeed)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeChangeGroundSpeed(this, groundspeed);
}
void
Vehicle::guidedModeChangeEquivalentAirspeed(double airspeed)
{
if (!guidedModeSupported()) {
qgcApp()->showAppMessage(guided_mode_not_supported_by_vehicle);
return;
}
_firmwarePlugin->guidedModeChangeEquivalentAirspeed(this, airspeed);
}
void Vehicle::guidedModeOrbit(const QGeoCoordinate& centerCoord, double radius, double amslAltitude)
{
if (!orbitModeSupported()) {
qgcApp()->showAppMessage(QStringLiteral("Orbit mode not supported by Vehicle."));
return;
}
if (capabilityBits() & MAV_PROTOCOL_CAPABILITY_COMMAND_INT) {
sendMavCommandInt(
defaultComponentId(),
MAV_CMD_DO_ORBIT,
MAV_FRAME_GLOBAL,
true, // show error if fails
static_cast<float>(radius),
static_cast<float>(qQNaN()), // Use default velocity
0, // Vehicle points to center
static_cast<float>(qQNaN()), // reserved
centerCoord.latitude(), centerCoord.longitude(), static_cast<float>(amslAltitude));
} else {
sendMavCommand(
defaultComponentId(),
MAV_CMD_DO_ORBIT,
true, // show error if fails
static_cast<float>(radius),
static_cast<float>(qQNaN()), // Use default velocity
0, // Vehicle points to center
static_cast<float>(qQNaN()), // reserved
static_cast<float>(centerCoord.latitude()),
static_cast<float>(centerCoord.longitude()),
static_cast<float>(amslAltitude));
}
}
void Vehicle::guidedModeROI(const QGeoCoordinate& centerCoord)
{
if (!roiModeSupported()) {
qgcApp()->showAppMessage(QStringLiteral("ROI mode not supported by Vehicle."));
return;
}
if (capabilityBits() & MAV_PROTOCOL_CAPABILITY_COMMAND_INT) {
sendMavCommandInt(
defaultComponentId(),
MAV_CMD_DO_SET_ROI_LOCATION,
MAV_FRAME_GLOBAL,
true, // show error if fails
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
centerCoord.latitude(),
centerCoord.longitude(),
static_cast<float>(centerCoord.altitude()));
} else {
sendMavCommand(
defaultComponentId(),
MAV_CMD_DO_SET_ROI_LOCATION,
true, // show error if fails
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(centerCoord.latitude()),
static_cast<float>(centerCoord.longitude()),
static_cast<float>(centerCoord.altitude()));
}
}
void Vehicle::stopGuidedModeROI()
{
if (!roiModeSupported()) {
qgcApp()->showAppMessage(QStringLiteral("ROI mode not supported by Vehicle."));
return;
}
if (capabilityBits() & MAV_PROTOCOL_CAPABILITY_COMMAND_INT) {
sendMavCommandInt(
defaultComponentId(),
MAV_CMD_DO_SET_ROI_NONE,
MAV_FRAME_GLOBAL,
true, // show error if fails
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<double>(qQNaN()), // Empty
static_cast<double>(qQNaN()), // Empty
static_cast<float>(qQNaN())); // Empty
} else {
sendMavCommand(
defaultComponentId(),
MAV_CMD_DO_SET_ROI_NONE,
true, // show error if fails
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN()), // Empty
static_cast<float>(qQNaN())); // Empty
}
}
void Vehicle::pauseVehicle()
{
if (!pauseVehicleSupported()) {
qgcApp()->showAppMessage(QStringLiteral("Pause not supported by vehicle."));
return;
}
_firmwarePlugin->pauseVehicle(this);
}
void Vehicle::abortLanding(double climbOutAltitude)
{
sendMavCommand(
defaultComponentId(),
MAV_CMD_DO_GO_AROUND,
true, // show error if fails
static_cast<float>(climbOutAltitude));
}
bool Vehicle::guidedMode() const
{
return _firmwarePlugin->isGuidedMode(this);
}
void Vehicle::setGuidedMode(bool guidedMode)
{
return _firmwarePlugin->setGuidedMode(this, guidedMode);
}
void Vehicle::emergencyStop()
{
sendMavCommand(
_defaultComponentId,
MAV_CMD_COMPONENT_ARM_DISARM,
true, // show error if fails
0.0f,
21196.0f); // Magic number for emergency stop
}
void Vehicle::setCurrentMissionSequence(int seq)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "setCurrentMissionSequence: primary link gone!";
return;
}
mavlink_message_t msg;
if (!_firmwarePlugin->sendHomePositionToVehicle()) {
seq--;
}
mavlink_msg_mission_set_current_pack_chan(
static_cast<uint8_t>(_mavlink->getSystemId()),
static_cast<uint8_t>(_mavlink->getComponentId()),
sharedLink->mavlinkChannel(),
&msg,
static_cast<uint8_t>(id()),
_compID,
static_cast<uint16_t>(seq));
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
void Vehicle::sendMavCommand(int compId, MAV_CMD command, bool showError, float param1, float param2, float param3, float param4, float param5, float param6, float param7)
{
_sendMavCommandWorker(false, // commandInt
showError,
nullptr, // resultHandler
nullptr, // resultHandlerData
compId,
command,
MAV_FRAME_GLOBAL,
param1, param2, param3, param4, param5, param6, param7);
}
void Vehicle::sendCommand(int compId, int command, bool showError, double param1, double param2, double param3, double param4, double param5, double param6, double param7)
{
sendMavCommand(
compId, static_cast<MAV_CMD>(command),
showError,
static_cast<float>(param1),
static_cast<float>(param2),
static_cast<float>(param3),
static_cast<float>(param4),
static_cast<float>(param5),
static_cast<float>(param6),
static_cast<float>(param7));
}
void Vehicle::sendMavCommandWithHandler(MavCmdResultHandler resultHandler, void *resultHandlerData, int compId, MAV_CMD command, float param1, float param2, float param3, float param4, float param5, float param6, float param7)
{
_sendMavCommandWorker(false, // commandInt
false, // showError
resultHandler,
resultHandlerData,
compId,
command,
MAV_FRAME_GLOBAL,
param1, param2, param3, param4, param5, param6, param7);
}
void Vehicle::sendMavCommandInt(int compId, MAV_CMD command, MAV_FRAME frame, bool showError, float param1, float param2, float param3, float param4, double param5, double param6, float param7)
{
_sendMavCommandWorker(true, // commandInt
showError,
nullptr, // resultHandler
nullptr, // resultHandlerData
compId,
command,
frame,
param1, param2, param3, param4, param5, param6, param7);
}
bool Vehicle::isMavCommandPending(int targetCompId, MAV_CMD command)
{
return ((-1) < _findMavCommandListEntryIndex(targetCompId, command));
}
int Vehicle::_findMavCommandListEntryIndex(int targetCompId, MAV_CMD command)
{
for (int i=0; i<_mavCommandList.count(); i++) {
const MavCommandListEntry_t& entry = _mavCommandList[i];
if (entry.targetCompId == targetCompId && entry.command == command) {
return i;
}
}
return -1;
}
bool Vehicle::_sendMavCommandShouldRetry(MAV_CMD command)
{
switch (command) {
#ifdef QT_DEBUG
// These MockLink command should be retried so we can create unit tests to test retry code
case MockLink::MAV_CMD_MOCKLINK_ALWAYS_RESULT_ACCEPTED:
case MockLink::MAV_CMD_MOCKLINK_ALWAYS_RESULT_FAILED:
case MockLink::MAV_CMD_MOCKLINK_SECOND_ATTEMPT_RESULT_ACCEPTED:
case MockLink::MAV_CMD_MOCKLINK_SECOND_ATTEMPT_RESULT_FAILED:
case MockLink::MAV_CMD_MOCKLINK_NO_RESPONSE:
return true;
#endif
// In general we should not retry any commands. This is for safety reasons. For example you don't want an ARM command
// to timeout with no response over a noisy link twice and then suddenly have the third try work 6 seconds later. At that
// point the user could have walked up to the vehicle to see what is going wrong.
//
// We do retry commands which are part of the initial vehicle connect sequence. This makes this process work better over noisy
// links where commands could be lost. Also these commands tend to just be requesting status so if they end up being delayed
// there are no safety concerns that could occur.
case MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES:
case MAV_CMD_REQUEST_PROTOCOL_VERSION:
case MAV_CMD_REQUEST_MESSAGE:
case MAV_CMD_PREFLIGHT_STORAGE:
case MAV_CMD_RUN_PREARM_CHECKS:
return true;
default:
return false;
}
}
bool Vehicle::_commandCanBeDuplicated(MAV_CMD command)
{
// For some commands we don't care about response as much as we care about sending them regularly.
// This test avoids commands not being sent due to an ACK not being received yet.
// MOTOR_TEST in ardusub is a case where we need a constant stream of commands so it doesn't time out.
switch (command) {
case MAV_CMD_DO_MOTOR_TEST:
return true;
default:
return false;
}
}
void Vehicle::_sendMavCommandWorker(bool commandInt, bool showError, MavCmdResultHandler resultHandler, void* resultHandlerData, int targetCompId, MAV_CMD command, MAV_FRAME frame, float param1, float param2, float param3, float param4, double param5, double param6, float param7)
{
if ((targetCompId == MAV_COMP_ID_ALL) || (isMavCommandPending(targetCompId, command) && !_commandCanBeDuplicated(command))) {
bool compIdAll = targetCompId == MAV_COMP_ID_ALL;
QString rawCommandName = _toolbox->missionCommandTree()->rawName(command);
qCDebug(VehicleLog) << QStringLiteral("_sendMavCommandWorker failing %1").arg(compIdAll ? "MAV_COMP_ID_ALL not supportded" : "duplicate command") << rawCommandName;
// If we send multiple versions of the same command to a component there is no way to discern which COMMAND_ACK we get back goes with which.
// Because of this we fail in that case.
MavCmdResultFailureCode_t failureCode = compIdAll ? MavCmdResultCommandResultOnly : MavCmdResultFailureDuplicateCommand;
if (resultHandler) {
(*resultHandler)(resultHandlerData, targetCompId, MAV_RESULT_FAILED, 0, failureCode);
} else {
emit mavCommandResult(_id, targetCompId, command, MAV_RESULT_FAILED, failureCode);
}
if (showError) {
qgcApp()->showAppMessage(tr("Unable to send command: %1.").arg(compIdAll ? tr("Internal error - MAV_COMP_ID_ALL not supported") : tr("Waiting on previous response to same command.")));
}
return;
}
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "_sendMavCommandWorker: primary link gone!";
return;
}
MavCommandListEntry_t entry;
entry.useCommandInt = commandInt;
entry.targetCompId = targetCompId;
entry.command = command;
entry.frame = frame;
entry.showError = showError;
entry.resultHandler = resultHandler;
entry.resultHandlerData = resultHandlerData;
entry.rgParam1 = param1;
entry.rgParam2 = param2;
entry.rgParam3 = param3;
entry.rgParam4 = param4;
entry.rgParam5 = param5;
entry.rgParam6 = param6;
entry.rgParam7 = param7;
entry.maxTries = _sendMavCommandShouldRetry(command) ? _mavCommandMaxRetryCount : 1;
entry.ackTimeoutMSecs = sharedLink->linkConfiguration()->isHighLatency() ? _mavCommandAckTimeoutMSecsHighLatency : _mavCommandAckTimeoutMSecs;
entry.elapsedTimer.start();
_mavCommandList.append(entry);
_sendMavCommandFromList(_mavCommandList.count() - 1);
}
void Vehicle::_sendMavCommandFromList(int index)
{
MavCommandListEntry_t commandEntry = _mavCommandList[index];
QString rawCommandName = _toolbox->missionCommandTree()->rawName(commandEntry.command);
if (++_mavCommandList[index].tryCount > commandEntry.maxTries) {
qCDebug(VehicleLog) << "_sendMavCommandFromList giving up after max retries" << rawCommandName;
_mavCommandList.removeAt(index);
if (commandEntry.resultHandler) {
(*commandEntry.resultHandler)(commandEntry.resultHandlerData, commandEntry.targetCompId, MAV_RESULT_FAILED, 0, MavCmdResultFailureNoResponseToCommand);
} else {
emit mavCommandResult(_id, commandEntry.targetCompId, commandEntry.command, MAV_RESULT_FAILED, MavCmdResultFailureNoResponseToCommand);
}
if (commandEntry.showError) {
qgcApp()->showAppMessage(tr("Vehicle did not respond to command: %1").arg(rawCommandName));
}
return;
}
if (commandEntry.tryCount > 1 && !px4Firmware() && commandEntry.command == MAV_CMD_START_RX_PAIR) {
// The implementation of this command comes from the IO layer and is shared across stacks. So for other firmwares
// we aren't really sure whether they are correct or not.
return;
}
qCDebug(VehicleLog) << "_sendMavCommandFromList command:tryCount" << rawCommandName << commandEntry.tryCount;
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "_sendMavCommandFromList: primary link gone!";
return;
}
mavlink_message_t msg;
if (commandEntry.useCommandInt) {
mavlink_command_int_t cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.target_system = _id;
cmd.target_component = commandEntry.targetCompId;
cmd.command = commandEntry.command;
cmd.frame = commandEntry.frame;
cmd.param1 = commandEntry.rgParam1;
cmd.param2 = commandEntry.rgParam2;
cmd.param3 = commandEntry.rgParam3;
cmd.param4 = commandEntry.rgParam4;
cmd.x = commandEntry.frame == MAV_FRAME_MISSION ? commandEntry.rgParam5 : commandEntry.rgParam5 * 1e7;
cmd.y = commandEntry.frame == MAV_FRAME_MISSION ? commandEntry.rgParam6 : commandEntry.rgParam6 * 1e7;
cmd.z = commandEntry.rgParam7;
mavlink_msg_command_int_encode_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
&cmd);
} else {
mavlink_command_long_t cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.target_system = _id;
cmd.target_component = commandEntry.targetCompId;
cmd.command = commandEntry.command;
cmd.confirmation = 0;
cmd.param1 = commandEntry.rgParam1;
cmd.param2 = commandEntry.rgParam2;
cmd.param3 = commandEntry.rgParam3;
cmd.param4 = commandEntry.rgParam4;
cmd.param5 = static_cast<float>(commandEntry.rgParam5);
cmd.param6 = static_cast<float>(commandEntry.rgParam6);
cmd.param7 = commandEntry.rgParam7;
mavlink_msg_command_long_encode_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
&cmd);
}
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
void Vehicle::_sendMavCommandResponseTimeoutCheck(void)
{
if (_mavCommandList.isEmpty()) {
return;
}
// Walk the list backwards since _sendMavCommandFromList can remove entries
for (int i=_mavCommandList.count()-1; i>=0; i--) {
MavCommandListEntry_t& commandEntry = _mavCommandList[i];
if (commandEntry.elapsedTimer.elapsed() > commandEntry.ackTimeoutMSecs) {
// Try sending command again
_sendMavCommandFromList(i);
}
}
}
void Vehicle::_handleCommandAck(mavlink_message_t& message)
{
mavlink_command_ack_t ack;
mavlink_msg_command_ack_decode(&message, &ack);
QString rawCommandName =_toolbox->missionCommandTree()->rawName(static_cast<MAV_CMD>(ack.command));
qCDebug(VehicleLog) << QStringLiteral("_handleCommandAck command(%1) result(%2)").arg(rawCommandName).arg(QGCMAVLink::mavResultToString(static_cast<MAV_RESULT>(ack.result)));
if (ack.command == MAV_CMD_DO_SET_ROI_LOCATION) {
if (ack.result == MAV_RESULT_ACCEPTED) {
_isROIEnabled = true;
emit isROIEnabledChanged();
}
}
if (ack.command == MAV_CMD_DO_SET_ROI_NONE) {
if (ack.result == MAV_RESULT_ACCEPTED) {
_isROIEnabled = false;
emit isROIEnabledChanged();
}
}
if (ack.command == MAV_CMD_PREFLIGHT_STORAGE) {
auto result = (ack.result == MAV_RESULT_ACCEPTED);
emit sensorsParametersResetAck(result);
}
#if !defined(NO_ARDUPILOT_DIALECT)
if (ack.command == MAV_CMD_FLASH_BOOTLOADER && ack.result == MAV_RESULT_ACCEPTED) {
qgcApp()->showAppMessage(tr("Bootloader flash succeeded"));
}
#endif
int entryIndex = _findMavCommandListEntryIndex(message.compid, static_cast<MAV_CMD>(ack.command));
bool commandInList = false;
if (entryIndex != -1) {
MavCommandListEntry_t commandEntry = _mavCommandList.takeAt(entryIndex);
if (commandEntry.command == ack.command) {
if (commandEntry.resultHandler) {
(*commandEntry.resultHandler)(commandEntry.resultHandlerData, message.compid, static_cast<MAV_RESULT>(ack.result), ack.progress, MavCmdResultCommandResultOnly);
} else {
if (commandEntry.showError) {
switch (ack.result) {
case MAV_RESULT_TEMPORARILY_REJECTED:
qgcApp()->showAppMessage(tr("%1 command temporarily rejected").arg(rawCommandName));
break;
case MAV_RESULT_DENIED:
qgcApp()->showAppMessage(tr("%1 command denied").arg(rawCommandName));
break;
case MAV_RESULT_UNSUPPORTED:
qgcApp()->showAppMessage(tr("%1 command not supported").arg(rawCommandName));
break;
case MAV_RESULT_FAILED:
qgcApp()->showAppMessage(tr("%1 command failed").arg(rawCommandName));
break;
default:
// Do nothing
break;
}
}
emit mavCommandResult(_id, message.compid, ack.command, ack.result, MavCmdResultCommandResultOnly);
}
commandInList = true;
}
}
if (!commandInList) {
qCDebug(VehicleLog) << "_handleCommandAck Ack not in list" << rawCommandName;
}
// advance PID tuning setup/teardown
if (ack.command == MAV_CMD_SET_MESSAGE_INTERVAL) {
_mavlinkStreamConfig.gotSetMessageIntervalAck();
}
}
void Vehicle::_waitForMavlinkMessage(WaitForMavlinkMessageResultHandler resultHandler, void* resultHandlerData, int messageId, int timeoutMsecs)
{
qCDebug(VehicleLog) << "_waitForMavlinkMessage msg:timeout" << messageId << timeoutMsecs;
if (_waitForMavlinkMessageResultHandler) {
qCCritical(VehicleLog) << "_waitForMavlinkMessage: collision";
}
_waitForMavlinkMessageResultHandler = resultHandler;
_waitForMavlinkMessageResultHandlerData = resultHandlerData;
_waitForMavlinkMessageId = messageId;
_waitForMavlinkMessageTimeoutActive = false; // Timer doesn't start until ack is received from command request
_waitForMavlinkMessageTimeoutMsecs = timeoutMsecs;
}
void Vehicle::_waitForMavlinkMessageClear(void)
{
qCDebug(VehicleLog) << "_waitForMavlinkMessageClear";
_waitForMavlinkMessageResultHandler = nullptr;
_waitForMavlinkMessageResultHandlerData = nullptr;
_waitForMavlinkMessageId = 0;
_waitForMavlinkMessageTimeoutActive = false;
}
void Vehicle::_waitForMavlinkMessageMessageReceived(const mavlink_message_t& message)
{
if (_waitForMavlinkMessageId != 0) {
if (_waitForMavlinkMessageId == message.msgid) {
WaitForMavlinkMessageResultHandler resultHandler = _waitForMavlinkMessageResultHandler;
void* resultHandlerData = _waitForMavlinkMessageResultHandlerData;
qCDebug(VehicleLog) << "_waitForMavlinkMessageMessageReceived message received" << _waitForMavlinkMessageId;
_waitForMavlinkMessageClear();
(*resultHandler)(resultHandlerData, false /* noResponseFromVehicle */, message);
} else if (_waitForMavlinkMessageTimeoutActive && _waitForMavlinkMessageElapsed.elapsed() > _waitForMavlinkMessageTimeoutMsecs) {
WaitForMavlinkMessageResultHandler resultHandler = _waitForMavlinkMessageResultHandler;
void* resultHandlerData = _waitForMavlinkMessageResultHandlerData;
qCDebug(VehicleLog) << "_waitForMavlinkMessageMessageReceived message timed out" << _waitForMavlinkMessageId;
_waitForMavlinkMessageClear();
(*resultHandler)(resultHandlerData, true /* noResponseFromVehicle */, message);
}
}
}
void Vehicle::requestMessage(RequestMessageResultHandler resultHandler, void* resultHandlerData, int compId, int messageId, float param1, float param2, float param3, float param4, float param5)
{
RequestMessageInfo_t* pInfo = new RequestMessageInfo_t;
*pInfo = { };
pInfo->vehicle = this;
pInfo->msgId = messageId;
pInfo->compId = compId;
pInfo->resultHandler = resultHandler;
pInfo->resultHandlerData = resultHandlerData;
_waitForMavlinkMessage(_requestMessageWaitForMessageResultHandler, pInfo, pInfo->msgId, 1000);
_sendMavCommandWorker(false, // commandInt
false, // showError
_requestMessageCmdResultHandler,
pInfo, // resultHandlerData
compId,
MAV_CMD_REQUEST_MESSAGE,
MAV_FRAME_GLOBAL,
messageId,
param1, param2, param3, param4, param5, 0);
}
void Vehicle::_requestMessageCmdResultHandler(void* resultHandlerData, int /*compId*/, MAV_RESULT result, uint8_t progress, MavCmdResultFailureCode_t failureCode)
{
RequestMessageInfo_t* pInfo = static_cast<RequestMessageInfo_t*>(resultHandlerData);
Vehicle* vehicle = pInfo->vehicle;
pInfo->commandAckReceived = true;
if (result != MAV_RESULT_ACCEPTED) {
mavlink_message_t message;
RequestMessageResultHandlerFailureCode_t requestMessageFailureCode;
switch (failureCode) {
case Vehicle::MavCmdResultCommandResultOnly:
requestMessageFailureCode = RequestMessageFailureCommandError;
break;
case Vehicle::MavCmdResultFailureNoResponseToCommand:
requestMessageFailureCode = RequestMessageFailureCommandNotAcked;
break;
case Vehicle::MavCmdResultFailureDuplicateCommand:
requestMessageFailureCode = RequestMessageFailureDuplicateCommand;
break;
}
vehicle->_waitForMavlinkMessageClear();
(*pInfo->resultHandler)(pInfo->resultHandlerData, result, requestMessageFailureCode, message);
return;
}
if (pInfo->messageReceived) {
(*pInfo->resultHandler)(pInfo->resultHandlerData, result, RequestMessageNoFailure, pInfo->message);
delete pInfo;
} else {
vehicle->_waitForMavlinkMessageTimeoutActive = true;
vehicle->_waitForMavlinkMessageElapsed.restart();
}
}
void Vehicle::_requestMessageWaitForMessageResultHandler(void* resultHandlerData, bool noResponsefromVehicle, const mavlink_message_t& message)
{
RequestMessageInfo_t* pInfo = static_cast<RequestMessageInfo_t*>(resultHandlerData);
pInfo->messageReceived = true;
if (pInfo->commandAckReceived) {
(*pInfo->resultHandler)(pInfo->resultHandlerData, noResponsefromVehicle ? MAV_RESULT_FAILED : MAV_RESULT_ACCEPTED, noResponsefromVehicle ? RequestMessageFailureMessageNotReceived : RequestMessageNoFailure, message);
} else {
// Result handler will be called when we get the Ack
pInfo->message = message;
}
}
void Vehicle::setPrearmError(const QString& prearmError)
{
_prearmError = prearmError;
emit prearmErrorChanged(_prearmError);
if (!_prearmError.isEmpty()) {
_prearmErrorTimer.start();
}
}
void Vehicle::_prearmErrorTimeout()
{
setPrearmError(QString());
}
void Vehicle::setFirmwareVersion(int majorVersion, int minorVersion, int patchVersion, FIRMWARE_VERSION_TYPE versionType)
{
_firmwareMajorVersion = majorVersion;
_firmwareMinorVersion = minorVersion;
_firmwarePatchVersion = patchVersion;
_firmwareVersionType = versionType;
emit firmwareVersionChanged();
}
void Vehicle::setFirmwareCustomVersion(int majorVersion, int minorVersion, int patchVersion)
{
_firmwareCustomMajorVersion = majorVersion;
_firmwareCustomMinorVersion = minorVersion;
_firmwareCustomPatchVersion = patchVersion;
emit firmwareCustomVersionChanged();
}
QString Vehicle::firmwareVersionTypeString() const
{
switch (_firmwareVersionType) {
case FIRMWARE_VERSION_TYPE_DEV:
return QStringLiteral("dev");
case FIRMWARE_VERSION_TYPE_ALPHA:
return QStringLiteral("alpha");
case FIRMWARE_VERSION_TYPE_BETA:
return QStringLiteral("beta");
case FIRMWARE_VERSION_TYPE_RC:
return QStringLiteral("rc");
case FIRMWARE_VERSION_TYPE_OFFICIAL:
default:
return QStringLiteral("");
}
}
void Vehicle::_rebootCommandResultHandler(void* resultHandlerData, int /*compId*/, MAV_RESULT commandResult, uint8_t progress, MavCmdResultFailureCode_t failureCode)
{
Q_UNUSED(progress)
Vehicle* vehicle = static_cast<Vehicle*>(resultHandlerData);
if (commandResult != MAV_RESULT_ACCEPTED) {
switch (failureCode) {
case MavCmdResultCommandResultOnly:
qCDebug(VehicleLog) << QStringLiteral("MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN error(%1)").arg(commandResult);
break;
case MavCmdResultFailureNoResponseToCommand:
qCDebug(VehicleLog) << "MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN failed: no response from vehicle";
break;
case MavCmdResultFailureDuplicateCommand:
qCDebug(VehicleLog) << "MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN failed: duplicate command";
break;
}
qgcApp()->showAppMessage(tr("Vehicle reboot failed."));
} else {
vehicle->closeVehicle();
}
}
void Vehicle::rebootVehicle()
{
sendMavCommandWithHandler(_rebootCommandResultHandler, this, _defaultComponentId, MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN, 1);
}
void Vehicle::startCalibration(Vehicle::CalibrationType calType)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "startCalibration: primary link gone!";
return;
}
float param1 = 0;
float param2 = 0;
float param3 = 0;
float param4 = 0;
float param5 = 0;
float param6 = 0;
float param7 = 0;
switch (calType) {
case CalibrationGyro:
param1 = 1;
break;
case CalibrationMag:
param2 = 1;
break;
case CalibrationRadio:
param4 = 1;
break;
case CalibrationCopyTrims:
param4 = 2;
break;
case CalibrationAccel:
param5 = 1;
break;
case CalibrationLevel:
param5 = 2;
break;
case CalibrationEsc:
param7 = 1;
break;
case CalibrationPX4Airspeed:
param6 = 1;
break;
case CalibrationPX4Pressure:
param3 = 1;
break;
case CalibrationAPMCompassMot:
param6 = 1;
break;
case CalibrationAPMPressureAirspeed:
param3 = 1;
break;
case CalibrationAPMPreFlight:
param3 = 1; // GroundPressure/Airspeed
if (multiRotor() || rover()) {
// Gyro cal for ArduCopter only
param1 = 1;
}
case CalibrationAPMAccelSimple:
param5 = 4;
break;
}
// We can't use sendMavCommand here since we have no idea how long it will be before the command returns a result. This in turn
// causes the retry logic to break down.
mavlink_message_t msg;
mavlink_msg_command_long_pack_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
id(),
defaultComponentId(), // target component
MAV_CMD_PREFLIGHT_CALIBRATION, // command id
0, // 0=first transmission of command
param1, param2, param3, param4, param5, param6, param7);
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
void Vehicle::stopCalibration(bool showError)
{
sendMavCommand(defaultComponentId(), // target component
MAV_CMD_PREFLIGHT_CALIBRATION, // command id
showError,
0, // gyro cal
0, // mag cal
0, // ground pressure
0, // radio cal
0, // accel cal
0, // airspeed cal
0); // unused
}
void Vehicle::startUAVCANBusConfig(void)
{
sendMavCommand(defaultComponentId(), // target component
MAV_CMD_PREFLIGHT_UAVCAN, // command id
true, // showError
1); // start config
}
void Vehicle::stopUAVCANBusConfig(void)
{
sendMavCommand(defaultComponentId(), // target component
MAV_CMD_PREFLIGHT_UAVCAN, // command id
true, // showError
0); // stop config
}
void Vehicle::setSoloFirmware(bool soloFirmware)
{
if (soloFirmware != _soloFirmware) {
_soloFirmware = soloFirmware;
emit soloFirmwareChanged(soloFirmware);
}
}
void Vehicle::motorTest(int motor, int percent, int timeoutSecs, bool showError)
{
sendMavCommand(_defaultComponentId, MAV_CMD_DO_MOTOR_TEST, showError, motor, MOTOR_TEST_THROTTLE_PERCENT, percent, timeoutSecs, 0, MOTOR_TEST_ORDER_BOARD);
}
QString Vehicle::brandImageIndoor() const
{
return _firmwarePlugin->brandImageIndoor(this);
}
QString Vehicle::brandImageOutdoor() const
{
return _firmwarePlugin->brandImageOutdoor(this);
}
void Vehicle::setOfflineEditingDefaultComponentId(int defaultComponentId)
{
if (_offlineEditingVehicle) {
_defaultComponentId = defaultComponentId;
} else {
qCWarning(VehicleLog) << "Call to Vehicle::setOfflineEditingDefaultComponentId on vehicle which is not offline";
}
}
void Vehicle::setVtolInFwdFlight(bool vtolInFwdFlight)
{
if (_vtolInFwdFlight != vtolInFwdFlight) {
sendMavCommand(_defaultComponentId,
MAV_CMD_DO_VTOL_TRANSITION,
true, // show errors
vtolInFwdFlight ? MAV_VTOL_STATE_FW : MAV_VTOL_STATE_MC, // transition state
0, 0, 0, 0, 0, 0); // param 2-7 unused
}
}
void Vehicle::startMavlinkLog()
{
sendMavCommand(_defaultComponentId, MAV_CMD_LOGGING_START, false /* showError */);
}
void Vehicle::stopMavlinkLog()
{
sendMavCommand(_defaultComponentId, MAV_CMD_LOGGING_STOP, false /* showError */);
}
void Vehicle::_ackMavlinkLogData(uint16_t sequence)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "_ackMavlinkLogData: primary link gone!";
return;
}
mavlink_message_t msg;
mavlink_logging_ack_t ack;
memset(&ack, 0, sizeof(ack));
ack.sequence = sequence;
ack.target_component = _defaultComponentId;
ack.target_system = id();
mavlink_msg_logging_ack_encode_chan(
_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
&ack);
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
void Vehicle::_handleMavlinkLoggingData(mavlink_message_t& message)
{
mavlink_logging_data_t log;
mavlink_msg_logging_data_decode(&message, &log);
if (static_cast<size_t>(log.length) > sizeof(log.data)) {
qWarning() << "Invalid length for LOGGING_DATA, discarding." << log.length;
} else {
emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence,
log.first_message_offset, QByteArray((const char*)log.data, log.length), false);
}
}
void Vehicle::_handleMavlinkLoggingDataAcked(mavlink_message_t& message)
{
mavlink_logging_data_acked_t log;
mavlink_msg_logging_data_acked_decode(&message, &log);
_ackMavlinkLogData(log.sequence);
if (static_cast<size_t>(log.length) > sizeof(log.data)) {
qWarning() << "Invalid length for LOGGING_DATA_ACKED, discarding." << log.length;
} else {
emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence,
log.first_message_offset, QByteArray((const char*)log.data, log.length), false);
}
}
void Vehicle::setFirmwarePluginInstanceData(QObject* firmwarePluginInstanceData)
{
firmwarePluginInstanceData->setParent(this);
_firmwarePluginInstanceData = firmwarePluginInstanceData;
}
QString Vehicle::missionFlightMode() const
{
return _firmwarePlugin->missionFlightMode();
}
QString Vehicle::pauseFlightMode() const
{
return _firmwarePlugin->pauseFlightMode();
}
QString Vehicle::rtlFlightMode() const
{
return _firmwarePlugin->rtlFlightMode();
}
QString Vehicle::smartRTLFlightMode() const
{
return _firmwarePlugin->smartRTLFlightMode();
}
bool Vehicle::supportsSmartRTL() const
{
return _firmwarePlugin->supportsSmartRTL();
}
QString Vehicle::landFlightMode() const
{
return _firmwarePlugin->landFlightMode();
}
QString Vehicle::takeControlFlightMode() const
{
return _firmwarePlugin->takeControlFlightMode();
}
QString Vehicle::followFlightMode() const
{
return _firmwarePlugin->followFlightMode();
}
QString Vehicle::vehicleImageOpaque() const
{
if(_firmwarePlugin)
return _firmwarePlugin->vehicleImageOpaque(this);
else
return QString();
}
QString Vehicle::vehicleImageOutline() const
{
if(_firmwarePlugin)
return _firmwarePlugin->vehicleImageOutline(this);
else
return QString();
}
QString Vehicle::vehicleImageCompass() const
{
if(_firmwarePlugin)
return _firmwarePlugin->vehicleImageCompass(this);
else
return QString();
}
const QVariantList& Vehicle::toolIndicators()
{
if(_firmwarePlugin) {
return _firmwarePlugin->toolIndicators(this);
}
static QVariantList emptyList;
return emptyList;
}
const QVariantList& Vehicle::modeIndicators()
{
if(_firmwarePlugin) {
return _firmwarePlugin->modeIndicators(this);
}
static QVariantList emptyList;
return emptyList;
}
const QVariantList& Vehicle::staticCameraList() const
{
if (_firmwarePlugin) {
return _firmwarePlugin->cameraList(this);
}
static QVariantList emptyList;
return emptyList;
}
void Vehicle::_setupAutoDisarmSignalling()
{
QString param = _firmwarePlugin->autoDisarmParameter(this);
if (!param.isEmpty() && _parameterManager->parameterExists(FactSystem::defaultComponentId, param)) {
Fact* fact = _parameterManager->getParameter(FactSystem::defaultComponentId,param);
connect(fact, &Fact::rawValueChanged, this, &Vehicle::autoDisarmChanged);
emit autoDisarmChanged();
}
}
bool Vehicle::autoDisarm()
{
QString param = _firmwarePlugin->autoDisarmParameter(this);
if (!param.isEmpty() && _parameterManager->parameterExists(FactSystem::defaultComponentId, param)) {
Fact* fact = _parameterManager->getParameter(FactSystem::defaultComponentId,param);
return fact->rawValue().toDouble() > 0;
}
return false;
}
void Vehicle::_handleADSBVehicle(const mavlink_message_t& message)
{
mavlink_adsb_vehicle_t adsbVehicleMsg;
static const int maxTimeSinceLastSeen = 15;
mavlink_msg_adsb_vehicle_decode(&message, &adsbVehicleMsg);
if ((adsbVehicleMsg.flags & ADSB_FLAGS_VALID_COORDS) && adsbVehicleMsg.tslc <= maxTimeSinceLastSeen) {
ADSBVehicle::ADSBVehicleInfo_t vehicleInfo;
vehicleInfo.availableFlags = 0;
vehicleInfo.icaoAddress = adsbVehicleMsg.ICAO_address;
vehicleInfo.location.setLatitude(adsbVehicleMsg.lat / 1e7);
vehicleInfo.location.setLongitude(adsbVehicleMsg.lon / 1e7);
vehicleInfo.availableFlags |= ADSBVehicle::LocationAvailable;
vehicleInfo.callsign = adsbVehicleMsg.callsign;
vehicleInfo.availableFlags |= ADSBVehicle::CallsignAvailable;
if (adsbVehicleMsg.flags & ADSB_FLAGS_VALID_ALTITUDE) {
vehicleInfo.altitude = (double)adsbVehicleMsg.altitude / 1e3;
vehicleInfo.availableFlags |= ADSBVehicle::AltitudeAvailable;
}
if (adsbVehicleMsg.flags & ADSB_FLAGS_VALID_HEADING) {
vehicleInfo.heading = (double)adsbVehicleMsg.heading / 100.0;
vehicleInfo.availableFlags |= ADSBVehicle::HeadingAvailable;
}
_toolbox->adsbVehicleManager()->adsbVehicleUpdate(vehicleInfo);
}
}
void Vehicle::_updateDistanceHeadingToHome()
{
if (coordinate().isValid() && homePosition().isValid()) {
_distanceToHomeFact.setRawValue(coordinate().distanceTo(homePosition()));
if (_distanceToHomeFact.rawValue().toDouble() > 1.0) {
_headingToHomeFact.setRawValue(coordinate().azimuthTo(homePosition()));
} else {
_headingToHomeFact.setRawValue(qQNaN());
}
} else {
_distanceToHomeFact.setRawValue(qQNaN());
_headingToHomeFact.setRawValue(qQNaN());
}
}
void Vehicle::_updateHeadingToNextWP()
{
const int currentIndex = _missionManager->currentIndex();
QList<MissionItem*> llist = _missionManager->missionItems();
if(llist.size()>currentIndex && currentIndex!=-1
&& llist[currentIndex]->coordinate().longitude()!=0.0
&& coordinate().distanceTo(llist[currentIndex]->coordinate())>5.0 ){
_headingToNextWPFact.setRawValue(coordinate().azimuthTo(llist[currentIndex]->coordinate()));
}
else{
_headingToNextWPFact.setRawValue(qQNaN());
}
}
void Vehicle::_updateMissionItemIndex()
{
const int currentIndex = _missionManager->currentIndex();
unsigned offset = 0;
if (!_firmwarePlugin->sendHomePositionToVehicle()) {
offset = 1;
}
_missionItemIndexFact.setRawValue(currentIndex + offset);
}
void Vehicle::_updateDistanceToGCS()
{
QGeoCoordinate gcsPosition = _toolbox->qgcPositionManager()->gcsPosition();
if (coordinate().isValid() && gcsPosition.isValid()) {
_distanceToGCSFact.setRawValue(coordinate().distanceTo(gcsPosition));
} else {
_distanceToGCSFact.setRawValue(qQNaN());
}
}
void Vehicle::_updateHomepoint()
{
const bool setHomeCmdSupported = firmwarePlugin()->supportedMissionCommands(vehicleClass()).contains(MAV_CMD_DO_SET_HOME);
const bool updateHomeActivated = _toolbox->settingsManager()->flyViewSettings()->updateHomePosition()->rawValue().toBool();
if(setHomeCmdSupported && updateHomeActivated){
QGeoCoordinate gcsPosition = _toolbox->qgcPositionManager()->gcsPosition();
if (coordinate().isValid() && gcsPosition.isValid()) {
sendMavCommand(defaultComponentId(),
MAV_CMD_DO_SET_HOME, false,
0,
0, 0, 0,
static_cast<float>(gcsPosition.latitude()) ,
static_cast<float>(gcsPosition.longitude()),
static_cast<float>(gcsPosition.altitude()));
}
}
}
void Vehicle::_updateHobbsMeter()
{
_hobbsFact.setRawValue(hobbsMeter());
}
void Vehicle::forceInitialPlanRequestComplete()
{
_initialPlanRequestComplete = true;
emit initialPlanRequestCompleteChanged(true);
}
void Vehicle::sendPlan(QString planFile)
{
PlanMasterController::sendPlanToVehicle(this, planFile);
}
QString Vehicle::hobbsMeter()
{
return _firmwarePlugin->getHobbsMeter(this);
}
void Vehicle::_vehicleParamLoaded(bool ready)
{
//-- TODO: This seems silly but can you think of a better
// way to update this?
if(ready) {
emit hobbsMeterChanged();
}
}
void Vehicle::_trafficUpdate(bool /*alert*/, QString /*traffic_id*/, QString /*vehicle_id*/, QGeoCoordinate /*location*/, float /*heading*/)
{
#if 0
// This is ifdef'ed out for now since this code doesn't mesh with the recent ADSB manager changes. Also airmap isn't in any
// released build. So not going to waste time trying to fix up unused code.
if (_trafficVehicleMap.contains(traffic_id)) {
_trafficVehicleMap[traffic_id]->update(alert, location, heading);
} else {
ADSBVehicle* vehicle = new ADSBVehicle(location, heading, alert, this);
_trafficVehicleMap[traffic_id] = vehicle;
_adsbVehicles.append(vehicle);
}
#endif
}
void Vehicle::_mavlinkMessageStatus(int uasId, uint64_t totalSent, uint64_t totalReceived, uint64_t totalLoss, float lossPercent)
{
if(uasId == _id) {
_mavlinkSentCount = totalSent;
_mavlinkReceivedCount = totalReceived;
_mavlinkLossCount = totalLoss;
_mavlinkLossPercent = lossPercent;
emit mavlinkStatusChanged();
}
}
int Vehicle::versionCompare(QString& compare)
{
return _firmwarePlugin->versionCompare(this, compare);
}
int Vehicle::versionCompare(int major, int minor, int patch)
{
return _firmwarePlugin->versionCompare(this, major, minor, patch);
}
void Vehicle::setPIDTuningTelemetryMode(PIDTuningTelemetryMode mode)
{
bool liveUpdate = mode != ModeDisabled;
setLiveUpdates(liveUpdate);
_setpointFactGroup.setLiveUpdates(liveUpdate);
_localPositionFactGroup.setLiveUpdates(liveUpdate);
_localPositionSetpointFactGroup.setLiveUpdates(liveUpdate);
switch (mode) {
case ModeDisabled:
_mavlinkStreamConfig.restoreDefaults();
break;
case ModeRateAndAttitude:
_mavlinkStreamConfig.setHighRateRateAndAttitude();
break;
case ModeVelocityAndPosition:
_mavlinkStreamConfig.setHighRateVelAndPos();
break;
case ModeAltitudeAndAirspeed:
_mavlinkStreamConfig.setHighRateAltAirspeed();
// reset the altitude offset to the current value, so the plotted value is around 0
if (!qIsNaN(_altitudeTuningOffset)) {
_altitudeTuningOffset += _altitudeTuningFact.rawValue().toDouble();
_altitudeTuningSetpointFact.setRawValue(0.f);
_altitudeTuningFact.setRawValue(0.f);
}
break;
}
}
void Vehicle::_setMessageInterval(int messageId, int rate)
{
sendMavCommand(defaultComponentId(),
MAV_CMD_SET_MESSAGE_INTERVAL,
true, // show error
messageId,
rate);
}
bool Vehicle::isInitialConnectComplete() const
{
return !_initialConnectStateMachine->active();
}
void Vehicle::_initializeCsv()
{
if(!_toolbox->settingsManager()->appSettings()->saveCsvTelemetry()->rawValue().toBool()){
return;
}
QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss");
QString fileName = QString("%1 vehicle%2.csv").arg(now).arg(_id);
QDir saveDir(_toolbox->settingsManager()->appSettings()->telemetrySavePath());
_csvLogFile.setFileName(saveDir.absoluteFilePath(fileName));
if (!_csvLogFile.open(QIODevice::Append)) {
qCWarning(VehicleLog) << "unable to open file for csv logging, Stopping csv logging!";
return;
}
QTextStream stream(&_csvLogFile);
QStringList allFactNames;
allFactNames << factNames();
for (const QString& groupName: factGroupNames()) {
for(const QString& factName: getFactGroup(groupName)->factNames()){
allFactNames << QString("%1.%2").arg(groupName, factName);
}
}
qCDebug(VehicleLog) << "Facts logged to csv:" << allFactNames;
stream << "Timestamp," << allFactNames.join(",") << "\n";
}
void Vehicle::_writeCsvLine()
{
// Only save the logs after the the vehicle gets armed, unless "Save logs even if vehicle was not armed" is checked
if(!_csvLogFile.isOpen() &&
(_armed || _toolbox->settingsManager()->appSettings()->telemetrySaveNotArmed()->rawValue().toBool())){
_initializeCsv();
}
if(!_csvLogFile.isOpen()){
return;
}
QStringList allFactValues;
QTextStream stream(&_csvLogFile);
// Write timestamp to csv file
allFactValues << QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz"));
// Write Vehicle's own facts
for (const QString& factName : factNames()) {
allFactValues << getFact(factName)->cookedValueString();
}
// write facts from Vehicle's FactGroups
for (const QString& groupName: factGroupNames()) {
for (const QString& factName : getFactGroup(groupName)->factNames()) {
allFactValues << getFactGroup(groupName)->getFact(factName)->cookedValueString();
}
}
stream << allFactValues.join(",") << "\n";
}
#if !defined(NO_ARDUPILOT_DIALECT)
void Vehicle::flashBootloader()
{
sendMavCommand(defaultComponentId(),
MAV_CMD_FLASH_BOOTLOADER,
true, // show error
0, 0, 0, 0, // param 1-4 not used
290876); // magic number
}
#endif
void Vehicle::gimbalControlValue(double pitch, double yaw)
{
if (apmFirmware()) {
// ArduPilot firmware treats this values as centi-degrees
pitch *= 100;
yaw *= 100;
}
//qDebug() << "Gimbal:" << pitch << yaw;
sendMavCommand(
_defaultComponentId,
MAV_CMD_DO_MOUNT_CONTROL,
false, // show errors
static_cast<float>(pitch), // Pitch 0 - 90
0, // Roll (not used)
static_cast<float>(yaw), // Yaw -180 - 180
0, // Altitude (not used)
0, // Latitude (not used)
0, // Longitude (not used)
MAV_MOUNT_MODE_MAVLINK_TARGETING); // MAVLink Roll,Pitch,Yaw
}
void Vehicle::gimbalPitchStep(int direction)
{
if(_haveGimbalData) {
//qDebug() << "Pitch:" << _curGimbalPitch << direction << (_curGimbalPitch + direction);
double p = static_cast<double>(_curGimbalPitch + direction);
gimbalControlValue(p, static_cast<double>(_curGimbalYaw));
}
}
void Vehicle::gimbalYawStep(int direction)
{
if(_haveGimbalData) {
//qDebug() << "Yaw:" << _curGimbalYaw << direction << (_curGimbalYaw + direction);
double y = static_cast<double>(_curGimbalYaw + direction);
gimbalControlValue(static_cast<double>(_curGimbalPitch), y);
}
}
void Vehicle::centerGimbal()
{
if(_haveGimbalData) {
gimbalControlValue(0.0, 0.0);
}
}
void Vehicle::_handleGimbalOrientation(const mavlink_message_t& message)
{
mavlink_mount_orientation_t o;
mavlink_msg_mount_orientation_decode(&message, &o);
if(fabsf(_curGimbalRoll - o.roll) > 0.5f) {
_curGimbalRoll = o.roll;
emit gimbalRollChanged();
}
if(fabsf(_curGimbalPitch - o.pitch) > 0.5f) {
_curGimbalPitch = o.pitch;
emit gimbalPitchChanged();
}
if(fabsf(_curGimbalYaw - o.yaw) > 0.5f) {
_curGimbalYaw = o.yaw;
emit gimbalYawChanged();
}
if(!_haveGimbalData) {
_haveGimbalData = true;
emit gimbalDataChanged();
}
}
void Vehicle::_handleObstacleDistance(const mavlink_message_t& message)
{
mavlink_obstacle_distance_t o;
mavlink_msg_obstacle_distance_decode(&message, &o);
_objectAvoidance->update(&o);
}
void Vehicle::updateFlightDistance(double distance)
{
_flightDistanceFact.setRawValue(_flightDistanceFact.rawValue().toDouble() + distance);
}
void Vehicle::sendParamMapRC(const QString& paramName, double scale, double centerValue, int tuningID, double minValue, double maxValue)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "sendParamMapRC: primary link gone!";
return;
}
mavlink_message_t message;
char param_id_cstr[MAVLINK_MSG_PARAM_MAP_RC_FIELD_PARAM_ID_LEN] = {};
// Copy string into buffer, ensuring not to exceed the buffer size
for (unsigned int i = 0; i < sizeof(param_id_cstr); i++) {
if ((int)i < paramName.length()) {
param_id_cstr[i] = paramName.toLatin1()[i];
}
}
mavlink_msg_param_map_rc_pack_chan(static_cast<uint8_t>(_mavlink->getSystemId()),
static_cast<uint8_t>(_mavlink->getComponentId()),
sharedLink->mavlinkChannel(),
&message,
_id,
MAV_COMP_ID_AUTOPILOT1,
param_id_cstr,
-1, // parameter name specified as string in previous argument
static_cast<uint8_t>(tuningID),
static_cast<float>(centerValue),
static_cast<float>(scale),
static_cast<float>(minValue),
static_cast<float>(maxValue));
sendMessageOnLinkThreadSafe(sharedLink.get(), message);
}
void Vehicle::clearAllParamMapRC(void)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog)<< "clearAllParamMapRC: primary link gone!";
return;
}
char param_id_cstr[MAVLINK_MSG_PARAM_MAP_RC_FIELD_PARAM_ID_LEN] = {};
for (int i = 0; i < 3; i++) {
mavlink_message_t message;
mavlink_msg_param_map_rc_pack_chan(static_cast<uint8_t>(_mavlink->getSystemId()),
static_cast<uint8_t>(_mavlink->getComponentId()),
sharedLink->mavlinkChannel(),
&message,
_id,
MAV_COMP_ID_AUTOPILOT1,
param_id_cstr,
-2, // Disable map for specified tuning id
i, // tuning id
0, 0, 0, 0); // unused
sendMessageOnLinkThreadSafe(sharedLink.get(), message);
}
}
void Vehicle::sendJoystickDataThreadSafe(float roll, float pitch, float yaw, float thrust, quint16 buttons)
{
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog)<< "sendJoystickDataThreadSafe: primary link gone!";
return;
}
if (sharedLink->linkConfiguration()->isHighLatency()) {
return;
}
mavlink_message_t message;
// Incoming values are in the range -1:1
float axesScaling = 1.0 * 1000.0;
float newRollCommand = roll * axesScaling;
float newPitchCommand = pitch * axesScaling; // Joystick data is reverse of mavlink values
float newYawCommand = yaw * axesScaling;
float newThrustCommand = thrust * axesScaling;
mavlink_msg_manual_control_pack_chan(
static_cast<uint8_t>(_mavlink->getSystemId()),
static_cast<uint8_t>(_mavlink->getComponentId()),
sharedLink->mavlinkChannel(),
&message,
static_cast<uint8_t>(_id),
static_cast<int16_t>(newPitchCommand),
static_cast<int16_t>(newRollCommand),
static_cast<int16_t>(newThrustCommand),
static_cast<int16_t>(newYawCommand),
buttons,
0, 0, 0, 0);
sendMessageOnLinkThreadSafe(sharedLink.get(), message);
}
void Vehicle::triggerSimpleCamera()
{
sendMavCommand(_defaultComponentId,
MAV_CMD_DO_DIGICAM_CONTROL,
true, // show errors
0.0, 0.0, 0.0, 0.0, // param 1-4 unused
1.0); // trigger camera
}
void Vehicle::setGripperAction(GRIPPER_ACTIONS gripperAction)
{
sendMavCommand(
_defaultComponentId,
MAV_CMD_DO_GRIPPER,
false, // Don't show errors
0, // Param1: Gripper ID (Always set to 0)
gripperAction, // Param2: Gripper Action
0, 0, 0, 0, 0); // Param 3 ~ 7 : unused
}