MAVLinkXMLParser.cc 44.9 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2 3 4 5 6 7 8 9
/*=====================================================================
======================================================================*/

/**
 * @file
 *   @brief Implementation of class MAVLinkXMLParser
 *   @author Lorenz Meier <mail@qgroundcontrol.org>
 */

pixhawk's avatar
pixhawk committed
10 11 12 13
#include <QFile>
#include <QDir>
#include <QPair>
#include <QList>
14
#include <QMap>
pixhawk's avatar
pixhawk committed
15
#include <QDateTime>
16
#include <QLocale>
pixhawk's avatar
pixhawk committed
17 18 19 20 21
#include "MAVLinkXMLParser.h"

#include <QDebug>

MAVLinkXMLParser::MAVLinkXMLParser(QDomDocument* document, QString outputDirectory, QObject* parent) : QObject(parent),
22 23 24
    doc(document),
    outputDirName(outputDirectory),
    fileName("")
pixhawk's avatar
pixhawk committed
25 26 27 28 29 30 31
{
}

MAVLinkXMLParser::MAVLinkXMLParser(QString document, QString outputDirectory, QObject* parent) : QObject(parent)
{
    doc = new QDomDocument();
    QFile file(document);
32
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
pixhawk's avatar
pixhawk committed
33 34 35
        const QString instanceText(QString::fromUtf8(file.readAll()));
        doc->setContent(instanceText);
    }
36
    fileName = document;
pixhawk's avatar
pixhawk committed
37 38 39 40 41 42 43
    outputDirName = outputDirectory;
}

MAVLinkXMLParser::~MAVLinkXMLParser()
{
}

pixhawk's avatar
pixhawk committed
44 45 46
/**
 * Generate C-code (C-89 compliant) out of the XML protocol specs.
 */
