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