QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
APMDataFlashUtility.cc
Go to the documentation of this file.
2
3#include <bit>
4
5#include <QtCore/QHash>
6#include <QtCore/qfloat16.h>
7
8#include "LittleEndian.h"
10
11QGC_LOGGING_CATEGORY(APMDataFlashUtilityLog, "Utilities.APMDataFlashUtility")
12
14{
15
16// ============================================================================
17// Format Character Functions
18// ============================================================================
19
20int formatCharSize(char c)
21{
22 switch (c) {
23 case 'b': case 'B': case 'M':
24 return 1;
25 case 'h': case 'H': case 'c': case 'C': case 'g': // g = half-precision float
26 return 2;
27 case 'i': case 'I': case 'e': case 'E': case 'L': case 'f':
28 return 4;
29 case 'd': case 'q': case 'Q':
30 return 8;
31 case 'n':
32 return 4;
33 case 'N':
34 return 16;
35 case 'Z': case 'a': // Z = 64-char string, a = 64-byte array (32 int16)
36 return 64;
37 default:
38 return 0;
39 }
40}
41
42int calculatePayloadSize(const QString &format)
43{
44 int size = 0;
45 for (const QChar &ch : format) {
46 size += formatCharSize(ch.toLatin1());
47 }
48 return size;
49}
50
51// ============================================================================
52// Half-Precision Float Conversion
53// ============================================================================
54
55float halfToFloat(uint16_t bits)
56{
57 return static_cast<float>(std::bit_cast<qfloat16>(bits));
58}
59
60// ============================================================================
61// Value Parsing
62// ============================================================================
63
64QVariant parseValue(const char* data, qint64 size, char formatChar)
65{
66 const int requiredSize = formatCharSize(formatChar);
67 if (requiredSize == 0) {
68 qCWarning(APMDataFlashUtilityLog) << "Unsupported DataFlash format character:" << formatChar;
69 return {};
70 }
71 if (!data || size < requiredSize) {
72 qCWarning(APMDataFlashUtilityLog) << "Missing or truncated DataFlash value for format:" << formatChar;
73 return {};
74 }
75
76 const std::span<const uint8_t> bytes{reinterpret_cast<const uint8_t*>(data),
77 static_cast<std::size_t>(requiredSize)};
78 switch (formatChar) {
79 case 'b':
80 return *LittleEndian::read<int8_t>(bytes);
81 case 'B':
82 case 'M':
83 return *LittleEndian::read<uint8_t>(bytes);
84 case 'h': {
85 const auto val = *LittleEndian::read<int16_t>(bytes);
86 return val;
87 }
88 case 'H': {
89 const auto val = *LittleEndian::read<uint16_t>(bytes);
90 return val;
91 }
92 case 'c': {
93 const auto val = *LittleEndian::read<int16_t>(bytes);
94 return val / 100.0;
95 }
96 case 'C': {
97 const auto val = *LittleEndian::read<uint16_t>(bytes);
98 return val / 100.0;
99 }
100 case 'i': {
101 const auto val = *LittleEndian::read<int32_t>(bytes);
102 return val;
103 }
104 case 'I': {
105 const auto val = *LittleEndian::read<uint32_t>(bytes);
106 return val;
107 }
108 case 'e': {
109 const auto val = *LittleEndian::read<int32_t>(bytes);
110 return val / 100.0;
111 }
112 case 'E': {
113 const auto val = *LittleEndian::read<uint32_t>(bytes);
114 return val / 100.0;
115 }
116 case 'L': {
117 const auto val = *LittleEndian::read<int32_t>(bytes);
118 return val / 1.0e7; // Latitude/longitude in degrees
119 }
120 case 'f': {
121 const auto val = *LittleEndian::read<float>(bytes);
122 return static_cast<double>(val);
123 }
124 case 'd': {
125 const auto val = *LittleEndian::read<double>(bytes);
126 return val;
127 }
128 case 'q': {
129 const auto val = *LittleEndian::read<int64_t>(bytes);
130 return static_cast<qlonglong>(val);
131 }
132 case 'Q': {
133 const auto val = *LittleEndian::read<uint64_t>(bytes);
134 return static_cast<qulonglong>(val);
135 }
136 case 'g': {
137 const auto bits = *LittleEndian::read<uint16_t>(bytes);
138 return static_cast<double>(halfToFloat(bits));
139 }
140 case 'n':
141 return QString::fromLatin1(data, qstrnlen(data, 4));
142 case 'N':
143 return QString::fromLatin1(data, qstrnlen(data, 16));
144 case 'Z':
145 return QString::fromLatin1(data, qstrnlen(data, 64));
146 case 'a':
147 // 64-byte array (32 int16 values) - return as raw bytes
148 return QByteArray(data, 64);
149 default:
150 return QVariant();
151 }
152}
153
154QMap<QString, QVariant> parseMessage(const char* data, qint64 size, const MessageFormat& fmt)
155{
156 if (!data || size < 0) {
157 qCWarning(APMDataFlashUtilityLog) << "Invalid DataFlash message payload";
158 return {};
159 }
160
161 QMap<QString, QVariant> result;
162 qint64 offset = 0;
163
164 for (qsizetype i = 0; i < fmt.format.size(); ++i) {
165 const char formatChar = fmt.format.at(i).toLatin1();
166
167 const int fieldSize = formatCharSize(formatChar);
168 const QVariant value = parseValue(data + offset, size - offset, formatChar);
169 if (!value.isValid()) {
170 return {};
171 }
172 if (i < fmt.columns.size()) {
173 result[fmt.columns.at(i)] = value;
174 }
175 offset += fieldSize;
176 }
177
178 return result;
179}
180
181// ============================================================================
182// Header and Message Detection
183// ============================================================================
184
185bool isValidHeader(const char *data, qint64 size)
186{
187 if (size < 3) {
188 return false;
189 }
190 return static_cast<uint8_t>(data[0]) == kHeaderByte1 &&
191 static_cast<uint8_t>(data[1]) == kHeaderByte2;
192}
193
194qint64 findNextHeader(const char *data, qint64 size, qint64 offset)
195{
196 while (offset + 2 < size) {
197 if (static_cast<uint8_t>(data[offset]) == kHeaderByte1 &&
198 static_cast<uint8_t>(data[offset + 1]) == kHeaderByte2) {
199 return offset;
200 }
201 ++offset;
202 }
203 return -1;
204}
205
206// ============================================================================
207// FMT Message Parsing
208// ============================================================================
209
211{
212 MessageFormat fmt;
213 fmt.type = static_cast<uint8_t>(data[0]);
214 fmt.length = static_cast<uint8_t>(data[1]);
215 fmt.name = QString::fromLatin1(data + 2, qstrnlen(data + 2, 4));
216 fmt.format = QString::fromLatin1(data + 6, qstrnlen(data + 6, 16));
217 const QString columnsStr = QString::fromLatin1(data + 22, qstrnlen(data + 22, 64));
218 fmt.columns = columnsStr.trimmed().split(',');
219 return fmt;
220}
221
222bool parseFmtMessages(const char *data, qint64 size, QMap<uint8_t, MessageFormat> &formats)
223{
224 formats.clear();
225
226 if (!isValidHeader(data, size)) {
227 return false;
228 }
229
230 QHash<uint8_t, QString> unitsByType;
231 qint64 pos = 0;
232
233 while (pos + 3 <= size) {
234 // Find next message header
235 if (static_cast<uint8_t>(data[pos]) != kHeaderByte1 ||
236 static_cast<uint8_t>(data[pos + 1]) != kHeaderByte2) {
237 ++pos;
238 continue;
239 }
240
241 const uint8_t msgType = static_cast<uint8_t>(data[pos + 2]);
242 pos += 3;
243
244 if (msgType == kFmtMessageType) {
245 if (pos + kFmtPayloadSize > size) {
246 break;
247 }
248
249 const MessageFormat fmt = parseFmtPayload(data + pos);
250 if (fmt.length < 3) {
251 qCWarning(APMDataFlashUtilityLog)
252 << "Ignoring FMT with invalid length:" << fmt.name << static_cast<int>(fmt.length);
253 } else {
254 formats[fmt.type] = fmt;
255 }
256 pos += kFmtPayloadSize;
257 } else {
258 // Skip message if we know its length
259 if (formats.contains(msgType)) {
260 const MessageFormat& msgFmt = formats[msgType];
261 const int payloadSize = msgFmt.length - 3; // -3 for header already consumed
262 if ((msgFmt.name == QLatin1String("FMTU")) && (pos + payloadSize <= size)) {
263 const QMap<QString, QVariant> values = parseMessage(data + pos, payloadSize, msgFmt);
264 const QVariant fmtType = values.value(QStringLiteral("FmtType"));
265 if (fmtType.isValid()) {
266 unitsByType.insert(static_cast<uint8_t>(fmtType.toUInt()),
267 values.value(QStringLiteral("UnitIds")).toString());
268 }
269 }
270 pos += payloadSize;
271 } else {
272 // Unknown format, try to find next header
273 ++pos;
274 }
275 }
276 }
277
278 for (auto it = unitsByType.cbegin(); it != unitsByType.cend(); ++it) {
279 const auto fmtIt = formats.find(it.key());
280 if (fmtIt == formats.end()) {
281 continue;
282 }
283 const qsizetype index = it.value().indexOf(QLatin1Char('#'));
284 if ((index >= 0) && (index < fmtIt->columns.size())) {
285 fmtIt->instanceColumn = fmtIt->columns.at(index);
286 }
287 }
288
289 return !formats.isEmpty();
290}
291
292// ============================================================================
293// Message Iteration
294// ============================================================================
295
296int iterateMessages(const char *data, qint64 size,
297 const QMap<uint8_t, MessageFormat> &formats,
298 const MessageCallback &callback,
299 const std::function<void(float)> &progressCallback)
300{
301 int count = 0;
302 qint64 pos = 0;
303
304 while (pos + 3 <= size) {
305 // Find next message header
306 if (static_cast<uint8_t>(data[pos]) != kHeaderByte1 ||
307 static_cast<uint8_t>(data[pos + 1]) != kHeaderByte2) {
308 ++pos;
309 continue;
310 }
311
312 const uint8_t msgType = static_cast<uint8_t>(data[pos + 2]);
313 pos += 3;
314
315 if (!formats.contains(msgType)) {
316 continue;
317 }
318
319 const MessageFormat &fmt = formats[msgType];
320 const int payloadSize = fmt.length - 3;
321
322 if (pos + payloadSize > size) {
323 break;
324 }
325
326 ++count;
327 if (!callback(msgType, data + pos, payloadSize, fmt)) {
328 break;
329 }
330
331 pos += payloadSize;
332
333 if (progressCallback && (count % 1000 == 0)) {
334 progressCallback(static_cast<float>(pos) / static_cast<float>(size));
335 }
336 }
337
338 return count;
339}
340
341} // namespace APMDataFlashUtility
#define QGC_LOGGING_CATEGORY(name, categoryStr)
float halfToFloat(uint16_t bits)
std::function< bool(uint8_t msgType, const char *payload, int payloadSize, const MessageFormat &fmt)> MessageCallback
constexpr uint8_t kHeaderByte2
constexpr uint8_t kFmtMessageType
constexpr uint8_t kHeaderByte1
qint64 findNextHeader(const char *data, qint64 size, qint64 offset)
bool isValidHeader(const char *data, qint64 size)
QVariant parseValue(const char *data, qint64 size, char formatChar)
int calculatePayloadSize(const QString &format)
bool parseFmtMessages(const char *data, qint64 size, QMap< uint8_t, MessageFormat > &formats)
constexpr int kFmtPayloadSize
int iterateMessages(const char *data, qint64 size, const QMap< uint8_t, MessageFormat > &formats, const MessageCallback &callback, const std::function< void(float)> &progressCallback)
QMap< QString, QVariant > parseMessage(const char *data, qint64 size, const MessageFormat &fmt)
MessageFormat parseFmtPayload(const char *data)