Skip to content
Snippets Groups Projects
Vehicle.cc 91.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • /****************************************************************************
     *
     *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
     *
     * QGroundControl is licensed according to the terms in the file
     * COPYING.md in the root of the source code directory.
     *
     ****************************************************************************/
    
    
    #include "Vehicle.h"
    #include "MAVLinkProtocol.h"
    #include "FirmwarePluginManager.h"
    #include "LinkManager.h"
    #include "FirmwarePlugin.h"
    
    #include "JoystickManager.h"
    
    Don Gagne's avatar
    Don Gagne committed
    #include "MissionManager.h"
    
    Don Gagne's avatar
    Don Gagne committed
    #include "MissionController.h"
    
    #include "PlanMasterController.h"
    
    #include "GeoFenceManager.h"
    #include "RallyPointManager.h"
    
    #include "CoordinateVector.h"
    
    #include "ParameterManager.h"
    
    #include "QGCApplication.h"
    
    dogmaphobic's avatar
    dogmaphobic committed
    #include "QGCImageProvider.h"
    
    #include "GAudioOutput.h"
    
    Jimmy Johnson's avatar
    Jimmy Johnson committed
    #include "FollowMe.h"
    
    #include "MissionCommandTree.h"
    
    #include "QGroundControlQmlGlobal.h"
    
    #include "SettingsManager.h"
    
    #include "QGCQGeoCoordinate.h"
    
    
    QGC_LOGGING_CATEGORY(VehicleLog, "VehicleLog")
    
    
    #define UPDATE_TIMER 50
    #define DEFAULT_LAT  38.965767f
    #define DEFAULT_LON -120.083923f
    
    
    extern const char* guided_mode_not_supported_by_vehicle;
    
    
    const char* Vehicle::_settingsGroup =               "Vehicle%1";        // %1 replaced with mavlink system id
    const char* Vehicle::_joystickModeSettingsKey =     "JoystickMode";
    const char* Vehicle::_joystickEnabledSettingsKey =  "JoystickEnabled";
    
    Don Gagne's avatar
    Don Gagne committed
    const char* Vehicle::_rollFactName =                "roll";
    const char* Vehicle::_pitchFactName =               "pitch";
    const char* Vehicle::_headingFactName =             "heading";
    const char* Vehicle::_airSpeedFactName =            "airSpeed";
    const char* Vehicle::_groundSpeedFactName =         "groundSpeed";
    const char* Vehicle::_climbRateFactName =           "climbRate";
    const char* Vehicle::_altitudeRelativeFactName =    "altitudeRelative";
    const char* Vehicle::_altitudeAMSLFactName =        "altitudeAMSL";
    
    const char* Vehicle::_flightDistanceFactName =      "flightDistance";
    
    Don Gagne's avatar
    Don Gagne committed
    
    const char* Vehicle::_gpsFactGroupName =        "gps";
    const char* Vehicle::_batteryFactGroupName =    "battery";
    
    Don Gagne's avatar
    Don Gagne committed
    const char* Vehicle::_windFactGroupName =       "wind";
    
    const char* Vehicle::_vibrationFactGroupName =  "vibration";
    
    const char* Vehicle::_temperatureFactGroupName = "temperature";
    
    Vehicle::Vehicle(LinkInterface*             link,
                     int                        vehicleId,
    
                     int                        defaultComponentId,
    
                     MAV_AUTOPILOT              firmwareType,
                     MAV_TYPE                   vehicleType,
                     FirmwarePluginManager*     firmwarePluginManager,
                     JoystickManager*           joystickManager)
    
    Don Gagne's avatar
    Don Gagne committed
        : FactGroup(_vehicleUIUpdateRateMSecs, ":/json/Vehicle/VehicleFact.json")
        , _id(vehicleId)
    
        , _defaultComponentId(defaultComponentId)
    
        , _active(false)
    
        , _offlineEditingVehicle(false)
    
        , _firmwareType(firmwareType)
    
        , _vehicleType(vehicleType)
    
        , _firmwarePlugin(NULL)
    
        , _firmwarePluginInstanceData(NULL)
    
        , _autopilotPlugin(NULL)
    
        , _mavlink(NULL)
        , _soloFirmware(false)
    
        , _settingsManager(qgcApp()->toolbox()->settingsManager())
    
        , _joystickMode(JoystickModeRC)
    
        , _joystickEnabled(false)
    
        , _uas(NULL)
    
        , _mav(NULL)
        , _currentMessageCount(0)
        , _messageCount(0)
        , _currentErrorCount(0)
        , _currentWarningCount(0)
        , _currentNormalCount(0)
        , _currentMessageType(MessageNone)
        , _updateCount(0)
    
        , _rcRSSI(255)
        , _rcRSSIstore(255)
    
    Don Gagne's avatar
    Don Gagne committed
        , _autoDisconnect(false)
    
    Don Gagne's avatar
    Don Gagne committed
        , _flying(false)
    
        , _onboardControlSensorsPresent(0)
        , _onboardControlSensorsEnabled(0)
        , _onboardControlSensorsHealth(0)
        , _onboardControlSensorsUnhealthy(0)
    
        , _gpsRawIntMessageAvailable(false)
        , _globalPositionIntMessageAvailable(false)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        , _defaultCruiseSpeed(_settingsManager->appSettings()->offlineEditingCruiseSpeed()->rawValue().toDouble())
        , _defaultHoverSpeed(_settingsManager->appSettings()->offlineEditingHoverSpeed()->rawValue().toDouble())
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _telemetryRRSSI(0)
        , _telemetryLRSSI(0)
        , _telemetryRXErrors(0)
        , _telemetryFixed(0)
        , _telemetryTXBuffer(0)
        , _telemetryLNoise(0)
        , _telemetryRNoise(0)
    
        , _vehicleCapabilitiesKnown(false)
        , _supportsMissionItemInt(false)
    
        , _connectionLost(false)
        , _connectionLostEnabled(true)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        , _initialPlanRequestComplete(false)
    
        , _missionManager(NULL)
    
        , _missionManagerInitialRequestSent(false)
    
        , _geoFenceManager(NULL)
    
        , _geoFenceManagerInitialRequestSent(false)
        , _rallyPointManager(NULL)
        , _rallyPointManagerInitialRequestSent(false)
    
    Don Gagne's avatar
    Don Gagne committed
        , _armed(false)
        , _base_mode(0)
        , _custom_mode(0)
    
        , _nextSendMessageMultipleIndex(0)
    
        , _firmwarePluginManager(firmwarePluginManager)
        , _joystickManager(joystickManager)
    
    dogmaphobic's avatar
    dogmaphobic committed
        , _flowImageIndex(0)
    
    Don Gagne's avatar
    Don Gagne committed
        , _allLinksInactiveSent(false)
    
        , _messagesReceived(0)
        , _messagesSent(0)
        , _messagesLost(0)
        , _messageSeq(0)
        , _compID(0)
        , _heardFrom(false)
    
        , _firmwareMajorVersion(versionNotSetValue)
        , _firmwareMinorVersion(versionNotSetValue)
        , _firmwarePatchVersion(versionNotSetValue)
    
        , _firmwareVersionType(FIRMWARE_VERSION_TYPE_OFFICIAL)
    
        , _lastAnnouncedLowBatteryPercent(100)
    
    Don Gagne's avatar
    Don Gagne committed
        , _rollFact             (0, _rollFactName,              FactMetaData::valueTypeDouble)
        , _pitchFact            (0, _pitchFactName,             FactMetaData::valueTypeDouble)
        , _headingFact          (0, _headingFactName,           FactMetaData::valueTypeDouble)
        , _groundSpeedFact      (0, _groundSpeedFactName,       FactMetaData::valueTypeDouble)
        , _airSpeedFact         (0, _airSpeedFactName,          FactMetaData::valueTypeDouble)
        , _climbRateFact        (0, _climbRateFactName,         FactMetaData::valueTypeDouble)
        , _altitudeRelativeFact (0, _altitudeRelativeFactName,  FactMetaData::valueTypeDouble)
        , _altitudeAMSLFact     (0, _altitudeAMSLFactName,      FactMetaData::valueTypeDouble)
    
        , _flightDistanceFact   (0, _flightDistanceFactName,    FactMetaData::valueTypeDouble)
    
    Don Gagne's avatar
    Don Gagne committed
        , _gpsFactGroup(this)
    
    Don Gagne's avatar
    Don Gagne committed
        , _batteryFactGroup(this)
    
    Don Gagne's avatar
    Don Gagne committed
        , _windFactGroup(this)
    
        , _vibrationFactGroup(this)
    
        , _temperatureFactGroup(this)
    
    {
        _addLink(link);
    
    Jacob Walser's avatar
    Jacob Walser committed
        connect(_joystickManager, &JoystickManager::activeJoystickChanged, this, &Vehicle::_activeJoystickChanged);
    
    
        _mavlink = qgcApp()->toolbox()->mavlinkProtocol();
    
    dogmaphobic's avatar
    dogmaphobic committed
        connect(_mavlink, &MAVLinkProtocol::messageReceived,     this, &Vehicle::_mavlinkMessageReceived);
    
    Gus Grubba's avatar
    Gus Grubba committed
        connect(_mavlink, &MAVLinkProtocol::radioStatusChanged,  this, &Vehicle::_telemetryChanged);
    
        connect(this, &Vehicle::_sendMessageOnLinkOnThread, this, &Vehicle::_sendMessageOnLink, Qt::QueuedConnection);
    
    Don Gagne's avatar
    Don Gagne committed
        connect(this, &Vehicle::flightModeChanged,          this, &Vehicle::_handleFlightModeChanged);
    
        connect(this, &Vehicle::armedChanged,               this, &Vehicle::_announceArmedChanged);
    
        _uas = new UAS(_mavlink, this, _firmwarePluginManager);
    
    Don Gagne's avatar
    Don Gagne committed
        connect(_uas, &UAS::imageReady,                     this, &Vehicle::_imageReady);
        connect(this, &Vehicle::remoteControlRSSIChanged,   this, &Vehicle::_remoteControlRSSIChanged);
    
    Don Gagne's avatar
    Don Gagne committed
        _autopilotPlugin = _firmwarePlugin->autopilotPlugin(this);
    
    Jimmy Johnson's avatar
    Jimmy Johnson committed
        // connect this vehicle to the follow me handle manager
        connect(this, &Vehicle::flightModeChanged,qgcApp()->toolbox()->followMe(), &FollowMe::followMeHandleManager);
    
    
    Don Gagne's avatar
    Don Gagne committed
        // PreArm Error self-destruct timer
        connect(&_prearmErrorTimer, &QTimer::timeout, this, &Vehicle::_prearmErrorTimeout);
        _prearmErrorTimer.setInterval(_prearmErrorTimeoutMSecs);
        _prearmErrorTimer.setSingleShot(true);
    
    
        // Connection Lost timer
        _connectionLostTimer.setInterval(_connectionLostTimeoutMSecs);
    
        _connectionLostTimer.setSingleShot(false);
        _connectionLostTimer.start();
        connect(&_connectionLostTimer, &QTimer::timeout, this, &Vehicle::_connectionLostTimeout);
    
        // Send MAV_CMD ack timer
        _mavCommandAckTimer.setSingleShot(true);
        _mavCommandAckTimer.setInterval(_mavCommandAckTimeoutMSecs);
        connect(&_mavCommandAckTimer, &QTimer::timeout, this, &Vehicle::_sendMavCommandAgain);
    
    
        // Listen for system messages
    
    dogmaphobic's avatar
    dogmaphobic committed
        connect(qgcApp()->toolbox()->uasMessageHandler(), &UASMessageHandler::textMessageCountChanged,  this, &Vehicle::_handleTextMessage);
        connect(qgcApp()->toolbox()->uasMessageHandler(), &UASMessageHandler::textMessageReceived,      this, &Vehicle::_handletextMessageReceived);
    
        // Now connect the new UAS
        connect(_mav, SIGNAL(attitudeChanged                    (UASInterface*,double,double,double,quint64)),              this, SLOT(_updateAttitude(UASInterface*, double, double, double, quint64)));
        connect(_mav, SIGNAL(attitudeChanged                    (UASInterface*,int,double,double,double,quint64)),          this, SLOT(_updateAttitude(UASInterface*,int,double, double, double, quint64)));
    
        _loadSettings();
    
        // Ask the vehicle for firmware version info.
        sendMavCommand(MAV_COMP_ID_ALL,                         // Don't know default component id yet.
                        MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES,
                       false,                                   // No error shown if fails
                        1);                                     // Request firmware version
    
        _firmwarePlugin->initializeVehicle(this);
    
        _sendMultipleTimer.start(_sendMessageMultipleIntraMessageDelay);
        connect(&_sendMultipleTimer, &QTimer::timeout, this, &Vehicle::_sendMessageMultipleNext);
    
        _mapTrajectoryTimer.setInterval(_mapTrajectoryMsecsBetweenPoints);
        connect(&_mapTrajectoryTimer, &QTimer::timeout, this, &Vehicle::_addNewMapTrajectoryPoint);
    
    // 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)
    
        , _active(false)
    
        , _offlineEditingVehicle(true)
    
        , _firmwareType(firmwareType)
        , _vehicleType(vehicleType)
    
        , _firmwarePlugin(NULL)
    
        , _firmwarePluginInstanceData(NULL)
    
        , _autopilotPlugin(NULL)
    
        , _mavlink(NULL)
        , _soloFirmware(false)
        , _settingsManager(qgcApp()->toolbox()->settingsManager())
    
        , _joystickMode(JoystickModeRC)
        , _joystickEnabled(false)
        , _uas(NULL)
        , _mav(NULL)
        , _currentMessageCount(0)
        , _messageCount(0)
        , _currentErrorCount(0)
        , _currentWarningCount(0)
        , _currentNormalCount(0)
        , _currentMessageType(MessageNone)
        , _updateCount(0)
    
        , _rcRSSI(255)
        , _rcRSSIstore(255)
    
        , _autoDisconnect(false)
    
        , _flying(false)
        , _onboardControlSensorsPresent(0)
        , _onboardControlSensorsEnabled(0)
        , _onboardControlSensorsHealth(0)
        , _onboardControlSensorsUnhealthy(0)
    
        , _gpsRawIntMessageAvailable(false)
        , _globalPositionIntMessageAvailable(false)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        , _defaultCruiseSpeed(_settingsManager->appSettings()->offlineEditingCruiseSpeed()->rawValue().toDouble())
        , _defaultHoverSpeed(_settingsManager->appSettings()->offlineEditingHoverSpeed()->rawValue().toDouble())
    
        , _vehicleCapabilitiesKnown(true)
        , _supportsMissionItemInt(false)
    
        , _connectionLost(false)
        , _connectionLostEnabled(true)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        , _initialPlanRequestComplete(false)
    
        , _missionManager(NULL)
    
        , _missionManagerInitialRequestSent(false)
    
        , _geoFenceManager(NULL)
    
        , _geoFenceManagerInitialRequestSent(false)
        , _rallyPointManager(NULL)
        , _rallyPointManagerInitialRequestSent(false)
    
        , _armed(false)
        , _base_mode(0)
        , _custom_mode(0)
        , _nextSendMessageMultipleIndex(0)
    
        , _firmwarePluginManager(firmwarePluginManager)
    
        , _joystickManager(NULL)
        , _flowImageIndex(0)
        , _allLinksInactiveSent(false)
        , _messagesReceived(0)
        , _messagesSent(0)
        , _messagesLost(0)
        , _messageSeq(0)
        , _compID(0)
        , _heardFrom(false)
    
        , _firmwareMajorVersion(versionNotSetValue)
        , _firmwareMinorVersion(versionNotSetValue)
        , _firmwarePatchVersion(versionNotSetValue)
    
        , _gitHash(versionNotSetValue)
    
        , _lastAnnouncedLowBatteryPercent(100)
    
        , _rollFact             (0, _rollFactName,              FactMetaData::valueTypeDouble)
        , _pitchFact            (0, _pitchFactName,             FactMetaData::valueTypeDouble)
        , _headingFact          (0, _headingFactName,           FactMetaData::valueTypeDouble)
        , _groundSpeedFact      (0, _groundSpeedFactName,       FactMetaData::valueTypeDouble)
        , _airSpeedFact         (0, _airSpeedFactName,          FactMetaData::valueTypeDouble)
        , _climbRateFact        (0, _climbRateFactName,         FactMetaData::valueTypeDouble)
        , _altitudeRelativeFact (0, _altitudeRelativeFactName,  FactMetaData::valueTypeDouble)
        , _altitudeAMSLFact     (0, _altitudeAMSLFactName,      FactMetaData::valueTypeDouble)
        , _gpsFactGroup(this)
        , _batteryFactGroup(this)
        , _windFactGroup(this)
        , _vibrationFactGroup(this)
    {
    
        _firmwarePlugin->initializeVehicle(this);
    
    }
    
    void Vehicle::_commonInit(void)
    {
        _firmwarePlugin = _firmwarePluginManager->firmwarePluginForAutopilot(_firmwareType, _vehicleType);
    
    
        _missionManager = new MissionManager(this);
    
        connect(_missionManager, &MissionManager::error,                    this, &Vehicle::_missionManagerError);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_missionLoadComplete);
    
        connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_clearCameraTriggerPoints);
        connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &Vehicle::_clearTrajectoryPoints);
        connect(_missionManager, &MissionManager::sendComplete,             this, &Vehicle::_clearCameraTriggerPoints);
        connect(_missionManager, &MissionManager::sendComplete,             this, &Vehicle::_clearTrajectoryPoints);
    
        _parameterManager = new ParameterManager(this);
    
    Don Gagne's avatar
    Don Gagne committed
        connect(_parameterManager, &ParameterManager::parametersReadyChanged, this, &Vehicle::_parametersReady);
    
        // GeoFenceManager needs to access ParameterManager so make sure to create after
    
        _geoFenceManager = _firmwarePlugin->newGeoFenceManager(this);
    
        connect(_geoFenceManager, &GeoFenceManager::error,          this, &Vehicle::_geoFenceManagerError);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(_geoFenceManager, &GeoFenceManager::loadComplete,   this, &Vehicle::_geoFenceLoadComplete);
    
        _rallyPointManager = _firmwarePlugin->newRallyPointManager(this);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(_rallyPointManager, &RallyPointManager::error,          this, &Vehicle::_rallyPointManagerError);
        connect(_rallyPointManager, &RallyPointManager::loadComplete,   this, &Vehicle::_rallyPointLoadComplete);
    
        // Offline editing vehicle tracks settings changes for offline editing settings
    
        connect(_settingsManager->appSettings()->offlineEditingFirmwareType(),  &Fact::rawValueChanged, this, &Vehicle::_offlineFirmwareTypeSettingChanged);
        connect(_settingsManager->appSettings()->offlineEditingVehicleType(),   &Fact::rawValueChanged, this, &Vehicle::_offlineVehicleTypeSettingChanged);
        connect(_settingsManager->appSettings()->offlineEditingCruiseSpeed(),   &Fact::rawValueChanged, this, &Vehicle::_offlineCruiseSpeedSettingChanged);
        connect(_settingsManager->appSettings()->offlineEditingHoverSpeed(),    &Fact::rawValueChanged, this, &Vehicle::_offlineHoverSpeedSettingChanged);
    
        // Build FactGroup object model
    
        _addFact(&_rollFact,                _rollFactName);
        _addFact(&_pitchFact,               _pitchFactName);
        _addFact(&_headingFact,             _headingFactName);
        _addFact(&_groundSpeedFact,         _groundSpeedFactName);
        _addFact(&_airSpeedFact,            _airSpeedFactName);
        _addFact(&_climbRateFact,           _climbRateFactName);
        _addFact(&_altitudeRelativeFact,    _altitudeRelativeFactName);
        _addFact(&_altitudeAMSLFact,        _altitudeAMSLFactName);
    
        _addFact(&_flightDistanceFact,      _flightDistanceFactName);
    
    
        _addFactGroup(&_gpsFactGroup,       _gpsFactGroupName);
        _addFactGroup(&_batteryFactGroup,   _batteryFactGroupName);
        _addFactGroup(&_windFactGroup,      _windFactGroupName);
        _addFactGroup(&_vibrationFactGroup, _vibrationFactGroupName);
    
        _addFactGroup(&_temperatureFactGroup, _temperatureFactGroupName);
    
    
        _flightDistanceFact.setRawValue(0);
    
    Vehicle::~Vehicle()
    {
    
    Don Gagne's avatar
    Don Gagne committed
        qCDebug(VehicleLog) << "~Vehicle" << this;
    
    
        delete _missionManager;
        _missionManager = NULL;
    
        delete _autopilotPlugin;
        _autopilotPlugin = NULL;
    
    
        delete _mav;
        _mav = NULL;
    
    void Vehicle::_offlineFirmwareTypeSettingChanged(QVariant value)
    {
        _firmwareType = static_cast<MAV_AUTOPILOT>(value.toInt());
        emit firmwareTypeChanged();
    }
    
    void Vehicle::_offlineVehicleTypeSettingChanged(QVariant value)
    {
        _vehicleType = static_cast<MAV_TYPE>(value.toInt());
        emit vehicleTypeChanged();
    }
    
    void Vehicle::_offlineCruiseSpeedSettingChanged(QVariant value)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        _defaultCruiseSpeed = value.toDouble();
        emit defaultCruiseSpeedChanged(_defaultCruiseSpeed);
    
    }
    
    void Vehicle::_offlineHoverSpeedSettingChanged(QVariant value)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        _defaultHoverSpeed = value.toDouble();
        emit defaultHoverSpeedChanged(_defaultHoverSpeed);
    
    }
    
    QString Vehicle::firmwareTypeString(void) const
    {
        if (px4Firmware()) {
            return QStringLiteral("PX4 Pro");
        } else if (apmFirmware()) {
            return QStringLiteral("ArduPilot");
        } else {
            return tr("MAVLink Generic");
        }
    }
    
    QString Vehicle::vehicleTypeString(void) const
    {
        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;
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    void Vehicle::_telemetryChanged(LinkInterface*, unsigned rxerrors, unsigned fixed, int rssi, int remrssi, unsigned txbuf, unsigned noise, unsigned remnoise)
    {
        if(_telemetryLRSSI != rssi) {
            _telemetryLRSSI = rssi;
            emit telemetryLRSSIChanged(_telemetryLRSSI);
        }
        if(_telemetryRRSSI != remrssi) {
            _telemetryRRSSI = remrssi;
            emit telemetryRRSSIChanged(_telemetryRRSSI);
        }
        if(_telemetryRXErrors != rxerrors) {
            _telemetryRXErrors = rxerrors;
            emit telemetryRXErrorsChanged(_telemetryRXErrors);
        }
        if(_telemetryFixed != fixed) {
            _telemetryFixed = fixed;
            emit telemetryFixedChanged(_telemetryFixed);
        }
        if(_telemetryTXBuffer != txbuf) {
            _telemetryTXBuffer = txbuf;
            emit telemetryTXBufferChanged(_telemetryTXBuffer);
        }
        if(_telemetryLNoise != noise) {
            _telemetryLNoise = noise;
            emit telemetryLNoiseChanged(_telemetryLNoise);
        }
        if(_telemetryRNoise != remnoise) {
            _telemetryRNoise = remnoise;
            emit telemetryRNoiseChanged(_telemetryRNoise);
        }
    }
    
    void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
    {
    
    Don Gagne's avatar
    Don Gagne committed
        if (message.sysid != _id && message.sysid != 0) {
    
        if (!_containsLink(link)) {
            _addLink(link);
        }
    
        //-- 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 = (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();
            }
        }
    
    
    
        // Mark this vehicle as active
        _connectionActive();
    
    
    Don Gagne's avatar
    Don Gagne committed
        // Give the plugin a change to adjust the message contents
    
        if (!_firmwarePlugin->adjustIncomingMavlinkMessage(this, &message)) {
            return;
        }
    
    Don Gagne's avatar
    Don Gagne committed
        switch (message.msgid) {
    
    Don Gagne's avatar
    Don Gagne committed
        case MAVLINK_MSG_ID_HOME_POSITION:
            _handleHomePosition(message);
            break;
        case MAVLINK_MSG_ID_HEARTBEAT:
            _handleHeartbeat(message);
            break;
        case MAVLINK_MSG_ID_RC_CHANNELS:
            _handleRCChannels(message);
            break;
        case MAVLINK_MSG_ID_RC_CHANNELS_RAW:
            _handleRCChannelsRaw(message);
            break;
    
    Don Gagne's avatar
    Don Gagne committed
        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_VIBRATION:
            _handleVibration(message);
            break;
    
    Don Gagne's avatar
    Don Gagne committed
        case MAVLINK_MSG_ID_EXTENDED_SYS_STATE:
            _handleExtendedSysState(message);
            break;
    
        case MAVLINK_MSG_ID_COMMAND_ACK:
            _handleCommandAck(message);
            break;
    
        case MAVLINK_MSG_ID_AUTOPILOT_VERSION:
    
    Gus Grubba's avatar
    Gus Grubba committed
            _handleAutopilotVersion(link, message);
    
        case MAVLINK_MSG_ID_WIND_COV:
            _handleWindCov(message);
            break;
    
        case MAVLINK_MSG_ID_HIL_ACTUATOR_CONTROLS:
            _handleHilActuatorControls(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_SCALED_PRESSURE:
            _handleScaledPressure(message);
            break;
        case MAVLINK_MSG_ID_SCALED_PRESSURE2:
            _handleScaledPressure2(message);
            break;
        case MAVLINK_MSG_ID_SCALED_PRESSURE3:
            _handleScaledPressure3(message);
    
            break;        
        case MAVLINK_MSG_ID_CAMERA_FEEDBACK:
            _handleCameraFeedback(message);
            break;
    
        case MAVLINK_MSG_ID_CAMERA_IMAGE_CAPTURED:
            _handleCameraImageCaptured(message);
    
        case MAVLINK_MSG_ID_SERIAL_CONTROL:
        {
            mavlink_serial_control_t ser;
            mavlink_msg_serial_control_decode(&message, &ser);
            emit mavlinkSerialControl(ser.device, ser.flags, ser.timeout, ser.baudrate, QByteArray(reinterpret_cast<const char*>(ser.data), ser.count));
        }
            break;
    
    Don Gagne's avatar
    Don Gagne committed
        // Following are ArduPilot dialect messages
    
        case MAVLINK_MSG_ID_WIND:
            _handleWind(message);
            break;
    
    Don Gagne's avatar
    Don Gagne committed
        emit mavlinkMessageReceived(message);
    
        _uas->receiveMessage(message);
    }
    
    
    
    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));
    }
    
    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::_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);
    }
    
    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) {
    
                //-- Set these here and emit a single signal instead of 3 for the same variable (_coordinate)
                _coordinate.setLatitude(gpsRawInt.lat  / (double)1E7);
                _coordinate.setLongitude(gpsRawInt.lon / (double)1E7);
                _coordinate.setAltitude(gpsRawInt.alt  / 1000.0);
                emit coordinateChanged(_coordinate);
    
                _altitudeAMSLFact.setRawValue(gpsRawInt.alt / 1000.0);
    
            }
        }
    
        _gpsFactGroup.count()->setRawValue(gpsRawInt.satellites_visible == 255 ? 0 : gpsRawInt.satellites_visible);
    
        _gpsFactGroup.hdop()->setRawValue(gpsRawInt.eph == UINT16_MAX ? std::numeric_limits<double>::quiet_NaN() : gpsRawInt.eph / 100.0);
        _gpsFactGroup.vdop()->setRawValue(gpsRawInt.epv == UINT16_MAX ? std::numeric_limits<double>::quiet_NaN() : gpsRawInt.epv / 100.0);
        _gpsFactGroup.courseOverGround()->setRawValue(gpsRawInt.cog == UINT16_MAX ? std::numeric_limits<double>::quiet_NaN() : gpsRawInt.cog / 100.0);
    
        _gpsFactGroup.lock()->setRawValue(gpsRawInt.fix_type);
    }
    
    void Vehicle::_handleGlobalPositionInt(mavlink_message_t& message)
    {
        mavlink_global_position_int_t globalPositionInt;
        mavlink_msg_global_position_int_decode(&message, &globalPositionInt);
    
    
        _globalPositionIntMessageAvailable = true;
    
        //-- Set these here and emit a single signal instead of 3 for the same variable (_coordinate)
        _coordinate.setLatitude(globalPositionInt.lat  / (double)1E7);
        _coordinate.setLongitude(globalPositionInt.lon / (double)1E7);
        _coordinate.setAltitude(globalPositionInt.alt  / 1000.0);
        emit coordinateChanged(_coordinate);
    
        _altitudeRelativeFact.setRawValue(globalPositionInt.relative_alt / 1000.0);
        _altitudeAMSLFact.setRawValue(globalPositionInt.alt / 1000.0);
    
    }
    
    void Vehicle::_handleAltitude(mavlink_message_t& message)
    {
        mavlink_altitude_t altitude;
        mavlink_msg_altitude_decode(&message, &altitude);
    
    
        // If data from GPS is available it takes precedence over ALTITUDE message
        if (!_globalPositionIntMessageAvailable) {
            _altitudeRelativeFact.setRawValue(altitude.altitude_relative);
            if (!_gpsRawIntMessageAvailable) {
                _altitudeAMSLFact.setRawValue(altitude.altitude_amsl);
            }
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    void Vehicle::_setCapabilities(uint64_t capabilityBits)
    {
        if (capabilityBits & MAV_PROTOCOL_CAPABILITY_MISSION_INT) {
            _supportsMissionItemInt = true;
        }
        _vehicleCapabilitiesKnown = true;
    
        qCDebug(VehicleLog) << QString("Vehicle %1 MISSION_ITEM_INT").arg(_supportsMissionItemInt ? QStringLiteral("supports") : QStringLiteral("does not support"));
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    void Vehicle::_handleAutopilotVersion(LinkInterface *link, mavlink_message_t& message)
    
        Q_UNUSED(link);
    
    
        mavlink_autopilot_version_t autopilotVersion;
        mavlink_msg_autopilot_version_decode(&message, &autopilotVersion);
    
        if (autopilotVersion.flight_sw_version != 0) {
            int majorVersion, minorVersion, patchVersion;
            FIRMWARE_VERSION_TYPE versionType;
    
            majorVersion = (autopilotVersion.flight_sw_version >> (8*3)) & 0xFF;
            minorVersion = (autopilotVersion.flight_sw_version >> (8*2)) & 0xFF;
            patchVersion = (autopilotVersion.flight_sw_version >> (8*1)) & 0xFF;
            versionType = (FIRMWARE_VERSION_TYPE)((autopilotVersion.flight_sw_version >> (8*0)) & 0xFF);
    
    Don Gagne's avatar
    Don Gagne committed
            setFirmwareVersion(majorVersion, minorVersion, patchVersion, versionType);
    
    
        // Git hash
        if (autopilotVersion.flight_custom_version[0] != 0) {
    
            // PX4 Firmware stores the first 16 characters of the git hash as binary, with the individual bytes in reverse order
            if (px4Firmware()) {
                _gitHash = "";
                QByteArray array((char*)autopilotVersion.flight_custom_version, 8);
                for (int i = 7; i >= 0; i--) {
                    _gitHash.append(QString("%1").arg(autopilotVersion.flight_custom_version[i], 2, 16, QChar('0')));
                }
            } else {
                // APM Firmware stores the first 8 characters of the git hash as an ASCII character string
                _gitHash = QString::fromUtf8((char*)autopilotVersion.flight_custom_version, 8);
            }
    
            emit gitHashChanged(_gitHash);
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        _setCapabilities(autopilotVersion.capabilities);
        _startPlanRequest();
    
    void Vehicle::_handleHilActuatorControls(mavlink_message_t &message)
    {
        mavlink_hil_actuator_controls_t hil;
        mavlink_msg_hil_actuator_controls_decode(&message, &hil);
        emit hilActuatorControlsChanged(hil.time_usec, hil.flags,
                                        hil.controls[0],
                                        hil.controls[1],
                                        hil.controls[2],
                                        hil.controls[3],
                                        hil.controls[4],
                                        hil.controls[5],
                                        hil.controls[6],
                                        hil.controls[7],
                                        hil.controls[8],
                                        hil.controls[9],
                                        hil.controls[10],
                                        hil.controls[11],
                                        hil.controls[12],
                                        hil.controls[13],
                                        hil.controls[14],
                                        hil.controls[15],
                                        hil.mode);
    }
    
    
    void Vehicle::_handleCommandAck(mavlink_message_t& message)
    {
    
        mavlink_command_ack_t ack;
        mavlink_msg_command_ack_decode(&message, &ack);
    
    
        if (ack.command == MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES && ack.result != MAV_RESULT_ACCEPTED) {
            // We aren't going to get a response back for capabilities, so stop waiting for it before we ask for mission items
            qCDebug(VehicleLog) << "Vehicle failed to responded to MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES with error. Starting mission request.";
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            _setCapabilities(0);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            _startPlanRequest();
    
        if (_mavCommandQueue.count() && ack.command == _mavCommandQueue[0].command) {
    
    Don Gagne's avatar
    Don Gagne committed
            _mavCommandAckTimer.stop();
    
            showError = _mavCommandQueue[0].showError;
            _mavCommandQueue.removeFirst();
        }
    
    
    Don Gagne's avatar
    Don Gagne committed
        emit mavCommandResult(_id, message.compid, ack.command, ack.result, false /* noResponsefromVehicle */);
    
    
        if (showError) {
            QString commandName = qgcApp()->toolbox()->missionCommandTree()->friendlyName((MAV_CMD)ack.command);
    
            switch (ack.result) {
            case MAV_RESULT_TEMPORARILY_REJECTED:
                qgcApp()->showMessage(tr("%1 command temporarily rejected").arg(commandName));
                break;
            case MAV_RESULT_DENIED:
                qgcApp()->showMessage(tr("%1 command denied").arg(commandName));
                break;
            case MAV_RESULT_UNSUPPORTED:
                qgcApp()->showMessage(tr("%1 command not supported").arg(commandName));
                break;
            case MAV_RESULT_FAILED:
                qgcApp()->showMessage(tr("%1 command failed").arg(commandName));
                break;
    
        _sendNextQueuedMavCommand();
    
    Don Gagne's avatar
    Don Gagne committed
    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) {
    
    Don Gagne's avatar
    Don Gagne committed
        case MAV_LANDED_STATE_UNDEFINED:
    
    Don Gagne's avatar
    Don Gagne committed
            break;
        case MAV_LANDED_STATE_ON_GROUND:
            setFlying(false);
            break;
        case MAV_LANDED_STATE_IN_AIR:
            setFlying(true);
            return;
        }
    }
    
    
    void Vehicle::_handleVibration(mavlink_message_t& message)
    {
        mavlink_vibration_t vibration;
        mavlink_msg_vibration_decode(&message, &vibration);
    
        _vibrationFactGroup.xAxis()->setRawValue(vibration.vibration_x);
        _vibrationFactGroup.yAxis()->setRawValue(vibration.vibration_y);
        _vibrationFactGroup.zAxis()->setRawValue(vibration.vibration_z);
        _vibrationFactGroup.clipCount1()->setRawValue(vibration.clipping_0);
        _vibrationFactGroup.clipCount2()->setRawValue(vibration.clipping_1);
        _vibrationFactGroup.clipCount3()->setRawValue(vibration.clipping_2);
    }
    
    
    void Vehicle::_handleWindCov(mavlink_message_t& message)
    {
        mavlink_wind_cov_t wind;
        mavlink_msg_wind_cov_decode(&message, &wind);
    
        float direction = qRadiansToDegrees(qAtan2(wind.wind_y, wind.wind_x));
        float speed = qSqrt(qPow(wind.wind_x, 2) + qPow(wind.wind_y, 2));
    
        _windFactGroup.direction()->setRawValue(direction);
        _windFactGroup.speed()->setRawValue(speed);
        _windFactGroup.verticalSpeed()->setRawValue(0);
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    void Vehicle::_handleWind(mavlink_message_t& message)
    {
        mavlink_wind_t wind;
        mavlink_msg_wind_decode(&message, &wind);
    
        _windFactGroup.direction()->setRawValue(wind.direction);
        _windFactGroup.speed()->setRawValue(wind.speed);
        _windFactGroup.verticalSpeed()->setRawValue(wind.speed_z);
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    void Vehicle::_handleSysStatus(mavlink_message_t& message)
    {
        mavlink_sys_status_t sysStatus;
        mavlink_msg_sys_status_decode(&message, &sysStatus);
    
        if (sysStatus.current_battery == -1) {
            _batteryFactGroup.current()->setRawValue(VehicleBatteryFactGroup::_currentUnavailable);
        } else {
    
            // Current is in Amps, current_battery is 10 * milliamperes (1 = 10 milliampere)
    
            _batteryFactGroup.current()->setRawValue((float)sysStatus.current_battery / 100.0f);
    
    Don Gagne's avatar
    Don Gagne committed
        }
        if (sysStatus.voltage_battery == UINT16_MAX) {
            _batteryFactGroup.voltage()->setRawValue(VehicleBatteryFactGroup::_voltageUnavailable);
        } else {
            _batteryFactGroup.voltage()->setRawValue((double)sysStatus.voltage_battery / 1000.0);
        }
        _batteryFactGroup.percentRemaining()->setRawValue(sysStatus.battery_remaining);
    
        if (sysStatus.battery_remaining > 0 &&
                sysStatus.battery_remaining < _settingsManager->appSettings()->batteryPercentRemainingAnnounce()->rawValue().toInt() &&
                sysStatus.battery_remaining < _lastAnnouncedLowBatteryPercent) {
            _lastAnnouncedLowBatteryPercent = sysStatus.battery_remaining;
            _say(QString("%1 low battery: %2 percent remaining").arg(_vehicleIdSpeech()).arg(sysStatus.battery_remaining));
    
    
        _onboardControlSensorsPresent = sysStatus.onboard_control_sensors_present;
        _onboardControlSensorsEnabled = sysStatus.onboard_control_sensors_enabled;
        _onboardControlSensorsHealth = sysStatus.onboard_control_sensors_health;
    
        uint32_t newSensorsUnhealthy = _onboardControlSensorsEnabled & ~_onboardControlSensorsHealth;
        if (newSensorsUnhealthy != _onboardControlSensorsUnhealthy) {
            _onboardControlSensorsUnhealthy = newSensorsUnhealthy;
            emit unhealthySensorsChanged();
        }
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    void Vehicle::_handleBatteryStatus(mavlink_message_t& message)
    {
        mavlink_battery_status_t bat_status;
        mavlink_msg_battery_status_decode(&message, &bat_status);
    
        if (bat_status.temperature == INT16_MAX) {
            _batteryFactGroup.temperature()->setRawValue(VehicleBatteryFactGroup::_temperatureUnavailable);
        } else {
            _batteryFactGroup.temperature()->setRawValue((double)bat_status.temperature / 100.0);
        }
        if (bat_status.current_consumed == -1) {
            _batteryFactGroup.mahConsumed()->setRawValue(VehicleBatteryFactGroup::_mahConsumedUnavailable);
        } else {
            _batteryFactGroup.mahConsumed()->setRawValue(bat_status.current_consumed);
        }
    
        int cellCount = 0;
        for (int i=0; i<10; i++) {
            if (bat_status.voltages[i] != UINT16_MAX) {
                cellCount++;
            }
        }
        if (cellCount == 0) {
            cellCount = -1;
        }
    
        _batteryFactGroup.cellCount()->setRawValue(cellCount);
    }
    
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    void Vehicle::_setHomePosition(QGeoCoordinate& homeCoord)
    {
        if (homeCoord != _homePosition) {
            _homePosition = homeCoord;
            emit homePositionChanged(_homePosition);
        }
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    void Vehicle::_handleHomePosition(mavlink_message_t& message)
    {
        mavlink_home_position_t homePos;
    
    Don Gagne's avatar
    Don Gagne committed
        mavlink_msg_home_position_decode(&message, &homePos);
    
    Don Gagne's avatar
    Don Gagne committed
    
        QGeoCoordinate newHomePosition (homePos.latitude / 10000000.0,
                                        homePos.longitude / 10000000.0,
                                        homePos.altitude / 1000.0);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        _setHomePosition(newHomePosition);
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    void Vehicle::_handleHeartbeat(mavlink_message_t& message)
    {
    
        if (message.compid != _defaultComponentId) {
            return;
        }
    
    
    Don Gagne's avatar
    Don Gagne committed
        mavlink_heartbeat_t heartbeat;
    
    Don Gagne's avatar
    Don Gagne committed
        mavlink_msg_heartbeat_decode(&message, &heartbeat);
    
    Don Gagne's avatar
    Don Gagne committed
        bool newArmed = heartbeat.base_mode & MAV_MODE_FLAG_DECODE_POSITION_SAFETY;
    
    Don Gagne's avatar
    Don Gagne committed
        if (_armed != newArmed) {
            _armed = newArmed;
            emit armedChanged(_armed);
    
            // We are transitioning to the armed state, begin tracking trajectory points for the map
            if (_armed) {
                _mapTrajectoryStart();
    
                _clearCameraTriggerPoints();
    
            } else {
                _mapTrajectoryStop();
            }
    
    Don Gagne's avatar
    Don Gagne committed
        }
    
        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();
            }
    
    Don Gagne's avatar
    Don Gagne committed
            _base_mode = heartbeat.base_mode;
            _custom_mode = heartbeat.custom_mode;
    
            if (previousFlightMode != flightMode()) {