pixhawk's avatar
pixhawk committed
47 48 49 50 51 52
bool MAVLinkXMLParser::generate()
{
    // Process result
    bool success = true;

    // Only generate if output dir is correctly set
53
    if (outputDirName == "") {
54 55 56 57 58
        emit parseState(tr("<font color=\"red\">ERROR: No output directory given.\nAbort.</font>"));
        return false;
    }

    QString topLevelOutputDirName = outputDirName;
pixhawk's avatar
pixhawk committed
59 60 61 62

    // print out the element names of all elements that are direct children
    // of the outermost element.
    QDomElement docElem = doc->documentElement();
63 64 65 66 67 68
    QDomNode n = docElem;//.firstChild();
    QDomNode p = docElem;

    // Sanity check variables
    QList<int>* usedMessageIDs = new QList<int>();
    QMap<QString, QString>* usedMessageNames = new QMap<QString, QString>();
69
    QMap<QString, QString>* usedEnumNames = new QMap<QString, QString>();
pixhawk's avatar
pixhawk committed
70 71 72 73

    QList< QPair<QString, QString> > cFiles;
    QString lcmStructDefs = "";

lm's avatar
lm committed
74
    QString pureFileName;
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    QString pureIncludeFileName;

    QFileInfo fInfo(this->fileName);
    pureFileName = fInfo.baseName().split(".", QString::SkipEmptyParts).first();

    // XML parsed and converted to C code. Now generating the files
    outputDirName += QDir::separator() + pureFileName;
    QDateTime now = QDateTime::currentDateTime().toUTC();
    QLocale loc(QLocale::English);
    QString dateFormat = "dddd, MMMM d yyyy, hh:mm UTC";
    QString date = loc.toString(now, dateFormat);
    QString includeLine = "#include \"%1\"\n";
    QString mainHeaderName = pureFileName + ".h";
    QString messagesDirName = ".";//"generated";
    QDir dir(outputDirName + "/" + messagesDirName);

pixhawk's avatar
pixhawk committed
91 92
    int mavlinkVersion = 0;

93 94 95 96 97 98 99 100 101 102
    // we need to gather the message lengths across multiple includes,
    // which we can do via detecting recursion
    static unsigned message_lengths[256];
    static int highest_message_id;
    static int recursion_level;

    if (recursion_level == 0) {
        highest_message_id = 0;
        memset(message_lengths, 0, sizeof(message_lengths));
    }
103 104 105 106 107 108 109 110 111


    // Start main header
    QString mainHeader = QString("/** @file\n *\t@brief MAVLink comm protocol.\n *\t@see http://pixhawk.ethz.ch/software/mavlink\n *\t Generated on %1\n */\n#ifndef " + pureFileName.toUpper() + "_H\n#define " + pureFileName.toUpper() + "_H\n\n").arg(date); // The main header includes all messages
    // Mark all code as C code
    mainHeader += "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n";
    mainHeader += "\n#include \"../protocol.h\"\n";
    mainHeader += "\n#define MAVLINK_ENABLED_" + pureFileName.toUpper() + "\n\n";

112 113
    QString enums;

lm's avatar
lm committed
114

115
    // Run through root children
116
    while(!n.isNull()) {
pixhawk's avatar
pixhawk committed
117 118
        // Each child is a message
        QDomElement e = n.toElement(); // try to convert the node to an element.
119 120
        if(!e.isNull()) {
            if (e.tagName() == "mavlink") {
121 122
                p = n;
                n = n.firstChild();
123
                while (!n.isNull()) {
124
                    e = n.toElement();
125
                    if (!e.isNull()) {
126
                        // Handle all include tags
127
                        if (e.tagName() == "include") {
128
                            QString incFileName = e.text();
129
                            // Load file
130
                            //QDomDocument includeDoc = QDomDocument();
pixhawk's avatar
pixhawk committed
131

132 133
                            // Prepend file path if it is a relative path and
                            // make it relative to opened file
134 135 136
                            QFileInfo fInfo(incFileName);

                            QString incFilePath;
137
                            if (fInfo.isRelative()) {
138
                                QFileInfo rInfo(this->fileName);
139 140
                                incFilePath = rInfo.absoluteDir().canonicalPath() + "/" + incFileName;
                                pureIncludeFileName = fInfo.baseName().split(".", QString::SkipEmptyParts).first();
141
                            }
pixhawk's avatar
pixhawk committed
142

143
                            QFile file(incFilePath);
144
                            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
145 146 147 148 149 150
                                emit parseState(QString("<font color=\"green\">Included messages from file: %1</font>").arg(incFileName));
                                // NEW MODE: CREATE INDIVIDUAL FOLDERS
                                // Create new output directory, parse included XML and generate C-code
                                MAVLinkXMLParser includeParser(incFilePath, topLevelOutputDirName, this);
                                connect(&includeParser, SIGNAL(parseState(QString)), this, SIGNAL(parseState(QString)));
                                // Generate and write
151
                                recursion_level++;
152
                                includeParser.generate();
153
                                recursion_level--;
154 155 156 157
                                mainHeader += "\n#include \"../" + pureIncludeFileName + "/" + pureIncludeFileName + ".h\"\n";


                                // OLD MODE: MERGE BOTH FILES
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
                                //                                        const QString instanceText(QString::fromUtf8(file.readAll()));
                                //                                        includeDoc.setContent(instanceText);
                                //                                // Get all messages
                                //                                QDomNode in = includeDoc.documentElement().firstChild();
                                //                                QDomElement ie = in.toElement();
                                //                                if (!ie.isNull())
                                //                                {
                                //                                    if (ie.tagName() == "messages" || ie.tagName() == "include")
                                //                                    {
                                //                                        QDomNode ref = n.parentNode().insertAfter(in, n);
                                //                                        if (ref.isNull())
                                //                                        {
                                //                                            emit parseState(QString("<font color=\"red\">ERROR: Inclusion failed: XML syntax error in file %1. Wrong/misspelled XML?\nAbort.</font>").arg(fileName));
                                //                                            return false;
                                //                                        }
                                //                                    }
                                //                                }
175 176

                                emit parseState(QString("<font color=\"green\">End of inclusion from file: %1</font>").arg(incFileName));
177
                            } else {
178 179 180
                                // Include file could not be opened
                                emit parseState(QString("<font color=\"red\">ERROR: Failed including file: %1, file is not readable. Wrong/misspelled filename?\nAbort.</font>").arg(fileName));
                                return false;
181
                            }
182

pixhawk's avatar
pixhawk committed
183
                        }
184
                        // Handle all enum tags
185
                        else if (e.tagName() == "version") {
pixhawk's avatar
pixhawk committed
186 187 188 189 190
                            //QString fieldType = e.attribute("type", "");
                            //QString fieldName = e.attribute("name", "");
                            QString fieldText = e.text();

                            // Check if version has been previously set
191
                            if (mavlinkVersion != 0) {
pixhawk's avatar
pixhawk committed
192 193 194 195 196 197
                                emit parseState(QString("<font color=\"red\">ERROR: Protocol version tag set twice, please use it only once. First version was %1, second version is %2.\nAbort.</font>").arg(mavlinkVersion).arg(fieldText));
                                return false;
                            }

                            bool ok;
                            int version = fieldText.toInt(&ok);
198
                            if (ok && (version > 0) && (version < 256)) {
pixhawk's avatar
pixhawk committed
199 200
                                // Set MAVLink version
                                mavlinkVersion = version;
201
                            } else {
pixhawk's avatar
pixhawk committed
202 203 204 205 206
                                emit parseState(QString("<font color=\"red\">ERROR: Reading version string failed: %1, string is not an integer number between 1 and 255.\nAbort.</font>").arg(fieldText));
                                return false;
                            }
                        }
                        // Handle all enum tags
207
                        else if (e.tagName() == "enums") {
208 209 210
                            // One down into the enums list
                            p = n;
                            n = n.firstChild();
211
                            while (!n.isNull()) {
212 213 214 215 216 217 218
                                e = n.toElement();

                                QString currEnum;
                                QString currEnumEnd;
                                // Comment
                                QString comment;

219
                                if(!e.isNull() && e.tagName() == "enum") {
220 221
                                    // Get enum name
                                    QString enumName = e.attribute("name", "").toLower();
222
                                    if (enumName.size() == 0) {
223 224
                                        emit parseState(tr("<font color=\"red\">ERROR: Missing required name=\"\" attribute for tag %2 near line %1\nAbort.</font>").arg(QString::number(e.lineNumber()), e.tagName()));
                                        return false;
225
                                    } else {
226
                                        // Sanity check: Accept only enum names not used previously
227
                                        if (usedEnumNames->contains(enumName)) {
228 229
                                            emit parseState(tr("<font color=\"red\">ERROR: Enum name %1 used twice, second occurence near line %2 of file %3\nAbort.</font>").arg(enumName, QString::number(e.lineNumber()), fileName));
                                            return false;
230
                                        } else {
231 232 233 234 235
                                            usedEnumNames->insert(enumName, QString::number(e.lineNumber()));
                                        }

                                        // Everything sane, starting with enum content
                                        currEnum = "enum " + enumName.toUpper() + "\n{\n";
236
                                        currEnumEnd = QString("\t%1_ENUM_END\n};\n\n").arg(enumName.toUpper());
237 238 239

                                        int nextEnumValue = 0;

240
                                        // Get the enum fields
241
                                        QDomNode f = e.firstChild();
242
                                        while (!f.isNull()) {
243
                                            QDomElement e2 = f.toElement();
244
                                            if (!e2.isNull() && e2.tagName() == "entry") {
245 246 247 248 249 250
                                                QString fieldValue = e2.attribute("value", "");

                                                // If value was given, use it, if not, use the enum iterator
                                                // value. The iterator value gets reset by manual values

                                                QString fieldName = e2.attribute("name", "");
251
                                                if (fieldValue.length() == 0) {
252 253
                                                    fieldValue = QString::number(nextEnumValue);
                                                    nextEnumValue++;
254
                                                } else {
255 256
                                                    bool ok;
                                                    nextEnumValue = fieldValue.toInt(&ok) + 1;
257
                                                    if (!ok) {
258 259 260 261 262 263 264
                                                        emit parseState(tr("<font color=\"red\">ERROR: Enum entry %1 has not a valid number (%2) in the value field.\nAbort.</font>").arg(fieldName, fieldValue));
                                                        return false;
                                                    }
                                                }

                                                // Add comment of field if there is one
                                                QString fieldComment;
265
                                                if (e2.text().length() > 0) {
pixhawk's avatar
pixhawk committed
266
                                                    fieldComment = " /* " + e2.text() + "*/";
267
                                                    fieldComment = fieldComment.replace("\n", " ");
268 269
                                                }
                                                currEnum += "\t" + fieldName.toUpper() + "=" + fieldValue + "," + fieldComment + "\n";
270
                                            } else if(!e2.isNull() && e2.tagName() == "description") {
271
                                                comment = e2.text().replace("\n", " ") + comment;
272 273 274 275 276 277
                                            }
                                            f = f.nextSibling();
                                        }
                                    }
                                    // Add the last parsed enum
                                    // Remove the last comma, as the last value has none
278 279 280
                                    // ENUM END MARKER IS LAST ENTRY, COMMA REMOVAL NOT NEEDED
                                    //int commaPosition = currEnum.lastIndexOf(",");
                                    //currEnum.remove(commaPosition, 1);
281

282
                                    enums += "/** @brief " + comment  + " */\n" + currEnum + currEnumEnd;
283 284 285 286 287 288 289
                                } // Element is non-zero and element name is <enum>
                                n = n.nextSibling();
                            } // While through <enums>
                            // One up, back into the <mavlink> structure
                            n = p;
                        }

290
                        // Handle all message tags
291
                        else if (e.tagName() == "messages") {
292 293
                            p = n;
                            n = n.firstChild();
294
                            while (!n.isNull()) {
295
                                e = n.toElement();
296
                                if(!e.isNull()) {
297 298 299
                                    //if (e.isNull()) continue;
                                    // Get message name
                                    QString messageName = e.attribute("name", "").toLower();
300
                                    if (messageName.size() == 0) {
301 302
                                        emit parseState(tr("<font color=\"red\">ERROR: Missing required name=\"\" attribute for tag %2 near line %1\nAbort.</font>").arg(QString::number(e.lineNumber()), e.tagName()));
                                        return false;
303
                                    } else {
304 305 306 307 308 309
                                        // Get message id
                                        bool ok;
                                        int messageId = e.attribute("id", "-1").toInt(&ok, 10);
                                        emit parseState(tr("Compiling message <strong>%1 \t(#%3)</strong> \tnear line %2").arg(messageName, QString::number(n.lineNumber()), QString::number(messageId)));

                                        // Sanity check: Accept only message IDs not used previously
310
                                        if (usedMessageIDs->contains(messageId)) {
311 312
                                            emit parseState(tr("<font color=\"red\">ERROR: Message ID %1 used twice, second occurence near line %2 of file %3\nAbort.</font>").arg(QString::number(messageId), QString::number(e.lineNumber()), fileName));
                                            return false;
313
                                        } else {
314 315 316 317
                                            usedMessageIDs->append(messageId);
                                        }

                                        // Sanity check: Accept only message names not used previously
318
                                        if (usedMessageNames->contains(messageName)) {
319 320
                                            emit parseState(tr("<font color=\"red\">ERROR: Message name %1 used twice, second occurence near line %2 of file %3\nAbort.</font>").arg(messageName, QString::number(e.lineNumber()), fileName));
                                            return false;
321
                                        } else {
322 323 324
                                            usedMessageNames->insert(messageName, QString::number(e.lineNumber()));
                                        }

325 326
                                        QString channelType("mavlink_channel_t");
                                        QString messageType("mavlink_message_t");
327 328

                                        // Build up function call
pixhawk's avatar
pixhawk committed
329 330 331 332 333
                                        QString commentContainer("/**\n * @brief Pack a %1 message\n * @param system_id ID of this system\n * @param component_id ID of this component (e.g. 200 for IMU)\n * @param msg The MAVLink message to compress the data into\n *\n%2 * @return length of the message in bytes (excluding serial stream start sign)\n */\n");
                                        QString commentPackChanContainer("/**\n * @brief Pack a %1 message\n * @param system_id ID of this system\n * @param component_id ID of this component (e.g. 200 for IMU)\n * @param chan The MAVLink channel this message was sent over\n * @param msg The MAVLink message to compress the data into\n%2 * @return length of the message in bytes (excluding serial stream start sign)\n */\n");
                                        QString commentSendContainer("/**\n * @brief Send a %1 message\n * @param chan MAVLink channel to send the message\n *\n%2 */\n");
                                        QString commentEncodeContainer("/**\n * @brief Encode a %1 struct into a message\n *\n * @param system_id ID of this system\n * @param component_id ID of this component (e.g. 200 for IMU)\n * @param msg The MAVLink message to compress the data into\n * @param %1 C-struct to read the message contents from\n */\n");
                                        QString commentDecodeContainer("/**\n * @brief Decode a %1 message into a struct\n *\n * @param msg The message to decode\n * @param %1 C-struct to decode the message contents into\n */\n");
334
                                        QString commentEntry(" * @param %1 %2\n");
335
                                        QString idDefine = QString("#define MAVLINK_MSG_ID_%1 %2").arg(messageName.toUpper(), QString::number(messageId));
336
                                        QString arrayDefines;
337
                                        QString cStructName = QString("mavlink_%1_t").arg(messageName);
338 339 340
                                        QString cStruct("typedef struct __%1 \n{\n%2\n} %1;");
                                        QString cStructLines;
                                        QString encode("static inline uint16_t mavlink_msg_%1_encode(uint8_t system_id, uint8_t component_id, mavlink_message_t* msg, const %2* %1)\n{\n\treturn mavlink_msg_%1_pack(%3);\n}\n");
341

342 343
                                        QString decode("static inline void mavlink_msg_%1_decode(const mavlink_message_t* msg, %2* %1)\n{\n%3}\n");
                                        QString pack("static inline uint16_t mavlink_msg_%1_pack(uint8_t system_id, uint8_t component_id, mavlink_message_t* msg%2)\n{\n\tuint16_t i = 0;\n\tmsg->msgid = MAVLINK_MSG_ID_%3;\n\n%4\n\treturn mavlink_finalize_message(msg, system_id, component_id, i);\n}\n\n");
344 345
                                        QString packChan("static inline uint16_t mavlink_msg_%1_pack_chan(uint8_t system_id, uint8_t component_id, uint8_t chan, mavlink_message_t* msg%2)\n{\n\tuint16_t i = 0;\n\tmsg->msgid = MAVLINK_MSG_ID_%3;\n\n%4\n\treturn mavlink_finalize_message_chan(msg, system_id, component_id, chan, i);\n}\n\n");
                                        QString compactSend("#ifdef MAVLINK_USE_CONVENIENCE_FUNCTIONS\n\nstatic inline void mavlink_msg_%3_send(%1 chan%5)\n{\n\t%2 msg;\n\tmavlink_msg_%3_pack_chan(mavlink_system.sysid, mavlink_system.compid, chan, &msg%4);\n\tmavlink_send_uart(chan, &msg);\n}\n\n#endif");
346
                                        //QString compactStructSend = "#ifdef MAVLINK_USE_CONVENIENCE_FUNCTIONS\n\nstatic inline void mavlink_msg_%3_struct_send(%1 chan%5)\n{\n\t%2 msg;\n\tmavlink_msg_%3_encode(mavlink_system.sysid, mavlink_system.compid, &msg%4);\n\tmavlink_send_uart(chan, &msg);\n}\n\n#endif";
347 348 349 350 351 352 353 354
                                        QString unpacking;
                                        QString prepends;
                                        QString packParameters;
                                        QString packArguments("system_id, component_id, msg");
                                        QString packLines;
                                        QString decodeLines;
                                        QString sendArguments;
                                        QString commentLines;
355
                                        unsigned message_length = 0;
356

357 358 359

                                        // Get the message fields
                                        QDomNode f = e.firstChild();
360
                                        while (!f.isNull()) {
361
                                            QDomElement e2 = f.toElement();
362
                                            if (!e2.isNull() && e2.tagName() == "field") {
363 364 365 366
                                                QString fieldType = e2.attribute("type", "");
                                                QString fieldName = e2.attribute("name", "");
                                                QString fieldText = e2.text();

367 368 369
                                                QString unpackingCode;
                                                QString unpackingComment = QString("/**\n * @brief Get field %1 from %2 message\n *\n * @return %3\n */\n").arg(fieldName, messageName, fieldText);

pixhawk's avatar
pixhawk committed
370
                                                // Send arguments do not work for the version field
371
                                                if (!fieldType.contains("uint8_t_mavlink_version")) {
pixhawk's avatar
pixhawk committed
372 373
                                                    // Send arguments are the same for integral types and arrays
                                                    sendArguments += ", " + fieldName;
374
                                                    commentLines += commentEntry.arg(fieldName, fieldText.replace("\n", " "));
pixhawk's avatar
pixhawk committed
375 376 377 378
                                                }

                                                // MAVLink version field
                                                // this is a special field always containing the version define
379
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
pixhawk's avatar
pixhawk committed
380 381 382 383 384 385 386
                                                    // Add field to C structure
                                                    cStructLines += QString("\t%1 %2; ///< %3\n").arg("uint8_t", fieldName, fieldText);
                                                    // Add pack line to message_xx_pack function
                                                    packLines += QString("\ti += put_uint8_t_by_index(%1, i, msg->payload); // %2\n").arg(mavlinkVersion).arg(fieldText);
                                                    // Add decode function for this type
                                                    decodeLines += QString("\t%1->%2 = mavlink_msg_%1_get_%2(msg);\n").arg(messageName, fieldName);
                                                }
387 388

                                                // Array handling is different from simple types
389
                                                else if (fieldType.startsWith("array")) {
390 391 392 393 394 395 396 397
                                                    int arrayLength = QString(fieldType.split("[").at(1).split("]").first()).toInt();
                                                    QString arrayType = fieldType.split("[").first();
                                                    packParameters += QString(", const ") + QString("int8_t*") + " " + fieldName;
                                                    packArguments += ", " + messageName + "->" + fieldName;

                                                    // Add field to C structure
                                                    cStructLines += QString("\t%1 %2[%3]; ///< %4\n").arg("int8_t", fieldName, QString::number(arrayLength), fieldText);
                                                    // Add pack line to message_xx_pack function
pixhawk's avatar
pixhawk committed
398
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), fieldText);
399 400
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
401
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
402
                                                } else if (fieldType.startsWith("string")) {
403 404 405 406 407 408 409 410
                                                    int arrayLength = QString(fieldType.split("[").at(1).split("]").first()).toInt();
                                                    QString arrayType = fieldType.split("[").first();
                                                    packParameters += QString(", const ") + QString("char*") + " " + fieldName;
                                                    packArguments += ", " + messageName + "->" + fieldName;

                                                    // Add field to C structure
                                                    cStructLines += QString("\t%1 %2[%3]; ///< %4\n").arg("char", fieldName, QString::number(arrayLength), fieldText);
                                                    // Add pack line to message_xx_pack function
pixhawk's avatar
pixhawk committed
411
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), e2.text());
412 413
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
414
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
415
                                                }
416
                                                // Expand array handling to all valid mavlink data types
417
                                                else if(fieldType.contains('[') && fieldType.contains(']')) {
418 419 420 421 422 423 424 425
                                                    int arrayLength = QString(fieldType.split("[").at(1).split("]").first()).toInt();
                                                    QString arrayType = fieldType.split("[").first();
                                                    packParameters += QString(", const ") + arrayType + "* " + fieldName;
                                                    packArguments += ", " + messageName + "->" + fieldName;

                                                    // Add field to C structure
                                                    cStructLines += QString("\t%1 %2[%3]; ///< %4\n").arg(arrayType, fieldName, QString::number(arrayLength), fieldText);
                                                    // Add pack line to message_xx_pack function
426
                                                    packLines += QString("\ti += put_array_by_index((const int8_t*)%1, sizeof(%2)*%3, i, msg->payload); // %4\n").arg(fieldName, arrayType, QString::number(arrayLength), fieldText);
427 428 429 430 431 432 433
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));

                                                    unpackingCode = QString("\n\tmemcpy(r_data, msg->payload%1, sizeof(%2)*%3);\n\treturn sizeof(%2)*%3;").arg(prepends, arrayType, QString::number(arrayLength));

                                                    unpacking += unpackingComment + QString("static inline uint16_t mavlink_msg_%1_get_%2(const mavlink_message_t* msg, %3* r_data)\n{\n%4\n}\n\n").arg(messageName, fieldName, arrayType, unpackingCode);
434
                                                    //                                                    decodeLines += "";
435 436
                                                    prepends += QString("+sizeof(%1)*%2").arg(arrayType, QString::number(arrayLength));

437
                                                } else
438 439 440 441 442 443 444 445
                                                    // Handle simple types like integers and floats
                                                {
                                                    packParameters += ", " + fieldType + " " + fieldName;
                                                    packArguments += ", " + messageName + "->" + fieldName;

                                                    // Add field to C structure
                                                    cStructLines += QString("\t%1 %2; ///< %3\n").arg(fieldType, fieldName, fieldText);
                                                    // Add pack line to message_xx_pack function
pixhawk's avatar
pixhawk committed
446
                                                    packLines += QString("\ti += put_%1_by_index(%2, i, msg->payload); // %3\n").arg(fieldType, fieldName, e2.text());
447 448 449
                                                    // Add decode function for this type
                                                    decodeLines += QString("\t%1->%2 = mavlink_msg_%1_get_%2(msg);\n").arg(messageName, fieldName);
                                                }
pixhawk's avatar
pixhawk committed
450

451

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
                                                // message length calculation
                                                unsigned element_multiplier = 1;
                                                unsigned element_length = 0;
                                                const struct {
                                                    const char *prefix;
                                                    unsigned length;
                                                } length_map[] = {
                                                        { "array",  1 },
                                                        { "char",   1 },
                                                        { "uint8",  1 },
                                                        { "int8",   1 },
                                                        { "uint16", 2 },
                                                        { "int16",  2 },
                                                        { "uint32", 4 },
                                                        { "int32",  4 },
                                                        { "uint64", 8 },
                                                        { "int64",  8 },
                                                        { "float",  4 },
                                                        { "double", 8 },
                                                };
                                                if (fieldType.contains("[")) {
                                                    element_multiplier = fieldType.split("[").at(1).split("]").first().toInt();
                                                }
                                                for (unsigned i=0; i<sizeof(length_map)/sizeof(length_map[0]); i++) {
                                                    if (fieldType.startsWith(length_map[i].prefix)) {
                                                        element_length = length_map[i].length * element_multiplier;
                                                        break;
                                                    }
                                                }
                                                if (element_length == 0) {
                                                    emit parseState(tr("<font color=\"red\">ERROR: Unable to calculate length for %2 near line %1\nAbort.</font>").arg(QString::number(e.lineNumber()), fieldType));
                                                }
                                                message_length += element_length;

486
                                                //
487
                                                //                                                QString unpackingCode;
488

489
                                                if (fieldType == "uint8_t_mavlink_version") {
490
                                                    unpackingCode = QString("\treturn (%1)(msg->payload%2)[0];").arg("uint8_t", prepends);
491
                                                } else if (fieldType == "uint8_t" || fieldType == "int8_t") {
492
                                                    unpackingCode = QString("\treturn (%1)(msg->payload%2)[0];").arg(fieldType, prepends);
493
                                                } else if (fieldType == "uint16_t" || fieldType == "int16_t") {
494
                                                    unpackingCode = QString("\tgeneric_16bit r;\n\tr.b[1] = (msg->payload%1)[0];\n\tr.b[0] = (msg->payload%1)[1];\n\treturn (%2)r.s;").arg(prepends).arg(fieldType);
495
                                                } else if (fieldType == "uint32_t" || fieldType == "int32_t") {
496
                                                    unpackingCode = QString("\tgeneric_32bit r;\n\tr.b[3] = (msg->payload%1)[0];\n\tr.b[2] = (msg->payload%1)[1];\n\tr.b[1] = (msg->payload%1)[2];\n\tr.b[0] = (msg->payload%1)[3];\n\treturn (%2)r.i;").arg(prepends).arg(fieldType);
497
                                                } else if (fieldType == "float") {
498
                                                    unpackingCode = QString("\tgeneric_32bit r;\n\tr.b[3] = (msg->payload%1)[0];\n\tr.b[2] = (msg->payload%1)[1];\n\tr.b[1] = (msg->payload%1)[2];\n\tr.b[0] = (msg->payload%1)[3];\n\treturn (%2)r.f;").arg(prepends).arg(fieldType);
499
                                                } else if (fieldType == "uint64_t" || fieldType == "int64_t") {
500
                                                    unpackingCode = QString("\tgeneric_64bit r;\n\tr.b[7] = (msg->payload%1)[0];\n\tr.b[6] = (msg->payload%1)[1];\n\tr.b[5] = (msg->payload%1)[2];\n\tr.b[4] = (msg->payload%1)[3];\n\tr.b[3] = (msg->payload%1)[4];\n\tr.b[2] = (msg->payload%1)[5];\n\tr.b[1] = (msg->payload%1)[6];\n\tr.b[0] = (msg->payload%1)[7];\n\treturn (%2)r.ll;").arg(prepends).arg(fieldType);
501 502
                                                } else if (fieldType.startsWith("array")) {
                                                    // fieldtype formatis string[n] where n is the number of bytes, extract n from field type string
503
                                                    unpackingCode = QString("\n\tmemcpy(r_data, msg->payload%1, %2);\n\treturn %2;").arg(prepends, fieldType.split("[").at(1).split("]").first());
504 505
                                                } else if (fieldType.startsWith("string")) {
                                                    // fieldtype formatis string[n] where n is the number of bytes, extract n from field type string
506 507 508
                                                    unpackingCode = QString("\n\tstrcpy(r_data, msg->payload%1, %2);\n\treturn %2;").arg(prepends, fieldType.split("[").at(1).split("]").first());
                                                }

509

510
                                                // Generate the message decoding function
511
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
512
                                                    unpacking += unpackingComment + QString("static inline %1 mavlink_msg_%2_get_%3(const mavlink_message_t* msg)\n{\n%4\n}\n\n").arg("uint8_t", messageName, fieldName, unpackingCode);
pixhawk's avatar
pixhawk committed
513 514 515
                                                    decodeLines += "";
                                                    prepends += "+sizeof(uint8_t)";
                                                }
516
                                                // Array handling is different from simple types
517
                                                else if (fieldType.startsWith("array")) {
518 519 520 521
                                                    unpacking += unpackingComment + QString("static inline uint16_t mavlink_msg_%1_get_%2(const mavlink_message_t* msg, int8_t* r_data)\n{\n%4\n}\n\n").arg(messageName, fieldName, unpackingCode);
                                                    decodeLines += "";
                                                    QString arrayLength = QString(fieldType.split("[").at(1).split("]").first());
                                                    prepends += "+" + arrayLength;
522
                                                } else if (fieldType.startsWith("string")) {
523 524 525 526
                                                    unpacking += unpackingComment + QString("static inline uint16_t mavlink_msg_%1_get_%2(const mavlink_message_t* msg, char* r_data)\n{\n%4\n}\n\n").arg(messageName, fieldName, unpackingCode);
                                                    decodeLines += "";
                                                    QString arrayLength = QString(fieldType.split("[").at(1).split("]").first());
                                                    prepends += "+" + arrayLength;
527
                                                } else if(fieldType.contains('[') && fieldType.contains(']')) {
528
                                                    // prevent this case from being caught in the following else
529
                                                } else {
530 531 532 533 534 535 536 537
                                                    unpacking += unpackingComment + QString("static inline %1 mavlink_msg_%2_get_%3(const mavlink_message_t* msg)\n{\n%4\n}\n\n").arg(fieldType, messageName, fieldName, unpackingCode);
                                                    decodeLines += "";
                                                    prepends += "+sizeof(" + e2.attribute("type", "void") + ")";
                                                }
                                            }
                                            f = f.nextSibling();
                                        }

538 539 540 541 542
                                        if (messageId > highest_message_id) {
                                            highest_message_id = messageId;
                                        }
                                        message_lengths[messageId] = message_length;

543 544 545
                                        cStruct = cStruct.arg(cStructName, cStructLines);
                                        lcmStructDefs.append("\n").append(cStruct).append("\n");
                                        pack = pack.arg(messageName, packParameters, messageName.toUpper(), packLines);
546
                                        packChan = packChan.arg(messageName, packParameters, messageName.toUpper(), packLines);
547 548 549
                                        encode = encode.arg(messageName).arg(cStructName).arg(packArguments);
                                        decode = decode.arg(messageName).arg(cStructName).arg(decodeLines);
                                        compactSend = compactSend.arg(channelType, messageType, messageName, sendArguments, packParameters);
pixhawk's avatar
pixhawk committed
550
                                        QString cFile = "// MESSAGE " + messageName.toUpper() + " PACKING\n\n" + idDefine + "\n\n" + cStruct + "\n\n" + arrayDefines + "\n\n" + commentContainer.arg(messageName.toLower(), commentLines) + pack + commentPackChanContainer.arg(messageName.toLower(), commentLines) + packChan + commentEncodeContainer.arg(messageName.toLower()) + encode + "\n" + commentSendContainer.arg(messageName.toLower(), commentLines) + compactSend + "\n" + "// MESSAGE " + messageName.toUpper() + " UNPACKING\n\n" + unpacking + commentDecodeContainer.arg(messageName.toLower()) + decode;
551 552 553 554 555 556 557 558 559 560 561
                                        cFiles.append(qMakePair(QString("mavlink_msg_%1.h").arg(messageName), cFile));
                                    } // Check if tag = message
                                } // Check if e = NULL
                                n = n.nextSibling();
                            } // While through <message>
                            n = p;

                        } // Check if tag = messages
                    } // Check if e = NULL
                    n = n.nextSibling();
                } // While through include and messages
