MAVLinkXMLParser.cc 46.2 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 153
                                // Abort if inclusion fails
                                if (!includeParser.generate()) return false;
154
                                recursion_level--;
155 156 157 158
                                mainHeader += "\n#include \"../" + pureIncludeFileName + "/" + pureIncludeFileName + ".h\"\n";


                                // OLD MODE: MERGE BOTH FILES
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
                                //                                        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;
                                //                                        }
                                //                                    }
                                //                                }
176 177

                                emit parseState(QString("<font color=\"green\">End of inclusion from file: %1</font>").arg(incFileName));
178
                            } else {
179 180 181
                                // 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;
182
                            }
183

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

                            // Check if version has been previously set
192
                            if (mavlinkVersion != 0) {
pixhawk's avatar
pixhawk committed
193 194 195 196 197 198
                                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);
199
                            if (ok && (version > 0) && (version < 256)) {
pixhawk's avatar
pixhawk committed
200 201
                                // Set MAVLink version
                                mavlinkVersion = version;
202
                            } else {
pixhawk's avatar
pixhawk committed
203 204 205 206 207
                                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
208
                        else if (e.tagName() == "enums") {
209 210 211
                            // One down into the enums list
                            p = n;
                            n = n.firstChild();
212
                            while (!n.isNull()) {
213 214 215 216 217 218 219
                                e = n.toElement();

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

220
                                if(!e.isNull() && e.tagName() == "enum") {
221 222
                                    // Get enum name
                                    QString enumName = e.attribute("name", "").toLower();
223
                                    if (enumName.size() == 0) {
224 225
                                        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;
226
                                    } else {
227
                                        // Sanity check: Accept only enum names not used previously
228
                                        if (usedEnumNames->contains(enumName)) {
229 230
                                            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;
231
                                        } else {
232 233 234 235 236
                                            usedEnumNames->insert(enumName, QString::number(e.lineNumber()));
                                        }

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

                                        int nextEnumValue = 0;

241
                                        // Get the enum fields
242
                                        QDomNode f = e.firstChild();
243
                                        while (!f.isNull()) {
244
                                            QDomElement e2 = f.toElement();
245
                                            if (!e2.isNull() && e2.tagName() == "entry") {
246 247 248 249 250 251
                                                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", "");
252
                                                if (fieldValue.length() == 0) {
253 254
                                                    fieldValue = QString::number(nextEnumValue);
                                                    nextEnumValue++;
255
                                                } else {
256 257
                                                    bool ok;
                                                    nextEnumValue = fieldValue.toInt(&ok) + 1;
258
                                                    if (!ok) {
259 260 261 262 263 264 265
                                                        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;
lm's avatar
lm committed
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
                                                if (e2.text().length() > 0)
                                                {
                                                    QString sep(" | ");
                                                    QDomNode pp = e2.firstChild();
                                                    while (!pp.isNull()) {
                                                        QDomElement pp2 = pp.toElement();
                                                        if (pp2.isText() || pp2.isCDATASection())
                                                        {
                                                            fieldComment +=  pp2.nodeValue() + sep;
                                                        }
                                                        else if (pp2.isElement())
                                                        {
                                                            fieldComment += pp2.text() + sep;
                                                            pp = pp.nextSibling();
                                                        }
                                                    }
282
                                                    fieldComment = fieldComment.replace("\n", " ");
lm's avatar
lm committed
283
                                                    fieldComment = " /* " + fieldComment.simplified() + " */";
284 285
                                                }
                                                currEnum += "\t" + fieldName.toUpper() + "=" + fieldValue + "," + fieldComment + "\n";
lm's avatar
lm committed
286 287 288 289
                                            }
                                            else if(!e2.isNull() && e2.tagName() == "description")
                                            {
                                                comment = " " + e2.text().replace("\n", " ") + comment;
290 291 292 293 294 295
                                            }
                                            f = f.nextSibling();
                                        }
                                    }
                                    // Add the last parsed enum
                                    // Remove the last comma, as the last value has none
296 297 298
                                    // ENUM END MARKER IS LAST ENTRY, COMMA REMOVAL NOT NEEDED
                                    //int commaPosition = currEnum.lastIndexOf(",");
                                    //currEnum.remove(commaPosition, 1);
299

300
                                    enums += "/** @brief " + comment  + " */\n" + currEnum + currEnumEnd;
301 302 303 304 305 306 307
                                } // Element is non-zero and element name is <enum>
                                n = n.nextSibling();
                            } // While through <enums>
                            // One up, back into the <mavlink> structure
                            n = p;
                        }

308
                        // Handle all message tags
309
                        else if (e.tagName() == "messages") {
310 311
                            p = n;
                            n = n.firstChild();
312
                            while (!n.isNull()) {
313
                                e = n.toElement();
314
                                if(!e.isNull()) {
315 316 317
                                    //if (e.isNull()) continue;
                                    // Get message name
                                    QString messageName = e.attribute("name", "").toLower();
318
                                    if (messageName.size() == 0) {
319 320
                                        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;
321
                                    } else {
322 323 324 325 326 327
                                        // 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
328
                                        if (usedMessageIDs->contains(messageId)) {
329 330
                                            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;
331
                                        } else {
332 333 334 335
                                            usedMessageIDs->append(messageId);
                                        }

                                        // Sanity check: Accept only message names not used previously
336
                                        if (usedMessageNames->contains(messageName)) {
337 338
                                            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;
339
                                        } else {
340 341 342
                                            usedMessageNames->insert(messageName, QString::number(e.lineNumber()));
                                        }

343 344
                                        QString channelType("mavlink_channel_t");
                                        QString messageType("mavlink_message_t");
345 346

                                        // Build up function call
pixhawk's avatar
pixhawk committed
347 348 349 350 351
                                        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");
352
                                        QString commentEntry(" * @param %1 %2\n");
353
                                        QString idDefine = QString("#define MAVLINK_MSG_ID_%1 %2").arg(messageName.toUpper(), QString::number(messageId));
354
                                        QString arrayDefines;
355
                                        QString cStructName = QString("mavlink_%1_t").arg(messageName);
356 357 358
                                        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");
359

360 361
                                        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");
362 363
                                        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");
364
                                        //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";
365 366 367 368 369 370 371 372
                                        QString unpacking;
                                        QString prepends;
                                        QString packParameters;
                                        QString packArguments("system_id, component_id, msg");
                                        QString packLines;
                                        QString decodeLines;
                                        QString sendArguments;
                                        QString commentLines;
373
                                        unsigned message_length = 0;
374

375 376 377

                                        // Get the message fields
                                        QDomNode f = e.firstChild();
378
                                        while (!f.isNull()) {
379
                                            QDomElement e2 = f.toElement();
380
                                            if (!e2.isNull() && e2.tagName() == "field") {
381 382 383 384
                                                QString fieldType = e2.attribute("type", "");
                                                QString fieldName = e2.attribute("name", "");
                                                QString fieldText = e2.text();

385 386 387
                                                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
388
                                                // Send arguments do not work for the version field
389
                                                if (!fieldType.contains("uint8_t_mavlink_version")) {
pixhawk's avatar
pixhawk committed
390 391
                                                    // Send arguments are the same for integral types and arrays
                                                    sendArguments += ", " + fieldName;
392
                                                    commentLines += commentEntry.arg(fieldName, fieldText.replace("\n", " "));
pixhawk's avatar
pixhawk committed
393 394 395 396
                                                }

                                                // MAVLink version field
                                                // this is a special field always containing the version define
397
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
pixhawk's avatar
pixhawk committed
398 399 400 401 402 403 404
                                                    // 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);
                                                }
405 406

                                                // Array handling is different from simple types
407
                                                else if (fieldType.startsWith("array")) {
408 409 410 411 412 413 414 415
                                                    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
416
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), fieldText);
417 418
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
419
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
420
                                                } else if (fieldType.startsWith("string")) {
421 422 423 424 425 426 427 428
                                                    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
429
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), e2.text());
430 431
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
432
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
433
                                                }
434
                                                // Expand array handling to all valid mavlink data types
435
                                                else if(fieldType.contains('[') && fieldType.contains(']')) {
436 437 438 439 440 441 442 443
                                                    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
444
                                                    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);
445 446 447 448 449 450 451
                                                    // 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);
452
                                                    //                                                    decodeLines += "";
453 454
                                                    prepends += QString("+sizeof(%1)*%2").arg(arrayType, QString::number(arrayLength));

455
                                                } else
456 457 458 459 460 461 462 463
                                                    // 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
464
                                                    packLines += QString("\ti += put_%1_by_index(%2, i, msg->payload); // %3\n").arg(fieldType, fieldName, e2.text());
465 466 467
                                                    // 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
468

469

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
                                                // 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;

504
                                                //
505
                                                //                                                QString unpackingCode;
506

507
                                                if (fieldType == "uint8_t_mavlink_version") {
508
                                                    unpackingCode = QString("\treturn (%1)(msg->payload%2)[0];").arg("uint8_t", prepends);
509
                                                } else if (fieldType == "uint8_t" || fieldType == "int8_t") {
510
                                                    unpackingCode = QString("\treturn (%1)(msg->payload%2)[0];").arg(fieldType, prepends);
511
                                                } else if (fieldType == "uint16_t" || fieldType == "int16_t") {
512
                                                    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);
513
                                                } else if (fieldType == "uint32_t" || fieldType == "int32_t") {
514
                                                    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);
515
                                                } else if (fieldType == "float") {
516
                                                    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);
517
                                                } else if (fieldType == "uint64_t" || fieldType == "int64_t") {
518
                                                    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);
