QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
ParameterManager.cc
Go to the documentation of this file.
2#include "ParameterManager.h"
3#include "BulkRefreshJob.h"
4
5#include <algorithm>
6
7#include <QtCore/QDir>
8#include <QtCore/QSet>
9#include <QtCore/QTextStream>
10
11#include "AutoPilotPlugin.h"
12#include "CompInfoParam.h"
14#include "FactGroup.h"
15#include "FirmwarePlugin.h"
16#include "FTPManager.h"
17#include "MAVLinkProtocol.h"
18#include "AppMessages.h"
19#include "QGCMath.h"
20#include "QGCApplication.h"
21#include "QGCLoggingCategory.h"
22#include "QGCMAVLink.h"
23#include "Vehicle.h"
24#include "VehicleLinkManager.h"
25#include "QGCStateMachine.h"
26#include "MultiVehicleManager.h"
27
28#include <QtCore/QEasingCurve>
29#include <QtCore/QFile>
30#include <QtCore/QStandardPaths>
31#include <QtCore/QVariantAnimation>
32
33QGC_LOGGING_CATEGORY(ParameterManagerLog, "FactSystem.ParameterManager")
34QGC_LOGGING_CATEGORY(ParameterManagerVerbose1Log, "FactSystem.ParameterManager:verbose1")
35QGC_LOGGING_CATEGORY(ParameterManagerVerbose2Log, "FactSystem.ParameterManager:verbose2")
36QGC_LOGGING_CATEGORY(ParameterManagerDebugCacheFailureLog, "FactSystem.ParameterManager:debugCacheFailure") // Turn on to debug parameter cache crc misses
37
39 : QObject(vehicle)
40 , _vehicle(vehicle)
41 , _logReplay(!vehicle->vehicleLinkManager()->primaryLink().expired() && vehicle->vehicleLinkManager()->primaryLink().lock()->isLogReplay())
42 , _disableAllRetries(_logReplay)
43 , _waitForParamValueAckMs(QGC::runningUnitTests() ? 50 : kWaitForParamValueAckMs)
44 , _tryftp(vehicle->apmFirmware() || vehicle->px4Firmware())
45{
46 qCDebug(ParameterManagerLog) << this;
47
48 if (_vehicle->isOfflineEditingVehicle()) {
49 _loadOfflineEditingParams();
50 return;
51 }
52
53 if (_logReplay) {
54 qCDebug(ParameterManagerLog) << this << "In log replay mode";
55 }
56
57 _hashCheckTimer.setSingleShot(true);
58 _hashCheckTimer.setInterval(QGC::runningUnitTests() ? kTestHashCheckTimeoutMs : kHashCheckTimeoutMs);
59 (void) connect(&_hashCheckTimer, &QTimer::timeout, this, &ParameterManager::_hashCheckTimeout);
60
61 _paramRequestListTimer.setSingleShot(true);
62 _paramRequestListTimer.setInterval(QGC::runningUnitTests() ? kTestInitialRequestIntervalMs : kParamRequestListTimeoutMs);
63 (void) connect(&_paramRequestListTimer, &QTimer::timeout, this, &ParameterManager::_paramRequestListTimeout);
64
65 _waitingParamTimeoutTimer.setSingleShot(true);
66 _waitingParamTimeoutTimer.setInterval(QGC::runningUnitTests() ? 500 : 3000);
67 if (!_logReplay) {
68 (void) connect(&_waitingParamTimeoutTimer, &QTimer::timeout, this, &ParameterManager::_waitingParamTimeout);
69 }
70
71 // Ensure the cache directory exists
72 (void) QDir().mkpath(parameterCacheDir().absolutePath());
73}
74
76{
77 qCDebug(ParameterManagerLog) << this;
78}
79
80void ParameterManager::_updateProgressBar()
81{
82 // The bar is initial-load and user-refresh UI; components still loading in the background must not revive it
83 if (_initialLoadComplete && !_refreshAllProgressActive) {
84 return;
85 }
86
87 int waitingReadParamIndexCount = 0;
88
89 for (const int compId: _waitingReadParamIndexMap.keys()) {
90 waitingReadParamIndexCount += _waitingReadParamIndexMap[compId].count();
91 }
92
93 if (waitingReadParamIndexCount == 0) {
94 _refreshAllProgressActive = false;
95 if (_readParamIndexProgressActive) {
96 _readParamIndexProgressActive = false;
97 _setLoadProgress(0.0);
98 return;
99 }
100 } else {
101 _readParamIndexProgressActive = true;
102 _setLoadProgress(static_cast<double>(_totalParamCount - waitingReadParamIndexCount) / static_cast<double>(_totalParamCount));
103 return;
104 }
105}
106
108{
109 if (message.msgid == MAVLINK_MSG_ID_PARAM_VALUE) {
110 mavlink_param_value_t param_value{};
111 mavlink_msg_param_value_decode(&message, &param_value);
112
113 // This will null terminate the name string
114 char parameterNameWithNull[MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN + 1] = {};
115 (void) strncpy(parameterNameWithNull, param_value.param_id, MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN);
116 const QString parameterName(parameterNameWithNull);
117
118 // FTP download ignores the PARAM_VALUE stream, but PX4 _HASH_CHECK is a PARAM_VALUE
119 // and must still be handled so the cache can skip the download.
120 if (_tryftp && (message.compid == MAV_COMP_ID_AUTOPILOT1) && !_initialLoadComplete
121 && (parameterName != QStringLiteral("_HASH_CHECK"))) {
122 return;
123 }
124
125 mavlink_param_union_t paramUnion{};
126 paramUnion.param_float = param_value.param_value;
127 paramUnion.type = param_value.param_type;
128
129 QVariant parameterValue;
130 if (!_mavlinkParamUnionToVariant(paramUnion, parameterValue)) {
131 return;
132 }
133
134 _handleParamValue(message.compid, parameterName, param_value.param_count, param_value.param_index, static_cast<MAV_PARAM_TYPE>(param_value.param_type), parameterValue);
135 }
136}
137
138void ParameterManager::_handleParamValue(int componentId, const QString &parameterName, int parameterCount, int parameterIndex, MAV_PARAM_TYPE mavParamType, const QVariant &parameterValue)
139{
140
141 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) <<
142 "_parameterUpdate" <<
143 "name:" << parameterName <<
144 "count:" << parameterCount <<
145 "index:" << parameterIndex <<
146 "mavType:" << mavParamType <<
147 "value:" << parameterValue <<
148 ")";
149
150 // ArduPilot has this strange behavior of streaming parameters that we didn't ask for. This even happens before it responds to the
151 // PARAM_REQUEST_LIST. We disregard any of this until the initial request is responded to.
152 if ((parameterIndex == 65535) && (parameterName != QStringLiteral("_HASH_CHECK")) && _paramRequestListTimer.isActive()) {
153 qCDebug(ParameterManagerLog) << "Disregarding unrequested param prior to initial list response" << parameterName;
154 return;
155 }
156
157 if (_vehicle->px4Firmware() && (parameterName == "_HASH_CHECK")) {
158 _hashCheckTimer.stop();
159 if (!_initialLoadComplete && !_logReplay) {
160 /* we received a cache hash, potentially load from cache */
161 _tryCacheHashLoad(_vehicle->id(), componentId, parameterValue);
162 }
163 return;
164 }
165
166 _paramRequestListTimer.stop();
167
168 // Used to debug cache crc misses (turn on ParameterManagerDebugCacheFailureLog)
169 if (!_initialLoadComplete && !_logReplay && _debugCacheCRC.contains(componentId) && _debugCacheCRC[componentId]) {
170 if (_debugCacheMap[componentId].contains(parameterName)) {
171 const ParamTypeVal &cacheParamTypeVal = _debugCacheMap[componentId][parameterName];
172 const size_t dataSize = FactMetaData::typeToSize(static_cast<FactMetaData::ValueType_t>(cacheParamTypeVal.first));
173 const void *const cacheData = cacheParamTypeVal.second.constData();
174 const void *const vehicleData = parameterValue.constData();
175
176 if (memcmp(cacheData, vehicleData, dataSize) != 0) {
177 qCDebug(ParameterManagerVerbose1Log) << "Cache/Vehicle values differ for name:cache:actual" << parameterName << parameterValue << cacheParamTypeVal.second;
178 }
179 _debugCacheParamSeen[componentId][parameterName] = true;
180 } else {
181 qCDebug(ParameterManagerVerbose1Log) << "Parameter missing from cache" << parameterName;
182 }
183 }
184
185 _waitingParamTimeoutTimer.stop();
186
187 _paramValuesReceivedThisCycle[componentId]++;
188 _silentCycleCount.remove(componentId);
189
190 // Update our total parameter counts
191 if (!_paramCountMap.contains(componentId)) {
192 _paramCountMap[componentId] = parameterCount;
193 _totalParamCount += parameterCount;
194 }
195
196 // If we've never seen this component id before, setup the index wait lists.
197 if (!_waitingReadParamIndexMap.contains(componentId)) {
198 // Add all indices to the wait list, parameter index is 0-based
199 for (int waitingIndex = 0; waitingIndex < parameterCount; waitingIndex++) {
200 // This will add the new component id, as well as the the new waiting index and set the retry count for that index to 0
201 _waitingReadParamIndexMap[componentId][waitingIndex] = 0;
202 }
203
204 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "Seeing component for first time - paramcount:" << parameterCount;
205 }
206
207 if (!_waitingReadParamIndexMap[componentId].contains(parameterIndex)) {
208 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "Unrequested param update" << parameterName;
209 }
210
211 // Remove this parameter from the waiting lists
212 if (_waitingReadParamIndexMap[componentId].contains(parameterIndex)) {
213 _waitingReadParamIndexMap[componentId].remove(parameterIndex);
214 (void) _indexBatchQueue.removeOne(qMakePair(componentId, parameterIndex));
215 _fillIndexBatchQueue(false /* waitingParamTimeout */);
216 }
217
218 // Track how many parameters we are still waiting for
219 int waitingReadParamIndexCount = 0;
220
221 for (const int waitingComponentId: _waitingReadParamIndexMap.keys()) {
222 waitingReadParamIndexCount += _waitingReadParamIndexMap[waitingComponentId].count();
223 }
224 if (waitingReadParamIndexCount) {
225 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "waitingReadParamIndexCount:" << waitingReadParamIndexCount;
226 }
227
228 const int readWaitingParamCount = waitingReadParamIndexCount;
229 const int totalWaitingParamCount = readWaitingParamCount;
230 if (totalWaitingParamCount) {
231 // More params to wait for, restart timer
232 _waitingParamTimeoutTimer.start();
233 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(-1) << "Restarting _waitingParamTimeoutTimer: totalWaitingParamCount:" << totalWaitingParamCount;
234 } else if (!_mapCompId2FactMap.contains(_vehicle->defaultComponentId())) {
235 // Still waiting for parameters from default component
236 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Restarting _waitingParamTimeoutTimer (still waiting for default component params)";
237 _waitingParamTimeoutTimer.start();
238 } else {
239 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(-1) << "Not restarting _waitingParamTimeoutTimer (all requests satisfied)";
240 }
241
242 _updateProgressBar();
243
244 Fact *fact = nullptr;
245 if (_mapCompId2FactMap.contains(componentId) && _mapCompId2FactMap[componentId].contains(parameterName)) {
246 fact = _mapCompId2FactMap[componentId][parameterName];
247 } else {
248 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "Adding new fact" << parameterName;
249
250 fact = new Fact(componentId, parameterName, mavTypeToFactType(mavParamType), this);
251 FactMetaData *const factMetaData = _vehicle->compInfoManager()->compInfoParam(componentId)->factMetaDataForName(parameterName, fact->type());
252 fact->setMetaData(factMetaData);
253
254 _mapCompId2FactMap[componentId][parameterName] = fact;
255
256 // We need to know when the fact value changes so we can update the vehicle
257 (void) connect(fact, &Fact::containerRawValueChanged, this, &ParameterManager::_factRawValueUpdated);
258
259 emit factAdded(componentId, fact);
260 }
261
262 fact->containerSetRawValue(parameterValue);
263
264 // Update param cache. The param cache is only used on PX4 Firmware since ArduPilot and Solo have volatile params
265 // which invalidate the cache. The Solo also streams param updates in flight for things like gimbal values
266 // which in turn causes a perf problem with all the param cache updates.
267 if (!_logReplay && _vehicle->px4Firmware()) {
268 if (_prevWaitingReadParamIndexCount != 0 && readWaitingParamCount == 0) {
269 // All reads just finished, update the cache
270 _writeLocalParamCache(_vehicle->id(), componentId);
271 }
272 }
273
274 _prevWaitingReadParamIndexCount = waitingReadParamIndexCount;
275
276 _checkInitialLoadComplete();
277
278 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "_handleParamValue complete";
279}
280
281QString ParameterManager::_vehicleAndComponentString(int componentId) const
282{
283 // If there are multiple vehicles include the vehicle id for disambiguation
284 QString vehicleIdStr;
285 if (MultiVehicleManager::instance()->vehicles()->count() > 1) {
286 vehicleIdStr = QStringLiteral("veh: %1").arg(_vehicle->id());
287 }
288
289 // IF we have parameters for multiple components include the component id for disambiguation
290 QString componentIdStr;
291 if (_mapCompId2FactMap.keys().count() > 1) {
292 componentIdStr = QStringLiteral("comp: %1").arg(componentId);
293 }
294
295 if (!vehicleIdStr.isEmpty() && !componentIdStr.isEmpty()) {
296 return vehicleIdStr + QStringLiteral(" ") + componentIdStr;
297 } else if (!vehicleIdStr.isEmpty()) {
298 return vehicleIdStr;
299 } else if (!componentIdStr.isEmpty()) {
300 return componentIdStr;
301 } else {
302 return QString();
303 }
304}
305
306void ParameterManager::_mavlinkParamSet(int componentId, const QString &paramName, FactMetaData::ValueType_t valueType, const QVariant &rawValue)
307{
308 auto paramSetEncoder = [this, componentId, paramName, valueType, rawValue](uint8_t /*systemId*/, uint8_t channel, mavlink_message_t *message) -> void {
309 const MAV_PARAM_TYPE paramType = factTypeToMavType(valueType);
310
311 mavlink_param_union_t union_value{};
312 if (!_fillMavlinkParamUnion(valueType, rawValue, union_value)) {
313 return;
314 }
315
316 char paramId[MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN + 1] = {};
317 (void) strncpy(paramId, paramName.toLocal8Bit().constData(), MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN);
318
319 (void) mavlink_msg_param_set_pack_chan(
322 channel,
323 message,
324 static_cast<uint8_t>(_vehicle->id()),
325 static_cast<uint8_t>(componentId),
326 paramId,
327 union_value.param_float,
328 static_cast<uint8_t>(paramType));
329 };
330
331 auto checkForCorrectParamValue = [this, componentId, paramName, rawValue](const mavlink_message_t &message) -> bool {
332 if (message.compid != componentId) {
333 return false;
334 }
335
336 mavlink_param_value_t param_value{};
337 mavlink_msg_param_value_decode(&message, &param_value);
338
339 // This will null terminate the name string
340 char parameterNameWithNull[MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN + 1] = {};
341 (void) strncpy(parameterNameWithNull, param_value.param_id, MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN);
342 const QString parameterName(parameterNameWithNull);
343
344 if (parameterName != paramName) {
345 return false;
346 }
347
348 // Check that the value matches what we expect within tolerance, if it doesn't match then this message is not for us
349 QVariant receivedValue;
350 mavlink_param_union_t param_union;
351 param_union.param_float = param_value.param_value;
352 param_union.type = param_value.param_type;
353 if (!_mavlinkParamUnionToVariant(param_union, receivedValue)) {
354 return false;
355 }
356 if (rawValue.typeId() != receivedValue.typeId()) {
357 qCWarning(ParameterManagerLog) << "QVariant type mismatch on PARAM_VALUE ack for" << paramName << ": expected type" << rawValue.typeId() << "got type" << receivedValue.typeId();
358 return false;
359 }
360 if (param_value.param_type == MAV_PARAM_TYPE_REAL32) {
361 // Float comparison must be fuzzy
362 return QGC::fuzzyCompare(rawValue.toFloat(), receivedValue.toFloat());
363 } else {
364 return receivedValue == rawValue;
365 }
366 };
367
368 auto checkForParamError = [componentId, paramName](const mavlink_message_t &message) -> bool {
369 if (message.compid != componentId) {
370 return false;
371 }
372
373 mavlink_param_error_t paramError{};
374 mavlink_msg_param_error_decode(&message, &paramError);
375
376 char paramId[MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN + 1] = {};
377 (void) strncpy(paramId, paramError.param_id, MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN);
378
379 return QString(paramId) == paramName;
380 };
381
382 // State Machine:
383 // Send PARAM_SET - 2 retries after initial attempt
384 // Increment pending write count
385 // Wait for PARAM_VALUE ack or PARAM_ERROR rejection
386 // Decrement pending write count
387 //
388 // timeout:
389 // Decrement pending write count
390 // Back up to PARAM_SET for retries
391 //
392 // error (PARAM_ERROR or retries exhausted):
393 // Refresh parameter from vehicle
394 // Notify user of failure
395
396 // Create states
397 auto stateMachine = new QGCStateMachine(QStringLiteral("ParameterManager PARAM_SET"), vehicle(), this);
398 auto sendParamSetState = new SendMavlinkMessageState(stateMachine, paramSetEncoder, kParamSetRetryCount);
399 auto incPendingWriteCountState = new FunctionState(QStringLiteral("ParameterManager increment pending write count"), stateMachine, [this]() {
400 _incrementPendingWriteCount();
401 });
402 auto decPendingWriteCountState = new FunctionState(QStringLiteral("ParameterManager decrement pending write count"), stateMachine, [this]() {
403 _decrementPendingWriteCount();
404 });
405 auto retryDecPendingWriteCountState = new FunctionState(QStringLiteral("ParameterManager retry decrement pending write count"), stateMachine, [this]() {
406 _decrementPendingWriteCount();
407 });
408 auto errorDecPendingWriteCountState = new FunctionState(QStringLiteral("ParameterManager error decrement pending write count"), stateMachine, [this]() {
409 _decrementPendingWriteCount();
410 });
411 auto waitAckState = new WaitForParamResponseState(stateMachine, _waitForParamValueAckMs, checkForCorrectParamValue, checkForParamError);
412 auto paramRefreshState = new FunctionState(QStringLiteral("ParameterManager param refresh"), stateMachine, [this, waitAckState, componentId, paramName]() {
413 // A definitive "does not exist" rejection means there is nothing on the vehicle
414 // to refresh - stop here rather than generating a redundant read failure.
415 if (waitAckState->lastParamError() == MAV_PARAM_ERROR_DOES_NOT_EXIST) {
416 qCDebug(ParameterManagerLog) << "Skipping post-write-failure refresh, param does not exist on vehicle:" << paramName << _vehicleAndComponentString(componentId);
417 return;
418 }
419 refreshParameter(componentId, paramName);
420 });
421 auto userNotifyState = new FunctionState(QStringLiteral("ParameterManager user notify"), stateMachine, [waitAckState, paramName, this, componentId]() {
422 const QString errorDetail = waitAckState->lastParamErrorString();
423 const QString msg = errorDetail.isEmpty()
424 ? QStringLiteral("Parameter write failed: param: %1 %2").arg(paramName, _vehicleAndComponentString(componentId))
425 : QStringLiteral("Parameter write failed: param: %1 %2 - %3").arg(paramName, _vehicleAndComponentString(componentId), errorDetail);
427 });
428 auto logSuccessState = new FunctionState(QStringLiteral("ParameterManager log success"), stateMachine, [this, componentId, paramName]() {
429 qCDebug(ParameterManagerLog) << "Parameter write succeeded: param:" << paramName << _vehicleAndComponentString(componentId);
430 emit _paramSetSuccess(componentId, paramName);
431 });
432 auto logFailureState = new FunctionState(QStringLiteral("ParameterManager log failure"), stateMachine, [this, componentId, paramName]() {
433 qCDebug(ParameterManagerLog) << "Parameter write failed: param:" << paramName << _vehicleAndComponentString(componentId);
434 emit _paramSetFailure(componentId, paramName);
435 });
436 auto finalState = new QGCFinalState(stateMachine);
437
438 // Successful state machine transitions
439 stateMachine->setInitialState(sendParamSetState);
440 sendParamSetState->addThisTransition (&QGCState::advance, incPendingWriteCountState);
441 incPendingWriteCountState->addThisTransition(&QGCState::advance, waitAckState);
442 waitAckState->addThisTransition (&QGCState::advance, decPendingWriteCountState);
443 decPendingWriteCountState->addThisTransition(&QGCState::advance, logSuccessState);
444 logSuccessState->addThisTransition (&QGCState::advance, finalState);
445
446 // Retry transitions (timeout path)
447 waitAckState->addTransition(waitAckState, &WaitStateBase::timeout, retryDecPendingWriteCountState);
448 retryDecPendingWriteCountState->addThisTransition(&QGCState::advance, sendParamSetState);
449
450 // PARAM_ERROR path (definitive rejection - no retries)
451 waitAckState->addThisTransition (&QGCState::error, errorDecPendingWriteCountState);
452 errorDecPendingWriteCountState->addThisTransition (&QGCState::advance, logFailureState);
453
454 // Error transitions (retries exhausted)
455 sendParamSetState->addThisTransition(&QGCState::error, logFailureState);
456
457 // Error state branching transitions
458 logFailureState->addThisTransition (&QGCState::advance, userNotifyState);
459 userNotifyState->addThisTransition (&QGCState::advance, paramRefreshState);
460 paramRefreshState->addThisTransition(&QGCState::advance, finalState);
461
462 qCDebug(ParameterManagerLog) << "Starting state machine for PARAM_SET on: " << paramName << _vehicleAndComponentString(componentId);
463 stateMachine->start();
464}
465
466bool ParameterManager::_fillMavlinkParamUnion(FactMetaData::ValueType_t valueType, const QVariant &rawValue, mavlink_param_union_t &paramUnion) const
467{
468 bool ok = false;
469
470 switch (valueType) {
472 paramUnion.param_uint8 = static_cast<uint8_t>(rawValue.toUInt(&ok));
473 break;
475 paramUnion.param_int8 = static_cast<int8_t>(rawValue.toInt(&ok));
476 break;
478 paramUnion.param_uint16 = static_cast<uint16_t>(rawValue.toUInt(&ok));
479 break;
481 paramUnion.param_int16 = static_cast<int16_t>(rawValue.toInt(&ok));
482 break;
484 paramUnion.param_uint32 = static_cast<uint32_t>(rawValue.toUInt(&ok));
485 break;
487 paramUnion.param_float = rawValue.toFloat(&ok);
488 break;
490 paramUnion.param_int32 = static_cast<int32_t>(rawValue.toInt(&ok));
491 break;
492 default:
493 qCCritical(ParameterManagerLog) << "Internal Error: Unsupported fact value type" << valueType;
494 paramUnion.param_int32 = static_cast<int32_t>(rawValue.toInt(&ok));
495 break;
496 }
497
498 if (!ok) {
499 qCCritical(ParameterManagerLog) << "Fact Failed to Convert to Param Type:" << valueType;
500 return false;
501 }
502
503 return true;
504}
505
506bool ParameterManager::_mavlinkParamUnionToVariant(const mavlink_param_union_t &paramUnion, QVariant &outValue) const
507{
508 switch (paramUnion.type) {
509 case MAV_PARAM_TYPE_REAL32:
510 outValue = QVariant(paramUnion.param_float);
511 return true;
512 case MAV_PARAM_TYPE_UINT8:
513 outValue = QVariant(static_cast<quint32>(paramUnion.param_uint8));
514 return true;
515 case MAV_PARAM_TYPE_INT8:
516 outValue = QVariant(static_cast<qint32>(paramUnion.param_int8));
517 return true;
518 case MAV_PARAM_TYPE_UINT16:
519 outValue = QVariant(static_cast<quint32>(paramUnion.param_uint16));
520 return true;
521 case MAV_PARAM_TYPE_INT16:
522 outValue = QVariant(static_cast<qint32>(paramUnion.param_int16));
523 return true;
524 case MAV_PARAM_TYPE_UINT32:
525 outValue = QVariant(paramUnion.param_uint32);
526 return true;
527 case MAV_PARAM_TYPE_INT32:
528 outValue = QVariant(paramUnion.param_int32);
529 return true;
530 default:
531 qCCritical(ParameterManagerLog) << "ParameterManager::_mavlinkParamUnionToVariant - unsupported MAV_PARAM_TYPE" << paramUnion.type;
532 return false;
533 }
534}
535
536void ParameterManager::_factRawValueUpdated(const QVariant &rawValue)
537{
538 Fact *const fact = qobject_cast<Fact*>(sender());
539 if (!fact) {
540 qCWarning(ParameterManagerLog) << "Internal error";
541 return;
542 }
543
544 if (fact->vehicleRebootRequired()) {
545 _vehicle->setRebootRequired();
547 "Vehicle reboot required for changes to take effect. Use the power indicator in the toolbar to reboot."));
548 }
549
550 _mavlinkParamSet(fact->componentId(), fact->name(), fact->type(), rawValue);
551}
552
553void ParameterManager::_ftpDownloadComplete(const QString &fileName, const QString &errorMsg)
554{
555 bool continueWithDefaultParameterdownload = true;
556 bool immediateRetry = false;
557
558 _ftpDownloadInProgress = false;
559 (void) disconnect(_vehicle->ftpManager(), &FTPManager::downloadComplete, this, &ParameterManager::_ftpDownloadComplete);
560 (void) disconnect(_vehicle->ftpManager(), &FTPManager::commandProgress, this, &ParameterManager::_ftpDownloadProgress);
561
562 if (errorMsg.isEmpty()) {
563 qCDebug(ParameterManagerLog) << "ParameterManager::_ftpDownloadComplete : Parameter file received:" << fileName;
564 if (_parseParamFile(fileName)) {
565 qCDebug(ParameterManagerLog) << "ParameterManager::_ftpDownloadComplete : Parsed!";
566 return;
567 } else {
568 qCDebug(ParameterManagerLog) << "ParameterManager::_ftpDownloadComplete : Error in parameter file";
569 /* This should not happen... */
570 }
571 } else if (errorMsg.contains("File Not Found")) {
572 qCDebug(ParameterManagerLog) << "ParameterManager-ftp: No Parameterfile on vehicle - Start Conventional Parameter Download";
573 if (_initialRequestRetryCount == 0) {
574 immediateRetry = true;
575 }
576 } else if ((_loadProgress > 0.0001) && (_loadProgress < 0.01)) { /* FTP supported but too slow */
577 qCDebug(ParameterManagerLog) << "ParameterManager-ftp progress too slow - Start Conventional Parameter Download";
578 } else if (_initialRequestRetryCount == 1) {
579 qCDebug(ParameterManagerLog) << "ParameterManager-ftp: Too many retries - Start Conventional Parameter Download";
580 } else {
581 qCDebug(ParameterManagerLog) << "ParameterManager-ftp Retry:" << _initialRequestRetryCount;
582 continueWithDefaultParameterdownload = false;
583 }
584
585 if (continueWithDefaultParameterdownload) {
586 _tryftp = false;
587 _initialRequestRetryCount = 0;
588 /* If we receive "File not Found" this indicates that the vehicle does not support
589 * the parameter download via ftp. If we received this without retry, then we
590 * can immediately response with the conventional parameter download request, because
591 * we have no indication of communication link congestion.*/
592 if (immediateRetry) {
593 _paramRequestListTimeout();
594 } else {
595 _paramRequestListTimer.start();
596 }
597 } else {
598 _paramRequestListTimer.start();
599 }
600}
601
602void ParameterManager::_ftpDownloadProgress(float progress)
603{
604 qCDebug(ParameterManagerVerbose1Log) << "ParameterManager::_ftpDownloadProgress:" << progress;
605 _setLoadProgress(static_cast<double>(progress));
606 if (progress > 0.001) {
607 _paramRequestListTimer.stop();
608 }
609}
610
611void ParameterManager::_resetHashCheck()
612{
613 _hashCheckTimer.stop();
614 _hashCheckDone = false;
615}
616
618{
619 _resetHashCheck();
621 _refreshAllProgressActive = true;
622 _startParameterDownload(componentId);
623}
624
626{
627 _resetHashCheck();
628 _cacheOnlyHashCheck = true;
629
630 const SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
631 if (!sharedLink) {
633 return;
634 }
635
636 if (sharedLink->linkConfiguration()->isHighLatency() || _logReplay) {
637 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Cache-only hash check: high latency or log replay link, signalling failure";
639 return;
640 }
641
642 if (_vehicle->px4Firmware() && !_initialLoadComplete) {
643 _hashCheckTimer.start();
644 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Cache-only hash check: requesting _HASH_CHECK";
645 _requestHashCheck(MAV_COMP_ID_AUTOPILOT1);
646 } else {
647 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Cache-only hash check: not available, signalling failure";
649 }
650}
651
652void ParameterManager::_startParameterDownload(uint8_t componentId)
653{
654 const SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
655 if (!sharedLink) {
656 return;
657 }
658
659 if (sharedLink->linkConfiguration()->isHighLatency() || _logReplay) {
660 // These links don't load params
661 _parametersReady = true;
662 _missingParameters = true;
663 _initialLoadComplete = true;
664 _waitingForDefaultComponent = false;
665 emit parametersReadyChanged(_parametersReady);
666 emit missingParametersChanged(_missingParameters);
667 return;
668 }
669
670 if (_vehicle->px4Firmware() && !_initialLoadComplete && !_hashCheckDone) {
671 // PX4: Try _HASH_CHECK first to see if we can load from cache without a full parameter stream
672 _cacheOnlyHashCheck = false;
673 _hashCheckTimer.start();
674 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Requesting _HASH_CHECK before full parameter list";
675 const uint8_t hashCheckCompId = (componentId == MAV_COMP_ID_ALL)
676 ? static_cast<uint8_t>(MAV_COMP_ID_AUTOPILOT1)
677 : componentId;
678 _requestHashCheck(hashCheckCompId);
679 } else if (_tryftp && ((componentId == MAV_COMP_ID_ALL) || (componentId == MAV_COMP_ID_AUTOPILOT1))) {
680 if (_ftpDownloadInProgress) {
681 // A retry while the file is still transferring would disconnect the completion handler below
682 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Parameter file download already in progress";
683 _refreshAllProgressActive = false;
684 return;
685 }
686 if (!_initialLoadComplete) {
687 _paramRequestListTimer.start();
688 }
689 FTPManager *const ftpManager = _vehicle->ftpManager();
690 (void) connect(ftpManager, &FTPManager::downloadComplete, this, &ParameterManager::_ftpDownloadComplete);
691 // Other components may still be re-reading over the stream path; only their timer keeps them going
692 if (!_anyComponentWaiting()) {
693 _waitingParamTimeoutTimer.stop();
694 }
695 if (ftpManager->download(MAV_COMP_ID_AUTOPILOT1,
696 QStringLiteral("@PARAM/param.pck?withdefaults=1"),
697 QStandardPaths::writableLocation(QStandardPaths::TempLocation),
698 QStringLiteral("param.pck"),
699 false /* No filesize check */)) {
700 _ftpDownloadInProgress = true;
701 (void) connect(ftpManager, &FTPManager::commandProgress, this, &ParameterManager::_ftpDownloadProgress);
702 } else {
703 qCWarning(ParameterManagerLog) << "ParameterManager::_startParameterDownload FTPManager::download returned failure";
704 (void) disconnect(ftpManager, &FTPManager::downloadComplete, this, &ParameterManager::_ftpDownloadComplete);
705 _refreshAllProgressActive = false;
706 }
707 } else {
708 if (!_initialLoadComplete) {
709 _paramRequestListTimer.start();
710 }
711
712 // Reset index wait lists
713 for (int cid: _paramCountMap.keys()) {
714 // Add/Update all indices to the wait list, parameter index is 0-based
715 if ((componentId != MAV_COMP_ID_ALL) && (componentId != cid)) {
716 continue;
717 }
718 for (int waitingIndex = 0; waitingIndex < _paramCountMap[cid]; waitingIndex++) {
719 // This will add a new waiting index if needed and set the retry count for that index to 0
720 _waitingReadParamIndexMap[cid][waitingIndex] = 0;
721 }
722 }
723
724 mavlink_message_t msg{};
725 mavlink_msg_param_request_list_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
727 sharedLink->mavlinkChannel(),
728 &msg,
729 _vehicle->id(),
730 componentId);
731 (void) _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
732 }
733
734 const QString what = (componentId == MAV_COMP_ID_ALL) ? "MAV_COMP_ID_ALL" : QString::number(componentId);
735 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Request to refresh all parameters for component ID:" << what;
736}
737
738int ParameterManager::_actualComponentId(int componentId) const
739{
740 if (componentId == defaultComponentId) {
741 componentId = _vehicle->defaultComponentId();
742 if (componentId == defaultComponentId) {
743 qCWarning(ParameterManagerLog) << _logVehiclePrefix(-1) << "Default component id not set";
744 }
745 }
746
747 return componentId;
748}
749
750void ParameterManager::refreshParameter(int componentId, const QString &paramName)
751{
752 componentId = _actualComponentId(componentId);
753
754 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "refreshParameter - name:" << paramName << ")";
755
756 _mavlinkParamRequestRead(componentId, paramName, -1, true /* notifyFailure */);
757}
758
759void ParameterManager::refreshParametersPrefix(int componentId, const QString &namePrefix)
760{
761 componentId = _actualComponentId(componentId);
762 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "refreshParametersPrefix - name:" << namePrefix << ")";
763
764 if (!_mapCompId2FactMap.contains(componentId)) {
765 return;
766 }
767 for (const QString &paramName: _mapCompId2FactMap[componentId].keys()) {
768 if (paramName.startsWith(namePrefix)) {
769 refreshParameter(componentId, paramName);
770 }
771 }
772}
773
774void ParameterManager::bulkRefresh(int componentId, const QStringList &names, bool notifyFailure)
775{
776 componentId = _actualComponentId(componentId);
777
778 if (!_mapCompId2FactMap.contains(componentId)) {
779 return;
780 }
781 const QMap<QString, Fact *> &factMap = _mapCompId2FactMap[componentId];
782 QStringList resolved;
783 QSet<QString> seen;
784 for (const QString &entry : names) {
785 if (entry.endsWith(QLatin1Char('*'))) {
786 const QString prefix = entry.chopped(1);
787 if (prefix.isEmpty()) {
788 qCWarning(ParameterManagerLog) << "bulkRefresh: ignoring bare '*' entry";
789 continue;
790 }
791 for (auto it = factMap.cbegin(); it != factMap.cend(); ++it) {
792 if (it.key().startsWith(prefix) && !seen.contains(it.key())) {
793 seen.insert(it.key());
794 resolved.append(it.key());
795 }
796 }
797 } else if (factMap.contains(entry)) {
798 if (!seen.contains(entry)) {
799 seen.insert(entry);
800 resolved.append(entry);
801 }
802 } else {
803 qCWarning(ParameterManagerLog) << "bulkRefresh: unknown param name (skipped):" << entry;
804 }
805 }
806
807 if (resolved.isEmpty()) {
808 return;
809 }
810
811 qCDebug(ParameterManagerLog) << "bulkRefresh: resolved" << resolved.count() << "params";
812 new BulkRefreshJob(
813 this, componentId, resolved, notifyFailure,
814 [this, componentId](const QString &name) {
815 _mavlinkParamRequestRead(componentId, name, -1, false /* notifyFailure */);
816 },
817 this);
818}
819
820bool ParameterManager::parameterExists(int componentId, const QString &paramName) const
821{
822 bool ret = false;
823
824 componentId = _actualComponentId(componentId);
825 if (_mapCompId2FactMap.contains(componentId)) {
826 ret = _mapCompId2FactMap[componentId].contains(_remapParamNameToVersion(paramName));
827 }
828
829 return ret;
830}
831
832Fact *ParameterManager::getParameter(int componentId, const QString &paramName)
833{
834 componentId = _actualComponentId(componentId);
835
836 const QString mappedParamName = _remapParamNameToVersion(paramName);
837 if (!_mapCompId2FactMap.contains(componentId) || !_mapCompId2FactMap[componentId].contains(mappedParamName)) {
838 qgcApp()->reportMissingParameter(componentId, mappedParamName);
839 return &_defaultFact;
840 }
841
842 return _mapCompId2FactMap[componentId][mappedParamName];
843}
844
845QStringList ParameterManager::parameterNames(int componentId) const
846{
847 QStringList names;
848
849 const int compId = _actualComponentId(componentId);
850 const QMap<QString, Fact*> &factMap = _mapCompId2FactMap[compId];
851 for (const QString &paramName: factMap.keys()) {
852 names << paramName;
853 }
854
855 return names;
856}
857
858bool ParameterManager::_fillIndexBatchQueue(bool waitingParamTimeout)
859{
860 if (!_indexBatchQueueActive) {
861 return false;
862 }
863
864 if (waitingParamTimeout) {
865 // We timed out, clear the queue and try again
866 qCDebug(ParameterManagerLog) << "Refilling index based batch queue due to timeout";
867 _indexBatchQueue.clear();
868 } else {
869 qCDebug(ParameterManagerLog) << "Refilling index based batch queue due to received parameter";
870 }
871
872 for (const int componentId: _waitingReadParamIndexMap.keys()) {
873 if (_waitingReadParamIndexMap[componentId].count()) {
874 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "_waitingReadParamIndexMap count" << _waitingReadParamIndexMap[componentId].count();
875 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "_waitingReadParamIndexMap (index, retry count)" << _waitingReadParamIndexMap[componentId];
876 }
877
878 for (const int paramIndex: _waitingReadParamIndexMap[componentId].keys()) {
879 if (_indexBatchQueue.contains(qMakePair(componentId, paramIndex))) {
880 // Don't add more than once
881 continue;
882 }
883
884 if (_indexBatchQueue.count() >= kIndexBatchMaxOutstanding) {
885 break;
886 }
887
888 _waitingReadParamIndexMap[componentId][paramIndex]++; // Bump retry count
889 if (_disableAllRetries || (_waitingReadParamIndexMap[componentId][paramIndex] > _maxInitialLoadRetrySingleParam)) {
890 // Give up on this index
891 _failedReadParamIndexMap[componentId] << paramIndex;
892 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "Giving up on (paramIndex:" << paramIndex << "retryCount:" << _waitingReadParamIndexMap[componentId][paramIndex] << ")";
893 (void) _waitingReadParamIndexMap[componentId].remove(paramIndex);
894 } else {
895 // Retry again
896 _indexBatchQueue.append(qMakePair(componentId, paramIndex));
897 _sendParamRequestReadIndex(componentId, paramIndex);
898 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "Read re-request for (paramIndex:" << paramIndex << "retryCount:" << _waitingReadParamIndexMap[componentId][paramIndex] << ")";
899 }
900 }
901 }
902
903 return (!_indexBatchQueue.isEmpty());
904}
905
906void ParameterManager::_waitingParamTimeout()
907{
908 if (_logReplay) {
909 return;
910 }
911
912 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "_waitingParamTimeout after" << _waitingParamTimeoutTimer.interval() << "ms";
913
914 // Now that we have timed out for possibly the first time we can activate the index batch queue
915 _indexBatchQueueActive = true;
916
917 _giveUpOnUnresponsiveComponents();
918
919 // First check for any missing parameters from the initial index based load
920 bool paramsRequested = _fillIndexBatchQueue(true /* waitingParamTimeout */);
921 if (!paramsRequested && !_waitingForDefaultComponent && !_mapCompId2FactMap.contains(_vehicle->defaultComponentId())) {
922 // Initial load is complete but we still don't have any default component params. Wait one more cycle to see if the
923 // any show up.
924 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Restarting _waitingParamTimeoutTimer - still don't have default component params" << _vehicle->defaultComponentId();
925 _waitingParamTimeoutTimer.start();
926 _waitingForDefaultComponent = true;
927 return;
928 }
929 _waitingForDefaultComponent = false;
930
931 _checkInitialLoadComplete();
932 // Giving up on the last waiting index produces no PARAM_VALUE, so the bar must be recomputed here too
933 _updateProgressBar();
934
935 if (paramsRequested) {
936 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Restarting _waitingParamTimeoutTimer - re-request";
937 _waitingParamTimeoutTimer.start();
938 }
939}
940
941void ParameterManager::_giveUpOnUnresponsiveComponents()
942{
943 const int defaultCompId = _vehicle->defaultComponentId();
944
945 for (const int componentId: _waitingReadParamIndexMap.keys()) {
946 if (componentId == defaultCompId) {
947 continue;
948 }
949
950 const int outstanding = static_cast<int>(std::count_if(_indexBatchQueue.cbegin(), _indexBatchQueue.cend(),
951 [componentId](const QPair<int, int> &entry) { return entry.first == componentId; }));
952 if ((outstanding < kUnresponsiveMinOutstanding) || (_paramValuesReceivedThisCycle.value(componentId, 0) != 0)) {
953 continue;
954 }
955
956 if (++_silentCycleCount[componentId] < kUnresponsiveSilentCycles) {
957 continue;
958 }
959
960 const QList<int> remaining = _waitingReadParamIndexMap[componentId].keys();
961 qCWarning(ParameterManagerLog) << _logVehiclePrefix(componentId) << "Component unresponsive for" << kUnresponsiveSilentCycles
962 << "cycles, giving up on remaining parameters:" << remaining.count();
963 _failedReadParamIndexMap[componentId] << remaining;
964 _waitingReadParamIndexMap[componentId].clear();
965 (void) _indexBatchQueue.removeIf([componentId](const QPair<int, int> &entry) { return entry.first == componentId; });
966 }
967
968 _paramValuesReceivedThisCycle.clear();
969}
970
971void ParameterManager::_requestHashCheck(uint8_t componentId)
972{
973 const SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
974 if (!sharedLink) {
975 return;
976 }
977
978 qCDebug(ParameterManagerLog) << _logVehiclePrefix(componentId) << "Sending PARAM_REQUEST_READ for _HASH_CHECK";
979
980 char paramId[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN + 1] = {};
981 (void) strncpy(paramId, "_HASH_CHECK", MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN);
982
983 mavlink_message_t msg{};
984 (void) mavlink_msg_param_request_read_pack_chan(
987 sharedLink->mavlinkChannel(),
988 &msg,
989 static_cast<uint8_t>(_vehicle->id()),
990 componentId,
991 paramId,
992 -1);
993
994 (void) _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
995}
996
997void ParameterManager::_sendParamRequestReadIndex(int componentId, int paramIndex)
998{
999 const SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
1000 if (!sharedLink) {
1001 return;
1002 }
1003
1004 // Packer copies the full fixed-width field, so a short literal would read out of bounds
1005 char paramId[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN + 1] = {};
1006 mavlink_message_t msg{};
1007 (void) mavlink_msg_param_request_read_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
1009 sharedLink->mavlinkChannel(),
1010 &msg,
1011 static_cast<uint8_t>(_vehicle->id()),
1012 static_cast<uint8_t>(componentId),
1013 paramId,
1014 static_cast<int16_t>(paramIndex));
1015 (void) _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
1016}
1017
1018void ParameterManager::_mavlinkParamRequestRead(int componentId, const QString &paramName, int paramIndex, bool notifyFailure)
1019{
1020 auto paramRequestReadEncoder = [this, componentId, paramName, paramIndex](uint8_t /*systemId*/, uint8_t channel, mavlink_message_t *message) -> void {
1021 char paramId[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN + 1] = {};
1022 (void) strncpy(paramId, paramName.toLocal8Bit().constData(), MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN);
1023
1024 (void) mavlink_msg_param_request_read_pack_chan(MAVLinkProtocol::instance()->getSystemId(), // QGC system id
1025 MAVLinkProtocol::getComponentId(), // QGC component id
1026 channel,
1027 message,
1028 static_cast<uint8_t>(_vehicle->id()),
1029 static_cast<uint8_t>(componentId),
1030 paramId,
1031 static_cast<int16_t>(paramIndex));
1032 };
1033
1034 auto checkForCorrectParamValue = [componentId, paramName, paramIndex](const mavlink_message_t &message) -> bool {
1035 if (message.compid != componentId) {
1036 return false;
1037 }
1038
1039 mavlink_param_value_t param_value{};
1040 mavlink_msg_param_value_decode(&message, &param_value);
1041
1042 // This will null terminate the name string
1043 char parameterNameWithNull[MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN + 1] = {};
1044 (void) strncpy(parameterNameWithNull, param_value.param_id, MAVLINK_MSG_PARAM_VALUE_FIELD_PARAM_ID_LEN);
1045 const QString parameterName(parameterNameWithNull);
1046
1047 // Check that this is for the parameter we requested
1048 if (paramIndex != -1) {
1049 // Index based request
1050 if (param_value.param_index != paramIndex) {
1051 return false;
1052 }
1053 } else {
1054 // Name based request
1055 if (parameterName != paramName) {
1056 return false;
1057 }
1058 }
1059
1060 return true;
1061 };
1062
1063 auto checkForParamError = [componentId, paramName, paramIndex](const mavlink_message_t &message) -> bool {
1064 if (message.compid != componentId) {
1065 return false;
1066 }
1067
1068 mavlink_param_error_t paramError{};
1069 mavlink_msg_param_error_decode(&message, &paramError);
1070
1071 char paramId[MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN + 1] = {};
1072 (void) strncpy(paramId, paramError.param_id, MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN);
1073
1074 if (paramIndex != -1) {
1075 return paramError.param_index == paramIndex;
1076 } else {
1077 return QString(paramId) == paramName;
1078 }
1079 };
1080
1081 // State Machine:
1082 // Send PARAM_REQUEST_READ - 2 retries after initial attempt
1083 // Wait for PARAM_VALUE ack or PARAM_ERROR rejection
1084 //
1085 // timeout:
1086 // Back up to PARAM_REQUEST_READ for retries
1087 //
1088 // error (PARAM_ERROR or retries exhausted):
1089 // Notify user of failure
1090
1091 // Create states
1092 auto stateMachine = new QGCStateMachine(QStringLiteral("PARAM_REQUEST_READ"), vehicle(), this);
1093 auto sendParamRequestReadState = new SendMavlinkMessageState(stateMachine, paramRequestReadEncoder, kParamRequestReadRetryCount);
1094 auto waitAckState = new WaitForParamResponseState(stateMachine, _waitForParamValueAckMs, checkForCorrectParamValue, checkForParamError);
1095 auto userNotifyState = new FunctionState(QStringLiteral("User notify"), stateMachine, [waitAckState, paramName, this, componentId]() {
1096 const QString errorDetail = waitAckState->lastParamErrorString();
1097 const QString msg = errorDetail.isEmpty()
1098 ? QStringLiteral("Parameter read failed: param: %1 %2").arg(paramName, _vehicleAndComponentString(componentId))
1099 : QStringLiteral("Parameter read failed: param: %1 %2 - %3").arg(paramName, _vehicleAndComponentString(componentId), errorDetail);
1101 });
1102 auto logSuccessState = new FunctionState(QStringLiteral("Log success"), stateMachine, [this, componentId, paramName, paramIndex]() {
1103 qCDebug(ParameterManagerLog) << "PARAM_REQUEST_READ succeeded: name:" << paramName << "index" << paramIndex << _vehicleAndComponentString(componentId);
1104 emit _paramRequestReadSuccess(componentId, paramName, paramIndex);
1105 });
1106 auto logFailureState = new FunctionState(QStringLiteral("Log failure"), stateMachine, [this, componentId, paramName, paramIndex]() {
1107 qCDebug(ParameterManagerLog) << "PARAM_REQUEST_READ failed: param:" << paramName << "index" << paramIndex << _vehicleAndComponentString(componentId);
1108 emit _paramRequestReadFailure(componentId, paramName, paramIndex);
1109 });
1110 auto finalState = new QGCFinalState(stateMachine);
1111
1112 // Successful state machine transitions
1113 stateMachine->setInitialState(sendParamRequestReadState);
1114 sendParamRequestReadState->addThisTransition(&QGCState::advance, waitAckState);
1115 waitAckState->addThisTransition (&QGCState::advance, logSuccessState);
1116 logSuccessState->addThisTransition (&QGCState::advance, finalState);
1117
1118 // Retry transitions (timeout path)
1119 waitAckState->addTransition(waitAckState, &WaitStateBase::timeout, sendParamRequestReadState);
1120
1121 // PARAM_ERROR path (definitive rejection - no retries)
1122 waitAckState->addThisTransition(&QGCState::error, logFailureState);
1123
1124 // Error transitions (retries exhausted)
1125 sendParamRequestReadState->addThisTransition(&QGCState::error, logFailureState);
1126
1127 // Error state branching transitions
1128 if (notifyFailure) {
1129 logFailureState->addThisTransition (&QGCState::advance, userNotifyState);
1130 } else {
1131 logFailureState->addThisTransition (&QGCState::advance, finalState);
1132 }
1133 userNotifyState->addThisTransition (&QGCState::advance, finalState);
1134
1135 qCDebug(ParameterManagerLog) << "Starting state machine for PARAM_REQUEST_READ on: " << paramName << _vehicleAndComponentString(componentId);
1136 stateMachine->start();
1137}
1138
1139void ParameterManager::_writeLocalParamCache(int vehicleId, int componentId)
1140{
1141 CacheMapName2ParamTypeVal cacheMap;
1142
1143 for (const QString &paramName: _mapCompId2FactMap[componentId].keys()) {
1144 const Fact *const fact = _mapCompId2FactMap[componentId][paramName];
1145 cacheMap[paramName] = ParamTypeVal(fact->type(), fact->rawValue());
1146 }
1147
1148 QFile cacheFile(parameterCacheFile(vehicleId, componentId));
1149 if (cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1150 QDataStream ds(&cacheFile);
1151 ds << cacheMap;
1152 qCDebug(ParameterManagerLog) << "Parameter cache written" << cacheFile.fileName() << "paramCount:" << cacheMap.count();
1153 } else {
1154 qCWarning(ParameterManagerLog) << "Failed to open cache file for writing" << cacheFile.fileName();
1155 }
1156}
1157
1159{
1160 // Use application-specific subdirectory to isolate parallel test runs
1161 const QFileInfo settingsFile(QSettings().fileName());
1162 const QString basePath = settingsFile.dir().absolutePath();
1163 const QString appName = settingsFile.completeBaseName();
1164 return QDir(basePath + QDir::separator() + appName + QDir::separator() + QStringLiteral("ParamCache"));
1165}
1166
1167QString ParameterManager::parameterCacheFile(int vehicleId, int componentId)
1168{
1169 return parameterCacheDir().filePath(QStringLiteral("%1_%2.v2").arg(vehicleId).arg(componentId));
1170}
1171
1172void ParameterManager::_tryCacheHashLoad(int vehicleId, int componentId, const QVariant &hashValue)
1173{
1174 qCDebug(ParameterManagerLog) << "Attemping load from cache";
1175
1176 /* The datastructure of the cache table */
1177 CacheMapName2ParamTypeVal cacheMap;
1178 QFile cacheFile(parameterCacheFile(vehicleId, componentId));
1179 if (!cacheFile.exists()) {
1180 qCDebug(ParameterManagerLog) << "Parameter cache usage failed - No parameter cache file";
1181 if (!_hashCheckDone) {
1182 _hashCheckDone = true;
1183 if (_cacheOnlyHashCheck) {
1184 qCDebug(ParameterManagerLog) << "Cache-only hash check: no cache file, signalling failure";
1185 emit cacheCheckOnlyFailed();
1186 return;
1187 }
1188 // Standalone hash check path — fall back to FTP / PARAM_REQUEST_LIST
1189 _startParameterDownload(MAV_COMP_ID_ALL);
1190 }
1191 // If already in PARAM_REQUEST_LIST flow, just let the stream continue
1192 return;
1193 }
1194 (void) cacheFile.open(QIODevice::ReadOnly);
1195
1196 /* Deserialize the parameter cache table */
1197 QDataStream ds(&cacheFile);
1198 ds >> cacheMap;
1199
1200 /* compute the crc of the local cache to check against the remote */
1201 uint32_t crc32_value = 0;
1202 for (const QString &name: cacheMap.keys()) {
1203 const ParamTypeVal &paramTypeVal = cacheMap[name];
1204 const FactMetaData::ValueType_t factType = static_cast<FactMetaData::ValueType_t>(paramTypeVal.first);
1205
1206 if (_vehicle->compInfoManager()->compInfoParam(MAV_COMP_ID_AUTOPILOT1)->factMetaDataForName(name, factType)->volatileValue()) {
1207 // Does not take part in CRC
1208 qCDebug(ParameterManagerLog) << "Volatile parameter" << name;
1209 } else {
1210 const void *const vdat = paramTypeVal.second.constData();
1211 const FactMetaData::ValueType_t cacheFactType = static_cast<FactMetaData::ValueType_t>(paramTypeVal.first);
1212 crc32_value = QGC::crc32(reinterpret_cast<const uint8_t *>(qPrintable(name)), name.length(), crc32_value);
1213 crc32_value = QGC::crc32(static_cast<const uint8_t *>(vdat), FactMetaData::typeToSize(cacheFactType), crc32_value);
1214 }
1215 }
1216
1217 /* if the two param set hashes match, just load from the disk */
1218 if (crc32_value == hashValue.toUInt()) {
1219 _hashCheckDone = true;
1220 _paramRequestListTimer.stop();
1221 qCDebug(ParameterManagerLog) << "Parameters loaded from cache" << qPrintable(QFileInfo(cacheFile).absoluteFilePath());
1222
1223 const int count = cacheMap.count();
1224 int index = 0;
1225 for (const QString &name: cacheMap.keys()) {
1226 const ParamTypeVal &paramTypeVal = cacheMap[name];
1227 const FactMetaData::ValueType_t factType = static_cast<FactMetaData::ValueType_t>(paramTypeVal.first);
1228 const MAV_PARAM_TYPE mavParamType = factTypeToMavType(factType);
1229 _handleParamValue(componentId, name, count, index++, mavParamType, paramTypeVal.second);
1230 }
1231
1232 const SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
1233 if (sharedLink) {
1234 mavlink_param_set_t p{};
1235 mavlink_param_union_t union_value{};
1236
1237 // Return the hash value to notify we don't want any more updates
1238 p.param_type = MAV_PARAM_TYPE_UINT32;
1239 (void) strncpy(p.param_id, "_HASH_CHECK", sizeof(p.param_id));
1240 union_value.param_uint32 = crc32_value;
1241 p.param_value = union_value.param_float;
1242 p.target_system = static_cast<uint8_t>(_vehicle->id());
1243 p.target_component = static_cast<uint8_t>(componentId);
1244
1245 mavlink_message_t msg{};
1246 (void) mavlink_msg_param_set_encode_chan(MAVLinkProtocol::instance()->getSystemId(),
1248 sharedLink->mavlinkChannel(),
1249 &msg,
1250 &p);
1251 (void) _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
1252 }
1253
1254 // Give the user some feedback things loaded properly
1255 QVariantAnimation *const ani = new QVariantAnimation(this);
1256 ani->setEasingCurve(QEasingCurve::OutCubic);
1257 ani->setStartValue(0.0);
1258 ani->setEndValue(1.0);
1259 ani->setDuration(750);
1260
1261 (void) connect(ani, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
1262 _setLoadProgress(value.toDouble());
1263 });
1264
1265 // Hide 500ms after animation finishes
1266 connect(ani, &QVariantAnimation::finished, this, [this] {
1267 QTimer::singleShot(500, this, [this] {
1268 _setLoadProgress(0);
1269 });
1270 });
1271
1272 ani->start(QAbstractAnimation::DeleteWhenStopped);
1273 } else {
1274 qCDebug(ParameterManagerLog) << "Parameters cache match failed" << qPrintable(QFileInfo(cacheFile).absoluteFilePath());
1275 if (ParameterManagerDebugCacheFailureLog().isDebugEnabled()) {
1276 _debugCacheCRC[componentId] = true;
1277 _debugCacheMap[componentId] = cacheMap;
1278 for (const QString &name: cacheMap.keys()) {
1279 _debugCacheParamSeen[componentId][name] = false;
1280 }
1281 QGC::showAppMessage(tr("Parameter cache CRC match failed"));
1282 }
1283 if (!_hashCheckDone) {
1284 _hashCheckDone = true;
1285 if (_cacheOnlyHashCheck) {
1286 qCDebug(ParameterManagerLog) << "Cache-only hash check: CRC mismatch, signalling failure";
1287 emit cacheCheckOnlyFailed();
1288 return;
1289 }
1290 // Standalone hash check path — fall back to FTP / PARAM_REQUEST_LIST
1291 _startParameterDownload(MAV_COMP_ID_ALL);
1292 }
1293 // If already in PARAM_REQUEST_LIST flow, just let the stream continue
1294 }
1295}
1296
1297void ParameterManager::writeParametersToStream(QTextStream &stream) const
1298{
1299 stream << "# Onboard parameters for Vehicle " << _vehicle->id() << "\n";
1300 stream << "#\n";
1301
1302 stream << "# Stack: " << QGCMAVLink::firmwareClassToCanonicalString(QGCMAVLink::firmwareClass(_vehicle->firmwareType())) << "\n";
1303 stream << "# Vehicle: " << QGCMAVLink::vehicleClassToCanonicalString(QGCMAVLink::vehicleClass(_vehicle->vehicleType())) << "\n";
1304 stream << "# Version: "
1305 << _vehicle->firmwareMajorVersion() << "."
1306 << _vehicle->firmwareMinorVersion() << "."
1307 << _vehicle->firmwarePatchVersion() << " "
1308 << _vehicle->firmwareVersionTypeString() << "\n";
1309 stream << "# Git Revision: " << _vehicle->gitHash() << "\n";
1310
1311 stream << "#\n";
1312 stream << "# Vehicle-Id Component-Id Name Value Type\n";
1313
1314 for (const int componentId: _mapCompId2FactMap.keys()) {
1315 for (const QString &paramName: _mapCompId2FactMap[componentId].keys()) {
1316 const Fact *const fact = _mapCompId2FactMap[componentId][paramName];
1317 if (fact) {
1318 stream << _vehicle->id() << "\t" << componentId << "\t" << paramName << "\t" << fact->rawValueStringFullPrecision() << "\t" << QStringLiteral("%1").arg(factTypeToMavType(fact->type())) << "\n";
1319 } else {
1320 qCWarning(ParameterManagerLog) << "Internal error: missing fact";
1321 }
1322 }
1323 }
1324
1325 stream.flush();
1326}
1327
1329{
1330 switch (factType) {
1332 return MAV_PARAM_TYPE_UINT8;
1334 return MAV_PARAM_TYPE_INT8;
1336 return MAV_PARAM_TYPE_UINT16;
1338 return MAV_PARAM_TYPE_INT16;
1340 return MAV_PARAM_TYPE_UINT32;
1342 return MAV_PARAM_TYPE_UINT64;
1344 return MAV_PARAM_TYPE_INT64;
1346 return MAV_PARAM_TYPE_REAL32;
1348 return MAV_PARAM_TYPE_REAL64;
1349 default:
1350 qCWarning(ParameterManagerLog) << "Unsupported fact type" << factType;
1351 [[fallthrough]];
1353 return MAV_PARAM_TYPE_INT32;
1354 }
1355}
1356
1358{
1359 switch (mavType) {
1360 case MAV_PARAM_TYPE_UINT8:
1362 case MAV_PARAM_TYPE_INT8:
1364 case MAV_PARAM_TYPE_UINT16:
1366 case MAV_PARAM_TYPE_INT16:
1368 case MAV_PARAM_TYPE_UINT32:
1370 case MAV_PARAM_TYPE_UINT64:
1372 case MAV_PARAM_TYPE_INT64:
1374 case MAV_PARAM_TYPE_REAL32:
1376 case MAV_PARAM_TYPE_REAL64:
1378 default:
1379 qCWarning(ParameterManagerLog) << "Unsupported mav param type" << mavType;
1380 [[fallthrough]];
1381 case MAV_PARAM_TYPE_INT32:
1383 }
1384}
1385
1386void ParameterManager::_checkInitialLoadComplete()
1387{
1388 if (_initialLoadComplete) {
1389 _checkOtherComponentsLoadComplete();
1390 return;
1391 }
1392
1393 // Only the default component gates readiness. Other components (e.g. DroneCAN nodes bridged by PX4) keep
1394 // loading in the background and are reported by _checkOtherComponentsLoadComplete when they finish.
1395 const int defaultCompId = _vehicle->defaultComponentId();
1396 if (_waitingReadParamIndexMap.contains(defaultCompId) && !_waitingReadParamIndexMap[defaultCompId].isEmpty()) {
1397 return;
1398 }
1399
1400 if (!_mapCompId2FactMap.contains(defaultCompId)) {
1401 // No default component params yet, not done yet
1402 return;
1403 }
1404
1405 // We aren't waiting for any more initial parameter updates, initial parameter loading is complete
1406 _initialLoadComplete = true;
1407 _refreshAllProgressActive = false;
1408 _setLoadProgress(0.0);
1409
1410 // Parameter cache crc failure debugging
1411 for (const int componentId: _debugCacheParamSeen.keys()) {
1412 if (!_logReplay && _debugCacheCRC.contains(componentId) && _debugCacheCRC[componentId]) {
1413 for (const QString &paramName: _debugCacheParamSeen[componentId].keys()) {
1414 if (!_debugCacheParamSeen[componentId][paramName]) {
1415 qCDebug(ParameterManagerLog) << "Parameter in cache but not on vehicle componentId:Name" << componentId << paramName;
1416 }
1417 }
1418 }
1419 }
1420 _debugCacheCRC.clear();
1421
1422 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Initial load complete";
1423
1424 _missingParameters = !_failedReadParamIndexMap.value(defaultCompId).isEmpty();
1425 if (_missingParameters) {
1426 QStringList failedIndices;
1427 for (const int paramIndex: _failedReadParamIndexMap[defaultCompId]) {
1428 failedIndices.append(QString::number(paramIndex));
1429 }
1430 qCWarning(ParameterManagerLog) << _logVehiclePrefix(defaultCompId) << "The following parameter indices could not be loaded after the maximum number of retries:" << failedIndices.join(QStringLiteral(", "));
1431 const QString errorMsg = tr("%1 was unable to retrieve the full set of parameters from vehicle %2. "
1432 "This will cause %1 to be unable to display its full user interface. "
1433 "If you are using modified firmware, you may need to resolve any vehicle startup errors to resolve the issue. "
1434 "If you are using standard firmware, you may need to upgrade to a newer version to resolve the issue.").arg(QCoreApplication::applicationName()).arg(_vehicle->id());
1435 qCDebug(ParameterManagerLog) << errorMsg;
1436 QGC::showAppMessage(errorMsg);
1437 }
1438
1439 // Signal load complete
1440 _parametersReady = true;
1442 emit parametersReadyChanged(true);
1443 emit missingParametersChanged(_missingParameters);
1444
1445 _checkOtherComponentsLoadComplete();
1446}
1447
1448bool ParameterManager::_anyComponentWaiting() const
1449{
1450 for (auto it = _waitingReadParamIndexMap.cbegin(); it != _waitingReadParamIndexMap.cend(); ++it) {
1451 if (!it.value().isEmpty()) {
1452 return true;
1453 }
1454 }
1455 return false;
1456}
1457
1458void ParameterManager::_checkOtherComponentsLoadComplete()
1459{
1460 if (_otherComponentsReported) {
1461 return;
1462 }
1463
1464 if (_anyComponentWaiting()) {
1465 return;
1466 }
1467
1468 const int defaultCompId = _vehicle->defaultComponentId();
1469 QStringList failedIndices;
1470 QStringList failedComponentIds;
1471 for (const int componentId: _failedReadParamIndexMap.keys()) {
1472 if ((componentId == defaultCompId) || _failedReadParamIndexMap[componentId].isEmpty()) {
1473 continue;
1474 }
1475 failedComponentIds.append(QString::number(componentId));
1476 for (const int paramIndex: _failedReadParamIndexMap[componentId]) {
1477 failedIndices.append(QStringLiteral("%1:%2").arg(componentId).arg(paramIndex));
1478 }
1479 }
1480 if (failedComponentIds.isEmpty()) {
1481 return;
1482 }
1483 _otherComponentsReported = true;
1484
1485 qCWarning(ParameterManagerLog) << _logVehiclePrefix(-1) << "The following parameter indices could not be loaded after the maximum number of retries:" << failedIndices.join(QStringLiteral(", "));
1486 QString vehicleIdPrefix;
1487 if (MultiVehicleManager::instance()->vehicles()->count() > 1) {
1488 vehicleIdPrefix = tr("Vehicle %1: ").arg(_vehicle->id());
1489 }
1490 const QString errorMsg = vehicleIdPrefix + tr("%1 was unable to retrieve the full set of parameters from component(s) %2.").arg(QCoreApplication::applicationName(), failedComponentIds.join(QStringLiteral(", ")));
1491 qCDebug(ParameterManagerLog) << errorMsg;
1492 QGC::showAppMessage(errorMsg);
1493}
1494
1495void ParameterManager::_hashCheckTimeout()
1496{
1497 _hashCheckDone = true;
1498 if (_cacheOnlyHashCheck) {
1499 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "_HASH_CHECK timed out in cache-only mode, signalling failure";
1500 emit cacheCheckOnlyFailed();
1501 return;
1502 }
1503 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "_HASH_CHECK timed out, falling back to parameter download";
1504 _startParameterDownload(MAV_COMP_ID_ALL);
1505}
1506
1507void ParameterManager::_paramRequestListTimeout()
1508{
1509 if (_logReplay) {
1510 // Signal load complete
1511 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "_paramRequestListTimeout (log replay): Signalling load complete";
1512 _initialLoadComplete = true;
1513 _missingParameters = false;
1514 _parametersReady = true;
1516 emit parametersReadyChanged(true);
1517 emit missingParametersChanged(_missingParameters);
1518 return;
1519 }
1520
1521 if (!_disableAllRetries && (++_initialRequestRetryCount <= _maxInitialRequestListRetry)) {
1522 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Retrying initial parameter request list";
1523 _startParameterDownload(MAV_COMP_ID_ALL);
1524 return;
1525 }
1526
1527 qCDebug(ParameterManagerLog) << _logVehiclePrefix(-1) << "Initial parameter request list retries exhausted, giving up";
1528 if (!_vehicle->genericFirmware()) {
1529 const QString errorMsg = tr("Vehicle %1 did not respond to request for parameters. "
1530 "This will cause %2 to be unable to display its full user interface.").arg(_vehicle->id()).arg(QCoreApplication::applicationName());
1531 qCDebug(ParameterManagerLog) << errorMsg;
1532 QGC::showAppMessage(errorMsg);
1533 }
1535}
1536
1537QString ParameterManager::_remapParamNameToVersion(const QString &paramName) const
1538{
1539 static const QString noRemapPrefix = QStringLiteral("noremap.");
1540 if (paramName.startsWith(noRemapPrefix)) {
1541 return paramName.mid(noRemapPrefix.length());
1542 }
1543
1544 const int majorVersion = _vehicle->firmwareMajorVersion();
1545 const int minorVersion = _vehicle->firmwareMinorVersion();
1546
1547 if (majorVersion == Vehicle::versionNotSetValue) {
1548 // Vehicle version unknown
1549 return paramName;
1550 }
1551
1553 if (!majorVersionRemap.contains(majorVersion)) {
1554 // No mapping for this major version
1555 qCDebug(ParameterManagerVerbose1Log) << "_remapParamNameToVersion: no major version mapping";
1556 return paramName;
1557 }
1558
1559 const FirmwarePlugin::remapParamNameMinorVersionRemapMap_t &remapMinorVersion = majorVersionRemap[majorVersion];
1560 // We must map backwards from the highest known minor version to one above the vehicle's minor version
1561 QString mappedParamName = paramName;
1562 for (int currentMinorVersion = _vehicle->firmwarePlugin()->remapParamNameHigestMinorVersionNumber(majorVersion); currentMinorVersion>minorVersion; currentMinorVersion--) {
1563 if (remapMinorVersion.contains(currentMinorVersion)) {
1564 const FirmwarePlugin::remapParamNameMap_t &remap = remapMinorVersion[currentMinorVersion];
1565 if (remap.contains(mappedParamName)) {
1566 const QString toParamName = remap[mappedParamName];
1567 qCDebug(ParameterManagerLog) << "_remapParamNameToVersion: remapped currentMinor:from:to" << currentMinorVersion << mappedParamName << toParamName;
1568 mappedParamName = toParamName;
1569 }
1570 }
1571 }
1572
1573 return mappedParamName;
1574}
1575
1576void ParameterManager::_loadOfflineEditingParams()
1577{
1578 const QString paramFilename = _vehicle->firmwarePlugin()->offlineEditingParamFile(_vehicle);
1579 if (paramFilename.isEmpty()) {
1580 return;
1581 }
1582
1583 QFile paramFile(paramFilename);
1584 if (!paramFile.open(QFile::ReadOnly)) {
1585 qCWarning(ParameterManagerLog) << "Unable to open offline editing params file" << paramFilename;
1586 }
1587
1588 QTextStream paramStream(&paramFile);
1589 while (!paramStream.atEnd()) {
1590 const QString line = paramStream.readLine();
1591 if (line.startsWith("#")) {
1592 continue;
1593 }
1594
1595 const QStringList paramData = line.split("\t");
1596 Q_ASSERT(paramData.count() == 5);
1597
1598 const int offlineDefaultComponentId = paramData.at(1).toInt();
1599 _vehicle->setOfflineEditingDefaultComponentId(offlineDefaultComponentId);
1600 const QString paramName = paramData.at(2);
1601 const QString valStr = paramData.at(3);
1602 const MAV_PARAM_TYPE paramType = static_cast<MAV_PARAM_TYPE>(paramData.at(4).toUInt());
1603
1604 QVariant paramValue;
1605 switch (paramType) {
1606 case MAV_PARAM_TYPE_REAL32:
1607 paramValue = QVariant(valStr.toFloat());
1608 break;
1609 case MAV_PARAM_TYPE_UINT32:
1610 paramValue = QVariant(valStr.toUInt());
1611 break;
1612 case MAV_PARAM_TYPE_UINT16:
1613 paramValue = QVariant((quint16)valStr.toUInt());
1614 break;
1615 case MAV_PARAM_TYPE_INT16:
1616 paramValue = QVariant((qint16)valStr.toInt());
1617 break;
1618 case MAV_PARAM_TYPE_UINT8:
1619 paramValue = QVariant((quint8)valStr.toUInt());
1620 break;
1621 case MAV_PARAM_TYPE_INT8:
1622 paramValue = QVariant((qint8)valStr.toUInt());
1623 break;
1624 default:
1625 qCCritical(ParameterManagerLog) << "Unknown type" << paramType;
1626 [[fallthrough]];
1627 case MAV_PARAM_TYPE_INT32:
1628 paramValue = QVariant(valStr.toInt());
1629 break;
1630 }
1631
1632 Fact *const fact = new Fact(offlineDefaultComponentId, paramName, mavTypeToFactType(paramType), this);
1633
1634 FactMetaData *const factMetaData = _vehicle->compInfoManager()->compInfoParam(offlineDefaultComponentId)->factMetaDataForName(paramName, fact->type());
1635 fact->setMetaData(factMetaData);
1636
1637 _mapCompId2FactMap[defaultComponentId][paramName] = fact;
1638 }
1639
1640 _parametersReady = true;
1641 _initialLoadComplete = true;
1642 _debugCacheCRC.clear();
1643}
1644
1646{
1647 _vehicle->sendMavCommand(MAV_COMP_ID_AUTOPILOT1,
1648 MAV_CMD_PREFLIGHT_STORAGE,
1649 true, // showError
1650 2, // Reset params to default
1651 -1); // Don't do anything with mission storage
1652}
1653
1655{
1656 //-- https://github.com/PX4/Firmware/pull/11760
1657 Fact *const sysAutoConfigFact = getParameter(defaultComponentId, "SYS_AUTOCONFIG");
1658 if (sysAutoConfigFact) {
1659 sysAutoConfigFact->setRawValue(2);
1660 }
1661}
1662
1663QString ParameterManager::_logVehiclePrefix(int componentId) const
1664{
1665 if (componentId == -1) {
1666 return QStringLiteral("V:%1").arg(_vehicle->id());
1667 } else {
1668 return QStringLiteral("V:%1 C:%2").arg(_vehicle->id()).arg(componentId);
1669 }
1670}
1671
1672void ParameterManager::_setLoadProgress(double loadProgress)
1673{
1674 if (_loadProgress != loadProgress) {
1675 _loadProgress = loadProgress;
1676 emit loadProgressChanged(static_cast<float>(loadProgress));
1677 }
1678}
1679
1681{
1682 if (_parameterDownloadSkipped != skipped) {
1683 _parameterDownloadSkipped = skipped;
1685 }
1686}
1687
1689{
1690 return _paramCountMap.keys();
1691}
1692
1694{
1695 return _pendingWritesCount > 0;
1696}
1697
1698#ifdef QGC_UNITTEST_BUILD
1699void ParameterManager::setPendingWritesForTest(bool pending)
1700{
1701 const bool wasPending = (_pendingWritesCount > 0);
1702 _pendingWritesCount = pending ? 1 : 0;
1703 if (wasPending != pending) {
1704 emit pendingWritesChanged(pending);
1705 }
1706}
1707#endif
1708
1710{
1711 return _vehicle;
1712}
1713
1714
1715bool ParameterManager::_parseParamFile(const QString& filename)
1716{
1717 constexpr quint16 magic_standard = 0x671B;
1718 constexpr quint16 magic_withdefaults = 0x671C;
1719 quint32 no_of_parameters_found = 0;
1720 constexpr int componentId = MAV_COMP_ID_AUTOPILOT1;
1721 enum ap_var_type {
1722 AP_PARAM_NONE = 0,
1723 AP_PARAM_INT8,
1724 AP_PARAM_INT16,
1725 AP_PARAM_INT32,
1726 AP_PARAM_FLOAT,
1727 AP_PARAM_VECTOR3F,
1728 AP_PARAM_GROUP
1729 };
1730
1731 qCDebug(ParameterManagerLog) << "_parseParamFile:" << filename;
1732 QFile file(filename);
1733 if (!file.open(QIODevice::ReadOnly)) {
1734 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: Could not open downloaded parameter file.";
1735 return false;
1736 }
1737
1738 QDataStream in(&file);
1739 in.setByteOrder(QDataStream::LittleEndian);
1740
1741 quint16 magic, num_params, total_params;
1742 in >> magic;
1743 in >> num_params;
1744 in >> total_params;
1745
1746 if (in.status() != QDataStream::Ok) {
1747 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: Could not read Header";
1748 goto Error;
1749 }
1750
1751 qCDebug(ParameterManagerVerbose2Log) << "_parseParamFile: magic: 0x" << Qt::hex << magic;
1752 qCDebug(ParameterManagerVerbose2Log) << "_parseParamFile: num_params:" << num_params
1753 << "total_params:" << total_params;
1754
1755 if ((magic != magic_standard) && (magic != magic_withdefaults)) {
1756 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: File does not start with Magic";
1757 goto Error;
1758 }
1759 if (num_params > total_params) {
1760 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: total_params > num_params";
1761 goto Error;
1762 }
1763 if (num_params != total_params) {
1764 /* We requested all parameters, so this is an error here */
1765 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: total_params != num_params";
1766 goto Error;
1767 }
1768
1769 while (in.status() == QDataStream::Ok) {
1770 quint8 byte = 0;
1771 quint8 flags = 0;
1772 quint8 ptype = 0;
1773 quint8 name_len = 0;
1774 quint8 common_len = 0;
1775 bool withdefault = false;
1776 int no_read = 0;
1777 char name_buffer[17];
1778
1779 while (byte == 0x0) { // Eat padding bytes
1780 in >> byte;
1781 if (in.status() != QDataStream::Ok) {
1782 if (no_of_parameters_found == num_params) {
1783 goto Success;
1784 } else {
1785 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: unexpected EOF"
1786 << "number of parameters expected:" << num_params
1787 << "actual:" << no_of_parameters_found;
1788 goto Error;
1789 }
1790 }
1791 }
1792 ptype = byte & 0x0F;
1793 flags = (byte >> 4) & 0x0F;
1794 withdefault = (flags & 0x01) == 0x01;
1795 in >> byte;
1796 if (in.status() != QDataStream::Ok) {
1797 qCCritical(ParameterManagerLog) << "_parseParamFile: Error: Unexpected EOF while reading flags";
1798 goto Error;
1799 }
1800 name_len = ((byte >> 4) & 0x0F) + 1;
1801 common_len = byte & 0x0F;
1802 if ((name_len + common_len) > 16) {
1803 qCCritical(ParameterManagerLog) << "_parseParamFile: Error: common_len + name_len > 16"
1804 << "name_len" << name_len
1805 << "common_len" << common_len;
1806 goto Error;
1807 }
1808 no_read = in.readRawData(&name_buffer[common_len], static_cast<int>(name_len));
1809 if (no_read != name_len) {
1810 qCCritical(ParameterManagerLog) << "_parseParamFile: Error: Unexpected EOF while reading parameterName"
1811 << "Expected:" << name_len
1812 << "Actual:" << no_read;
1813 goto Error;
1814 }
1815 name_buffer[common_len + name_len] = '\0';
1816 const QString parameterName(name_buffer);
1817 qCDebug(ParameterManagerVerbose2Log) << "_parseParamFile: parameter" << parameterName
1818 << "name_len" << name_len
1819 << "common_len" << common_len
1820 << "ptype" << ptype
1821 << "flags" << flags;
1822
1823 QVariant parameterValue = 0;
1824 QVariant defaultValue;
1825 switch (static_cast<ap_var_type>(ptype)) {
1826 qint8 data8;
1827 qint16 data16;
1828 qint32 data32;
1829 float dfloat;
1830 case AP_PARAM_INT8:
1831 in >> data8;
1832 parameterValue = data8;
1833 if (withdefault) {
1834 in >> data8;
1835 defaultValue = data8;
1836 }
1837 break;
1838 case AP_PARAM_INT16:
1839 in >> data16;
1840 parameterValue = data16;
1841 if (withdefault) {
1842 in >> data16;
1843 defaultValue = data16;
1844 }
1845 break;
1846 case AP_PARAM_INT32:
1847 in >> data32;
1848 parameterValue = data32;
1849 if (withdefault) {
1850 in >> data32;
1851 defaultValue = data32;
1852 }
1853 break;
1854 case AP_PARAM_FLOAT:
1855 in >> data32;
1856 (void) memcpy(&dfloat, &data32, 4);
1857 parameterValue = dfloat;
1858 if (withdefault) {
1859 in >> data32;
1860 float ddefault;
1861 (void) memcpy(&ddefault, &data32, 4);
1862 defaultValue = ddefault;
1863 }
1864 break;
1865 default:
1866 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: type is out of range" << ptype;
1867 goto Error;
1868 break;
1869 }
1870 qCDebug(ParameterManagerVerbose2Log) << "paramValue" << parameterValue;
1871
1872 if (++no_of_parameters_found > num_params) {
1873 qCDebug(ParameterManagerLog) << "_parseParamFile: Error: more parameters in file than expected."
1874 << "Expected:" << num_params
1875 << "Actual:" << no_of_parameters_found;
1876 goto Error;
1877 }
1878
1879 const FactMetaData::ValueType_t factType = ((ptype == AP_PARAM_INT8) ? FactMetaData::valueTypeInt8 :
1880 (ptype == AP_PARAM_INT16) ? FactMetaData::valueTypeInt16 :
1881 (ptype == AP_PARAM_INT32) ? FactMetaData::valueTypeInt32 :
1882 FactMetaData::valueTypeFloat);
1883
1884 Fact *fact = nullptr;
1885 if (_mapCompId2FactMap.contains(componentId) && _mapCompId2FactMap[componentId].contains(parameterName)) {
1886 fact = _mapCompId2FactMap[componentId][parameterName];
1887 if (withdefault && defaultValue.isValid()) {
1888 // Firmware-provided defaults are authoritative: use the unchecked
1889 // setter so parameters that legitimately default to 0 ("disabled")
1890 // but have a metadata min > 0 don't produce false warnings.
1891 fact->metaData()->setRawDefaultValueFirmwareForce(defaultValue);
1892 }
1893 } else {
1894 qCDebug(ParameterManagerVerbose1Log) << _logVehiclePrefix(componentId) << "Adding new fact" << parameterName;
1895
1896 fact = new Fact(componentId, parameterName, factType, this);
1897 FactMetaData *const factMetaData = _vehicle->compInfoManager()->compInfoParam(componentId)->factMetaDataForName(parameterName, fact->type());
1898 fact->setMetaData(factMetaData);
1899
1900 _mapCompId2FactMap[componentId][parameterName] = fact;
1901
1902 // We need to know when the fact value changes so we can update the vehicle
1903 (void) connect(fact, &Fact::containerRawValueChanged, this, &ParameterManager::_factRawValueUpdated);
1904
1905 // Set default before emitting factAdded so QML sees defaultValueAvailable from the start
1906 if (withdefault && defaultValue.isValid()) {
1907 fact->metaData()->setRawDefaultValueFirmwareForce(defaultValue);
1908 }
1909
1910 emit factAdded(componentId, fact);
1911 }
1912 fact->containerSetRawValue(parameterValue);
1913 }
1914
1915Success:
1916 file.close();
1917 /* Create empty waiting lists as we have all parameters */
1918 _paramCountMap[componentId] = num_params;
1919 _totalParamCount += num_params;
1920 _waitingReadParamIndexMap[componentId] = QMap<int, int>();
1921 if (!_logReplay && _vehicle->px4Firmware()) {
1922 _writeLocalParamCache(_vehicle->id(), componentId);
1923 }
1924 _checkInitialLoadComplete();
1925 _refreshAllProgressActive = false;
1926 _setLoadProgress(0.0);
1927 return true;
1928
1929Error:
1930 file.close();
1931 return false;
1932}
1933
1934void ParameterManager::_incrementPendingWriteCount()
1935{
1936 _pendingWritesCount++;
1937 if (_pendingWritesCount == 1) {
1938 emit pendingWritesChanged(true);
1939 }
1940}
1941
1942void ParameterManager::_decrementPendingWriteCount()
1943{
1944 if (_pendingWritesCount == 0) {
1945 qCWarning(ParameterManagerLog) << "Internal Error: _pendingWriteCount == 0";
1946 return;
1947 }
1948
1949 _pendingWritesCount--;
1950 if (_pendingWritesCount == 0) {
1951 emit pendingWritesChanged(false);
1952 }
1953}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
#define qgcApp()
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct param_union mavlink_param_union_t
std::array< MsgOutKey, 4 > keys
Definition UBXPlan.cc:13
virtual void parametersReadyPreChecks()
FactMetaData * factMetaDataForName(const QString &name, FactMetaData::ValueType_t valueType)
CompInfoParam * compInfoParam(uint8_t compId)
void commandProgress(float value)
void downloadComplete(const QString &file, const QString &errorMsg)
bool download(uint8_t fromCompId, const QString &fromURI, const QString &toDir, const QString &fileName="", bool checksize=true)
Definition FTPManager.cc:32
Holds the meta data associated with a Fact.
void setRawDefaultValueFirmwareForce(const QVariant &rawDefaultValue)
static size_t typeToSize(ValueType_t type)
bool volatileValue() const
A Fact is used to hold a single value within the system.
Definition Fact.h:15
void setMetaData(FactMetaData *metaData, bool setDefaultFromMetaData=false)
Definition Fact.cc:742
void containerSetRawValue(const QVariant &value)
Value coming from Vehicle. This does NOT send a _containerRawValueChanged signal.
Definition Fact.cc:199
FactMetaData * metaData()
Definition Fact.h:172
int componentId() const
Definition Fact.h:92
FactMetaData::ValueType_t type() const
Definition Fact.h:127
void setRawValue(const QVariant &value)
Definition Fact.cc:135
QString name() const
Definition Fact.h:124
void containerRawValueChanged(const QVariant &value)
This signal is meant for use by Fact container implementations. Used to send changed values to vehicl...
QString rawValueStringFullPrecision() const
Returns the values as a string with full 18 digit precision if float/double.
Definition Fact.cc:446
bool vehicleRebootRequired() const
Definition Fact.cc:819
QVariant rawValue() const
Definition Fact.h:87
QMap< int, remapParamNameMap_t > remapParamNameMinorVersionRemapMap_t
virtual int remapParamNameHigestMinorVersionNumber(int) const
QMap< QString, QString > remapParamNameMap_t
virtual QString offlineEditingParamFile(Vehicle *) const
Return the resource file which contains the set of params loaded for offline editing.
QMap< int, remapParamNameMinorVersionRemapMap_t > remapParamNameMajorVersionMap_t
virtual const remapParamNameMajorVersionMap_t & paramNameRemapMajorVersionMap() const
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
static MultiVehicleManager * instance()
bool parameterExists(int componentId, const QString &paramName) const
void parameterDownloadSkippedChanged()
void mavlinkMessageReceived(const mavlink_message_t &message)
Fact * getParameter(int componentId, const QString &paramName)
void factAdded(int componentId, Fact *fact)
static FactMetaData::ValueType_t mavTypeToFactType(MAV_PARAM_TYPE mavType)
void setParameterDownloadSkipped(bool skipped)
QList< int > componentIds() const
void refreshParametersPrefix(int componentId, const QString &namePrefix)
Request a refresh on all parameters that begin with the specified prefix.
void initialParametersRequestFailed()
Vehicle never responded to PARAM_REQUEST_LIST, all retries exhausted.
void parametersReadyChanged(bool parametersReady)
double loadProgress() const
void bulkRefresh(int componentId, const QStringList &names, bool notifyFailure=true)
bool pendingWrites() const
static QDir parameterCacheDir()
void missingParametersChanged(bool missingParameters)
static constexpr int defaultComponentId
QStringList parameterNames(int componentId) const
Returns all parameter names.
void _paramRequestReadFailure(int componentId, const QString &paramName, int paramIndex)
void _paramRequestReadSuccess(int componentId, const QString &paramName, int paramIndex)
static constexpr int kIndexBatchMaxOutstanding
Maximum index re-reads in flight at once.
static constexpr int kParamSetRetryCount
Number of retries for PARAM_SET.
void _paramSetSuccess(int componentId, const QString &paramName)
static constexpr int kUnresponsiveMinOutstanding
Minimum in-flight re-reads to a component before a silent cycle counts against it.
void loadProgressChanged(float value)
void cacheCheckOnlyFailed()
void refreshParameter(int componentId, const QString &paramName)
Request a refresh on the specific parameter.
void pendingWritesChanged(bool pendingWrites)
static MAV_PARAM_TYPE factTypeToMavType(FactMetaData::ValueType_t factType)
static constexpr int kParamRequestReadRetryCount
Number of retries for PARAM_REQUEST_READ.
void resetAllToVehicleConfiguration()
Q_INVOKABLE void refreshAllParameters()
static QString parameterCacheFile(int vehicleId, int componentId)
void writeParametersToStream(QTextStream &stream) const
static constexpr int kUnresponsiveSilentCycles
Consecutive silent cycles before a non-default component is given up on.
void _paramSetFailure(int componentId, const QString &paramName)
Final state for a QGCStateMachine with logging support.
QGroundControl specific state machine with enhanced error handling.
QSignalTransition * addThisTransition(PointerToMemberFunction signal, QAbstractState *target)
Simpler version of QState::addTransition which assumes the sender is this.
Definition QGCState.h:31
Sends the specified MAVLink message to the vehicle.
WeakLinkInterfacePtr primaryLink() const
bool px4Firmware() const
Definition Vehicle.h:512
void sendMavCommand(int compId, MAV_CMD command, bool showError, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f, float param6=0.0f, float param7=0.0f)
Definition Vehicle.cc:2173
QString firmwareVersionTypeString() const
Definition Vehicle.cc:2319
MAV_TYPE vehicleType() const
Definition Vehicle.h:446
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:597
FirmwarePlugin * firmwarePlugin()
Provides access to the Firmware Plugin for this Vehicle.
Definition Vehicle.h:462
ComponentInformationManager * compInfoManager()
Definition Vehicle.h:599
int firmwareMinorVersion() const
Definition Vehicle.h:680
MAV_AUTOPILOT firmwareType() const
Definition Vehicle.h:445
void setRebootRequired()
Latches rebootRequired until the vehicle reboots.
Definition Vehicle.cc:2355
int id() const
Definition Vehicle.h:443
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1401
QString gitHash() const
Definition Vehicle.h:693
int defaultComponentId() const
Definition Vehicle.h:700
bool genericFirmware() const
Definition Vehicle.h:514
AutoPilotPlugin * autopilotPlugin()
Provides access to AutoPilotPlugin for this vehicle.
Definition Vehicle.h:459
void setOfflineEditingDefaultComponentId(int defaultComponentId)
Sets the default component id for an offline editing vehicle.
Definition Vehicle.cc:2515
int firmwarePatchVersion() const
Definition Vehicle.h:681
FTPManager * ftpManager()
Definition Vehicle.h:598
int firmwareMajorVersion() const
Definition Vehicle.h:679
Waits for either PARAM_VALUE (success) or PARAM_ERROR (rejection) from the vehicle.
QByteArray line(std::string_view command)
command as one CR/LF-terminated line.
constexpr std::chrono::microseconds remaining(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds lifetime)
Time left before a receipt reaches lifetime; zero for a missing, future or expired one.
std::optional< T > number(std::string_view field, int base=DECIMAL_BASE)
quint32 crc32(const quint8 *src, unsigned len, unsigned state)
Definition QGCMath.cc:66
bool runningUnitTests()
bool fuzzyCompare(double value1, double value2)
Returns true if the two values are equal or close. Correctly handles 0 and NaN values.
Definition QGCMath.cc:71
void showRebootAppMessage(const QString &message, const QString &title)
Modal reboot-required message. Debounced within 2 minutes.
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9
static const int versionNotSetValue