QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
InitialConnectStateMachine.cc
Go to the documentation of this file.
2#include "Vehicle.h"
3#include "QGCCorePlugin.h"
4#include "QGCOptions.h"
5#include "FirmwarePlugin.h"
6#include "ParameterManager.h"
8#include "MissionManager.h"
9#include "StandardModes.h"
10#include "GeoFenceManager.h"
11#include "RallyPointManager.h"
12#include "QGCLoggingCategory.h"
13
14#include <cstring>
15
16QGC_LOGGING_CATEGORY(InitialConnectStateMachineLog, "Vehicle.InitialConnectStateMachine")
17
18// ============================================================================
19// InitialConnectStateMachine Implementation
20// ============================================================================
21
23 : QGCStateMachine(QStringLiteral("InitialConnectStateMachine"), vehicle, parent)
24{
25 _createStates();
26 _wireTransitions();
27 _wireProgressTracking();
28 _wireTimeoutHandling();
29
30 setInitialState(_stateAutopilotVersion);
31}
32
36
38{
39 resetProgress();
41}
42
43void InitialConnectStateMachine::_createStates()
44{
45 // State 0: Request autopilot version
46 _stateAutopilotVersion = new RetryableRequestMessageState(
47 QStringLiteral("RequestAutopilotVersion"),
48 this,
49 MAVLINK_MSG_ID_AUTOPILOT_VERSION,
50 [this](Vehicle*, const mavlink_message_t& message) {
51 _handleAutopilotVersionSuccess(message);
52 },
53 _maxRetries,
54 MAV_COMP_ID_AUTOPILOT1,
55 _timeoutAutopilotVersion
56 );
57 _stateAutopilotVersion->setSkipPredicate([this]() {
58 return _shouldSkipAutopilotVersionRequest();
59 });
60 _stateAutopilotVersion->setFailureHandler([this](auto, auto) {
61 _handleAutopilotVersionFailure();
62 });
63
64 // State 1: Request standard modes
65 _stateStandardModes = new AsyncFunctionState(
66 QStringLiteral("RequestStandardModes"),
67 this,
68 [this](AsyncFunctionState* state) { _requestStandardModes(state); },
69 _timeoutStandardModes
70 );
71
72 // State 2: Request component information
73 _stateCompInfo = new AsyncFunctionState(
74 QStringLiteral("RequestCompInfo"),
75 this,
76 [this](AsyncFunctionState* state) { _requestCompInfo(state); },
77 _timeoutCompInfo
78 );
79
80 // State 3: Request parameters
81 _stateParameters = new AsyncFunctionState(
82 QStringLiteral("RequestParameters"),
83 this,
84 [this](AsyncFunctionState* state) { _requestParameters(state); },
85 _timeoutParameters
86 );
87
88 // State 4: Request mission (skippable)
89 _stateMission = new SkippableAsyncState(
90 QStringLiteral("RequestMission"),
91 this,
92 [this]() { return _shouldSkipForLinkType() || !_hasPrimaryLink(); },
93 [this](SkippableAsyncState* state) { _requestMission(state); },
94 []() {
95 qCDebug(InitialConnectStateMachineLog) << "Skipping mission load";
96 },
97 _timeoutMission
98 );
99
100 // State 5: Request geofence (skippable)
101 _stateGeoFence = new SkippableAsyncState(
102 QStringLiteral("RequestGeoFence"),
103 this,
104 [this]() {
105 return _shouldSkipForLinkType() || !_hasPrimaryLink() ||
106 !vehicle()->_geoFenceManager->supported();
107 },
108 [this](SkippableAsyncState* state) { _requestGeoFence(state); },
109 []() {
110 qCDebug(InitialConnectStateMachineLog) << "Skipping geofence load";
111 },
112 _timeoutGeoFence
113 );
114
115 // State 6: Request rally points (skippable)
116 _stateRallyPoints = new SkippableAsyncState(
117 QStringLiteral("RequestRallyPoints"),
118 this,
119 [this]() {
120 return _shouldSkipForLinkType() || !_hasPrimaryLink() ||
121 !vehicle()->_rallyPointManager->supported();
122 },
123 [this](SkippableAsyncState* state) { _requestRallyPoints(state); },
124 [this]() {
125 qCDebug(InitialConnectStateMachineLog) << "Skipping rally points load";
126 // Mark plan request complete when skipping
127 vehicle()->_initialPlanRequestComplete = true;
128 emit vehicle()->initialPlanRequestCompleteChanged(true);
129 },
130 _timeoutRallyPoints
131 );
132
133 // State 7: Signal completion
134 // Use RetryState with zero retries so completion participates in the unified
135 // retry/error state family while preserving immediate success behavior.
136 _stateComplete = new RetryState(
137 QStringLiteral("SignalComplete"),
138 this,
139 [this]() {
140 _signalComplete();
141 return true;
142 },
143 0,
144 0,
146 );
147
148 // Final state
149 _stateFinal = new QGCFinalState(QStringLiteral("Final"), this);
150}
151
152void InitialConnectStateMachine::_wireTransitions()
153{
154 // Linear progression - use completed() for WaitStateBase-derived states (more semantic)
155 _stateAutopilotVersion->addTransition(_stateAutopilotVersion, &WaitStateBase::completed, _stateStandardModes);
156 _stateStandardModes->addTransition(_stateStandardModes, &WaitStateBase::completed, _stateCompInfo);
157 _stateCompInfo->addTransition(_stateCompInfo, &WaitStateBase::completed, _stateParameters);
158 _stateParameters->addTransition(_stateParameters, &WaitStateBase::completed, _stateMission);
159
160 // SkippableAsyncStates: both completed and skipped go to next state
161 _stateMission->addTransition(_stateMission, &WaitStateBase::completed, _stateGeoFence);
162 _stateMission->addTransition(_stateMission, &SkippableAsyncState::skipped, _stateGeoFence);
163
164 _stateGeoFence->addTransition(_stateGeoFence, &WaitStateBase::completed, _stateRallyPoints);
165 _stateGeoFence->addTransition(_stateGeoFence, &SkippableAsyncState::skipped, _stateRallyPoints);
166
167 _stateRallyPoints->addTransition(_stateRallyPoints, &WaitStateBase::completed, _stateComplete);
168 _stateRallyPoints->addTransition(_stateRallyPoints, &SkippableAsyncState::skipped, _stateComplete);
169
170 // Complete -> Final (RetryState emits advance() on success)
171 _stateComplete->addTransition(_stateComplete, &QGCState::advance, _stateFinal);
172}
173
174void InitialConnectStateMachine::_wireProgressTracking()
175{
176 // Use QGCStateMachine's built-in weighted progress tracking
177 // Progress is automatically updated when states are entered
178 setProgressWeights({
179 {_stateAutopilotVersion, 1},
180 {_stateStandardModes, 1},
181 {_stateCompInfo, 5},
182 {_stateParameters, 5},
183 {_stateMission, 2},
184 {_stateGeoFence, 1},
185 {_stateRallyPoints, 1},
186 {_stateComplete, 1}
187 });
188}
189
190void InitialConnectStateMachine::_onSubProgressUpdate(double progressValue)
191{
192 setSubProgress(static_cast<float>(progressValue));
193}
194
195// ============================================================================
196// Timeout Handling
197// ============================================================================
198
199void InitialConnectStateMachine::_wireTimeoutHandling()
200{
201 // Note: _stateAutopilotVersion is RetryableRequestMessageState which handles its own retry
202
203 // Use addRetryTransition builder for cleaner timeout handling
204 addRetryTransition(_stateStandardModes, &WaitStateBase::timedOut, _stateCompInfo,
205 [this]() { _requestStandardModes(_stateStandardModes); }, _maxRetries);
206
207 addRetryTransition(_stateCompInfo, &WaitStateBase::timedOut, _stateParameters,
208 [this]() { _requestCompInfo(_stateCompInfo); }, _maxRetries);
209
210 addRetryTransition(_stateParameters, &WaitStateBase::timedOut, _stateMission,
211 [this]() { _requestParameters(_stateParameters); }, _maxRetries);
212
213 addRetryTransition(_stateMission, &WaitStateBase::timedOut, _stateGeoFence,
214 [this]() { _requestMission(_stateMission); }, _maxRetries);
215
216 addRetryTransition(_stateGeoFence, &WaitStateBase::timedOut, _stateRallyPoints,
217 [this]() { _requestGeoFence(_stateGeoFence); }, _maxRetries);
218
219 addRetryTransition(_stateRallyPoints, &WaitStateBase::timedOut, _stateComplete,
220 [this]() { _requestRallyPoints(_stateRallyPoints); }, _maxRetries);
221}
222
223// ============================================================================
224// Skip Predicates
225// ============================================================================
226
227bool InitialConnectStateMachine::_shouldSkipAutopilotVersionRequest() const
228{
229 SharedLinkInterfacePtr sharedLink = vehicle()->vehicleLinkManager()->primaryLink().lock();
230 if (!sharedLink) {
231 qCDebug(InitialConnectStateMachineLog) << "Skipping AUTOPILOT_VERSION: no primary link";
232 return true;
233 }
234 if (sharedLink->linkConfiguration()->isHighLatency() || sharedLink->isLogReplay()) {
235 qCDebug(InitialConnectStateMachineLog) << "Skipping AUTOPILOT_VERSION: high latency or log replay";
236 return true;
237 }
238 return false;
239}
240
241bool InitialConnectStateMachine::_shouldSkipForLinkType() const
242{
243 SharedLinkInterfacePtr sharedLink = vehicle()->vehicleLinkManager()->primaryLink().lock();
244 if (!sharedLink) {
245 return true;
246 }
247 return sharedLink->linkConfiguration()->isHighLatency() || sharedLink->isLogReplay();
248}
249
250bool InitialConnectStateMachine::_hasPrimaryLink() const
251{
252 SharedLinkInterfacePtr sharedLink = vehicle()->vehicleLinkManager()->primaryLink().lock();
253 return sharedLink != nullptr;
254}
255
256// ============================================================================
257// State Callbacks
258// ============================================================================
259
260void InitialConnectStateMachine::_handleAutopilotVersionSuccess(const mavlink_message_t& message)
261{
262 qCDebug(InitialConnectStateMachineLog) << "AUTOPILOT_VERSION received";
263
264 mavlink_autopilot_version_t autopilotVersion;
265 mavlink_msg_autopilot_version_decode(&message, &autopilotVersion);
266
267 vehicle()->_uid = (quint64)autopilotVersion.uid;
268 vehicle()->_firmwareBoardVendorId = autopilotVersion.vendor_id;
269 vehicle()->_firmwareBoardProductId = autopilotVersion.product_id;
270 emit vehicle()->vehicleUIDChanged();
271
272 if (autopilotVersion.flight_sw_version != 0) {
273 int majorVersion, minorVersion, patchVersion;
274 FIRMWARE_VERSION_TYPE versionType;
275
276 majorVersion = (autopilotVersion.flight_sw_version >> (8*3)) & 0xFF;
277 minorVersion = (autopilotVersion.flight_sw_version >> (8*2)) & 0xFF;
278 patchVersion = (autopilotVersion.flight_sw_version >> (8*1)) & 0xFF;
279 versionType = (FIRMWARE_VERSION_TYPE)((autopilotVersion.flight_sw_version >> (8*0)) & 0xFF);
280 vehicle()->setFirmwareVersion(majorVersion, minorVersion, patchVersion, versionType);
281 }
282
283 if (vehicle()->px4Firmware()) {
284 // Lower 3 bytes is custom version
285 int majorVersion, minorVersion, patchVersion;
286 majorVersion = autopilotVersion.flight_custom_version[2];
287 minorVersion = autopilotVersion.flight_custom_version[1];
288 patchVersion = autopilotVersion.flight_custom_version[0];
289 vehicle()->setFirmwareCustomVersion(majorVersion, minorVersion, patchVersion);
290
291 // PX4 Firmware stores the first 16 characters of the git hash as binary, with the individual bytes in reverse order
292 vehicle()->_gitHash = "";
293 for (int i = 7; i >= 0; i--) {
294 vehicle()->_gitHash.append(QString("%1").arg(autopilotVersion.flight_custom_version[i], 2, 16, QChar('0')));
295 }
296 } else {
297 // APM Firmware stores the first 8 characters of the git hash as an ASCII character string
298 char nullStr[9];
299 strncpy(nullStr, (char*)autopilotVersion.flight_custom_version, 8);
300 nullStr[8] = 0;
301 vehicle()->_gitHash = nullStr;
302 }
303
304 if (QGCCorePlugin::instance()->options()->checkFirmwareVersion() && !vehicle()->_checkLatestStableFWDone) {
305 vehicle()->_checkLatestStableFWDone = true;
306 vehicle()->_firmwarePlugin->checkIfIsLatestStable(vehicle());
307 }
308 emit vehicle()->gitHashChanged(vehicle()->_gitHash);
309
310 vehicle()->_setCapabilities(autopilotVersion.capabilities);
311}
312
313void InitialConnectStateMachine::_handleAutopilotVersionFailure()
314{
315 qCDebug(InitialConnectStateMachineLog) << "AUTOPILOT_VERSION request failed, setting assumed capabilities";
316
317 uint64_t assumedCapabilities = MAV_PROTOCOL_CAPABILITY_MAVLINK2;
318 if (vehicle()->px4Firmware() || vehicle()->apmFirmware()) {
319 // We make some assumptions for known firmware
320 assumedCapabilities |= MAV_PROTOCOL_CAPABILITY_MISSION_INT | MAV_PROTOCOL_CAPABILITY_COMMAND_INT |
321 MAV_PROTOCOL_CAPABILITY_MISSION_FENCE | MAV_PROTOCOL_CAPABILITY_MISSION_RALLY;
322 }
323 vehicle()->_setCapabilities(assumedCapabilities);
324}
325
326void InitialConnectStateMachine::_requestStandardModes(AsyncFunctionState* state)
327{
328 qCDebug(InitialConnectStateMachineLog) << "_stateRequestStandardModes";
329
330 state->connectToCompletion(vehicle()->_standardModes, &StandardModes::requestCompleted);
331 vehicle()->_standardModes->request();
332}
333
334void InitialConnectStateMachine::_requestCompInfo(AsyncFunctionState* state)
335{
336 qCDebug(InitialConnectStateMachineLog) << "_stateRequestCompInfo";
337
338 connect(vehicle()->_componentInformationManager, &ComponentInformationManager::progressUpdate,
339 this, &InitialConnectStateMachine::_onSubProgressUpdate, Qt::UniqueConnection);
340
341 // Ensure progress tracking is always cleaned up, including timeout/skip paths.
342 state->setOnExit([this]() {
343 disconnect(vehicle()->_componentInformationManager, &ComponentInformationManager::progressUpdate,
344 this, &InitialConnectStateMachine::_onSubProgressUpdate);
345 });
346
347 vehicle()->_componentInformationManager->requestAllComponentInformation(
348 [](void* requestAllCompleteFnData) {
349 auto* self = static_cast<InitialConnectStateMachine*>(requestAllCompleteFnData);
350 if (self->_stateCompInfo) {
351 self->_stateCompInfo->complete();
352 }
353 },
354 this
355 );
356}
357
358void InitialConnectStateMachine::_requestParameters(AsyncFunctionState* state)
359{
360 qCDebug(InitialConnectStateMachineLog) << "_stateRequestParameters";
361
362 connect(vehicle()->_parameterManager, &ParameterManager::loadProgressChanged,
363 this, &InitialConnectStateMachine::_onSubProgressUpdate, Qt::UniqueConnection);
364
365 state->connectToCompletion(vehicle()->_parameterManager, &ParameterManager::parametersReadyChanged,
366 [this](bool parametersReady) {
367 _onParametersReady(parametersReady);
368 });
369
370 // Ensure progress tracking is always cleaned up, including timeout/skip paths.
371 state->setOnExit([this]() {
372 disconnect(vehicle()->_parameterManager, &ParameterManager::loadProgressChanged,
373 this, &InitialConnectStateMachine::_onSubProgressUpdate);
374 });
375
376 vehicle()->_parameterManager->refreshAllParameters();
377}
378
379void InitialConnectStateMachine::_onParametersReady(bool parametersReady)
380{
381 qCDebug(InitialConnectStateMachineLog) << "_onParametersReady" << parametersReady;
382
383 // Disconnect progress tracking from parameter manager
384 disconnect(vehicle()->_parameterManager, &ParameterManager::loadProgressChanged,
385 this, &InitialConnectStateMachine::_onSubProgressUpdate);
386
387 if (parametersReady) {
388 // Send time to vehicle (twice for reliability on noisy links)
389 vehicle()->_sendQGCTimeToVehicle();
390 vehicle()->_sendQGCTimeToVehicle();
391
392 // Set up auto-disarm signalling
393 vehicle()->_setupAutoDisarmSignalling();
394
395 // Note: Speed limits are handled by Vehicle::_parametersReady
396
397 if (_stateParameters) {
398 _stateParameters->complete();
399 }
400 }
401}
402
403void InitialConnectStateMachine::_requestMission(SkippableAsyncState* state)
404{
405 qCDebug(InitialConnectStateMachineLog) << "_stateRequestMission";
406
407 connect(vehicle()->_missionManager, &MissionManager::progressPctChanged,
408 this, &InitialConnectStateMachine::_onSubProgressUpdate, Qt::UniqueConnection);
409
410 state->connectToCompletion(vehicle()->_missionManager, &MissionManager::newMissionItemsAvailable);
411
412 // Disconnect progress tracking on exit
413 state->setOnExit([this]() {
414 disconnect(vehicle()->_missionManager, &MissionManager::progressPctChanged,
415 this, &InitialConnectStateMachine::_onSubProgressUpdate);
416 });
417
418 vehicle()->_missionManager->loadFromVehicle();
419}
420
421void InitialConnectStateMachine::_requestGeoFence(SkippableAsyncState* state)
422{
423 qCDebug(InitialConnectStateMachineLog) << "_stateRequestGeoFence";
424
425 connect(vehicle()->_geoFenceManager, &GeoFenceManager::progressPctChanged,
426 this, &InitialConnectStateMachine::_onSubProgressUpdate, Qt::UniqueConnection);
427
428 state->connectToCompletion(vehicle()->_geoFenceManager, &GeoFenceManager::loadComplete);
429
430 // Disconnect progress tracking on exit
431 state->setOnExit([this]() {
432 disconnect(vehicle()->_geoFenceManager, &GeoFenceManager::progressPctChanged,
433 this, &InitialConnectStateMachine::_onSubProgressUpdate);
434 });
435
436 vehicle()->_geoFenceManager->loadFromVehicle();
437}
438
439void InitialConnectStateMachine::_requestRallyPoints(SkippableAsyncState* state)
440{
441 qCDebug(InitialConnectStateMachineLog) << "_stateRequestRallyPoints";
442
443 connect(vehicle()->_rallyPointManager, &RallyPointManager::progressPctChanged,
444 this, &InitialConnectStateMachine::_onSubProgressUpdate, Qt::UniqueConnection);
445
446 state->connectToCompletion(vehicle()->_rallyPointManager, &RallyPointManager::loadComplete,
447 [this]() {
448 // Mark initial plan request complete
449 vehicle()->_initialPlanRequestComplete = true;
450 emit vehicle()->initialPlanRequestCompleteChanged(true);
451
452 if (_stateRallyPoints) {
453 _stateRallyPoints->complete();
454 }
455 });
456
457 // Always clean up progress tracking when leaving this state.
458 state->setOnExit([this]() {
459 disconnect(vehicle()->_rallyPointManager, &RallyPointManager::progressPctChanged,
460 this, &InitialConnectStateMachine::_onSubProgressUpdate);
461 });
462
463 vehicle()->_rallyPointManager->loadFromVehicle();
464}
465
466void InitialConnectStateMachine::_signalComplete()
467{
468 qCDebug(InitialConnectStateMachineLog) << "Signalling initialConnectComplete";
469 connect(this, &QStateMachine::finished, vehicle(), [this]() {
470 emit vehicle()->initialConnectComplete();
471 }, Qt::SingleShotConnection);
472}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void connectToCompletion(typename QtPrivate::FunctionPointer< Func >::Object *sender, Func signal)
void complete()
Call this to signal that the async operation has completed successfully.
void progressUpdate(float progress)
void loadComplete(void)
void parametersReadyChanged(bool parametersReady)
void loadProgressChanged(float value)
void newMissionItemsAvailable(bool removeAllRequested)
void progressPctChanged(double progressPercentPct)
void setOnExit(ExitCallback callback)
Set a callback to be invoked when the state is exited.
Final state for a QGCStateMachine with logging support.
QGroundControl specific state machine with enhanced error handling.
void start()
Start the state machine with debug logging.
void loadComplete(void)
@ EmitError
Emit error() after retries exhausted.
Definition RetryState.h:22
void setSkipPredicate(SkipPredicate predicate)
void setFailureHandler(FailureHandler handler)
Set handler called when all retries fail.
void connectToCompletion(typename QtPrivate::FunctionPointer< Func >::Object *sender, Func signal)
void skipped()
Emitted when skip predicate returns true and the state is skipped.
void complete()
Call this to signal that the async operation has completed successfully.
void requestCompleted()
void completed()