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) {
130 const bool isV1 = (status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1);
131 if (isV1 && (message.msgid != MAVLINK_MSG_ID_HEARTBEAT) && (message.msgid != MAVLINK_MSG_ID_RADIO_STATUS)) {
138 _updateCounters(mavlinkChannel, message);
140 if (!linkPtr->linkConfiguration()->isForwarding()) {
142 _forwardSupport(message);
144 _logData(link, message);
146 if (!_updateStatus(link, linkPtr, mavlinkChannel, message)) {
152void MAVLinkProtocol::_updateCounters(uint8_t mavlinkChannel,
const mavlink_message_t& message)
154 _totalReceiveCounter[mavlinkChannel]++;
156 uint8_t& lastSeq = _lastIndex[mavlinkChannel][message.sysid][message.compid];
158 const QPair<uint8_t, uint8_t> key(message.sysid, message.compid);
160 if (!_firstMessageSeen[mavlinkChannel].contains(key)) {
161 _firstMessageSeen[mavlinkChannel].insert(key);
162 expectedSeq = message.seq;
163 }
else if (message.seq == lastSeq) {
167 expectedSeq = lastSeq + 1;
170 uint64_t lostMessages;
171 if (message.seq >= expectedSeq) {
172 lostMessages = message.seq - expectedSeq;
174 lostMessages =
static_cast<uint64_t
>(message.seq) + 256ULL - expectedSeq;
176 _totalLossCounter[mavlinkChannel] += lostMessages;
178 lastSeq = message.seq;
180 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
181 const float currentLossPercent = (
static_cast<double>(_totalLossCounter[mavlinkChannel]) / totalSent) * 100.0f;
182 _runningLossPercent[mavlinkChannel] = (currentLossPercent + _runningLossPercent[mavlinkChannel]) * 0.5f;
187 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
196 if (!forwardingLink) {
202 (void)forwardingLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
207 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
216 if (!forwardingSupportLink) {
221 (void)forwardingSupportLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
226 if (!_logSuspendError && !_logSuspendReplay && _tempLogFile->isOpen()) {
228 if (message.msgid != MAVLINK_MSG_ID_SETUP_SIGNING) {
231 const quint64 timestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
233 log_data.resize(
static_cast<qsizetype
>(
sizeof(timestamp)) + msgBytes.size());
234 qToBigEndian(timestamp,
reinterpret_cast<uint8_t*
>(log_data.data()));
235 std::memcpy(log_data.data() +
sizeof(timestamp), msgBytes.constData(), msgBytes.size());
236 if (_tempLogFile->write(log_data) != log_data.size()) {
237 const QString logErrorMessage =
238 QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.")
239 .arg(_tempLogFile->fileName());
242 _logSuspendError =
true;
246 if ((message.msgid == MAVLINK_MSG_ID_HEARTBEAT) && !_vehicleWasArmed) {
247 if (mavlink_msg_heartbeat_get_base_mode(&message) & MAV_MODE_FLAG_DECODE_POSITION_SAFETY) {
248 _vehicleWasArmed =
true;
253 switch (message.msgid) {
254 case MAVLINK_MSG_ID_HEARTBEAT: {
256 mavlink_heartbeat_t heartbeat{};
257 mavlink_msg_heartbeat_decode(&message, &heartbeat);
258 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, heartbeat.autopilot, heartbeat.type);
261 case MAVLINK_MSG_ID_HIGH_LATENCY: {
263 mavlink_high_latency_t highLatency{};
264 mavlink_msg_high_latency_decode(&message, &highLatency);
266 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, MAV_AUTOPILOT_GENERIC, MAV_TYPE_GENERIC);
269 case MAVLINK_MSG_ID_HIGH_LATENCY2: {
272 mavlink_msg_high_latency2_decode(&message, &highLatency2);
273 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, highLatency2.autopilot, highLatency2.type);
284 if ((_totalReceiveCounter[mavlinkChannel] % 31) == 0) {
285 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
287 _totalLossCounter[mavlinkChannel], _runningLossPercent[mavlinkChannel]);
292 if (linkPtr.use_count() == 1) {
299bool MAVLinkProtocol::_closeLogFile()
301 if (!_tempLogFile->isOpen()) {
305 if (_tempLogFile->size() == 0) {
306 (void)_tempLogFile->remove();
310 (void)_tempLogFile->flush();
311 _tempLogFile->close();
315void MAVLinkProtocol::_startLogging()
322 if (appSettings->disableAllPersistence()->rawValue().toBool()) {
326#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
332 if (_tempLogFile->isOpen()) {
336 if (_logSuspendReplay) {
341 const QString logPath =
343 if (logPath.isEmpty()) {
344 qCWarning(MAVLinkProtocolLog) <<
"Failed to generate temp log path";
345 _logSuspendError =
true;
349 _tempLogFile->setFileName(logPath);
350 if (!_tempLogFile->open(QIODevice::WriteOnly)) {
351 const QString message = QStringLiteral(
352 "Opening Flight Data file for writing failed. "
353 "Unable to write to %1. Please choose a different file location.")
354 .arg(_tempLogFile->fileName());
357 _logSuspendError =
true;
361 qCDebug(MAVLinkProtocolLog) <<
"Temp log" << _tempLogFile->fileName();
362 (void)_checkTelemetrySavePath();
364 _logSuspendError =
false;
367void MAVLinkProtocol::_stopLogging()
369 if (_tempLogFile->isOpen() && _closeLogFile()) {
372 if ((_vehicleWasArmed || mavlinkSettings->telemetrySaveNotArmed()->rawValue().toBool()) &&
373 mavlinkSettings->telemetrySave()->rawValue().toBool() &&
374 !appSettings->disableAllPersistence()->rawValue().toBool()) {
375 _saveTelemetryLog(_tempLogFile->fileName());
377 (void)QFile::remove(_tempLogFile->fileName());
381 _vehicleWasArmed =
false;
386 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
387 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
388 static const QStringList filterList(filter);
390 const QFileInfoList fileInfoList = tempDir.entryInfoList(filterList, QDir::Files);
391 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file count" << fileInfoList.count();
393 for (
const QFileInfo& fileInfo : fileInfoList) {
394 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file" << fileInfo.filePath();
395 if (fileInfo.size() == 0) {
396 (void)QFile::remove(fileInfo.filePath());
399 _saveTelemetryLog(fileInfo.filePath());
405 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
406 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
408 const QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
409 qCDebug(MAVLinkProtocolLog) <<
"Temp log file count" << fileInfoList.count();
411 for (
const QFileInfo& fileInfo : fileInfoList) {
412 qCDebug(MAVLinkProtocolLog) <<
"Temp log file" << fileInfo.filePath();
413 (void)QFile::remove(fileInfo.filePath());
417void MAVLinkProtocol::_saveTelemetryLog(
const QString& tempLogfile)
419 if (_checkTelemetrySavePath()) {
421 const QDir saveDir(saveDirPath);
423 const QString nameFormat(
"%1%2.%3");
424 const QString dtFormat(
"yyyy-MM-dd hh-mm-ss");
427 QString saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat), QString(),
429 while (saveDir.exists(saveFileName)) {
430 saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat),
434 const QString saveFilePath = saveDir.absoluteFilePath(saveFileName);
436 QFile in(tempLogfile);
437 if (!in.open(QIODevice::ReadOnly)) {
438 const QString
error =
439 tr(
"Unable to save telemetry log. Error opening source '%1': '%2'.").arg(tempLogfile, in.errorString());
441 (void)QFile::remove(tempLogfile);
445 QSaveFile out(saveFilePath);
446 out.setDirectWriteFallback(
true);
448 if (!out.open(QIODevice::WriteOnly)) {
449 const QString
error = tr(
"Unable to save telemetry log. Error opening destination '%1': '%2'.")
450 .arg(saveFilePath, out.errorString());
452 (void)QFile::remove(tempLogfile);
458 constexpr int bufferSize = 256 * 1024;
459 buffer.resize(bufferSize);
461 const qint64 n = in.read(buffer.data(), buffer.size());
466 const QString
error = tr(
"Unable to save telemetry log. Error reading source '%1': '%2'.")
467 .arg(tempLogfile, in.errorString());
470 (void)QFile::remove(tempLogfile);
473 if (out.write(buffer.constData(), n) != n) {
474 const QString
error = tr(
"Unable to save telemetry log. Error writing destination '%1': '%2'.")
475 .arg(saveFilePath, out.errorString());
478 (void)QFile::remove(tempLogfile);
484 const QString
error =
485 tr(
"Unable to finalize telemetry log '%1': '%2'.").arg(saveFilePath, out.errorString());
487 (void)QFile::remove(tempLogfile);
491 constexpr QFileDevice::Permissions perms =
492 QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther;
493 (void)out.setPermissions(perms);
496 (void)QFile::remove(tempLogfile);
499bool MAVLinkProtocol::_checkTelemetrySavePath()
502 if (saveDirPath.isEmpty()) {
503 const QString
error = tr(
"Unable to save telemetry log. Application save directory is not set.");
508 const QDir saveDir(saveDirPath);
509 if (!saveDir.exists()) {
510 const QString
error =
511 tr(
"Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath);
519void 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.
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.