519 520
                                                } else if (fieldType.startsWith("array")) {
                                                    // fieldtype formatis string[n] where n is the number of bytes, extract n from field type string
521
                                                    unpackingCode = QString("\n\tmemcpy(r_data, msg->payload%1, %2);\n\treturn %2;").arg(prepends, fieldType.split("[").at(1).split("]").first());
522 523
                                                } else if (fieldType.startsWith("string")) {
                                                    // fieldtype formatis string[n] where n is the number of bytes, extract n from field type string
524 525 526
                                                    unpackingCode = QString("\n\tstrcpy(r_data, msg->payload%1, %2);\n\treturn %2;").arg(prepends, fieldType.split("[").at(1).split("]").first());
                                                }

527

528
                                                // Generate the message decoding function
529
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
530
                                                    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
531 532 533
                                                    decodeLines += "";
                                                    prepends += "+sizeof(uint8_t)";
                                                }
534
                                                // Array handling is different from simple types
535
                                                else if (fieldType.startsWith("array")) {
536 537 538 539
                                                    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;
540
                                                } else if (fieldType.startsWith("string")) {
541 542 543 544
                                                    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;
545
                                                } else if(fieldType.contains('[') && fieldType.contains(']')) {
546
                                                    // prevent this case from being caught in the following else
547
                                                } else {
548 549 550 551 552 553 554 555
                                                    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();
                                        }

