/*===================================================================== QGroundControl Open Source Ground Control Station (c) 2009 - 2014 QGROUNDCONTROL PROJECT This file is part of the QGROUNDCONTROL project QGROUNDCONTROL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. QGROUNDCONTROL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QGROUNDCONTROL. If not, see . ======================================================================*/ /// @file /// @author Don Gagne #include "MissionManager.h" #include "Vehicle.h" #include "FirmwarePlugin.h" #include "MAVLinkProtocol.h" #include "QGCApplication.h" QGC_LOGGING_CATEGORY(MissionManagerLog, "MissionManagerLog") MissionManager::MissionManager(Vehicle* vehicle) : _vehicle(vehicle) , _cMissionItems(0) , _ackTimeoutTimer(NULL) , _retryAck(AckNone) { connect(_vehicle, &Vehicle::mavlinkMessageReceived, this, &MissionManager::_mavlinkMessageReceived); _ackTimeoutTimer = new QTimer(this); _ackTimeoutTimer->setSingleShot(true); _ackTimeoutTimer->setInterval(_ackTimeoutMilliseconds); connect(_ackTimeoutTimer, &QTimer::timeout, this, &MissionManager::_ackTimeout); } MissionManager::~MissionManager() { } void MissionManager::writeMissionItems(const QmlObjectListModel& missionItems) { bool skipFirstItem = !_vehicle->firmwarePlugin()->sendHomePositionToVehicle(); _retryCount = 0; _missionItems.clear(); int firstIndex = skipFirstItem ? 1 : 0; for (int i=firstIndex; i(missionItems[i]))); MissionItem* item = qobject_cast(_missionItems.get(_missionItems.count() - 1)); if (skipFirstItem) { // Home is in sequence 1, remainder of items start at sequence 1 item->setSequenceNumber(item->sequenceNumber() - 1); if (item->command() == MavlinkQmlSingleton::MAV_CMD_DO_JUMP) { item->setParam1((int)item->param1() - 1); } } } emit newMissionItemsAvailable(); qCDebug(MissionManagerLog) << "writeMissionItems count:" << _missionItems.count(); if (inProgress()) { qCDebug(MissionManagerLog) << "writeMissionItems called while transaction in progress"; return; } mavlink_message_t message; mavlink_mission_count_t missionCount; _expectedSequenceNumber = 0; missionCount.target_system = _vehicle->id(); missionCount.target_component = MAV_COMP_ID_MISSIONPLANNER; missionCount.count = _missionItems.count(); mavlink_msg_mission_count_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &missionCount); _vehicle->sendMessage(message); _startAckTimeout(AckMissionRequest); emit inProgressChanged(true); } void MissionManager::_retryWrite(void) { qCDebug(MissionManagerLog) << "_retryWrite"; mavlink_message_t message; mavlink_mission_count_t missionCount; _expectedSequenceNumber = 0; missionCount.target_system = _vehicle->id(); missionCount.target_component = MAV_COMP_ID_MISSIONPLANNER; missionCount.count = _missionItems.count(); mavlink_msg_mission_count_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &missionCount); _vehicle->sendMessage(message); _startAckTimeout(AckMissionRequest); } void MissionManager::requestMissionItems(void) { qCDebug(MissionManagerLog) << "requestMissionItems read sequence"; mavlink_message_t message; mavlink_mission_request_list_t request; _retryCount = 0; _clearMissionItems(); request.target_system = _vehicle->id(); request.target_component = MAV_COMP_ID_MISSIONPLANNER; mavlink_msg_mission_request_list_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &request); _vehicle->sendMessage(message); _startAckTimeout(AckMissionCount); emit inProgressChanged(true); } void MissionManager::_retryRead(void) { qCDebug(MissionManagerLog) << "_retryRead"; mavlink_message_t message; mavlink_mission_request_list_t request; _clearMissionItems(); request.target_system = _vehicle->id(); request.target_component = MAV_COMP_ID_MISSIONPLANNER; mavlink_msg_mission_request_list_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &request); _vehicle->sendMessage(message); _startAckTimeout(AckMissionCount); } void MissionManager::_ackTimeout(void) { AckType_t timedOutAck = _retryAck; _retryAck = AckNone; if (timedOutAck == AckNone) { qCWarning(MissionManagerLog) << "_ackTimeout timeout with AckNone"; _sendError(InternalError, "Internal error occured during Mission Item communication: _ackTimeOut:_retryAck == AckNone"); return; } if (!_retrySequence(timedOutAck)) { qCDebug(MissionManagerLog) << "_ackTimeout failed after max retries _retryAck:_retryCount" << _ackTypeToString(timedOutAck) << _retryCount; _sendError(AckTimeoutError, QString("Vehicle did not respond to mission item communication: %1").arg(_ackTypeToString(timedOutAck))); } } void MissionManager::_startAckTimeout(AckType_t ack) { _retryAck = ack; _ackTimeoutTimer->start(); } bool MissionManager::_stopAckTimeout(AckType_t expectedAck) { bool success = false; AckType_t savedRetryAck = _retryAck; _retryAck = AckNone; _ackTimeoutTimer->stop(); if (savedRetryAck != expectedAck) { qCDebug(MissionManagerLog) << "Invalid ack sequence _retryAck:expectedAck" << _ackTypeToString(savedRetryAck) << _ackTypeToString(expectedAck); if (_retrySequence(expectedAck)) { _sendError(ProtocolOrderError, QString("Vehicle responded incorrectly to mission item protocol sequence: %1:%2").arg(_ackTypeToString(savedRetryAck)).arg(_ackTypeToString(expectedAck))); } success = false; } else { success = true; } return success; } void MissionManager::_readTransactionComplete(void) { qCDebug(MissionManagerLog) << "_readTransactionComplete read sequence complete"; mavlink_message_t message; mavlink_mission_ack_t missionAck; missionAck.target_system = _vehicle->id(); missionAck.target_component = MAV_COMP_ID_MISSIONPLANNER; missionAck.type = MAV_MISSION_ACCEPTED; mavlink_msg_mission_ack_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &missionAck); _vehicle->sendMessage(message); emit newMissionItemsAvailable(); emit inProgressChanged(false); } void MissionManager::_handleMissionCount(const mavlink_message_t& message) { mavlink_mission_count_t missionCount; if (!_stopAckTimeout(AckMissionCount)) { return; } mavlink_msg_mission_count_decode(&message, &missionCount); _cMissionItems = missionCount.count; qCDebug(MissionManagerLog) << "_handleMissionCount count:" << _cMissionItems; if (_cMissionItems == 0) { _readTransactionComplete(); } else { _requestNextMissionItem(0); } } void MissionManager::_requestNextMissionItem(int sequenceNumber) { qCDebug(MissionManagerLog) << "_requestNextMissionItem sequenceNumber:" << sequenceNumber; if (sequenceNumber >= _cMissionItems) { qCWarning(MissionManagerLog) << "_requestNextMissionItem requested seqeuence number > item count sequenceNumber::_cMissionItems" << sequenceNumber << _cMissionItems; _sendError(InternalError, QString("QGroundControl requested mission item outside of range (internal error): %1:%2").arg(sequenceNumber).arg(_cMissionItems)); return; } mavlink_message_t message; mavlink_mission_request_t missionRequest; missionRequest.target_system = _vehicle->id(); missionRequest.target_component = MAV_COMP_ID_MISSIONPLANNER; missionRequest.seq = sequenceNumber; _expectedSequenceNumber = sequenceNumber; mavlink_msg_mission_request_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &missionRequest); _vehicle->sendMessage(message); _startAckTimeout(AckMissionItem); } void MissionManager::_handleMissionItem(const mavlink_message_t& message) { mavlink_mission_item_t missionItem; if (!_stopAckTimeout(AckMissionItem)) { return; } mavlink_msg_mission_item_decode(&message, &missionItem); qCDebug(MissionManagerLog) << "_handleMissionItem sequenceNumber:" << missionItem.seq; if (missionItem.seq != _expectedSequenceNumber) { qCDebug(MissionManagerLog) << "_handleMissionItem mission item received out of sequence expected:actual" << _expectedSequenceNumber << missionItem.seq; if (!_retrySequence(AckMissionItem)) { _sendError(ItemMismatchError, QString("Vehicle returned incorrect mission item: %1:%2").arg(_expectedSequenceNumber).arg(missionItem.seq)); } return; } MissionItem* item = new MissionItem(missionItem.seq, (MAV_CMD)missionItem.command, (MAV_FRAME)missionItem.frame, missionItem.param1, missionItem.param2, missionItem.param3, missionItem.param4, missionItem.x, missionItem.y, missionItem.z, missionItem.autocontinue, missionItem.current, this); _missionItems.append(item); int nextSequenceNumber = missionItem.seq + 1; if (nextSequenceNumber == _cMissionItems) { _readTransactionComplete(); } else { _requestNextMissionItem(nextSequenceNumber); } } void MissionManager::_clearMissionItems(void) { _cMissionItems = 0; _missionItems.clear(); } void MissionManager::_handleMissionRequest(const mavlink_message_t& message) { mavlink_mission_request_t missionRequest; if (!_stopAckTimeout(AckMissionRequest)) { return; } mavlink_msg_mission_request_decode(&message, &missionRequest); qCDebug(MissionManagerLog) << "_handleMissionRequest sequenceNumber:" << missionRequest.seq; if (missionRequest.seq != _expectedSequenceNumber) { qCDebug(MissionManagerLog) << "_handleMissionRequest invalid sequence number requested: _expectedSequenceNumber:missionRequest.seq" << _expectedSequenceNumber << missionRequest.seq; if (!_retrySequence(AckMissionRequest)) { _sendError(ItemMismatchError, QString("Vehicle requested incorrect mission item: %1:%2").arg(_expectedSequenceNumber).arg(missionRequest.seq)); } return; } _expectedSequenceNumber++; mavlink_message_t messageOut; mavlink_mission_item_t missionItem; MissionItem* item = (MissionItem*)_missionItems[missionRequest.seq]; missionItem.target_system = _vehicle->id(); missionItem.target_component = MAV_COMP_ID_MISSIONPLANNER; missionItem.seq = missionRequest.seq; missionItem.command = item->command(); missionItem.x = item->coordinate().latitude(); missionItem.y = item->coordinate().longitude(); missionItem.z = item->coordinate().altitude(); missionItem.param1 = item->param1(); missionItem.param2 = item->param2(); missionItem.param3 = item->param3(); missionItem.param4 = item->param4(); missionItem.frame = item->frame(); missionItem.current = missionRequest.seq == 0; missionItem.autocontinue = item->autoContinue(); mavlink_msg_mission_item_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &messageOut, &missionItem); _vehicle->sendMessage(messageOut); _startAckTimeout(AckMissionRequest); } void MissionManager::_handleMissionAck(const mavlink_message_t& message) { mavlink_mission_ack_t missionAck; // Save th retry ack before calling _stopAckTimeout since we'll need it to determine what // type of a protocol sequence we are in. AckType_t savedRetryAck = _retryAck; // We can get a MISSION_ACK with an error at any time, so if the Acks don't match it is not // a protocol sequence error. Call _stopAckTimeout with _retryAck so it will succeed no // matter what. if (!_stopAckTimeout(_retryAck)) { return; } mavlink_msg_mission_ack_decode(&message, &missionAck); qCDebug(MissionManagerLog) << "_handleMissionAck type:" << _missionResultToString((MAV_MISSION_RESULT)missionAck.type); switch (savedRetryAck) { case AckNone: // State machine is idle. Vehicle is confused. qCDebug(MissionManagerLog) << "_handleMissionAck vehicle sent ack while state machine is idle: error:" << _missionResultToString((MAV_MISSION_RESULT)missionAck.type); _sendError(VehicleError, QString("Vehicle sent unexpected MISSION_ACK message, error: %1").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); break; case AckMissionCount: // MISSION_COUNT message expected qCDebug(MissionManagerLog) << "_handleMissionAck vehicle sent ack when MISSION_COUNT expected: error:" << _missionResultToString((MAV_MISSION_RESULT)missionAck.type); if (!_retrySequence(AckMissionCount)) { _sendError(VehicleError, QString("Vehicle returned error: %1. Partial list of mission items may have been returned.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); } break; case AckMissionItem: // MISSION_ITEM expected qCDebug(MissionManagerLog) << "_handleMissionAck vehicle sent ack when MISSION_ITEM expected: error:" << _missionResultToString((MAV_MISSION_RESULT)missionAck.type); if (!_retrySequence(AckMissionItem)) { _sendError(VehicleError, QString("Vehicle returned error: %1. Partial list of mission items may have been returned.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); } break; case AckMissionRequest: // MISSION_REQUEST is expected, or MISSION_ACK to end sequence if (missionAck.type == MAV_MISSION_ACCEPTED) { if (_expectedSequenceNumber == _missionItems.count()) { qCDebug(MissionManagerLog) << "_handleMissionAck write sequence complete"; emit inProgressChanged(false); } else { qCDebug(MissionManagerLog) << "_handleMissionAck vehicle did not reqeust all items: _expectedSequenceNumber:_missionItems.count" << _expectedSequenceNumber << _missionItems.count(); if (!_retrySequence(AckMissionRequest)) { _sendError(MissingRequestsError, QString("Vehicle did not request all items during write sequence %1:%2. Vehicle only has partial list of mission items.").arg(_expectedSequenceNumber).arg(_missionItems.count())); } } } else { qCDebug(MissionManagerLog) << "_handleMissionAck ack error:" << _missionResultToString((MAV_MISSION_RESULT)missionAck.type); if (!_retrySequence(AckMissionRequest)) { _sendError(VehicleError, QString("Vehicle returned error: %1. Vehicle only has partial list of mission items.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); } } break; } } /// Called when a new mavlink message for out vehicle is received void MissionManager::_mavlinkMessageReceived(const mavlink_message_t& message) { switch (message.msgid) { case MAVLINK_MSG_ID_MISSION_COUNT: _handleMissionCount(message); break; case MAVLINK_MSG_ID_MISSION_ITEM: _handleMissionItem(message); break; case MAVLINK_MSG_ID_MISSION_REQUEST: _handleMissionRequest(message); break; case MAVLINK_MSG_ID_MISSION_ACK: _handleMissionAck(message); break; case MAVLINK_MSG_ID_MISSION_ITEM_REACHED: // FIXME: NYI break; case MAVLINK_MSG_ID_MISSION_CURRENT: // FIXME: NYI break; } } QmlObjectListModel* MissionManager::copyMissionItems(void) { QmlObjectListModel* list = new QmlObjectListModel(); for (int i=0; i<_missionItems.count(); i++) { list->append(new MissionItem(*qobject_cast(_missionItems[i]))); } return list; } void MissionManager::_sendError(ErrorCode_t errorCode, const QString& errorMsg) { emit inProgressChanged(false); emit error(errorCode, errorMsg); } /// Retry the protocol sequence given the specified ack /// @return true: sequence retried, false: out of retries bool MissionManager::_retrySequence(AckType_t ackType) { qCDebug(MissionManagerLog) << "_retrySequence ackType:" << _ackTypeToString(ackType) << "_retryCount" << _retryCount; switch (ackType) { case AckMissionCount: case AckMissionItem: if (++_retryCount <= _maxRetryCount) { // We are in the middle of a read sequence, start over _retryRead(); return true; } else { // Read sequence failed, signal for what we have up to this point emit newMissionItemsAvailable(); return false; } break; case AckMissionRequest: if (++_retryCount <= _maxRetryCount) { // We are in the middle of a write sequence, start over _retryWrite(); return true; } else { return false; } break; default: qCWarning(MissionManagerLog) << "_retrySequence fell through switch: ackType:" << _ackTypeToString(ackType); _sendError(InternalError, QString("Internal error occured during Mission Item communication: _retrySequence fell through switch: ackType:").arg(_ackTypeToString(ackType))); return false; } } QString MissionManager::_ackTypeToString(AckType_t ackType) { switch (ackType) { case AckNone: // State machine is idle return QString("No Ack"); case AckMissionCount: // MISSION_COUNT message expected return QString("MISSION_COUNT"); case AckMissionItem: ///< MISSION_ITEM expected return QString("MISSION_ITEM"); case AckMissionRequest: ///< MISSION_REQUEST is expected, or MISSION_ACK to end sequence return QString("MISSION_REQUEST"); default: qWarning(MissionManagerLog) << "Fell off end of switch statement"; return QString("QGC Internal Error"); } } QString MissionManager::_missionResultToString(MAV_MISSION_RESULT result) { switch (result) { case MAV_MISSION_ACCEPTED: return QString("Mission accepted (MAV_MISSION_ACCEPTED)"); break; case MAV_MISSION_ERROR: return QString("Unspecified error (MAV_MISSION_ERROR)"); break; case MAV_MISSION_UNSUPPORTED_FRAME: return QString("Coordinate frame is not supported (MAV_MISSION_UNSUPPORTED_FRAME)"); break; case MAV_MISSION_UNSUPPORTED: return QString("Command is not supported (MAV_MISSION_UNSUPPORTED)"); break; case MAV_MISSION_NO_SPACE: return QString("Mission item exceeds storage space (MAV_MISSION_NO_SPACE)"); break; case MAV_MISSION_INVALID: return QString("One of the parameters has an invalid value (MAV_MISSION_INVALID)"); break; case MAV_MISSION_INVALID_PARAM1: return QString("Param1 has an invalid value (MAV_MISSION_INVALID_PARAM1)"); break; case MAV_MISSION_INVALID_PARAM2: return QString("Param2 has an invalid value (MAV_MISSION_INVALID_PARAM2)"); break; case MAV_MISSION_INVALID_PARAM3: return QString("param3 has an invalid value (MAV_MISSION_INVALID_PARAM3)"); break; case MAV_MISSION_INVALID_PARAM4: return QString("Param4 has an invalid value (MAV_MISSION_INVALID_PARAM4)"); break; case MAV_MISSION_INVALID_PARAM5_X: return QString("X/Param5 has an invalid value (MAV_MISSION_INVALID_PARAM5_X)"); break; case MAV_MISSION_INVALID_PARAM6_Y: return QString("Y/Param6 has an invalid value (MAV_MISSION_INVALID_PARAM6_Y)"); break; case MAV_MISSION_INVALID_PARAM7: return QString("Param7 has an invalid value (MAV_MISSION_INVALID_PARAM7)"); break; case MAV_MISSION_INVALID_SEQUENCE: return QString("Received mission item out of sequence (MAV_MISSION_INVALID_SEQUENCE)"); break; case MAV_MISSION_DENIED: return QString("Not accepting any mission commands (MAV_MISSION_DENIED)"); break; default: qWarning(MissionManagerLog) << "Fell off end of switch statement"; return QString("QGC Internal Error"); } }