562
                // One up - current node = parent
563 564 565 566
                n = p;

            } // Check if tag = mavlink
        } // Check if e = NULL
pixhawk's avatar
pixhawk committed
567
        n = n.nextSibling();
568
    } // While through root children
pixhawk's avatar
pixhawk committed
569

pixhawk's avatar
pixhawk committed
570 571 572 573 574 575
    // Add version to main header

    mainHeader += "// MAVLINK VERSION\n\n";
    mainHeader += QString("#ifndef MAVLINK_VERSION\n#define MAVLINK_VERSION %1\n#endif\n\n").arg(mavlinkVersion);
    mainHeader += QString("#if (MAVLINK_VERSION == 0)\n#undef MAVLINK_VERSION\n#define MAVLINK_VERSION %1\n#endif\n\n").arg(mavlinkVersion);

576 577 578 579 580
    // Add enums to main header

    mainHeader += "// ENUM DEFINITIONS\n\n";
    mainHeader += enums;
    mainHeader += "\n";
581

582
    mainHeader += "// MESSAGE DEFINITIONS\n\n";
pixhawk's avatar
pixhawk committed
583
    // Create directory if it doesn't exist, report result in success
584
    if (!dir.exists()) success = success && dir.mkpath(outputDirName + "/" + messagesDirName);
