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
47 registerState(_stateRequestCompInfo);
51 "RequestCompInfoDeprecated",
53 [
this]() {
return _shouldSkipDeprecatedRequest(); },
56 _timeoutCompInfoRequest
58 registerState(_stateRequestDeprecated);
62 "RequestMetaDataJson",
65 _timeoutMetaDataDownload
67 registerState(_stateRequestMetaDataJson);
71 "RequestMetaDataJsonFallback",
73 [
this]() {
return _shouldSkipFallback(); },
76 _timeoutMetaDataDownload
78 registerState(_stateRequestMetaDataJsonFallback);
82 "RequestTranslationJson",
85 _timeoutMetaDataDownload
87 registerState(_stateRequestTranslationJson);
93 [
this]() {
return _shouldSkipTranslation(); },
98 registerState(_stateRequestTranslate);
103 _stateComplete = addErrorRecoveryState(
114 _stateFinal = addFinalState(
"Final");
116 setInitialState(_stateRequestCompInfo);
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()) {
239 Vehicle* vehicle = _compMgr->vehicle();
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()
281 Vehicle* vehicle = _compMgr->vehicle();
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(ComponentInformationManagerLog) <<
typeToString() <<
": requesting metadata (primary) from" << uri;
343 _activeAsyncState = _stateRequestMetaDataJson;
344 _activeSkippableState =
nullptr;
348void RequestMetaDataTypeStateMachine::_requestMetaDataJsonFallback()
350 qCDebug(ComponentInformationManagerLog) <<
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(ComponentInformationManagerLog) <<
typeToString() <<
":" << _metadataSourceToString(_metadataSource)
433 << sourceLabel <<
"(translated)" << _metadataUri;
435 qCDebug(ComponentInformationManagerLog) <<
typeToString() <<
":" << _metadataSourceToString(_metadataSource)
436 << sourceLabel << _metadataUri;
439 qCWarning(ComponentInformationManagerLog) <<
typeToString() <<
": failed to load metadata (primary and fallback)"
440 << (_metadataUri.isEmpty() ? _compInfo->
uriMetaData() : _metadataUri);
444const char* RequestMetaDataTypeStateMachine::_metadataSourceToString(MetadataSource source)
447 case MetadataSource::Cache:
return "loaded from cache";
448 case MetadataSource::FTP:
return "downloaded via FTP";
449 case MetadataSource::HTTP:
return "downloaded via HTTP";
450 case MetadataSource::None:
return "not available";
455void RequestMetaDataTypeStateMachine::_requestFile(
const QString& cacheFileTag,
bool crcValid,
const QString& uri, QString& outputFileName,
bool trackMetadataSource)
458 _currentCacheFileTag = cacheFileTag;
459 _currentFileName = &outputFileName;
460 _currentFileValidCrc = crcValid;
461 outputFileName.clear();
463 auto completeCurrentState = [
this]() {
464 if (_activeAsyncState) {
466 }
else if (_activeSkippableState) {
471 if (!_compInfo->
available() || uri.isEmpty()) {
472 qCDebug(ComponentInformationManagerLog) <<
typeToString() <<
": metadata not available, skipping download";
473 completeCurrentState();
477 const QString cachedFile = crcValid ? _compMgr->
fileCache().
access(cacheFileTag) :
"";
479 if (!cachedFile.isEmpty()) {
480 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Using cached file" << cachedFile;
481 if (trackMetadataSource) {
482 _metadataSource = MetadataSource::Cache;
485 outputFileName = cachedFile;
486 completeCurrentState();
491 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": CRC not available, cache bypassed";
493 qCDebug(RequestMetaDataTypeStateMachineLog) <<
typeToString() <<
": not found in cache, downloading";
496 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Downloading json" << uri;
498 if (_uriIsMAVLinkFTP(uri)) {
499 if (trackMetadataSource) {
500 _metadataSource = MetadataSource::FTP;
504 if (ftpManager->
download(MAV_COMP_ID_AUTOPILOT1, uri, QStandardPaths::writableLocation(QStandardPaths::TempLocation))) {
505 _downloadStartTime.start();
508 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"FTPManager::download returned failure";
510 completeCurrentState();
513 if (trackMetadataSource) {
514 _metadataSource = MetadataSource::HTTP;
518 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
520 _downloadStartTime.start();
522 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"QGCCachedFileDownload::download returned failure";
524 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
525 completeCurrentState();
530QString RequestMetaDataTypeStateMachine::_downloadCompleteJsonWorker(
const QString& fileName)
532 const QString tempPath = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(_currentCacheFileTag);
534 if (outputFileName.isEmpty()) {
535 qCWarning(RequestMetaDataTypeStateMachineLog) <<
"Inflate of compressed json failed" << _currentCacheFileTag;
538 if (_currentFileValidCrc) {
540 outputFileName = _compMgr->
fileCache().
insert(_currentCacheFileTag, outputFileName);
542 return outputFileName;
545void RequestMetaDataTypeStateMachine::_ftpDownloadComplete(
const QString& fileName,
const QString& errorMsg)
547 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"_ftpDownloadComplete fileName:errorMsg" << fileName << errorMsg;
552 if (errorMsg.isEmpty()) {
553 if (_currentFileName) {
554 *_currentFileName = _downloadCompleteJsonWorker(fileName);
557 qCDebug(ComponentInformationManagerLog) <<
typeToString() <<
": FTP download failed:" << errorMsg;
560 if (_activeAsyncState) {
562 }
else if (_activeSkippableState) {
567void RequestMetaDataTypeStateMachine::_ftpDownloadProgress(
float progress)
569 int elapsedSec = _downloadStartTime.elapsed() / 1000;
570 float totalDownloadTime = elapsedSec / progress;
573 const int maxDownloadTimeSec = 40;
574 if (elapsedSec > 10 && progress < 0.5 && totalDownloadTime > maxDownloadTimeSec) {
575 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"Slow download, aborting. Total time (s):" << totalDownloadTime;
580void RequestMetaDataTypeStateMachine::_httpDownloadComplete(
bool success,
const QString& localFile,
const QString& errorMsg,
bool fromCache)
582 qCDebug(RequestMetaDataTypeStateMachineLog) <<
"_httpDownloadComplete success:localFile:errorMsg:fromCache"
583 << success << localFile << errorMsg << fromCache;
586 this, &RequestMetaDataTypeStateMachine::_httpDownloadComplete);
588 if (success && errorMsg.isEmpty()) {
589 if (_currentFileName) {
590 *_currentFileName = _downloadCompleteJsonWorker(localFile);
593 qCDebug(ComponentInformationManagerLog) <<
typeToString() <<
": HTTP download failed:" << errorMsg;
596 if (_activeAsyncState) {
598 }
else if (_activeSkippableState) {
603bool 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)
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.
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()
RequestMessageResultHandlerFailureCode_t
@ RequestMessageFailureMessageNotReceived
@ RequestMessageFailureCommandNotAcked
@ RequestMessageFailureCommandError
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)