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.
540 lines
20 KiB
540 lines
20 KiB
/*===================================================================== |
|
|
|
QGroundControl Open Source Ground Control Station |
|
|
|
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
|
|
======================================================================*/ |
|
|
|
/// @file |
|
/// @author Don Gagne <don@thegagnes.com> |
|
|
|
#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) |
|
, _currentMissionItem(-1) |
|
{ |
|
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 QList<MissionItem*>& missionItems) |
|
{ |
|
bool skipFirstItem = !_vehicle->firmwarePlugin()->sendHomePositionToVehicle(); |
|
|
|
_missionItems.clear(); |
|
|
|
int firstIndex = skipFirstItem ? 1 : 0; |
|
|
|
for (int i=firstIndex; i<missionItems.count(); i++) { |
|
MissionItem* item = new MissionItem(*missionItems[i]); |
|
_missionItems.append(item); |
|
|
|
item->setIsCurrentItem(i == firstIndex); |
|
|
|
if (skipFirstItem) { |
|
// Home is in sequence 0, remainder of items start at sequence 1 |
|
item->setSequenceNumber(item->sequenceNumber() - 1); |
|
if (item->command() == 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; i<missionCount.count; i++) { |
|
_itemIndicesToRead << i; |
|
} |
|
_requestNextMissionItem(); |
|
} |
|
|
|
|
|
} |
|
|
|
void MissionManager::_requestNextMissionItem(void) |
|
{ |
|
qCDebug(MissionManagerLog) << "_requestNextMissionItem sequenceNumber:" << _itemIndicesToRead[0]; |
|
|
|
if (_itemIndicesToRead.count() == 0) { |
|
_sendError(InternalError, "Internal Error: Call to Vehicle _requestNextMissionItem with no more indices to read"); |
|
return; |
|
} |
|
|
|
mavlink_message_t message; |
|
mavlink_mission_request_t missionRequest; |
|
|
|
missionRequest.target_system = _vehicle->id(); |
|
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(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); |
|
|
|
if (item->command() == MAV_CMD_DO_JUMP) { |
|
// Home is in position 0 |
|
item->setParam1((int)item->param1() + 1); |
|
} |
|
|
|
_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 = _missionItems[missionRequest.seq]; |
|
|
|
missionItem.target_system = _vehicle->id(); |
|
missionItem.target_component = MAV_COMP_ID_MISSIONPLANNER; |
|
missionItem.seq = missionRequest.seq; |
|
missionItem.command = item->command(); |
|
missionItem.param1 = item->param1(); |
|
missionItem.param2 = item->param2(); |
|
missionItem.param3 = item->param3(); |
|
missionItem.param4 = item->param4(); |
|
missionItem.x = item->param5(); |
|
missionItem.y = item->param6(); |
|
missionItem.z = item->param7(); |
|
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: |
|
_handleMissionCurrent(message); |
|
break; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
void MissionManager::_handleMissionCurrent(const mavlink_message_t& message) |
|
{ |
|
mavlink_mission_current_t missionCurrent; |
|
|
|
mavlink_msg_mission_current_decode(&message, &missionCurrent); |
|
|
|
qCDebug(MissionManagerLog) << "_handleMissionCurrent seq:" << missionCurrent.seq; |
|
if (missionCurrent.seq != _currentMissionItem) { |
|
_currentMissionItem = missionCurrent.seq; |
|
emit currentItemChanged(_currentMissionItem); |
|
} |
|
}
|
|
|