585
    for (int i = 0; i < cFiles.size(); i++) {
pixhawk's avatar
pixhawk committed
586 587 588 589
        QFile rawFile(dir.filePath(cFiles.at(i).first));
        bool ok = rawFile.open(QIODevice::WriteOnly | QIODevice::Text);
        success = success && ok;
        rawFile.write(cFiles.at(i).second.toLatin1());
590
        rawFile.close();
pixhawk's avatar
pixhawk committed
591 592 593
        mainHeader += includeLine.arg(messagesDirName + "/" + cFiles.at(i).first);
    }

594 595 596 597 598 599 600 601 602
    mainHeader += "\n\n// MESSAGE LENGTHS\n\n";
    mainHeader += "#undef MAVLINK_MESSAGE_LENGTHS\n";
    mainHeader += "#define MAVLINK_MESSAGE_LENGTHS { ";
    for (int i=0; i<highest_message_id; i++) {
        mainHeader += QString::number(message_lengths[i]);
        if (i < highest_message_id-1) mainHeader += ", ";
    }
    mainHeader += " }\n\n";

pixhawk's avatar
pixhawk committed
603 604
    mainHeader += "#ifdef __cplusplus\n}\n#endif\n";
    mainHeader += "#endif";
lm's avatar
lm committed
605 606
    // Newline to make compiler happy
    mainHeader += "\n";
