/*===================================================================== 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 . ======================================================================*/ #include "MockLink.h" #include "QGCLoggingCategory.h" #include "QGCApplication.h" #include #include #include #include QGC_LOGGING_CATEGORY(MockLinkLog, "MockLinkLog") QGC_LOGGING_CATEGORY(MockLinkVerboseLog, "MockLinkVerboseLog") /// @file /// @brief Mock implementation of a Link. /// /// @author Don Gagne enum PX4_CUSTOM_MAIN_MODE { PX4_CUSTOM_MAIN_MODE_MANUAL = 1, PX4_CUSTOM_MAIN_MODE_ALTCTL, PX4_CUSTOM_MAIN_MODE_POSCTL, PX4_CUSTOM_MAIN_MODE_AUTO, PX4_CUSTOM_MAIN_MODE_ACRO, PX4_CUSTOM_MAIN_MODE_OFFBOARD, }; enum PX4_CUSTOM_SUB_MODE_AUTO { PX4_CUSTOM_SUB_MODE_AUTO_READY = 1, PX4_CUSTOM_SUB_MODE_AUTO_TAKEOFF, PX4_CUSTOM_SUB_MODE_AUTO_LOITER, PX4_CUSTOM_SUB_MODE_AUTO_MISSION, PX4_CUSTOM_SUB_MODE_AUTO_RTL, PX4_CUSTOM_SUB_MODE_AUTO_LAND, PX4_CUSTOM_SUB_MODE_AUTO_RTGS }; union px4_custom_mode { struct { uint16_t reserved; uint8_t main_mode; uint8_t sub_mode; }; uint32_t data; float data_float; }; float MockLink::_vehicleLatitude = 47.633033f; float MockLink::_vehicleLongitude = -122.08794f; float MockLink::_vehicleAltitude = 9872.5f; const char* MockConfiguration::_firmwareTypeKey = "FirmwareType"; const char* MockConfiguration::_vehicleTypeKey = "VehicleType"; const char* MockConfiguration::_sendStatusTextKey = "SendStatusText"; MockLink::MockLink(MockConfiguration* config) : _missionItemHandler(this, qgcApp()->toolbox()->mavlinkProtocol()) , _name("MockLink") , _connected(false) , _vehicleSystemId(128) // FIXME: Pull from eventual parameter manager , _vehicleComponentId(200) // FIXME: magic number? , _inNSH(false) , _mavlinkStarted(false) , _mavBaseMode(MAV_MODE_FLAG_MANUAL_INPUT_ENABLED | MAV_MODE_FLAG_CUSTOM_MODE_ENABLED) , _mavState(MAV_STATE_STANDBY) , _firmwareType(MAV_AUTOPILOT_PX4) , _vehicleType(MAV_TYPE_QUADROTOR) , _fileServer(NULL) , _sendStatusText(false) , _apmSendHomePositionOnEmptyList(false) , _sendHomePositionDelayCount(2) { _config = config; if (_config) { _firmwareType = config->firmwareType(); _vehicleType = config->vehicleType(); _sendStatusText = config->sendStatusText(); } union px4_custom_mode px4_cm; px4_cm.data = 0; px4_cm.main_mode = PX4_CUSTOM_MAIN_MODE_MANUAL; _mavCustomMode = px4_cm.data; _fileServer = new MockLinkFileServer(_vehicleSystemId, _vehicleComponentId, this); Q_CHECK_PTR(_fileServer); moveToThread(this); _loadParams(); QObject::connect(this, &MockLink::_incomingBytes, this, &MockLink::_handleIncomingBytes); } MockLink::~MockLink(void) { _disconnect(); } void MockLink::readBytes(void) { // FIXME: This is a bad virtual from LinkInterface? } bool MockLink::_connect(void) { if (!_connected) { _connected = true; start(); emit connected(); } return true; } bool MockLink::_disconnect(void) { if (_connected) { _connected = false; quit(); wait(); emit disconnected(); } return true; } void MockLink::run(void) { QTimer _timer1HzTasks; QTimer _timer10HzTasks; QTimer _timer50HzTasks; QObject::connect(&_timer1HzTasks, &QTimer::timeout, this, &MockLink::_run1HzTasks); QObject::connect(&_timer10HzTasks, &QTimer::timeout, this, &MockLink::_run10HzTasks); QObject::connect(&_timer50HzTasks, &QTimer::timeout, this, &MockLink::_run50HzTasks); _timer1HzTasks.start(1000); _timer10HzTasks.start(100); _timer50HzTasks.start(20); exec(); QObject::disconnect(&_timer1HzTasks, &QTimer::timeout, this, &MockLink::_run1HzTasks); QObject::disconnect(&_timer10HzTasks, &QTimer::timeout, this, &MockLink::_run10HzTasks); QObject::disconnect(&_timer50HzTasks, &QTimer::timeout, this, &MockLink::_run50HzTasks); _missionItemHandler.shutdown(); } void MockLink::_run1HzTasks(void) { if (_mavlinkStarted && _connected) { _sendHeartBeat(); if (_sendHomePositionDelayCount > 0) { // We delay home position a bit to be more realistic _sendHomePositionDelayCount--; } else { _sendHomePosition(); } if (_sendStatusText) { _sendStatusText = false; _sendStatusTextMessages(); } } } void MockLink::_run10HzTasks(void) { if (_mavlinkStarted && _connected) { _sendGpsRawInt(); } } void MockLink::_run50HzTasks(void) { if (_mavlinkStarted && _connected) { } } void MockLink::_loadParams(void) { QFile paramFile; if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) { if (_vehicleType == MAV_TYPE_FIXED_WING) { paramFile.setFileName(":/unittest/APMArduPlaneMockLink.params"); } else { paramFile.setFileName(":/unittest/APMArduCopterMockLink.params"); } } else { paramFile.setFileName(":/unittest/PX4MockLink.params"); } bool success = paramFile.open(QFile::ReadOnly); Q_UNUSED(success); Q_ASSERT(success); QTextStream paramStream(¶mFile); while (!paramStream.atEnd()) { QString line = paramStream.readLine(); if (line.startsWith("#")) { continue; } QStringList paramData = line.split("\t"); Q_ASSERT(paramData.count() == 5); int componentId = paramData.at(1).toInt(); QString paramName = paramData.at(2); QString valStr = paramData.at(3); uint paramType = paramData.at(4).toUInt(); QVariant paramValue; switch (paramType) { case MAV_PARAM_TYPE_REAL32: paramValue = QVariant(valStr.toFloat()); break; case MAV_PARAM_TYPE_UINT32: paramValue = QVariant(valStr.toUInt()); break; case MAV_PARAM_TYPE_INT32: paramValue = QVariant(valStr.toInt()); break; case MAV_PARAM_TYPE_UINT16: paramValue = QVariant((quint16)valStr.toUInt()); break; case MAV_PARAM_TYPE_INT16: paramValue = QVariant((qint16)valStr.toInt()); break; case MAV_PARAM_TYPE_UINT8: paramValue = QVariant((quint8)valStr.toUInt()); break; case MAV_PARAM_TYPE_INT8: paramValue = QVariant((qint8)valStr.toUInt()); break; default: qCritical() << "Unknown type" << paramType; paramValue = QVariant(valStr.toInt()); break; } qCDebug(MockLinkVerboseLog) << "Loading param" << paramName << paramValue; _mapParamName2Value[componentId][paramName] = paramValue; _mapParamName2MavParamType[paramName] = static_cast(paramType); } } void MockLink::_sendHeartBeat(void) { mavlink_message_t msg; mavlink_msg_heartbeat_pack(_vehicleSystemId, _vehicleComponentId, &msg, MAV_TYPE_QUADROTOR, // MAV_TYPE _firmwareType, // MAV_AUTOPILOT _mavBaseMode, // MAV_MODE _mavCustomMode, // custom mode _mavState); // MAV_STATE respondWithMavlinkMessage(msg); } void MockLink::respondWithMavlinkMessage(const mavlink_message_t& msg) { uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; int cBuffer = mavlink_msg_to_send_buffer(buffer, &msg); QByteArray bytes((char *)buffer, cBuffer); emit bytesReceived(this, bytes); } /// @brief Called when QGC wants to write bytes to the MAV void MockLink::writeBytes(const char* bytes, qint64 cBytes) { // Package up the data so we can signal it over to the right thread QByteArray byteArray(bytes, cBytes); emit _incomingBytes(byteArray); } /// @brief Handles bytes from QGC on the thread void MockLink::_handleIncomingBytes(const QByteArray bytes) { if (_inNSH) { _handleIncomingNSHBytes(bytes.constData(), bytes.count()); } else { if (bytes.startsWith(QByteArray("\r\r\r"))) { _inNSH = true; _handleIncomingNSHBytes(&bytes.constData()[3], bytes.count() - 3); } _handleIncomingMavlinkBytes((uint8_t *)bytes.constData(), bytes.count()); } } /// @brief Handle incoming bytes which are meant to be interpreted by the NuttX shell void MockLink::_handleIncomingNSHBytes(const char* bytes, int cBytes) { Q_UNUSED(cBytes); // Drop back out of NSH if (cBytes == 4 && bytes[0] == '\r' && bytes[1] == '\r' && bytes[2] == '\r') { _inNSH = false; return; } if (cBytes > 0) { qDebug() << "NSH:" << (const char*)bytes; if (strncmp(bytes, "sh /etc/init.d/rc.usb\n", cBytes) == 0) { // This is the mavlink start command _mavlinkStarted = true; } } } /// @brief Handle incoming bytes which are meant to be handled by the mavlink protocol void MockLink::_handleIncomingMavlinkBytes(const uint8_t* bytes, int cBytes) { mavlink_message_t msg; mavlink_status_t comm; for (qint64 i=0; i= 0 && request.param_index < _mapParamName2Value[componentId].count()); QString key = _mapParamName2Value[componentId].keys().at(request.param_index); Q_ASSERT(key.length() <= MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN); strcpy(paramId, key.toLocal8Bit().constData()); } Q_ASSERT(_mapParamName2Value[componentId].contains(paramId)); Q_ASSERT(_mapParamName2MavParamType.contains(paramId)); mavlink_msg_param_value_pack(_vehicleSystemId, componentId, // component id &responseMsg, // Outgoing message paramId, // Parameter name _floatUnionForParam(componentId, paramId), // Parameter value _mapParamName2MavParamType[paramId], // Parameter type _mapParamName2Value[componentId].count(), // Total number of parameters _mapParamName2Value[componentId].keys().indexOf(paramId)); // Index of this parameter respondWithMavlinkMessage(responseMsg); } void MockLink::emitRemoteControlChannelRawChanged(int channel, uint16_t raw) { uint16_t chanRaw[18]; for (int i=0; i<18; i++) { chanRaw[i] = UINT16_MAX; } chanRaw[channel] = raw; mavlink_message_t responseMsg; mavlink_msg_rc_channels_pack(_vehicleSystemId, _vehicleComponentId, &responseMsg, // Outgoing message 0, // time since boot, ignored 18, // channel count chanRaw[0], // channel raw value chanRaw[1], // channel raw value chanRaw[2], // channel raw value chanRaw[3], // channel raw value chanRaw[4], // channel raw value chanRaw[5], // channel raw value chanRaw[6], // channel raw value chanRaw[7], // channel raw value chanRaw[8], // channel raw value chanRaw[9], // channel raw value chanRaw[10], // channel raw value chanRaw[11], // channel raw value chanRaw[12], // channel raw value chanRaw[13], // channel raw value chanRaw[14], // channel raw value chanRaw[15], // channel raw value chanRaw[16], // channel raw value chanRaw[17], // channel raw value 0); // rss respondWithMavlinkMessage(responseMsg); } void MockLink::_handleFTP(const mavlink_message_t& msg) { Q_ASSERT(_fileServer); _fileServer->handleFTPMessage(msg); } void MockLink::_handleCommandLong(const mavlink_message_t& msg) { mavlink_command_long_t request; mavlink_msg_command_long_decode(&msg, &request); if (request.command == MAV_CMD_COMPONENT_ARM_DISARM) { if (request.param1 == 0.0f) { _mavBaseMode &= ~MAV_MODE_FLAG_SAFETY_ARMED; } else { _mavBaseMode |= MAV_MODE_FLAG_SAFETY_ARMED; } } } void MockLink::setMissionItemFailureMode(MockLinkMissionItemHandler::FailureMode_t failureMode, bool firstTimeOnly) { _missionItemHandler.setMissionItemFailureMode(failureMode, firstTimeOnly); } void MockLink::_sendHomePosition(void) { // APM stack does not yet support HOME_POSITION if (_firmwareType != MAV_AUTOPILOT_ARDUPILOTMEGA) { mavlink_message_t msg; float bogus[4]; bogus[0] = 0.0f; bogus[1] = 0.0f; bogus[2] = 0.0f; bogus[3] = 0.0f; mavlink_msg_home_position_pack(_vehicleSystemId, _vehicleComponentId, &msg, (int32_t)(_vehicleLatitude * 1E7), (int32_t)(_vehicleLongitude * 1E7), (int32_t)(_vehicleAltitude * 1000), 0.0f, 0.0f, 0.0f, &bogus[0], 0.0f, 0.0f, 0.0f); respondWithMavlinkMessage(msg); } } void MockLink::_sendGpsRawInt(void) { static uint64_t timeTick = 0; mavlink_message_t msg; mavlink_msg_gps_raw_int_pack(_vehicleSystemId, _vehicleComponentId, &msg, timeTick++, // time since boot 3, // 3D fix (int32_t)(_vehicleLatitude * 1E7), (int32_t)(_vehicleLongitude * 1E7), (int32_t)(_vehicleAltitude * 1000), UINT16_MAX, UINT16_MAX, // HDOP/VDOP not known UINT16_MAX, // velocity not known UINT16_MAX, // course over ground not known 8); // satellite count respondWithMavlinkMessage(msg); } void MockLink::_sendStatusTextMessages(void) { struct StatusMessage { MAV_SEVERITY severity; const char* msg; }; static const struct StatusMessage rgMessages[] = { { MAV_SEVERITY_INFO, "#Testing audio output" }, { MAV_SEVERITY_EMERGENCY, "Status text emergency" }, { MAV_SEVERITY_ALERT, "Status text alert" }, { MAV_SEVERITY_CRITICAL, "Status text critical" }, { MAV_SEVERITY_ERROR, "Status text error" }, { MAV_SEVERITY_WARNING, "Status text warning" }, { MAV_SEVERITY_NOTICE, "Status text notice" }, { MAV_SEVERITY_INFO, "Status text info" }, { MAV_SEVERITY_DEBUG, "Status text debug" }, }; for (size_t i=0; iseverity, status->msg); respondWithMavlinkMessage(msg); } } MockConfiguration::MockConfiguration(const QString& name) : LinkConfiguration(name) , _firmwareType(MAV_AUTOPILOT_PX4) , _vehicleType(MAV_TYPE_QUADROTOR) , _sendStatusText(false) { } MockConfiguration::MockConfiguration(MockConfiguration* source) : LinkConfiguration(source) { _firmwareType = source->_firmwareType; _vehicleType = source->_vehicleType; _sendStatusText = source->_sendStatusText; } void MockConfiguration::copyFrom(LinkConfiguration *source) { LinkConfiguration::copyFrom(source); MockConfiguration* usource = dynamic_cast(source); if (!usource) { qWarning() << "dynamic_cast failed" << source << usource; return; } _firmwareType = usource->_firmwareType; _vehicleType = usource->_vehicleType; _sendStatusText = usource->_sendStatusText; } void MockConfiguration::saveSettings(QSettings& settings, const QString& root) { settings.beginGroup(root); settings.setValue(_firmwareTypeKey, (int)_firmwareType); settings.setValue(_vehicleTypeKey, (int)_vehicleType); settings.setValue(_sendStatusTextKey, _sendStatusText); settings.sync(); settings.endGroup(); } void MockConfiguration::loadSettings(QSettings& settings, const QString& root) { settings.beginGroup(root); _firmwareType = (MAV_AUTOPILOT)settings.value(_firmwareTypeKey, (int)MAV_AUTOPILOT_PX4).toInt(); _vehicleType = (MAV_TYPE)settings.value(_vehicleTypeKey, (int)MAV_TYPE_QUADROTOR).toInt(); _sendStatusText = settings.value(_sendStatusTextKey, false).toBool(); settings.endGroup(); } void MockConfiguration::updateSettings() { if (_link) { MockLink* ulink = dynamic_cast(_link); if (ulink) { // Restart connect not supported qWarning() << "updateSettings not supported"; //ulink->_restartConnection(); } } }