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) {
129 const bool isV1 = (status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1);
130 if (isV1 && message.msgid != MAVLINK_MSG_ID_HEARTBEAT) {
136 _updateCounters(mavlinkChannel, message);
138 if (!linkPtr->linkConfiguration()->isForwarding()) {
140 _forwardSupport(message);
142 _logData(link, message);
144 if (!_updateStatus(link, linkPtr, mavlinkChannel, message)) {
150void MAVLinkProtocol::_updateCounters(uint8_t mavlinkChannel,
const mavlink_message_t& message)
152 _totalReceiveCounter[mavlinkChannel]++;
154 uint8_t& lastSeq = _lastIndex[mavlinkChannel][message.sysid][message.compid];
156 const QPair<uint8_t, uint8_t> key(message.sysid, message.compid);
158 if (!_firstMessageSeen[mavlinkChannel].contains(key)) {
159 _firstMessageSeen[mavlinkChannel].insert(key);
160 expectedSeq = message.seq;
161 }
else if (message.seq == lastSeq) {
165 expectedSeq = lastSeq + 1;
168 uint64_t lostMessages;
169 if (message.seq >= expectedSeq) {
170 lostMessages = message.seq - expectedSeq;
172 lostMessages =
static_cast<uint64_t
>(message.seq) + 256ULL - expectedSeq;
174 _totalLossCounter[mavlinkChannel] += lostMessages;
176 lastSeq = message.seq;
178 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
179 const float currentLossPercent = (
static_cast<double>(_totalLossCounter[mavlinkChannel]) / totalSent) * 100.0f;
180 _runningLossPercent[mavlinkChannel] = (currentLossPercent + _runningLossPercent[mavlinkChannel]) * 0.5f;
185 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
194 if (!forwardingLink) {
200 (void)forwardingLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
205 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
214 if (!forwardingSupportLink) {
219 (void)forwardingSupportLink->writeBytesThreadSafe(bytes.constData(), bytes.size());
224 if (!_logSuspendError && !_logSuspendReplay && _tempLogFile->isOpen()) {
226 if (message.msgid != MAVLINK_MSG_ID_SETUP_SIGNING) {
229 const quint64 timestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
231 log_data.resize(
static_cast<qsizetype
>(
sizeof(timestamp)) + msgBytes.size());
232 qToBigEndian(timestamp,
reinterpret_cast<uint8_t*
>(log_data.data()));
233 std::memcpy(log_data.data() +
sizeof(timestamp), msgBytes.constData(), msgBytes.size());
234 if (_tempLogFile->write(log_data) != log_data.size()) {
235 const QString logErrorMessage =
236 QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.")
237 .arg(_tempLogFile->fileName());
240 _logSuspendError =
true;
244 if ((message.msgid == MAVLINK_MSG_ID_HEARTBEAT) && !_vehicleWasArmed) {
245 if (mavlink_msg_heartbeat_get_base_mode(&message) & MAV_MODE_FLAG_DECODE_POSITION_SAFETY) {
246 _vehicleWasArmed =
true;
251 switch (message.msgid) {
252 case MAVLINK_MSG_ID_HEARTBEAT: {
254 mavlink_heartbeat_t heartbeat{};
255 mavlink_msg_heartbeat_decode(&message, &heartbeat);
256 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, heartbeat.autopilot, heartbeat.type);
259 case MAVLINK_MSG_ID_HIGH_LATENCY: {
261 mavlink_high_latency_t highLatency{};
262 mavlink_msg_high_latency_decode(&message, &highLatency);
264 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, MAV_AUTOPILOT_GENERIC, MAV_TYPE_GENERIC);
267 case MAVLINK_MSG_ID_HIGH_LATENCY2: {
270 mavlink_msg_high_latency2_decode(&message, &highLatency2);
271 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, highLatency2.autopilot, highLatency2.type);
282 if ((_totalReceiveCounter[mavlinkChannel] % 31) == 0) {
283 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
285 _totalLossCounter[mavlinkChannel], _runningLossPercent[mavlinkChannel]);
290 if (linkPtr.use_count() == 1) {
297bool MAVLinkProtocol::_closeLogFile()
299 if (!_tempLogFile->isOpen()) {
303 if (_tempLogFile->size() == 0) {
304 (void)_tempLogFile->remove();
308 (void)_tempLogFile->flush();
309 _tempLogFile->close();
313void MAVLinkProtocol::_startLogging()
320 if (appSettings->disableAllPersistence()->rawValue().toBool()) {
324#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
330 if (_tempLogFile->isOpen()) {
334 if (_logSuspendReplay) {
339 const QString logPath =
341 if (logPath.isEmpty()) {
342 qCWarning(MAVLinkProtocolLog) <<
"Failed to generate temp log path";
343 _logSuspendError =
true;
347 _tempLogFile->setFileName(logPath);
348 if (!_tempLogFile->open(QIODevice::WriteOnly)) {
349 const QString message = QStringLiteral(
350 "Opening Flight Data file for writing failed. "
351 "Unable to write to %1. Please choose a different file location.")
352 .arg(_tempLogFile->fileName());
355 _logSuspendError =
true;
359 qCDebug(MAVLinkProtocolLog) <<
"Temp log" << _tempLogFile->fileName();
360 (void)_checkTelemetrySavePath();
362 _logSuspendError =
false;
365void MAVLinkProtocol::_stopLogging()
367 if (_tempLogFile->isOpen() && _closeLogFile()) {
370 if ((_vehicleWasArmed || mavlinkSettings->telemetrySaveNotArmed()->rawValue().toBool()) &&
371 mavlinkSettings->telemetrySave()->rawValue().toBool() &&
372 !appSettings->disableAllPersistence()->rawValue().toBool()) {
373 _saveTelemetryLog(_tempLogFile->fileName());
375 (void)QFile::remove(_tempLogFile->fileName());
379 _vehicleWasArmed =
false;
384 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
385 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
386 static const QStringList filterList(filter);
388 const QFileInfoList fileInfoList = tempDir.entryInfoList(filterList, QDir::Files);
389 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file count" << fileInfoList.count();
391 for (
const QFileInfo& fileInfo : fileInfoList) {
392 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file" << fileInfo.filePath();
393 if (fileInfo.size() == 0) {
394 (void)QFile::remove(fileInfo.filePath());
397 _saveTelemetryLog(fileInfo.filePath());
403 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
404 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
406 const QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
407 qCDebug(MAVLinkProtocolLog) <<
"Temp log file count" << fileInfoList.count();
409 for (
const QFileInfo& fileInfo : fileInfoList) {
410 qCDebug(MAVLinkProtocolLog) <<
"Temp log file" << fileInfo.filePath();
411 (void)QFile::remove(fileInfo.filePath());
415void MAVLinkProtocol::_saveTelemetryLog(
const QString& tempLogfile)
417 if (_checkTelemetrySavePath()) {
419 const QDir saveDir(saveDirPath);
421 const QString nameFormat(
"%1%2.%3");
422 const QString dtFormat(
"yyyy-MM-dd hh-mm-ss");
425 QString saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat), QString(),
427 while (saveDir.exists(saveFileName)) {
428 saveFileName = nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat),
432 const QString saveFilePath = saveDir.absoluteFilePath(saveFileName);
434 QFile in(tempLogfile);
435 if (!in.open(QIODevice::ReadOnly)) {
436 const QString
error =
437 tr(
"Unable to save telemetry log. Error opening source '%1': '%2'.").arg(tempLogfile, in.errorString());
439 (void)QFile::remove(tempLogfile);
443 QSaveFile out(saveFilePath);
444 out.setDirectWriteFallback(
true);
446 if (!out.open(QIODevice::WriteOnly)) {
447 const QString
error = tr(
"Unable to save telemetry log. Error opening destination '%1': '%2'.")
448 .arg(saveFilePath, out.errorString());
450 (void)QFile::remove(tempLogfile);
456 constexpr int bufferSize = 256 * 1024;
457 buffer.resize(bufferSize);
459 const qint64 n = in.read(buffer.data(), buffer.size());
464 const QString
error = tr(
"Unable to save telemetry log. Error reading source '%1': '%2'.")
465 .arg(tempLogfile, in.errorString());
468 (void)QFile::remove(tempLogfile);
471 if (out.write(buffer.constData(), n) != n) {
472 const QString
error = tr(
"Unable to save telemetry log. Error writing destination '%1': '%2'.")
473 .arg(saveFilePath, out.errorString());
476 (void)QFile::remove(tempLogfile);
482 const QString
error =
483 tr(
"Unable to finalize telemetry log '%1': '%2'.").arg(saveFilePath, out.errorString());
485 (void)QFile::remove(tempLogfile);
489 constexpr QFileDevice::Permissions perms =
490 QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther;
491 (void)out.setPermissions(perms);
494 (void)QFile::remove(tempLogfile);
497bool MAVLinkProtocol::_checkTelemetrySavePath()
500 if (saveDirPath.isEmpty()) {
501 const QString
error = tr(
"Unable to save telemetry log. Application save directory is not set.");
506 const QDir saveDir(saveDirPath);
507 if (!saveDir.exists()) {
508 const QString
error =
509 tr(
"Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath);
517void 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()
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.