556 557 558 559 560
                                        if (messageId > highest_message_id) {
                                            highest_message_id = messageId;
                                        }
                                        message_lengths[messageId] = message_length;

561 562 563
                                        cStruct = cStruct.arg(cStructName, cStructLines);
                                        lcmStructDefs.append("\n").append(cStruct).append("\n");
                                        pack = pack.arg(messageName, packParameters, messageName.toUpper(), packLines);
564
                                        packChan = packChan.arg(messageName, packParameters, messageName.toUpper(), packLines);
565 566 567
                                        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
568
                                        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;
569 570 571 572 573 574 575 576 577 578 579
                                        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
580
                // One up - current node = parent
581 582 583 584
                n = p;

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

pixhawk's avatar
pixhawk committed
588 589 590 591 592 593
    // 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);

594 595 596 597 598
    // Add enums to main header

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

600
    mainHeader += "// MESSAGE DEFINITIONS\n\n";
pixhawk's avatar
pixhawk committed
601
    // Create directory if it doesn't exist, report result in success
602
    if (!dir.exists()) success = success && dir.mkpath(outputDirName + "/" + messagesDirName);
603
    for (int i = 0; i < cFiles.size(); i++) {
pixhawk's avatar
pixhawk committed
604 605 606 607
        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());
608
        rawFile.close();
pixhawk's avatar
pixhawk committed
609 610 611
        mainHeader += includeLine.arg(messagesDirName + "/" + cFiles.at(i).first);
    }

612 613 614 615 616 617 618 619 620
    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
621 622
    mainHeader += "#ifdef __cplusplus\n}\n#endif\n";
    mainHeader += "#endif";
lm's avatar
lm committed
623 624
    // Newline to make compiler happy
    mainHeader += "\n";
pixhawk's avatar
pixhawk committed
625 626 627 628 629 630

    // Write main header
    QFile rawHeader(outputDirName + "/" + mainHeaderName);
    bool ok = rawHeader.open(QIODevice::WriteOnly | QIODevice::Text);
    success = success && ok;
    rawHeader.write(mainHeader.toLatin1());
631 632 633 634 635 636 637 638 639 640 641 642
    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
643 644

    // Write C structs / lcm definitions
645 646 647 648
    //    QFile lcmStructs(outputDirName + "/mavlink.lcm");
    //    ok = lcmStructs.open(QIODevice::WriteOnly | QIODevice::Text);
    //    success = success && ok;
    //    lcmStructs.write(lcmStructDefs.toLatin1());
pixhawk's avatar
pixhawk committed
649 650 651

    return success;
}