QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MultiVehicleManager.cc
Go to the documentation of this file.
2#include "MAVLinkProtocol.h"
3#include "AppMessages.h"
4#include "ParameterManager.h"
5#include "SettingsManager.h"
6#include "MavlinkSettings.h"
8#include "QGCCorePlugin.h"
9#include "QGCOptions.h"
10#include "LinkManager.h"
11#include "Vehicle.h"
12#include "VehicleLinkManager.h"
13#include "LinkInterface.h"
14#include "QmlObjectListModel.h"
15#ifdef Q_OS_IOS
16#include "MobileScreenMgr.h"
17#elif defined(Q_OS_ANDROID)
18#include "AndroidInterface.h"
19#endif
20#include "QGCLoggingCategory.h"
21
22#include <QtCore/QApplicationStatic>
23#include <QtCore/QTimer>
24
25QGC_LOGGING_CATEGORY(MultiVehicleManagerLog, "Vehicle.MultiVehicleManager")
26
27Q_APPLICATION_STATIC(MultiVehicleManager, _multiVehicleManagerInstance);
28
30 : QObject(parent)
31 , _gcsHeartbeatTimer(new QTimer(this))
32 , _vehicles(new QmlObjectListModel(this))
33 , _selectedVehicles(new QmlObjectListModel(this))
34{
35 qCDebug(MultiVehicleManagerLog) << this;
36}
37
39{
40 qCDebug(MultiVehicleManagerLog) << this;
41}
42
44{
45 return _multiVehicleManagerInstance();
46}
47
49{
50 if (_initialized) {
51 return;
52 }
53
54 _offlineEditingVehicle = new Vehicle(Vehicle::MAV_AUTOPILOT_TRACK, Vehicle::MAV_TYPE_TRACK, this);
55
56 (void) connect(MAVLinkProtocol::instance(), &MAVLinkProtocol::vehicleHeartbeatInfo, this, &MultiVehicleManager::_vehicleHeartbeatInfo);
57
58 _gcsHeartbeatTimer->setInterval(kGCSHeartbeatRateMSecs);
59 _gcsHeartbeatTimer->setSingleShot(false);
60 (void) connect(_gcsHeartbeatTimer, &QTimer::timeout, this, &MultiVehicleManager::_sendGCSHeartbeat);
61 _gcsHeartbeatTimer->start();
62
63 _initialized = true;
64}
65
66void MultiVehicleManager::_vehicleHeartbeatInfo(LinkInterface* link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType)
67{
68 if (componentId != MAV_COMP_ID_AUTOPILOT1) {
69 // Don't create vehicles for components other than the autopilot
70 qCDebug(MultiVehicleManagerLog) << "Ignoring heartbeat from unknown component port:vehicleId:componentId:fwType:vehicleType"
71 << link->linkConfiguration()->name()
72 << vehicleId
73 << componentId
74 << vehicleFirmwareType
75 << vehicleType;
76 return;
77 }
78
79 // When you flash a new ArduCopter it does not set a FRAME_CLASS for some reason. This is the only ArduPilot variant which
80 // works this way. Because of this the vehicle type is not known at first connection. In order to make QGC work reasonably
81 // we assume ArduCopter for this case.
82 if ((vehicleType == MAV_TYPE_GENERIC) && (vehicleFirmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA)) {
83 vehicleType = MAV_TYPE_QUADROTOR;
84 }
85
86 switch (vehicleType) {
87 case MAV_TYPE_GCS:
88 case MAV_TYPE_ONBOARD_CONTROLLER:
89 case MAV_TYPE_GIMBAL:
90 case MAV_TYPE_ADSB:
91 // These are not vehicles, so don't create a vehicle for them
92 return;
93 default:
94 break;
95 }
96
97 if ((_vehicles->count() > 0) && !QGCCorePlugin::instance()->options()->multiVehicleEnabled()) {
98 return;
99 }
100
101 if (_ignoreVehicleIds.contains(vehicleId) || getVehicleById(vehicleId) || (vehicleId == 0)) {
102 return;
103 }
104
105 qCDebug(MultiVehicleManagerLog) << "Adding new vehicle link:vehicleId:componentId:vehicleFirmwareType:vehicleType "
106 << link->linkConfiguration()->name()
107 << vehicleId
108 << componentId
109 << vehicleFirmwareType
110 << vehicleType;
111
112 if (vehicleId == MAVLinkProtocol::instance()->getSystemId()) {
113 QGC::showAppMessage(tr("Warning: A vehicle is using the same system id as %1: %2").arg(QCoreApplication::applicationName()).arg(vehicleId));
114 }
115
116 Vehicle *const vehicle = new Vehicle(link, vehicleId, componentId, (MAV_AUTOPILOT)vehicleFirmwareType, (MAV_TYPE)vehicleType, this);
117 (void) connect(vehicle->vehicleLinkManager(), &VehicleLinkManager::allLinksRemoved, this, &MultiVehicleManager::_deleteVehiclePhase1);
118 (void) connect(vehicle->parameterManager(), &ParameterManager::parametersReadyChanged, this, &MultiVehicleManager::_vehicleParametersReadyChanged);
119
120 _vehicles->append(vehicle);
121
122 // Send QGC heartbeat ASAP, this allows PX4 to start accepting commands
123 _sendGCSHeartbeat();
124
125 SettingsManager::instance()->firmwareUpgradeSettings()->defaultFirmwareType()->setRawValue(vehicleFirmwareType);
126
127 emit vehicleAdded(vehicle);
128
129 if (_vehicles->count() > 1) {
130 QGC::showAppMessage(tr("Connected to Vehicle %1").arg(vehicleId));
131 } else {
132 setActiveVehicle(vehicle);
133 }
134
135#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
136 if (_vehicles->count() == 1) {
137 qCDebug(MultiVehicleManagerLog) << "keepScreenOn";
138 #if defined(Q_OS_ANDROID)
140 #elif defined(Q_OS_IOS)
141 MobileScreenMgr::setKeepScreenOn(true);
142 #endif
143 }
144#endif
145}
146
147void MultiVehicleManager::_deleteVehiclePhase1(Vehicle *vehicle)
148{
149 qCDebug(MultiVehicleManagerLog) << Q_FUNC_INFO << vehicle;
150
151 bool found = false;
152 for (int i = 0; i < _vehicles->count(); i++) {
153 if (_vehicles->get(i) == vehicle) {
154 (void) _vehicles->removeAt(i);
155 found = true;
156 break;
157 }
158 }
159
160 if (!found) {
161 qCWarning(MultiVehicleManagerLog) << "Vehicle not found in map!";
162 return;
163 }
164
165 deselectVehicle(vehicle->id());
166
167 _setActiveVehicleAvailable(false);
168 _setParameterReadyVehicleAvailable(false);
169 emit vehicleRemoved(vehicle);
170
171#if defined(Q_OS_ANDROID) || defined (Q_OS_IOS)
172 if (_vehicles->count() == 0) {
173 qCDebug(MultiVehicleManagerLog) << "restoreScreenOn";
174 #if defined(Q_OS_ANDROID)
176 #elif defined(Q_OS_IOS)
177 MobileScreenMgr::setKeepScreenOn(false);
178 #endif
179 }
180#endif
181
182 // We must let the above signals flow through the system as well as get back to the main loop event queue
183 // before we can actually delete the Vehicle. The reason is that Qml may be holding on to references to it.
184 // Even though the above signals should unload any Qml which has references, that Qml will not be destroyed
185 // until we get back to the main loop. So we set a short timer which will then fire after Qt has finished
186 // doing all of its internal nastiness to clean up the Qml. This works for both the normal running case
187 // as well as the unit testing case which of course has a different signal flow!
188 QTimer::singleShot(20, this, [this, vehicle]() {
189 _deleteVehiclePhase2(vehicle);
190 });
191}
192
193void MultiVehicleManager::_deleteVehiclePhase2(Vehicle *vehicle)
194{
195 qCDebug(MultiVehicleManagerLog) << Q_FUNC_INFO << vehicle;
196
199
200 Vehicle *newActiveVehicle = nullptr;
201 if (_vehicles->count() > 0) {
202 newActiveVehicle = qobject_cast<Vehicle*>(_vehicles->get(0));
203 }
204
205 _setActiveVehicle(newActiveVehicle);
206
207 if (_activeVehicle) {
208 _setActiveVehicleAvailable(true);
209 if (_activeVehicle->parameterManager()->parametersReady()) {
210 _setParameterReadyVehicleAvailable(true);
211 }
212 }
213
214 vehicle->deleteLater();
215}
216
218{
219 qCDebug(MultiVehicleManagerLog) << Q_FUNC_INFO << vehicle;
220
221 if (vehicle != _activeVehicle) {
222 if (_activeVehicle) {
223 // The sequence of signals is very important in order to not leave Qml elements connected
224 // to a non-existent vehicle.
225
226 // First we must signal that there is no active vehicle available. This will disconnect
227 // any existing ui from the currently active vehicle.
228 _setActiveVehicleAvailable(false);
229 _setParameterReadyVehicleAvailable(false);
230 }
231
232 QTimer::singleShot(20, this, [this, vehicle]() {
233 _setActiveVehiclePhase2(vehicle);
234 });
235 }
236}
237
238void MultiVehicleManager::_setActiveVehiclePhase2(Vehicle *vehicle)
239{
240 qCDebug(MultiVehicleManagerLog) << Q_FUNC_INFO << vehicle;
241
242 _setActiveVehicle(vehicle);
243
244 if (_activeVehicle) {
245 _setActiveVehicleAvailable(true);
246
247 if (_activeVehicle->parameterManager()->parametersReady()) {
248 _setParameterReadyVehicleAvailable(true);
249 }
250 }
251}
252
253void MultiVehicleManager::_vehicleParametersReadyChanged(bool parametersReady)
254{
255 ParameterManager *const paramMgr = qobject_cast<ParameterManager*>(sender());
256 if (!paramMgr) {
257 return;
258 }
259
260 if (paramMgr->vehicle() == _activeVehicle) {
261 _setParameterReadyVehicleAvailable(parametersReady);
262 }
263}
264
265void MultiVehicleManager::_sendGCSHeartbeat()
266{
267 if (!SettingsManager::instance()->mavlinkSettings()->sendGCSHeartbeat()->rawValue().toBool()) {
268 return;
269 }
270
271 const QList<SharedLinkInterfacePtr> sharedLinks = LinkManager::instance()->links();
272 for (const SharedLinkInterfacePtr &link: sharedLinks) {
273 if (!link->isConnected()) {
274 continue;
275 }
276
277 const SharedLinkConfigurationPtr linkConfiguration = link->linkConfiguration();
278 if (linkConfiguration->isHighLatency()) {
279 continue;
280 }
281
282 mavlink_message_t message{};
283 (void) mavlink_msg_heartbeat_pack_chan(
286 link->mavlinkChannel(),
287 &message,
288 MAV_TYPE_GCS,
289 MAV_AUTOPILOT_INVALID,
290 MAV_MODE_MANUAL_ARMED,
291 0,
292 MAV_STATE_ACTIVE
293 );
294
295 link->sendMessageThreadSafe(message);
296 }
297}
298
300{
301 if(!_vehicleSelected(vehicleId)) {
302 Vehicle *const vehicle = getVehicleById(vehicleId);
303 _selectedVehicles->append(vehicle);
304 return;
305 }
306}
307
309{
310 for (int i = 0; i < _selectedVehicles->count(); i++) {
311 Vehicle *const vehicle = qobject_cast<Vehicle*>(_selectedVehicles->get(i));
312 if (vehicle->id() == vehicleId) {
313 _selectedVehicles->removeAt(i);
314 return;
315 }
316 }
317}
318
320{
321 _selectedVehicles->clear();
322}
323
324bool MultiVehicleManager::_vehicleSelected(int vehicleId)
325{
326 for (int i = 0; i < _selectedVehicles->count(); i++) {
327 Vehicle *const vehicle = qobject_cast<Vehicle*>(_selectedVehicles->get(i));
328 if (vehicle->id() == vehicleId) {
329 return true;
330 }
331 }
332 return false;
333}
334
336{
337 for (int i = 0; i < _vehicles->count(); i++) {
338 Vehicle *const vehicle = qobject_cast<Vehicle*>(_vehicles->get(i));
339 if (vehicle->id() == vehicleId) {
340 return vehicle;
341 }
342 }
343
344 return nullptr;
345}
346
347void MultiVehicleManager::_setActiveVehicle(Vehicle *vehicle)
348{
349 if (vehicle != _activeVehicle) {
350 _activeVehicle = vehicle;
351 emit activeVehicleChanged(vehicle);
352 }
353}
354
355void MultiVehicleManager::_setActiveVehicleAvailable(bool activeVehicleAvailable)
356{
357 if (activeVehicleAvailable != _activeVehicleAvailable) {
358 _activeVehicleAvailable = activeVehicleAvailable;
360 }
361}
362
363void MultiVehicleManager::_setParameterReadyVehicleAvailable(bool parametersReady)
364{
365 if (parametersReady != _parameterReadyVehicleAvailable) {
366 _parameterReadyVehicleAvailable = parametersReady;
368 }
369}
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Q_APPLICATION_STATIC(MultiVehicleManager, _multiVehicleManagerInstance)
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
The link interface defines the interface for all links used to communicate with the ground station ap...
uint8_t mavlinkChannel() const
void sendMessageThreadSafe(mavlink_message_t &message)
virtual bool isConnected() const =0
SharedLinkConfigurationPtr linkConfiguration()
static LinkManager * instance()
QList< SharedLinkInterfacePtr > links()
static int getComponentId()
void vehicleHeartbeatInfo(LinkInterface *link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType)
static MAVLinkProtocol * instance()
int getSystemId() const
bool activeVehicleAvailable() const
static MultiVehicleManager * instance()
void activeVehicleAvailableChanged(bool activeVehicleAvailable)
Q_INVOKABLE void deselectAllVehicles()
Q_INVOKABLE void selectVehicle(int vehicleId)
void vehicleAdded(Vehicle *vehicle)
Q_INVOKABLE void deselectVehicle(int vehicleId)
void vehicleRemoved(Vehicle *vehicle)
void parameterReadyVehicleAvailableChanged(bool parameterReadyVehicleAvailable)
void activeVehicleChanged(Vehicle *activeVehicle)
Q_INVOKABLE Vehicle * getVehicleById(int vehicleId) const
void setActiveVehicle(Vehicle *vehicle)
void parametersReadyChanged(bool parametersReady)
bool parametersReady() const
virtual QGCOptions * options()
static QGCCorePlugin * instance()
virtual bool multiVehicleEnabled() const
Definition QGCOptions.h:123
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
Q_INVOKABLE QObject * get(int index)
QObject * removeAt(int index)
int count() const override final
void clear() override final
FirmwareUpgradeSettings * firmwareUpgradeSettings() const
static SettingsManager * instance()
void allLinksRemoved(Vehicle *vehicle)
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:579
int id() const
Definition Vehicle.h:429
static const MAV_AUTOPILOT MAV_AUTOPILOT_TRACK
Definition Vehicle.h:128
static const MAV_TYPE MAV_TYPE_TRACK
Definition Vehicle.h:129
ParameterManager * parameterManager()
Definition Vehicle.h:577
void setKeepScreenOn(bool on)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9