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