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

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

291
                        // Handle all message tags
292
                        else if (e.tagName() == "messages") {
293 294
                            p = n;
                            n = n.firstChild();
295
                            while (!n.isNull()) {
296
                                e = n.toElement();
297
                                if(!e.isNull()) {
298 299 300
                                    //if (e.isNull()) continue;
                                    // Get message name
                                    QString messageName = e.attribute("name", "").toLower();
301
                                    if (messageName.size() == 0) {
302 303
                                        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;
304
                                    } else {
305 306 307 308 309 310
                                        // 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
311
                                        if (usedMessageIDs->contains(messageId)) {
312 313
                                            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;
314
                                        } else {
315 316 317 318
                                            usedMessageIDs->append(messageId);
                                        }

                                        // Sanity check: Accept only message names not used previously
319
                                        if (usedMessageNames->contains(messageName)) {
320 321
                                            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;
322
                                        } else {
323 324 325
                                            usedMessageNames->insert(messageName, QString::number(e.lineNumber()));
                                        }

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

                                        // Build up function call
pixhawk's avatar
pixhawk committed
330 331 332 333 334
                                        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");
335
                                        QString commentEntry(" * @param %1 %2\n");
336
                                        QString idDefine = QString("#define MAVLINK_MSG_ID_%1 %2").arg(messageName.toUpper(), QString::number(messageId));
337
                                        QString arrayDefines;
338
                                        QString cStructName = QString("mavlink_%1_t").arg(messageName);
339 340 341
                                        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");
342

343 344
                                        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");
345 346
                                        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");
347
                                        //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";
348 349 350 351 352 353 354 355
                                        QString unpacking;
                                        QString prepends;
                                        QString packParameters;
                                        QString packArguments("system_id, component_id, msg");
                                        QString packLines;
                                        QString decodeLines;
                                        QString sendArguments;
                                        QString commentLines;
356
                                        unsigned message_length = 0;
357

358 359 360

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

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

                                                // MAVLink version field
                                                // this is a special field always containing the version define
380
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
pixhawk's avatar
pixhawk committed
381 382 383 384 385 386 387
                                                    // 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);
                                                }
388 389

                                                // Array handling is different from simple types
390
                                                else if (fieldType.startsWith("array")) {
391 392 393 394 395 396 397 398
                                                    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
399
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), fieldText);
400 401
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
402
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
403
                                                } else if (fieldType.startsWith("string")) {
404 405 406 407 408 409 410 411
                                                    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
412
                                                    packLines += QString("\ti += put_%1_by_index(%2, %3, i, msg->payload); // %4\n").arg(arrayType, fieldName, QString::number(arrayLength), e2.text());
413 414
                                                    // Add decode function for this type
                                                    decodeLines += QString("\tmavlink_msg_%1_get_%2(msg, %1->%2);\n").arg(messageName, fieldName);
415
                                                    arrayDefines += QString("#define MAVLINK_MSG_%1_FIELD_%2_LEN %3\n").arg(messageName.toUpper(), fieldName.toUpper(), QString::number(arrayLength));
416
                                                }
417
                                                // Expand array handling to all valid mavlink data types
418
                                                else if(fieldType.contains('[') && fieldType.contains(']')) {
419 420 421 422 423 424 425 426
                                                    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
427
                                                    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);
428 429 430 431 432 433 434
                                                    // 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);
435
                                                    //                                                    decodeLines += "";
436 437
                                                    prepends += QString("+sizeof(%1)*%2").arg(arrayType, QString::number(arrayLength));

438
                                                } else
439 440 441 442 443 444 445 446
                                                    // 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
447
                                                    packLines += QString("\ti += put_%1_by_index(%2, i, msg->payload); // %3\n").arg(fieldType, fieldName, e2.text());
448 449 450
                                                    // 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
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 486
                                                // 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;

487
                                                //
488
                                                //                                                QString unpackingCode;
489

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

510

511
                                                // Generate the message decoding function
512
                                                if (fieldType.contains("uint8_t_mavlink_version")) {
513
                                                    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
514 515 516
                                                    decodeLines += "";
                                                    prepends += "+sizeof(uint8_t)";
                                                }
517
                                                // Array handling is different from simple types
518
                                                else if (fieldType.startsWith("array")) {
519 520 521 522
                                                    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;
523
                                                } else if (fieldType.startsWith("string")) {
524 525 526 527
                                                    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;
528
                                                } else if(fieldType.contains('[') && fieldType.contains(']')) {
529
                                                    // prevent this case from being caught in the following else
530
                                                } else {
531 532 533 534 535 536 537 538
                                                    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();
                                        }

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

544 545 546
                                        cStruct = cStruct.arg(cStructName, cStructLines);
                                        lcmStructDefs.append("\n").append(cStruct).append("\n");
                                        pack = pack.arg(messageName, packParameters, messageName.toUpper(), packLines);
547
                                        packChan = packChan.arg(messageName, packParameters, messageName.toUpper(), packLines);
548 549 550
                                        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
551
                                        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;
552 553 554 555 556 557 558 559 560 561 562
                                        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
563
                // One up - current node = parent
564 565 566 567
                n = p;

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

pixhawk's avatar
pixhawk committed
571 572 573 574 575 576
    // 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);

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

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

583
    mainHeader += "// MESSAGE DEFINITIONS\n\n";
pixhawk's avatar
pixhawk committed
584
    // Create directory if it doesn't exist, report result in success
585
    if (!dir.exists()) success = success && dir.mkpath(outputDirName + "/" + messagesDirName);
586
    for (int i = 0; i < cFiles.size(); i++) {
pixhawk's avatar
pixhawk committed
587 588 589 590
        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());
591
        rawFile.close();
pixhawk's avatar
pixhawk committed
592 593 594
        mainHeader += includeLine.arg(messagesDirName + "/" + cFiles.at(i).first);
    }

595 596 597 598 599 600 601 602 603
    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
604 605
    mainHeader += "#ifdef __cplusplus\n}\n#endif\n";
    mainHeader += "#endif";
lm's avatar
lm committed
606 607
    // Newline to make compiler happy
    mainHeader += "\n";
pixhawk's avatar
pixhawk committed
608 609 610 611 612 613

    // Write main header
    QFile rawHeader(outputDirName + "/" + mainHeaderName);
    bool ok = rawHeader.open(QIODevice::WriteOnly | QIODevice::Text);
    success = success && ok;
    rawHeader.write(mainHeader.toLatin1());
614 615 616 617 618 619 620 621 622 623 624 625
    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
626 627

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

    return success;
}