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