/**************************************************************************** * * (c) 2009-2016 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ #include "MultiVehicleManager.h" #include "AutoPilotPlugin.h" #include "MAVLinkProtocol.h" #include "UAS.h" #include "QGCApplication.h" #include "FollowMe.h" #include "QGroundControlQmlGlobal.h" #include "ParameterManager.h" #ifdef __mobile__ #include "MobileScreenMgr.h" #endif #include QGC_LOGGING_CATEGORY(MultiVehicleManagerLog, "MultiVehicleManagerLog") const char* MultiVehicleManager::_gcsHeartbeatEnabledKey = "gcsHeartbeatEnabled"; MultiVehicleManager::MultiVehicleManager(QGCApplication* app) : QGCTool(app) , _activeVehicleAvailable(false) , _parameterReadyVehicleAvailable(false) , _activeVehicle(NULL) , _offlineEditingVehicle(NULL) , _firmwarePluginManager(NULL) , _joystickManager(NULL) , _mavlinkProtocol(NULL) , _gcsHeartbeatEnabled(true) { QSettings settings; _gcsHeartbeatEnabled = settings.value(_gcsHeartbeatEnabledKey, true).toBool(); _gcsHeartbeatTimer.setInterval(_gcsHeartbeatRateMSecs); _gcsHeartbeatTimer.setSingleShot(false); connect(&_gcsHeartbeatTimer, &QTimer::timeout, this, &MultiVehicleManager::_sendGCSHeartbeat); if (_gcsHeartbeatEnabled) { _gcsHeartbeatTimer.start(); } } void MultiVehicleManager::setToolbox(QGCToolbox *toolbox) { QGCTool::setToolbox(toolbox); _firmwarePluginManager = _toolbox->firmwarePluginManager(); _joystickManager = _toolbox->joystickManager(); _mavlinkProtocol = _toolbox->mavlinkProtocol(); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); qmlRegisterUncreatableType("QGroundControl.MultiVehicleManager", 1, 0, "MultiVehicleManager", "Reference only"); connect(_mavlinkProtocol, &MAVLinkProtocol::vehicleHeartbeatInfo, this, &MultiVehicleManager::_vehicleHeartbeatInfo); _offlineEditingVehicle = new Vehicle(static_cast(QGroundControlQmlGlobal::offlineEditingFirmwareType()->rawValue().toInt()), static_cast(QGroundControlQmlGlobal::offlineEditingVehicleType()->rawValue().toInt()), _firmwarePluginManager, this); } void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int vehicleMavlinkVersion, int vehicleFirmwareType, int vehicleType) { if (_ignoreVehicleIds.contains(vehicleId) || getVehicleById(vehicleId) || vehicleId == 0) { return; } qCDebug(MultiVehicleManagerLog()) << "Adding new vehicle link:vehicleId:vehicleMavlinkVersion:vehicleFirmwareType:vehicleType " << link->getName() << vehicleId << vehicleMavlinkVersion << vehicleFirmwareType << vehicleType; if (vehicleId == _mavlinkProtocol->getSystemId()) { _app->showMessage(QString("Warning: A vehicle is using the same system id as QGroundControl: %1").arg(vehicleId)); } // QSettings settings; // bool mavlinkVersionCheck = settings.value("VERSION_CHECK_ENABLED", true).toBool(); // if (mavlinkVersionCheck && vehicleMavlinkVersion != MAVLINK_VERSION) { // _ignoreVehicleIds += vehicleId; // _app->showMessage(QString("The MAVLink protocol version on vehicle #%1 and QGroundControl differ! " // "It is unsafe to use different MAVLink versions. " // "QGroundControl therefore refuses to connect to vehicle #%1, which sends MAVLink version %2 (QGroundControl uses version %3).").arg(vehicleId).arg(vehicleMavlinkVersion).arg(MAVLINK_VERSION)); // return; // } Vehicle* vehicle = new Vehicle(link, vehicleId, (MAV_AUTOPILOT)vehicleFirmwareType, (MAV_TYPE)vehicleType, _firmwarePluginManager, _joystickManager); connect(vehicle, &Vehicle::allLinksInactive, this, &MultiVehicleManager::_deleteVehiclePhase1); connect(vehicle->parameterManager(), &ParameterManager::parametersReadyChanged, this, &MultiVehicleManager::_vehicleParametersReadyChanged); _vehicles.append(vehicle); // Send QGC heartbeat ASAP, this allows PX4 to start accepting commands _sendGCSHeartbeat(); emit vehicleAdded(vehicle); if (_vehicles.count() > 1) { qgcApp()->showMessage(tr("Connected to Vehicle %1").arg(vehicleId)); } else { setActiveVehicle(vehicle); } // Mark link as active link->setActive(true); #ifdef __mobile__ if(_vehicles.count() == 1) { //-- Once a vehicle is connected, keep screen from going off qCDebug(MultiVehicleManagerLog) << "QAndroidJniObject::keepScreenOn"; MobileScreenMgr::setKeepScreenOn(true); } #endif } /// This slot is connected to the Vehicle::allLinksDestroyed signal such that the Vehicle is deleted /// and all other right things happen when the Vehicle goes away. void MultiVehicleManager::_deleteVehiclePhase1(Vehicle* vehicle) { qCDebug(MultiVehicleManagerLog) << "_deleteVehiclePhase1" << vehicle; _vehiclesBeingDeleted << vehicle; // Remove from map bool found = false; for (int i=0; i<_vehicles.count(); i++) { if (_vehicles[i] == vehicle) { _vehicles.removeAt(i); found = true; break; } } if (!found) { qWarning() << "Vehicle not found in map!"; } vehicle->setActive(false); vehicle->uas()->shutdownVehicle(); // First we must signal that a vehicle is no longer available. _activeVehicleAvailable = false; _parameterReadyVehicleAvailable = false; emit activeVehicleAvailableChanged(false); emit parameterReadyVehicleAvailableChanged(false); emit vehicleRemoved(vehicle); #ifdef __mobile__ if(_vehicles.count() == 0) { //-- Once no vehicles are connected, we no longer need to keep screen from going off qCDebug(MultiVehicleManagerLog) << "QAndroidJniObject::restoreScreenOn"; MobileScreenMgr::setKeepScreenOn(false); } #endif // We must let the above signals flow through the system as well as get back to the main loop event queue // before we can actually delete the Vehicle. The reason is that Qml may be holding on the references to it. // Even though the above signals should unload any Qml which has references, that Qml will not be destroyed // until we get back to the main loop. So we set a short timer which will then fire after Qt has finished // doing all of its internal nastiness to clean up the Qml. This works for both the normal running case // as well as the unit testing case whichof course has a different signal flow! QTimer::singleShot(20, this, &MultiVehicleManager::_deleteVehiclePhase2); } void MultiVehicleManager::_deleteVehiclePhase2(void) { qCDebug(MultiVehicleManagerLog) << "_deleteVehiclePhase2" << _vehiclesBeingDeleted[0]; /// Qml has been notified of vehicle about to go away and should be disconnected from it by now. /// This means we can now clear the active vehicle property and delete the Vehicle for real. Vehicle* newActiveVehicle = NULL; if (_vehicles.count()) { newActiveVehicle = qobject_cast(_vehicles[0]); } _activeVehicle = newActiveVehicle; emit activeVehicleChanged(newActiveVehicle); if (_activeVehicle) { _activeVehicle->setActive(true); emit activeVehicleAvailableChanged(true); if (_activeVehicle->parameterManager()->parametersReady()) { emit parameterReadyVehicleAvailableChanged(true); } } delete _vehiclesBeingDeleted[0]; _vehiclesBeingDeleted.removeAt(0); } void MultiVehicleManager::setActiveVehicle(Vehicle* vehicle) { qCDebug(MultiVehicleManagerLog) << "setActiveVehicle" << vehicle; if (vehicle != _activeVehicle) { if (_activeVehicle) { _activeVehicle->setActive(false); // The sequence of signals is very important in order to not leave Qml elements connected // to a non-existent vehicle. // First we must signal that there is no active vehicle available. This will disconnect // any existing ui from the currently active vehicle. _activeVehicleAvailable = false; _parameterReadyVehicleAvailable = false; emit activeVehicleAvailableChanged(false); emit parameterReadyVehicleAvailableChanged(false); } // See explanation in _deleteVehiclePhase1 _vehicleBeingSetActive = vehicle; QTimer::singleShot(20, this, &MultiVehicleManager::_setActiveVehiclePhase2); } } void MultiVehicleManager::_setActiveVehiclePhase2(void) { qCDebug(MultiVehicleManagerLog) << "_setActiveVehiclePhase2 _vehicleBeingSetActive" << _vehicleBeingSetActive; // Now we signal the new active vehicle _activeVehicle = _vehicleBeingSetActive; emit activeVehicleChanged(_activeVehicle); // And finally vehicle availability if (_activeVehicle) { _activeVehicle->setActive(true); _activeVehicleAvailable = true; emit activeVehicleAvailableChanged(true); if (_activeVehicle->parameterManager()->parametersReady()) { _parameterReadyVehicleAvailable = true; emit parameterReadyVehicleAvailableChanged(true); } } } void MultiVehicleManager::_vehicleParametersReadyChanged(bool parametersReady) { ParameterManager* paramMgr = dynamic_cast(sender()); if (!paramMgr) { qWarning() << "Dynamic cast failed!"; return; } if (paramMgr->vehicle() == _activeVehicle) { _parameterReadyVehicleAvailable = parametersReady; emit parameterReadyVehicleAvailableChanged(parametersReady); } } void MultiVehicleManager::saveSetting(const QString &name, const QString& value) { QSettings settings; settings.setValue(name, value); } QString MultiVehicleManager::loadSetting(const QString &name, const QString& defaultValue) { QSettings settings; return settings.value(name, defaultValue).toString(); } Vehicle* MultiVehicleManager::getVehicleById(int vehicleId) { for (int i=0; i< _vehicles.count(); i++) { Vehicle* vehicle = qobject_cast(_vehicles[i]); if (vehicle->id() == vehicleId) { return vehicle; } } return NULL; } void MultiVehicleManager::setGcsHeartbeatEnabled(bool gcsHeartBeatEnabled) { if (gcsHeartBeatEnabled != _gcsHeartbeatEnabled) { _gcsHeartbeatEnabled = gcsHeartBeatEnabled; emit gcsHeartBeatEnabledChanged(gcsHeartBeatEnabled); QSettings settings; settings.setValue(_gcsHeartbeatEnabledKey, gcsHeartBeatEnabled); if (gcsHeartBeatEnabled) { _gcsHeartbeatTimer.start(); } else { _gcsHeartbeatTimer.stop(); } } } void MultiVehicleManager::_sendGCSHeartbeat(void) { // Send a heartbeat out on each link QmlObjectListModel* links = _toolbox->linkManager()->links(); for (int i=0; icount(); i++) { LinkInterface* link = links->value(i); if (link->isConnected()) { mavlink_message_t message; mavlink_msg_heartbeat_pack_chan(_mavlinkProtocol->getSystemId(), _mavlinkProtocol->getComponentId(), link->mavlinkChannel(), &message, MAV_TYPE_GCS, // MAV_TYPE MAV_AUTOPILOT_INVALID, // MAV_AUTOPILOT MAV_MODE_MANUAL_ARMED, // MAV_MODE 0, // custom mode MAV_STATE_ACTIVE); // MAV_STATE uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; int len = mavlink_msg_to_send_buffer(buffer, &message); link->writeBytesSafe((const char*)buffer, len); } } } bool MultiVehicleManager::linkInUse(LinkInterface* link, Vehicle* skipVehicle) { for (int i=0; i< _vehicles.count(); i++) { Vehicle* vehicle = qobject_cast(_vehicles[i]); if (vehicle != skipVehicle) { if (vehicle->containsLink(link)) { return true; } } } return false; }