12#include <QtCore/QApplicationStatic>
14#include <QtCore/QFile>
15#include <QtCore/QFileInfo>
16#include <QtCore/QMetaType>
17#include <QtCore/QSettings>
18#include <QtCore/QStandardPaths>
19#include <QtCore/QTimer>
27 , _tempLogFile(new QFile(this))
29 qCDebug(MAVLinkProtocolLog) <<
this;
36 qCDebug(MAVLinkProtocolLog) <<
this;
41 return _mavlinkProtocolInstance();
58 _totalReceiveCounter[channel] = 0;
59 _totalLossCounter[channel] = 0;
60 _runningLossPercent[channel] = 0.f;
69 if (_logSuspendError || _logSuspendReplay || !_tempLogFile->isOpen()) {
73 const quint64 time =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
74 uint8_t bytes_time[
sizeof(quint64)]{};
75 qToBigEndian(time, bytes_time);
77 QByteArray logData = data;
78 QByteArray timeData = QByteArray::fromRawData(
reinterpret_cast<const char*
>(bytes_time),
sizeof(bytes_time));
79 (void) logData.prepend(timeData);
80 if (_tempLogFile->write(logData) != logData.length()) {
81 const QString message = QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.").arg(_tempLogFile->fileName());
84 _logSuspendError =
true;
92 qCDebug(MAVLinkProtocolLog) <<
"receiveBytes: link gone!" << data.size() <<
"bytes arrived too late";
96 for (uint8_t
byte: data) {
99 mavlink_status_t status{};
101 if (mavlink_parse_char(mavlinkChannel,
byte, &message, &status) != MAVLINK_FRAMING_OK) {
108 if (message.msgid != MAVLINK_MSG_ID_HEARTBEAT && (status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1)) {
113 _updateCounters(mavlinkChannel, message);
114 if (!linkPtr->linkConfiguration()->isForwarding()) {
116 _forwardSupport(message);
118 _logData(link, message);
120 if (!_updateStatus(link, linkPtr, mavlinkChannel, message)) {
126void MAVLinkProtocol::_updateCounters(uint8_t mavlinkChannel,
const mavlink_message_t &message)
128 _totalReceiveCounter[mavlinkChannel]++;
130 uint8_t &lastSeq = _lastIndex[message.sysid][message.compid];
132 const QPair<uint8_t,uint8_t> key(message.sysid, message.compid);
134 if (!_firstMessageSeen.contains(key)) {
135 _firstMessageSeen.insert(key);
136 expectedSeq = message.seq;
138 expectedSeq = lastSeq + 1;
141 uint64_t lostMessages;
142 if (message.seq >= expectedSeq) {
143 lostMessages = message.seq - expectedSeq;
145 lostMessages =
static_cast<uint64_t
>(message.seq) + 256ULL - expectedSeq;
147 _totalLossCounter[mavlinkChannel] += lostMessages;
149 lastSeq = message.seq;
151 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
152 const float currentLossPercent = (
static_cast<double>(_totalLossCounter[mavlinkChannel]) / totalSent) * 100.0f;
153 _runningLossPercent[mavlinkChannel] = (currentLossPercent + _runningLossPercent[mavlinkChannel]) * 0.5f;
158 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
162 if (!SettingsManager::instance()->mavlinkSettings()->forwardMavlink()->rawValue().toBool()) {
167 if (!forwardingLink) {
171 uint8_t buf[MAVLINK_MAX_PACKET_LEN]{};
172 const uint16_t len = mavlink_msg_to_send_buffer(buf, &message);
173 (void) forwardingLink->writeBytesThreadSafe(
reinterpret_cast<const char*
>(buf), len);
178 if (message.msgid == MAVLINK_MSG_ID_SETUP_SIGNING) {
182 if (!LinkManager::instance()->mavlinkSupportForwardingEnabled()) {
187 if (!forwardingSupportLink) {
191 uint8_t buf[MAVLINK_MAX_PACKET_LEN]{};
192 const uint16_t len = mavlink_msg_to_send_buffer(buf, &message);
193 (void) forwardingSupportLink->writeBytesThreadSafe(
reinterpret_cast<const char*
>(buf), len);
198 if (!_logSuspendError && !_logSuspendReplay && _tempLogFile->isOpen()) {
199 const quint64 timestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch() * 1000);
200 uint8_t buf[MAVLINK_MAX_PACKET_LEN +
sizeof(timestamp)]{};
201 qToBigEndian(timestamp, buf);
203 const qsizetype len = mavlink_msg_to_send_buffer(buf +
sizeof(timestamp), &message) +
sizeof(timestamp);
204 const QByteArray log_data(
reinterpret_cast<const char*
>(buf), len);
205 if (_tempLogFile->write(log_data) != len) {
206 const QString logErrorMessage = QStringLiteral(
"MAVLink Logging failed. Could not write to file %1, logging disabled.").arg(_tempLogFile->fileName());
209 _logSuspendError =
true;
212 if ((message.msgid == MAVLINK_MSG_ID_HEARTBEAT) && !_vehicleWasArmed) {
213 if (mavlink_msg_heartbeat_get_base_mode(&message) & MAV_MODE_FLAG_DECODE_POSITION_SAFETY) {
214 _vehicleWasArmed =
true;
219 switch (message.msgid) {
220 case MAVLINK_MSG_ID_HEARTBEAT: {
222 mavlink_heartbeat_t heartbeat{};
223 mavlink_msg_heartbeat_decode(&message, &heartbeat);
224 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, heartbeat.autopilot, heartbeat.type);
227 case MAVLINK_MSG_ID_HIGH_LATENCY: {
229 mavlink_high_latency_t highLatency{};
230 mavlink_msg_high_latency_decode(&message, &highLatency);
232 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, MAV_AUTOPILOT_GENERIC, MAV_TYPE_GENERIC);
235 case MAVLINK_MSG_ID_HIGH_LATENCY2: {
237 mavlink_high_latency2_t highLatency2{};
238 mavlink_msg_high_latency2_decode(&message, &highLatency2);
239 emit
vehicleHeartbeatInfo(link, message.sysid, message.compid, highLatency2.autopilot, highLatency2.type);
249 if ((_totalReceiveCounter[mavlinkChannel] % 31) == 0) {
250 const uint64_t totalSent = _totalReceiveCounter[mavlinkChannel] + _totalLossCounter[mavlinkChannel];
251 emit
mavlinkMessageStatus(message.sysid, totalSent, _totalReceiveCounter[mavlinkChannel], _totalLossCounter[mavlinkChannel], _runningLossPercent[mavlinkChannel]);
256 if (linkPtr.use_count() == 1) {
263bool MAVLinkProtocol::_closeLogFile()
265 if (!_tempLogFile->isOpen()) {
269 if (_tempLogFile->size() == 0) {
270 (void) _tempLogFile->remove();
274 (void) _tempLogFile->flush();
275 _tempLogFile->close();
279void MAVLinkProtocol::_startLogging()
281 if (
qgcApp()->runningUnitTests()) {
285 AppSettings *
const appSettings = SettingsManager::instance()->appSettings();
290#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
291 if (!SettingsManager::instance()->mavlinkSettings()->telemetrySave()->rawValue().toBool()) {
296 if (_tempLogFile->isOpen()) {
300 if (_logSuspendReplay) {
306 QStringLiteral(
"%1.%2").arg(_tempLogFileTemplate, _logFileExtension));
307 if (logPath.isEmpty()) {
308 qCWarning(MAVLinkProtocolLog) <<
"Failed to generate temp log path";
309 _logSuspendError =
true;
313 _tempLogFile->setFileName(logPath);
314 if (!_tempLogFile->open(QIODevice::WriteOnly)) {
315 const QString message = QStringLiteral(
"Opening Flight Data file for writing failed. "
316 "Unable to write to %1. Please choose a different file location.")
317 .arg(_tempLogFile->fileName());
320 _logSuspendError =
true;
324 qCDebug(MAVLinkProtocolLog) <<
"Temp log" << _tempLogFile->fileName();
325 (void) _checkTelemetrySavePath();
327 _logSuspendError =
false;
330void MAVLinkProtocol::_stopLogging()
332 if (_tempLogFile->isOpen() && _closeLogFile()) {
333 auto appSettings = SettingsManager::instance()->appSettings();
334 auto mavlinkSettings = SettingsManager::instance()->mavlinkSettings();
335 if ((_vehicleWasArmed || mavlinkSettings->telemetrySaveNotArmed()->rawValue().toBool()) &&
336 mavlinkSettings->telemetrySave()->rawValue().toBool() &&
338 _saveTelemetryLog(_tempLogFile->fileName());
340 (void) QFile::remove(_tempLogFile->fileName());
344 _vehicleWasArmed =
false;
349 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
350 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
351 static const QStringList filterList(filter);
353 const QFileInfoList fileInfoList = tempDir.entryInfoList(filterList, QDir::Files);
354 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file count" << fileInfoList.count();
356 for (
const QFileInfo &fileInfo: fileInfoList) {
357 qCDebug(MAVLinkProtocolLog) <<
"Orphaned log file" << fileInfo.filePath();
358 if (fileInfo.size() == 0) {
359 (void) QFile::remove(fileInfo.filePath());
362 _saveTelemetryLog(fileInfo.filePath());
368 static const QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
369 static const QString filter(QStringLiteral(
"*.%1").arg(_logFileExtension));
371 const QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
372 qCDebug(MAVLinkProtocolLog) <<
"Temp log file count" << fileInfoList.count();
374 for (
const QFileInfo &fileInfo: fileInfoList) {
375 qCDebug(MAVLinkProtocolLog) <<
"Temp log file" << fileInfo.filePath();
376 (void) QFile::remove(fileInfo.filePath());
380void MAVLinkProtocol::_saveTelemetryLog(
const QString &tempLogfile)
382 if (_checkTelemetrySavePath()) {
383 const QString saveDirPath = SettingsManager::instance()->appSettings()->
telemetrySavePath();
384 const QDir saveDir(saveDirPath);
386 const QString nameFormat(
"%1%2.%3");
387 const QString dtFormat(
"yyyy-MM-dd hh-mm-ss");
391 while (saveDir.exists(saveFileName)) {
395 const QString saveFilePath = saveDir.absoluteFilePath(saveFileName);
397 QFile in(tempLogfile);
398 if (!in.open(QIODevice::ReadOnly)) {
399 const QString
error = tr(
"Unable to save telemetry log. Error opening source '%1': '%2'.").arg(tempLogfile, in.errorString());
401 (void) QFile::remove(tempLogfile);
405 QSaveFile out(saveFilePath);
406 out.setDirectWriteFallback(
true);
408 if (!out.open(QIODevice::WriteOnly)) {
409 const QString
error = tr(
"Unable to save telemetry log. Error opening destination '%1': '%2'.").arg(saveFilePath, out.errorString());
411 (void) QFile::remove(tempLogfile);
417 constexpr int bufferSize = 256 * 1024;
418 buffer.resize(bufferSize);
420 const qint64 n = in.read(buffer.data(), buffer.size());
425 const QString
error = tr(
"Unable to save telemetry log. Error reading source '%1': '%2'.").arg(tempLogfile, in.errorString());
428 (void) QFile::remove(tempLogfile);
431 if (out.write(buffer.constData(), n) != n) {
432 const QString
error = tr(
"Unable to save telemetry log. Error writing destination '%1': '%2'.").arg(saveFilePath, out.errorString());
435 (void) QFile::remove(tempLogfile);
441 const QString
error = tr(
"Unable to finalize telemetry log '%1': '%2'.").arg(saveFilePath, out.errorString());
443 (void) QFile::remove(tempLogfile);
447 constexpr QFileDevice::Permissions perms =
448 QFileDevice::ReadOwner | QFileDevice::WriteOwner |
449 QFileDevice::ReadGroup |
450 QFileDevice::ReadOther;
451 (void) out.setPermissions(perms);
454 (void) QFile::remove(tempLogfile);
457bool MAVLinkProtocol::_checkTelemetrySavePath()
459 const QString saveDirPath = SettingsManager::instance()->appSettings()->
telemetrySavePath();
460 if (saveDirPath.isEmpty()) {
461 const QString
error = tr(
"Unable to save telemetry log. Application save directory is not set.");
466 const QDir saveDir(saveDirPath);
467 if (!saveDir.exists()) {
468 const QString
error = tr(
"Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath);
476void MAVLinkProtocol::_vehicleCountChanged()
478 if (MultiVehicleManager::instance()->vehicles()->count() == 0) {
485 return SettingsManager::instance()->mavlinkSettings()->
gcsMavlinkSystemID()->rawValue().toInt();
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Q_APPLICATION_STATIC(MAVLinkProtocol, _mavlinkProtocolInstance)
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Fact *disableAllPersistence READ disableAllPersistence CONSTANT Fact * disableAllPersistence()
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 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)
Message received and directly copied via signal.
void vehicleHeartbeatInfo(LinkInterface *link, int vehicleId, int componentId, int vehicleFirmwareType, int vehicleType)
Heartbeat received on link.
void logSentBytes(const LinkInterface *link, const QByteArray &data)
static MAVLinkProtocol * instance()
int getSystemId() const
Get the system id of this application.
void resetMetadataForLink(LinkInterface *link)
Reset the counters for all metadata for this link.
~MAVLinkProtocol()
Destructor for the MAVLinkProtocol class.
static QString getName()
Get the human-friendly name of this protocol.
static void deleteTempLogFiles()
Deletes any log files which are in the temp directory.
Fact *gcsMavlinkSystemID READ gcsMavlinkSystemID CONSTANT Fact * gcsMavlinkSystemID()
void vehicleRemoved(Vehicle *vehicle)
QString uniqueTempPath(const QString &templateName)