pixhawk's avatar
pixhawk committed
607 608 609 610 611 612

    // Write main header
    QFile rawHeader(outputDirName + "/" + mainHeaderName);
    bool ok = rawHeader.open(QIODevice::WriteOnly | QIODevice::Text);
    success = success && ok;
    rawHeader.write(mainHeader.toLatin1());
613 614 615 616 617 618 619 620 621 622 623 624
    rawHeader.close();

    // Write alias mavlink header
    QFile mavlinkHeader(outputDirName + "/mavlink.h");
    ok = mavlinkHeader.open(QIODevice::WriteOnly | QIODevice::Text);
    success = success && ok;
    QString mHeader = QString("/** @file\n *\t@brief MAVLink comm protocol.\n *\t@see http://pixhawk.ethz.ch/software/mavlink\n *\t Generated on %1\n */\n#ifndef MAVLINK_H\n#define MAVLINK_H\n\n").arg(date); // The main header includes all messages
    // Mark all code as C code
    mHeader += "#include \"" + mainHeaderName + "\"\n\n";
    mHeader += "#endif\n";
    mavlinkHeader.write(mHeader.toLatin1());
    mavlinkHeader.close();
pixhawk's avatar
pixhawk committed
625 626

    // Write C structs / lcm definitions
627 628 629 630
    //    QFile lcmStructs(outputDirName + "/mavlink.lcm");
    //    ok = lcmStructs.open(QIODevice::WriteOnly | QIODevice::Text);
    //    success = success && ok;
    //    lcmStructs.write(lcmStructDefs.toLatin1());
pixhawk's avatar
pixhawk committed
631 632 633

    return success;
}