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