/*===================================================================== 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) , _dedicatedLink(NULL) , _ackTimeoutTimer(NULL) , _retryAck(AckNone) , _readTransactionInProgress(false) , _writeTransactionInProgress(false) { 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(); _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; } // Prime write list for (int i=0; i<_missionItems.count(); i++) { _itemIndicesToWrite << i; } _writeTransactionInProgress = true; mavlink_message_t message; mavlink_mission_count_t missionCount; 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); _dedicatedLink = _vehicle->priorityLink(); _vehicle->sendMessageOnLink(_dedicatedLink, message); _startAckTimeout(AckMissionRequest); emit inProgressChanged(true); } void MissionManager::requestMissionItems(void) { qCDebug(MissionManagerLog) << "requestMissionItems read sequence"; mavlink_message_t message; mavlink_mission_request_list_t request; _requestItemRetryCount = 0; _itemIndicesToRead.clear(); _readTransactionInProgress = true; _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); _dedicatedLink = _vehicle->priorityLink(); _vehicle->sendMessageOnLink(_dedicatedLink, message); _startAckTimeout(AckMissionCount); emit inProgressChanged(true); } 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; } _sendError(AckTimeoutError, QString("Vehicle did not respond to mission item communication: %1").arg(_ackTypeToString(timedOutAck))); _finishTransaction(false); } 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) { _sendError(ProtocolOrderError, QString("Vehicle responded incorrectly to mission item protocol sequence: %1:%2").arg(_ackTypeToString(savedRetryAck)).arg(_ackTypeToString(expectedAck))); _finishTransaction(false); 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->sendMessageOnLink(_dedicatedLink, message); _finishTransaction(true); emit newMissionItemsAvailable(); } void MissionManager::_handleMissionCount(const mavlink_message_t& message) { mavlink_mission_count_t missionCount; if (!_stopAckTimeout(AckMissionCount)) { return; } mavlink_msg_mission_count_decode(&message, &missionCount); qCDebug(MissionManagerLog) << "_handleMissionCount count:" << missionCount.count; if (missionCount.count == 0) { _readTransactionComplete(); } else { // Prime read list for (int i=0; iid(); missionRequest.target_component = MAV_COMP_ID_MISSIONPLANNER; missionRequest.seq = _itemIndicesToRead[0]; mavlink_msg_mission_request_encode(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(), qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(), &message, &missionRequest); _vehicle->sendMessageOnLink(_dedicatedLink, 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 (_itemIndicesToRead.contains(missionItem.seq)) { _requestItemRetryCount = 0; _itemIndicesToRead.removeOne(missionItem.seq); MissionItem* item = new MissionItem(_vehicle, 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); } else { qCDebug(MissionManagerLog) << "_handleMissionItem mission item received item index which was not requested, disregrarding:" << missionItem.seq; if (++_requestItemRetryCount > _maxRetryCount) { _sendError(RequestRangeError, QString("Vehicle would not send item %1 after max retries. Read from Vehicle failed.").arg(_itemIndicesToRead[0])); _finishTransaction(false); return; } } if (_itemIndicesToRead.count() == 0) { _readTransactionComplete(); } else { _requestNextMissionItem(); } } void MissionManager::_clearMissionItems(void) { _itemIndicesToRead.clear(); _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 (!_itemIndicesToWrite.contains(missionRequest.seq)) { if (missionRequest.seq > _missionItems.count()) { _sendError(RequestRangeError, QString("Vehicle requested item outside range, count:request %1:%2. Send to Vehicle failed.").arg(_missionItems.count()).arg(missionRequest.seq)); _finishTransaction(false); return; } else { qCDebug(MissionManagerLog) << "_handleMissionRequest sequence number requested which has already been sent, sending again:" << missionRequest.seq; } } else { _itemIndicesToWrite.removeOne(missionRequest.seq); } 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->sendMessageOnLink(_dedicatedLink, messageOut); _startAckTimeout(AckMissionRequest); } void MissionManager::_handleMissionAck(const mavlink_message_t& message) { mavlink_mission_ack_t missionAck; // Save the 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. _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 _sendError(VehicleError, QString("Vehicle returned error: %1.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); _finishTransaction(false); break; case AckMissionItem: // MISSION_ITEM expected _sendError(VehicleError, QString("Vehicle returned error: %1. Partial list of mission items may have been returned.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); _finishTransaction(false); break; case AckMissionRequest: // MISSION_REQUEST is expected, or MISSION_ACK to end sequence if (missionAck.type == MAV_MISSION_ACCEPTED) { if (_itemIndicesToWrite.count() == 0) { qCDebug(MissionManagerLog) << "_handleMissionAck write sequence complete"; _finishTransaction(true); } else { _sendError(MissingRequestsError, QString("Vehicle did not request all items during write sequence, missed count %1. Vehicle only has partial list of mission items.").arg(_itemIndicesToWrite.count())); _finishTransaction(false); } } else { _sendError(VehicleError, QString("Vehicle returned error: %1. Vehicle only has partial list of mission items.").arg(_missionResultToString((MAV_MISSION_RESULT)missionAck.type))); _finishTransaction(false); } 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) { qCDebug(MissionManagerLog) << "Sending error" << errorCode << errorMsg; emit error(errorCode, errorMsg); } 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"); } } void MissionManager::_finishTransaction(bool success) { if (!success && _readTransactionInProgress) { // Read from vehicle failed, clear partial list _missionItems.clear(); emit newMissionItemsAvailable(); } _readTransactionInProgress = false; _writeTransactionInProgress = false; _itemIndicesToRead.clear(); _itemIndicesToWrite.clear(); emit inProgressChanged(false); } bool MissionManager::inProgress(void) { return _readTransactionInProgress || _writeTransactionInProgress; }