3#include <QtCore/QApplicationStatic>
6#include <QtCore/QFileInfo>
7#include <QtCore/QMetaType>
8#include <QtCore/QSettings>
9#include <QtCore/QStandardPaths>
10#include <QtCore/QTimer>
33 qCDebug(MAVLinkProtocolLog) <<
this;
40 qCDebug(MAVLinkProtocolLog) <<
this;
45 return _mavlinkProtocolInstance();
55 &MAVLinkProtocol::_vehicleCountChanged);
63 _totalReceiveCounter[channel] = 0;
64 _totalLossCounter[channel] = 0;
65 _runningLossPercent[channel] = 0.f;
74 _firstMessageSeen[channel].clear();
75 std::memset(_lastIndex[channel], 0,
sizeof(_lastIndex[channel]));
82 if (_logSuspendError || _logSuspendReplay || !_tempLogFile->isOpen()) {
86 const quint64 time =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
87 uint8_t bytes_time[
sizeof(quint64)]{};
88 qToBigEndian(time, bytes_time);
90 QByteArray logData = data;
91 QByteArray timeData = QByteArray::fromRawData(
reinterpret_cast<const char*
>(bytes_time),
sizeof(bytes_time));
92 (void)logData.prepend(timeData);
93 if (_tempLogFile->write(logData) != logData.length()) {
94 const QString message = QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.")
95 .arg(_tempLogFile->fileName());
98 _logSuspendError =
true;
106 qCDebug(MAVLinkProtocolLog) <<
"receiveBytes: link gone!" << data.size() <<
"bytes arrived too late";
110 for (uint8_t
byte : data) {
113 mavlink_status_t status{};
115 const uint8_t framing = mavlink_parse_char(mavlinkChannel,
byte, &message, &status);
116 if (framing == MAVLINK_FRAMING_OK || framing == MAVLINK_FRAMING_BAD_SIGNATURE) {
119 if (sigCtrl->processFrame(framing == MAVLINK_FRAMING_OK, message)) {
124 if (framing != MAVLINK_FRAMING_OK) {
128 if (message.msgid == MAVLINK_MSG_ID_RADIO_STATUS) {
134 const bool isV1 = (status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1);
135 if (isV1 && (message.msgid != MAVLINK_MSG_ID_HEARTBEAT) && (message.msgid != MAVLINK_MSG_ID_RADIO_STATUS)) {
142 _updateCounters(mavlinkChannel, message);
144 if (!linkPtr->linkConfiguration()->isForwarding()) {
146 _forwardSupport(message);
148 _logData(link, message);
150 if (!_updateStatus(link, linkPtr, mavlinkChannel, message)) {
156void MAVLinkProtocol::_updateCounters(uint8_t mavlinkChannel,
const mavlink_message_t& message)
158 _totalReceiveCounter[mavlinkChannel]++;
160 uint8_t& lastSeq = _lastIndex[mavlinkChannel][message.sysid][message.compid];
162 const QPair<uint8_t, uint8_t> key(message.sysid, message.compid);
164 if (!_firstMessageSeen[mavlinkChannel].contains(key)) {
165 _firstMessageSeen[mavlinkChannel].insert(key);
166 expectedSeq = message.seq;
167 }
else if (message.seq == lastSeq) {
171 expectedSeq = lastSeq + 1;
174 uint64_t lostMessages;
175 if (message.seq >= expectedSeq) {
176 lostMessages = message.seq - expectedSeq;
178 lostMessages =
static_cast<uint64_t
>(message.seq) + 256ULL - expectedSeq;
180 _totalLossCounter[mavlinkChannel] += lostMessages;
182 lastSeq = message.seq;
184 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
185 const float currentLossPercent = (
static_cast<double>(_totalLossCounter[mavlinkChannel]) / totalSent) * 100.0f;
186 _runningLossPercent[mavlinkChannel] = (currentLossPercent + _runningLossPercent[mavlinkChannel]) * 0.5f;
191 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
200 if (!forwardingLink) {
206 (void)forwardingLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
211 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
220 if (!forwardingSupportLink) {
225 (void)forwardingSupportLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
230 if (!_logSuspendError && !_logSuspendReplay && _tempLogFile->isOpen()) {
232 if (message.msgid != MAVLINK_MSG_ID_SETUP_SIGNING) {
235 const quint64 timestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
237 log_data.resize(
static_cast<qsizetype
>(
sizeof(timestamp)) + msgBytes.size());
238 qToBigEndian(timestamp,
reinterpret_cast<uint8_t*
>(log_data.data()));
239 std::memcpy(log_data.data() +
sizeof(timestamp), msgBytes.constData(), msgBytes.size());
240 if (_tempLogFile->write(log_data) != log_data.size()) {
241 const QString logErrorMessage =
242 QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.")
243 .arg(_tempLogFile->fileName());
246 _logSuspendError =
true;
250 if ((message.msgid == MAVLINK_MSG_ID_HEARTBEAT) && !_vehicleWasArmed) {
251 if (mavlink_msg_heartbeat_get_base_mode(&message) & MAV_MODE_FLAG_DECODE_POSITION_SAFETY) {
252 _vehicleWasArmed =
true;
257 switch (message.msgid) {
258 case MAVLINK_MSG_ID_HEARTBEAT: {
260 mavlink_heartbeat_t heartbeat{};
261 mavlink_msg_heartbeat_decode(&message, &heartbeat);
262 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, heartbeat.autopilot, heartbeat.type);
265 case MAVLINK_MSG_ID_HIGH_LATENCY: {
267 mavlink_high_latency_t highLatency{};
268 mavlink_msg_high_latency_decode(&message, &highLatency);
270 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, MAV_AUTOPILOT_GENERIC, MAV_TYPE_GENERIC);
273 case MAVLINK_MSG_ID_HIGH_LATENCY2: {
276 mavlink_msg_high_latency2_decode(&message, &highLatency2);
277 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, highLatency2.autopilot, highLatency2.type);
288 if ((_totalReceiveCounter[mavlinkChannel] % 31) == 0) {
289 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
291 _totalLossCounter[mavlinkChannel], _runningLossPercent[mavlinkChannel]);
296 if (linkPtr.use_count() == 1) {
303bool MAVLinkProtocol::_closeLogFile()
305 if (!_tempLogFile->isOpen()) {
309 if (_tempLogFile->size() == 0) {
310 (void)_tempLogFile->remove();
314 (void)_tempLogFile->flush();
315 _tempLogFile->close();
319void MAVLinkProtocol::_startLogging()
326 if (appSettings->disableAllPersistence()->rawValue().toBool()) {
330#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
336 if (_tempLogFile->isOpen()) {
340 if (_logSuspendReplay) {
345 const QString logPath =
347 if (logPath.isEmpty()) {
348 qCWarning(MAVLinkProtocolLog) <<
"Failed to generate temp log path";
349 _logSuspendError =
true;
353 _tempLogFile->setFileName(logPath);
354 if (!_tempLogFile->open(QIODevice::WriteOnly)) {
355 const QString message = QStringLiteral(
356 "Opening Flight Data file for writing failed. "
357 "Unable to write to %1. Please choose a different file location.")
358 .arg(_tempLogFile->fileName());
361 _logSuspendError =
true;
365 qCDebug(MAVLinkProtocolLog) <<
"Temp log" << _tempLogFile->fileName();
366 (void)_checkTelemetrySavePath();
368 _logSuspendError =
false;
371void MAVLinkProtocol::_stopLogging()
373 if (_tempLogFile->isOpen() && _closeLogFile()) {
376 if ((_vehicleWasArmed || mavlinkSettings->telemetrySaveNotArmed()->rawValue().toBool()) &&
377 mavlinkSettings->telemetrySave()->rawValue().toBool() &&
378 !appSettings->disableAllPersistence()->rawValue().toBool()) {
379 _saveTelemetryLog(_tempLogFile->fileName());
381 (void)QFile::remove(_tempLogFile->fileName());
385 _vehicleWasArmed =
false;
390 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
391 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
392 static const QStringList filterList(filter);
394 const QFileInfoList fileInfoList = tempDir.entryInfoList(filterList, QDir::Files);
395 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file count" << fileInfoList.count();
397 for (
const QFileInfo& fileInfo : fileInfoList) {
398 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file" << fileInfo.filePath();
399 if (fileInfo.size() == 0) {
400 (void)QFile::remove(fileInfo.filePath());
403 _saveTelemetryLog(fileInfo.filePath());
409 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
410 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
412 const QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
413 qCDebug(MAVLinkProtocolLog) <<
"Temp log file count" << fileInfoList.count();
415 for (
const QFileInfo& fileInfo : fileInfoList) {
416 qCDebug(MAVLinkProtocolLog) <<
"Temp log file" << fileInfo.filePath();
417 (void)QFile::remove(fileInfo.filePath());
421void MAVLinkProtocol::_saveTelemetryLog(
const QString& tempLogfile)
423 if (_checkTelemetrySavePath()) {
425 const QDir saveDir(saveDirPath);
427 const QString nameFormat(
"%1%2.%3");
428 const QString dtFormat(
"yyyy-MM-dd hh-mm-ss");
431 QString saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat), QString(),
433 while (saveDir.exists(saveFileName)) {
434 saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat),
438 const QString saveFilePath = saveDir.absoluteFilePath(saveFileName);
440 QFile in(tempLogfile);
441 if (!in.open(QIODevice::ReadOnly)) {
442 const QString
error =
443 tr(
"Unable to save telemetry log. Error opening source '%1': '%2'.").arg(tempLogfile, in.errorString());
445 (void)QFile::remove(tempLogfile);
449 QSaveFile out(saveFilePath);
450 out.setDirectWriteFallback(
true);
452 if (!out.open(QIODevice::WriteOnly)) {
453 const QString
error = tr(
"Unable to save telemetry log. Error opening destination '%1': '%2'.")
454 .arg(saveFilePath, out.errorString());
456 (void)QFile::remove(tempLogfile);
462 constexpr int bufferSize = 256 * 1024;
463 buffer.resize(bufferSize);
465 const qint64 n = in.read(buffer.data(), buffer.size());
470 const QString
error = tr(
"Unable to save telemetry log. Error reading source '%1': '%2'.")
471 .arg(tempLogfile, in.errorString());
474 (void)QFile::remove(tempLogfile);
477 if (out.write(buffer.constData(), n) != n) {
478 const QString
error = tr(
"Unable to save telemetry log. Error writing destination '%1': '%2'.")
479 .arg(saveFilePath, out.errorString());
482 (void)QFile::remove(tempLogfile);
488 const QString
error =
489 tr(
"Unable to finalize telemetry log '%1': '%2'.").arg(saveFilePath, out.errorString());
491 (void)QFile::remove(tempLogfile);
495 constexpr QFileDevice::Permissions perms =
496 QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther;
497 (void)out.setPermissions(perms);
500 (void)QFile::remove(tempLogfile);
503bool MAVLinkProtocol::_checkTelemetrySavePath()
506 if (saveDirPath.isEmpty()) {
507 const QString
error = tr(
"Unable to save telemetry log. Application save directory is not set.");
512 const QDir saveDir(saveDirPath);
513 if (!saveDir.exists()) {
514 const QString
error =
515 tr(
"Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath);
523void MAVLinkProtocol::_vehicleCountChanged()
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Q_APPLICATION_STATIC(MAVLinkProtocol, _mavlinkProtocolInstance)
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct __mavlink_high_latency2_t mavlink_high_latency2_t
QString telemetrySavePath()
static constexpr const char * telemetryFileExtension
The link interface defines the interface for all links used to communicate with the ground station ap...
void setDecodedFirstMavlinkPacket(bool decodedFirstMavlinkPacket)
uint8_t mavlinkChannel() const
void reportMavlinkV1Traffic()
void reportMavlinkV2Traffic()
Called when a v2 message is received: permanently suppresses the v1-only warning for this link.
SigningController * signing()
Per-link signing state and confirmation state machine. Non-null after channel allocation.
void reportRadioStatusReceived()
SharedLinkInterfacePtr sharedLinkInterfacePointerForLink(const LinkInterface *link)
static LinkManager * instance()
SharedLinkInterfacePtr mavlinkForwardingSupportLink()
Returns pointer to the mavlink support forwarding link, or nullptr if it does not exist.
SharedLinkInterfacePtr mavlinkForwardingLink()
Returns pointer to the mavlink forwarding link, or nullptr if it does not exist.
MAVLink micro air vehicle protocol reference implementation.
void receiveBytes(LinkInterface *link, const QByteArray &data)
void checkForLostLogFiles()
void mavlinkMessageStatus(int sysid, uint64_t totalSent, uint64_t totalReceived, uint64_t totalLoss, float lossPercent)
void messageReceived(LinkInterface *link, const mavlink_message_t &message)
void vehicleHeartbeatInfo(LinkInterface *link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType)
void resetSequenceTracking(LinkInterface *link)
Reset sequence tracking so signing transitions don't inflate loss counters.
void logSentBytes(const LinkInterface *link, const QByteArray &data)
static MAVLinkProtocol * instance()
void resetMetadataForLink(LinkInterface *link)
static void deleteTempLogFiles()
static MultiVehicleManager * instance()
void vehicleRemoved(Vehicle *vehicle)
static SettingsManager * instance()
AppSettings * appSettings() const
MavlinkSettings * mavlinkSettings() const
Owns MAVLink signing state and the deferred-confirmation state machine for one LinkInterface.
QByteArray serializeUnsignedCopy(const mavlink_message_t &message)
QString uniqueTempPath(const QString &templateName)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.