15#include <QtCore/QCoreApplication>
16#include <QtCore/QStandardPaths>
18#include <QtCore/QFile>
20QGC_LOGGING_CATEGORY(RequestMetaDataTypeStateMachineLog,
"ComponentInformation.RequestMetaDataTypeStateMachine")
26 qCDebug(RequestMetaDataTypeStateMachineLog) << Q_FUNC_INFO <<
this;
30 _wireTimeoutHandling();
35 qCDebug(RequestMetaDataTypeStateMachineLog) <<
this;
38void RequestMetaDataTypeStateMachine::_createStates()
42 "RequestCompMetadata",
45 _timeoutCompInfoRequest
51 "RequestCompInfoDeprecated",
53 [
this]() {
return _shouldSkipDeprecatedRequest(); },
56 _timeoutCompInfoRequest
62 "RequestMetaDataJson",
65 _timeoutMetaDataDownload
71 "RequestMetaDataJsonFallback",
73 [
this]() {
return _shouldSkipFallback(); },
76 _timeoutMetaDataDownload
82 "RequestTranslationJson",
85 _timeoutMetaDataDownload
93 [
this]() {
return _shouldSkipTranslation(); },
119void RequestMetaDataTypeStateMachine::_wireTransitions()
127 _stateRequestDeprecated->addTransition(_stateRequestDeprecated, &
WaitStateBase::completed, _stateRequestMetaDataJson);
131 _stateRequestMetaDataJson->addTransition(_stateRequestMetaDataJson, &
WaitStateBase::completed, _stateRequestMetaDataJsonFallback);
134 _stateRequestMetaDataJsonFallback->addTransition(_stateRequestMetaDataJsonFallback, &
WaitStateBase::completed, _stateRequestTranslationJson);
135 _stateRequestMetaDataJsonFallback->addTransition(_stateRequestMetaDataJsonFallback, &
SkippableAsyncState::skipped, _stateRequestTranslationJson);
138 _stateRequestTranslationJson->addTransition(_stateRequestTranslationJson, &
WaitStateBase::completed, _stateRequestTranslate);
151void RequestMetaDataTypeStateMachine::_wireTimeoutHandling()
156 _stateRequestCompInfo->addTransition(_stateRequestCompInfo, &
WaitStateBase::timedOut, _stateRequestDeprecated);
158 _stateRequestDeprecated->addTransition(_stateRequestDeprecated, &
WaitStateBase::timedOut, _stateRequestMetaDataJson);
160 _stateRequestMetaDataJson->addTransition(_stateRequestMetaDataJson, &
WaitStateBase::timedOut, _stateRequestMetaDataJsonFallback);
162 _stateRequestMetaDataJsonFallback->addTransition(_stateRequestMetaDataJsonFallback, &
WaitStateBase::timedOut, _stateRequestTranslationJson);
164 _stateRequestTranslationJson->addTransition(_stateRequestTranslationJson, &
WaitStateBase::timedOut, _stateRequestTranslate);
172 qCDebug(RequestMetaDataTypeStateMachineLog) << Q_FUNC_INFO <<
typeToString();
174 _jsonMetadataFileName.clear();
175 _jsonMetadataTranslatedFileName.clear();
176 _jsonTranslationFileName.clear();
177 _activeAsyncState =
nullptr;
178 _activeSkippableState =
nullptr;
179 _metadataSource = MetadataSource::None;
180 _metadataUri.clear();
181 _metadataIsFallback =
false;
188 if (!_compInfo)
return "Unknown";
190 switch (_compInfo->
type) {
191 case COMP_METADATA_TYPE_GENERAL:
return "COMP_METADATA_TYPE_GENERAL";
192 case COMP_METADATA_TYPE_PARAMETER:
return "COMP_METADATA_TYPE_PARAMETER";
193 case COMP_METADATA_TYPE_COMMANDS:
return "COMP_METADATA_TYPE_COMMANDS";
194 case COMP_METADATA_TYPE_PERIPHERALS:
return "COMP_METADATA_TYPE_PERIPHERALS";
195 case COMP_METADATA_TYPE_EVENTS:
return "COMP_METADATA_TYPE_EVENTS";
196 case COMP_METADATA_TYPE_ACTUATORS:
return "COMP_METADATA_TYPE_ACTUATORS";
197 default:
return "Unknown";
201bool RequestMetaDataTypeStateMachine::_shouldSkipCompInfoRequest()
const
203 return _compInfo->
type != COMP_METADATA_TYPE_GENERAL;
206bool RequestMetaDataTypeStateMachine::_shouldSkipDeprecatedRequest()
const
209 if (_compInfo->
type != COMP_METADATA_TYPE_GENERAL) {
214 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"COMPONENT_METADATA available, skipping COMPONENT_INFORMATION";
220bool RequestMetaDataTypeStateMachine::_shouldSkipFallback()
const
223 return !_jsonMetadataFileName.isEmpty();
226bool RequestMetaDataTypeStateMachine::_shouldSkipTranslation()
const
229 return _jsonTranslationFileName.isEmpty();
232void RequestMetaDataTypeStateMachine::_requestCompInfo()
234 if (_shouldSkipCompInfoRequest()) {
243 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Skipping component information request due to no primary link" <<
typeToString();
248 if (sharedLink->linkConfiguration()->isHighLatency() || sharedLink->isLogReplay()) {
249 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Skipping component information request due to link type" <<
typeToString();
254 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Requesting component metadata" <<
typeToString();
259 self->_handleCompMetadataResult(result, message);
262 MAV_COMP_ID_AUTOPILOT1,
263 MAVLINK_MSG_ID_COMPONENT_METADATA
267void RequestMetaDataTypeStateMachine::_handleCompMetadataResult(MAV_RESULT result,
const mavlink_message_t& message)
269 if (result == MAV_RESULT_ACCEPTED) {
270 mavlink_component_metadata_t componentMetadata;
271 mavlink_msg_component_metadata_decode(&message, &componentMetadata);
272 _compInfo->
setUriMetaData(componentMetadata.uri, componentMetadata.file_crc);
279void RequestMetaDataTypeStateMachine::_requestCompInfoDeprecated()
285 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Skipping deprecated component information request due to no primary link" <<
typeToString();
286 _stateRequestDeprecated->
complete();
290 if (sharedLink->linkConfiguration()->isHighLatency() || sharedLink->isLogReplay()) {
291 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Skipping deprecated component information request due to link type" <<
typeToString();
292 _stateRequestDeprecated->
complete();
296 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Requesting component information (deprecated)" <<
typeToString();
301 self->_handleCompInfoResult(result, failureCode, message);
304 MAV_COMP_ID_AUTOPILOT1,
305 MAVLINK_MSG_ID_COMPONENT_INFORMATION
311 if (result == MAV_RESULT_ACCEPTED) {
312 mavlink_component_information_t componentInformation;
313 mavlink_msg_component_information_decode(&message, &componentInformation);
314 _compInfo->
setUriMetaData(componentInformation.general_metadata_uri, componentInformation.general_metadata_file_crc);
316 switch (failureCode) {
321 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"MAV_CMD_REQUEST_MESSAGE COMPONENT_INFORMATION no response from vehicle" <<
typeToString();
324 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"MAV_CMD_REQUEST_MESSAGE COMPONENT_INFORMATION message not received" <<
typeToString();
331 _stateRequestDeprecated->
complete();
334void RequestMetaDataTypeStateMachine::_requestMetaDataJson()
341 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": requesting metadata (primary) from" << uri;
343 _activeAsyncState = _stateRequestMetaDataJson;
344 _activeSkippableState =
nullptr;
348void RequestMetaDataTypeStateMachine::_requestMetaDataJsonFallback()
350 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": primary failed, requesting metadata (fallback) from" << _compInfo->
uriMetaDataFallback();
351 _metadataIsFallback =
true;
358 _activeAsyncState =
nullptr;
359 _activeSkippableState = _stateRequestMetaDataJsonFallback;
363void RequestMetaDataTypeStateMachine::_requestTranslationJson()
369 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"No translation URI for" <<
typeToString();
370 _stateRequestTranslationJson->
complete();
374 _activeAsyncState = _stateRequestTranslationJson;
375 _activeSkippableState =
nullptr;
376 _requestFile(
"",
false, uri, _jsonTranslationFileName,
false);
379void RequestMetaDataTypeStateMachine::_requestTranslate()
382 this, &RequestMetaDataTypeStateMachine::_downloadAndTranslationComplete);
385 _jsonMetadataFileName,
389 this, &RequestMetaDataTypeStateMachine::_downloadAndTranslationComplete);
390 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"downloadAndTranslate() failed";
395void RequestMetaDataTypeStateMachine::_downloadAndTranslationComplete(QString translatedJsonTempFile, QString errorMsg)
398 this, &RequestMetaDataTypeStateMachine::_downloadAndTranslationComplete);
400 _jsonMetadataTranslatedFileName = translatedJsonTempFile;
401 if (!errorMsg.isEmpty()) {
402 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"Metadata translation failed:" << errorMsg;
408void RequestMetaDataTypeStateMachine::_completeRequest()
410 const bool success = !_jsonMetadataFileName.isEmpty();
411 const bool translated = !_jsonMetadataTranslatedFileName.isEmpty();
414 _compInfo->
setJson(_jsonMetadataTranslatedFileName);
415 QFile(_jsonMetadataTranslatedFileName).remove();
417 _compInfo->
setJson(_jsonMetadataFileName);
421 if (!_jsonMetadataCrcValid && !_jsonMetadataFileName.isEmpty()) {
422 QFile(_jsonMetadataFileName).remove();
424 if (!_jsonMetadataCrcValid && !_jsonTranslationFileName.isEmpty()) {
425 QFile(_jsonTranslationFileName).remove();
429 const char* sourceLabel = _metadataIsFallback ?
"(fallback)" :
"(primary)";
432 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
":" << _metadataSourceToString(_metadataSource)
433 << sourceLabel <<
"(translated)" << _metadataUri;
435 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
":" << _metadataSourceToString(_metadataSource)
436 << sourceLabel << _metadataUri;
439 const QString failureMsg = QStringLiteral(
"%1 : failed to load metadata (primary and fallback) %2")
443 qCDebug(RequestMetaDataTypeStateMachineLog).noquote() << failureMsg;
445 qCWarning(RequestMetaDataTypeStateMachineLog).noquote() << failureMsg;
450const char* RequestMetaDataTypeStateMachine::_metadataSourceToString(MetadataSource source)
453 case MetadataSource::Cache:
return "loaded from cache";
454 case MetadataSource::FTP:
return "downloaded via FTP";
455 case MetadataSource::HTTP:
return "downloaded via HTTP";
456 case MetadataSource::None:
return "not available";
461void RequestMetaDataTypeStateMachine::_requestFile(
const QString& cacheFileTag,
bool crcValid,
const QString& uri, QString& outputFileName,
bool trackMetadataSource)
464 _currentCacheFileTag = cacheFileTag;
465 _currentFileName = &outputFileName;
466 _currentFileValidCrc = crcValid;
467 outputFileName.clear();
469 auto completeCurrentState = [
this]() {
470 if (_activeAsyncState) {
472 }
else if (_activeSkippableState) {
477 if (!_compInfo->
available() || uri.isEmpty()) {
478 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": metadata not available, skipping download";
479 completeCurrentState();
483 const QString cachedFile = crcValid ? _compMgr->
fileCache().
access(cacheFileTag) :
"";
485 if (!cachedFile.isEmpty()) {
486 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Using cached file" << cachedFile;
487 if (trackMetadataSource) {
488 _metadataSource = MetadataSource::Cache;
491 outputFileName = cachedFile;
492 completeCurrentState();
497 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": CRC not available, cache bypassed";
499 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": not found in cache, downloading";
502 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Downloading json" << uri;
504 if (_uriIsMAVLinkFTP(uri)) {
505 if (trackMetadataSource) {
506 _metadataSource = MetadataSource::FTP;
510 if (ftpManager->
download(MAV_COMP_ID_AUTOPILOT1, uri, QStandardPaths::writableLocation(QStandardPaths::TempLocation))) {
511 _downloadStartTime.start();
514 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"FTPManager::download returned failure";
516 completeCurrentState();
519 if (trackMetadataSource) {
520 _metadataSource = MetadataSource::HTTP;
524 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
526 _downloadStartTime.start();
528 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"QGCCachedFileDownload::download returned failure";
530 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
531 completeCurrentState();
536QString RequestMetaDataTypeStateMachine::_downloadCompleteJsonWorker(
const QString& fileName)
538 const QString tempPath = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(_currentCacheFileTag);
540 if (outputFileName.isEmpty()) {
541 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"Inflate of compressed json failed" << _currentCacheFileTag;
544 if (_currentFileValidCrc) {
546 outputFileName = _compMgr->
fileCache().
insert(_currentCacheFileTag, outputFileName);
548 return outputFileName;
551void RequestMetaDataTypeStateMachine::_ftpDownloadComplete(
const QString& fileName,
const QString& errorMsg)
553 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"_ftpDownloadComplete fileName:errorMsg" << fileName << errorMsg;
558 if (errorMsg.isEmpty()) {
559 if (_currentFileName) {
560 *_currentFileName = _downloadCompleteJsonWorker(fileName);
563 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": FTP download failed:" << errorMsg;
566 if (_activeAsyncState) {
568 }
else if (_activeSkippableState) {
573void RequestMetaDataTypeStateMachine::_ftpDownloadProgress(
float progress)
575 int elapsedSec = _downloadStartTime.elapsed() / 1000;
576 float totalDownloadTime = elapsedSec /
progress;
579 const int maxDownloadTimeSec = 40;
580 if (elapsedSec > 10 && progress < 0.5 && totalDownloadTime > maxDownloadTimeSec) {
581 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Slow download, aborting. Total time (s):" << totalDownloadTime;
586void RequestMetaDataTypeStateMachine::_httpDownloadComplete(
bool success,
const QString& localFile,
const QString& errorMsg,
bool fromCache)
588 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"_httpDownloadComplete success:localFile:errorMsg:fromCache"
589 << success << localFile << errorMsg << fromCache;
592 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
594 if (success && errorMsg.isEmpty()) {
595 if (_currentFileName) {
596 *_currentFileName = _downloadCompleteJsonWorker(localFile);
599 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": HTTP download failed:" << errorMsg;
602 if (_activeAsyncState) {
604 }
else if (_activeSkippableState) {
609bool RequestMetaDataTypeStateMachine::_uriIsMAVLinkFTP(
const QString& uri)
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Cached file download with time-based expiration and fallback support.
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Calls a function when entered and waits for an external trigger to advance.
void complete()
Call this to signal that the async operation has completed successfully.
Base class for all CompInfo types.
const COMP_METADATA_TYPE type
const QString & uriMetaData() const
virtual void setJson(const QString &metaDataJsonFileName)=0
const QString & uriTranslation() const
uint32_t crcMetaData() const
void setUriMetaData(const QString &uri, uint32_t crc)
uint32_t crcMetaDataFallback() const
bool crcMetaDataValid() const
const QString & uriMetaDataFallback() const
bool crcMetaDataFallbackValid() const
@ EmitError
Emit error() signal (default)
static constexpr const char * mavlinkFTPScheme
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)
bool download(const QString &url, int maxCacheAgeSec)
void finished(bool success, const QString &localPath, const QString &errorMessage, bool fromCache)
static QString mavResultToString(uint8_t result)
QGroundControl specific state machine with enhanced error handling.
void start()
Start the state machine with debug logging.
QGCState * addErrorRecoveryState(const QString &stateName, ErrorRecoveryBuilder::Action action, int maxRetries=0, int retryDelayMsecs=1000, ErrorRecoveryBuilder::ExhaustedBehavior exhaustedBehavior=ErrorRecoveryBuilder::EmitError, ErrorRecoveryBuilder::Action fallback=nullptr, ErrorRecoveryBuilder::VoidAction rollback=nullptr, int timeoutMsecs=0)
Vehicle * vehicle() const
void setInitialState(QAbstractState *state, bool autoStart=false)
QGCFinalState * addFinalState(const QString &stateName=QString())
float progress() const
Get the current overall progress (0.0 to 1.0)
void registerState(QGCState *state)
Combines skip condition checking with async operation setup in a single state.
void skipped()
Emitted when skip predicate returns true and the state is skipped.
void complete()
Call this to signal that the async operation has completed successfully.
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
void requestMessage(RequestMessageResultHandler resultHandler, void *resultHandlerData, int compId, int messageId, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f)
FTPManager * ftpManager()
QString decompressIfNeeded(const QString &filePath, const QString &outputPath, bool removeOriginal)
RequestMessageResultHandlerFailureCode_t
@ RequestMessageFailureMessageNotReceived
@ RequestMessageFailureCommandNotAcked
@ RequestMessageFailureCommandError