QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FirmwareImage.cc
Go to the documentation of this file.
1#include "FirmwareImage.h"
2#include "JsonParsing.h"
3#include "QGCApplication.h"
4#include "FirmwarePlugin.h"
6#include "Bootloader.h"
8
9#include <QtCore/QDir>
10
11QGC_LOGGING_CATEGORY(FirmwareImageLog, "Vehicle.VehicleSetup.FirmwareImage")
12QGC_LOGGING_CATEGORY(FirmwareImageVerboseLog, "Vehicle.VehicleSetup.FirmwareImage.Verbose")
13#include <QtCore/QFile>
14#include <QtCore/QFileInfo>
15#include <QtCore/QJsonDocument>
16#include <QtCore/QJsonObject>
17#include <QtCore/QTextStream>
18
20 QObject(parent),
21 _imageSize(0)
22{
23
24}
25
26bool FirmwareImage::load(const QString& imageFilename, uint32_t boardId)
27{
28 _imageSize = 0;
29 _boardId = boardId;
30
31 if (imageFilename.endsWith(".bin")) {
32 _binFormat = true;
33 return _binLoad(imageFilename);
34 } else if (imageFilename.endsWith(".px4")) {
35 _binFormat = true;
36 return _px4Load(imageFilename);
37 } else if (imageFilename.endsWith(".apj")) {
38 _binFormat = true;
39 return _px4Load(imageFilename);
40 } else if (imageFilename.endsWith(".ihx")) {
41 _binFormat = false;
42 return _ihxLoad(imageFilename);
43 } else {
44 emit statusMessage("Unsupported file format");
45 return false;
46 }
47}
48
49bool FirmwareImage::_readByteFromStream(QTextStream& stream, uint8_t& byte)
50{
51 QString hex = stream.read(2);
52
53 if (hex.length() != 2) {
54 return false;
55 }
56
57 bool success;
58 byte = (uint8_t)hex.toInt(&success, 16);
59
60 return success;
61}
62
63bool FirmwareImage::_readWordFromStream(QTextStream& stream, uint16_t& word)
64{
65 QString hex = stream.read(4);
66
67 if (hex.length() != 4) {
68 return false;
69 }
70
71 bool success;
72 word = (uint16_t)hex.toInt(&success, 16);
73
74 return success;
75}
76
77bool FirmwareImage::_readBytesFromStream(QTextStream& stream, uint8_t byteCount, QByteArray& bytes)
78{
79 bytes.clear();
80
81 while (byteCount) {
82 uint8_t byte;
83
84 if (!_readByteFromStream(stream, byte)) {
85 return false;
86 }
87 bytes += byte;
88
89 byteCount--;
90 }
91
92 return true;
93}
94
95bool FirmwareImage::_ihxLoad(const QString& ihxFilename)
96{
97 _imageSize = 0;
98 _ihxBlocks.clear();
99
100 QFile ihxFile(ihxFilename);
101 if (!ihxFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
102 emit statusMessage(QString("Unable to open firmware file %1, error: %2").arg(ihxFilename, ihxFile.errorString()));
103 return false;
104 }
105
106 QTextStream stream(&ihxFile);
107
108 while (true) {
109 if (stream.read(1) != ":") {
110 emit statusMessage("Incorrectly formatted .ihx file, line does not begin with :");
111 return false;
112 }
113
114 uint8_t blockByteCount;
115 uint16_t address;
116 uint8_t recordType;
117 QByteArray bytes;
118 uint8_t crc;
119
120 if (!_readByteFromStream(stream, blockByteCount) ||
121 !_readWordFromStream(stream, address) ||
122 !_readByteFromStream(stream, recordType) ||
123 !_readBytesFromStream(stream, blockByteCount, bytes) ||
124 !_readByteFromStream(stream, crc)) {
125 emit statusMessage(tr("Incorrectly formatted line in .ihx file, line too short"));
126 return false;
127 }
128
129 if (!(recordType == 0 || recordType == 1)) {
130 emit statusMessage(tr("Unsupported record type in file: %1").arg(recordType));
131 return false;
132 }
133
134 if (recordType == 0) {
135 bool appendToLastBlock = false;
136
137 // Can we append this block to the last one?
138
139 if (_ihxBlocks.length()) {
140 int lastBlockIndex = _ihxBlocks.length() - 1;
141
142 if (_ihxBlocks[lastBlockIndex].address + _ihxBlocks[lastBlockIndex].bytes.length() == address) {
143 appendToLastBlock = true;
144 }
145 }
146
147 if (appendToLastBlock) {
148 _ihxBlocks[_ihxBlocks.length() - 1].bytes += bytes;
149 // Too noisy even for verbose
150 //qCDebug(FirmwareImageVerboseLog) << QString("_ihxLoad - append - address:%1 size:%2 block:%3").arg(address).arg(blockByteCount).arg(ihxBlockCount());
151 } else {
152 IntelHexBlock_t block;
153
154 block.address = address;
155 block.bytes = bytes;
156
157 _ihxBlocks += block;
158 qCDebug(FirmwareImageVerboseLog) << QString("_ihxLoad - new block - address:%1 size:%2 block:%3").arg(address).arg(blockByteCount).arg(ihxBlockCount());
159 }
160
161 _imageSize += blockByteCount;
162 } else if (recordType == 1) {
163 // EOF
164 qCDebug(FirmwareImageLog) << QString("_ihxLoad - EOF");
165 break;
166 }
167
168 // Move to next line
169 stream.readLine();
170 }
171
172 ihxFile.close();
173
174 return true;
175}
176
177bool FirmwareImage::isCompatible(uint32_t boardId, uint32_t firmwareId) {
178 bool result = false;
179 if (boardId == firmwareId ) {
180 result = true;
181 }
182 switch(boardId) {
184 if (firmwareId == 9) result = true;
185 break;
186 default:
187 break;
188 }
189 return result;
190}
191
192bool FirmwareImage::_px4Load(const QString& imageFilename)
193{
194 _imageSize = 0;
195
196 // We need to collect information from the .px4 file as well as pull the binary image out to a separate file.
197
198 QFile px4File(imageFilename);
199 if (!px4File.open(QIODevice::ReadOnly | QIODevice::Text)) {
200 emit statusMessage(tr("Unable to open firmware file %1, error: %2").arg(imageFilename, px4File.errorString()));
201 return false;
202 }
203
204 QByteArray bytes = px4File.readAll();
205 px4File.close();
206 QJsonDocument doc = QJsonDocument::fromJson(bytes);
207
208 if (doc.isNull()) {
209 emit statusMessage(tr("Supplied file is not a valid JSON document"));
210 return false;
211 }
212
213 QJsonObject px4Json = doc.object();
214
215 // Make sure the keys we need are available
216 QString errorString;
217 QStringList requiredKeys;
218 requiredKeys << _jsonBoardIdKey << _jsonImageKey << _jsonImageSizeKey;
219 if (!JsonParsing::validateRequiredKeys(px4Json, requiredKeys, errorString)) {
220 emit statusMessage(tr("Firmware file missing required key: %1").arg(errorString));
221 return false;
222 }
223
224 // Make sure the keys are the correct type
225 QStringList keys;
226 QList<QJsonValue::Type> types;
227 keys << _jsonBoardIdKey << _jsonParamXmlSizeKey << _jsonParamXmlKey << _jsonAirframeXmlSizeKey << _jsonAirframeXmlKey << _jsonImageSizeKey << _jsonImageKey << _jsonMavAutopilotKey;
228 types << QJsonValue::Double << QJsonValue::Double << QJsonValue::String << QJsonValue::Double << QJsonValue::String << QJsonValue::Double << QJsonValue::String << QJsonValue::Double;
229 if (!JsonParsing::validateKeyTypes(px4Json, keys, types, errorString)) {
230 emit statusMessage(tr("Firmware file has invalid key: %1").arg(errorString));
231 return false;
232 }
233
234 uint32_t firmwareBoardId = (uint32_t)px4Json.value(_jsonBoardIdKey).toInt();
235 if (!isCompatible(_boardId, firmwareBoardId)) {
236 emit statusMessage(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardId).arg(_boardId));
237 return false;
238 }
239
240 // What firmware type is this?
241 MAV_AUTOPILOT firmwareType = (MAV_AUTOPILOT)px4Json[_jsonMavAutopilotKey].toInt(MAV_AUTOPILOT_PX4);
242
243 // NOTE: PX4 firmware currently embeds parameter metadata as XML
244 // ("parameter_xml" key), but the metadata system now expects JSON.
245 // cacheParameterMetaDataFile() skips non-JSON content (debug-level
246 // log only). This is a no-op until PX4 upstream switches px_mkfw.py
247 // to embed JSON. Modern PX4 vehicles (v1.13+) serve metadata via
248 // the component metadata protocol on connect, so this only affects
249 // pre-v1.13 boards that lack that capability.
250 QByteArray decompressedBytes;
251 bool success = _decompressJsonValue(px4Json, // JSON object
252 bytes, // Raw bytes of JSON document
253 _jsonParamXmlSizeKey, // key which holds byte size
254 _jsonParamXmlKey, // key which holds compressed bytes
255 decompressedBytes); // Returned decompressed bytes
256 if (success) {
257 QString parameterFilename = QGCApplication::cachedParameterMetaDataFile();
258 QFile parameterFile(parameterFilename);
259
260 if (parameterFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
261 qint64 bytesWritten = parameterFile.write(decompressedBytes);
262 if (bytesWritten != decompressedBytes.length()) {
263 emit statusMessage(tr("Write failed for parameter meta data file, error: %1").arg(parameterFile.errorString()));
264 parameterFile.close();
265 QFile::remove(parameterFilename);
266 } else {
267 parameterFile.close();
268 }
269 } else {
270 emit statusMessage(tr("Unable to open parameter meta data file %1 for writing, error: %2").arg(parameterFilename, parameterFile.errorString()));
271 }
272
273 FirmwarePlugin *fwPlugin = FirmwarePluginManager::instance()->firmwarePluginForAutopilot(firmwareType, MAV_TYPE_GENERIC);
274 if (fwPlugin) {
275 fwPlugin->cacheParameterMetaDataFile(parameterFilename);
276 }
277 }
278
279 // Decompress the airframe xml and save to file
280 success = _decompressJsonValue(px4Json, // JSON object
281 bytes, // Raw bytes of JSON document
282 _jsonAirframeXmlSizeKey, // key which holds byte size
283 _jsonAirframeXmlKey, // key which holds compressed bytes
284 decompressedBytes); // Returned decompressed bytes
285 if (success) {
286 QString airframeFilename = QGCApplication::cachedAirframeMetaDataFile();
287 //qDebug() << airframeFilename;
288 QFile airframeFile(QGCApplication::cachedAirframeMetaDataFile());
289
290 if (airframeFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
291 qint64 bytesWritten = airframeFile.write(decompressedBytes);
292 if (bytesWritten != decompressedBytes.length()) {
293 // FIXME: What about these warnings?
294 emit statusMessage(tr("Write failed for airframe meta data file, error: %1").arg(airframeFile.errorString()));
295 airframeFile.close();
296 QFile::remove(airframeFilename);
297 } else {
298 airframeFile.close();
299 }
300 } else {
301 emit statusMessage(tr("Unable to open airframe meta data file %1 for writing, error: %2").arg(airframeFilename, airframeFile.errorString()));
302 }
303 }
304
305 // Decompress the image and save to file
306 _imageSize = px4Json.value(QString("image_size")).toInt();
307 success = _decompressJsonValue(px4Json, // JSON object
308 bytes, // Raw bytes of JSON document
309 _jsonImageSizeKey, // key which holds byte size
310 _jsonImageKey, // key which holds compressed bytes
311 decompressedBytes); // Returned decompressed bytes
312 if (!success) {
313 return false;
314 }
315
316 // Pad image to 4-byte boundary
317 while ((decompressedBytes.length() % 4) != 0) {
318 decompressedBytes.append(static_cast<char>(static_cast<unsigned char>(0xFF)));
319 }
320
321 // Store decompressed image file in same location as original download file
322 QDir imageDir = QFileInfo(imageFilename).dir();
323 QString decompressFilename = imageDir.filePath("PX4FlashUpgrade.bin");
324
325 QFile decompressFile(decompressFilename);
326 if (!decompressFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
327 emit statusMessage(tr("Unable to open decompressed file %1 for writing, error: %2").arg(decompressFilename, decompressFile.errorString()));
328 return false;
329 }
330
331 qint64 bytesWritten = decompressFile.write(decompressedBytes);
332 if (bytesWritten != decompressedBytes.length()) {
333 emit statusMessage(tr("Write failed for decompressed image file, error: %1").arg(decompressFile.errorString()));
334 return false;
335 }
336 decompressFile.close();
337
338 _binFilename = decompressFilename;
339
340 return true;
341}
342
344bool FirmwareImage::_decompressJsonValue(const QJsonObject& jsonObject,
345 const QByteArray& jsonDocBytes,
346 const QString& sizeKey,
347 const QString& bytesKey,
348 QByteArray& decompressedBytes)
349{
350 // Validate decompressed size key
351 if (!jsonObject.contains(sizeKey)) {
352 emit statusMessage(QString("Firmware file missing %1 key").arg(sizeKey));
353 return false;
354 }
355 int decompressedSize = jsonObject.value(QString(sizeKey)).toInt();
356 if (decompressedSize == 0) {
357 emit statusMessage(tr("Firmware file has invalid decompressed size for %1").arg(sizeKey));
358 return false;
359 }
360
361 // XXX Qt's JSON string handling is terribly broken, strings
362 // with some length (18K / 25K) are just weirdly cut.
363 // The code below works around this by manually 'parsing'
364 // for the image string. Since its compressed / checksummed
365 // this should be fine.
366
367 QStringList parts = QString(jsonDocBytes).split(QString("\"%1\": \"").arg(bytesKey));
368 if (parts.length() == 1) {
369 emit statusMessage(tr("Could not find compressed bytes for %1 in Firmware file").arg(bytesKey));
370 return false;
371 }
372 parts = parts.last().split("\"");
373 if (parts.length() == 1) {
374 emit statusMessage(tr("Incorrectly formed compressed bytes section for %1 in Firmware file").arg(bytesKey));
375 return false;
376 }
377
378 // Store decompressed size as first four bytes. This is required by qUncompress routine.
379 QByteArray raw;
380 raw.append((unsigned char)((decompressedSize >> 24) & 0xFF));
381 raw.append((unsigned char)((decompressedSize >> 16) & 0xFF));
382 raw.append((unsigned char)((decompressedSize >> 8) & 0xFF));
383 raw.append((unsigned char)((decompressedSize >> 0) & 0xFF));
384
385 QByteArray raw64 = parts.first().toUtf8();
386 raw.append(QByteArray::fromBase64(raw64));
387 decompressedBytes = qUncompress(raw);
388
389 if (decompressedBytes.length() == 0) {
390 emit statusMessage(tr("Firmware file has 0 length %1").arg(bytesKey));
391 return false;
392 }
393 if (decompressedBytes.length() != decompressedSize) {
394 emit statusMessage(tr("Size for decompressed %1 does not match stored size: Expected(%1) Actual(%2)").arg(decompressedSize).arg(decompressedBytes.length()));
395 return false;
396 }
397
398 return true;
399}
400
402{
403 return _ihxBlocks.length();
404}
405
406bool FirmwareImage::ihxGetBlock(uint16_t index, uint16_t& address, QByteArray& bytes) const
407{
408 address = 0;
409 bytes.clear();
410
411 if (index < ihxBlockCount()) {
412 address = _ihxBlocks[index].address;
413 bytes = _ihxBlocks[index].bytes;
414 return true;
415 } else {
416 return false;
417 }
418}
419
420bool FirmwareImage::_binLoad(const QString& imageFilename)
421{
422 QFile binFile(imageFilename);
423 if (!binFile.open(QIODevice::ReadOnly)) {
424 emit statusMessage(tr("Unabled to open firmware file %1, %2").arg(imageFilename, binFile.errorString()));
425 return false;
426 }
427
428 _imageSize = (uint32_t)binFile.size();
429
430 binFile.close();
431
432 _binFilename = imageFilename;
433
434 return true;
435}
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static const int boardIDPX4FMUV3
Definition Bootloader.h:39
Support for Intel Hex firmware file.
bool isCompatible(uint32_t boardId, uint32_t firmwareId)
bool load(const QString &imageFilename, uint32_t boardId)
void statusMessage(const QString &warningtring)
bool ihxGetBlock(uint16_t index, uint16_t &address, QByteArray &bytes) const
FirmwareImage(QObject *parent=0)
uint16_t ihxBlockCount(void) const
FirmwarePlugin * firmwarePluginForAutopilot(MAV_AUTOPILOT firmwareType, MAV_TYPE vehicleType)
static FirmwarePluginManager * instance()
The FirmwarePlugin class represents the methods and objects which are specific to a certain Firmware ...
void cacheParameterMetaDataFile(const QString &metaDataFile)
static QString cachedParameterMetaDataFile()
static QString cachedAirframeMetaDataFile()
bool validateKeyTypes(const QJsonObject &jsonObject, const QStringList &keys, const QList< QJsonValue::Type > &types, QString &errorString)
bool validateRequiredKeys(const QJsonObject &jsonObject, const QStringList &keys, QString &errorString)
Validates that all listed keys are present in the object.
bool decompressFile(const QString &inputPath, const QString &outputPath, Format format, ProgressCallback progress, qint64 maxDecompressedBytes)