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