Skip to content

Creating a custom function (Walkthrough)

thedjnK edited this page Oct 16, 2017 · 7 revisions

This is a short guide to creating an 'extension' for UwTerminalX that visually displays the space usage of a module and should be the basis for how to add code to UwTerminalX that communicates with a module as it goes through how to add to UwTerminalX's state machine system.

Part 1:

  1. Start by downloading the UwTerminalX source code from the master branch, you can do this by downloading a zip of the source code and extracting it, or cloning on your computer using the Github client. Once downloaded and extracted, open the UwTerminalX.pro project in Qt, it will ask you to configure it, accept the defaults. The full UwTerminalX project tree will be shown on the left-hand side of Qt. Right click the project, and click 'Add new'.

Image_1

  1. In the dialogue that opens, select a Qt class and click 'Qt Designer Form class' and click 'Choose...'.

Image_2

  1. Accept the default 'Dialogue without buttons' and click the Next button.

Image_3

  1. Change the class name to be 'UwxSpace' and capitalise the filenames to be UwxSpace.h, UwxSpace.cpp and UwxSpace.ui, then click Next.

Image_4

  1. No version control system has been setup so these options can be ignored, click Finish.

Image_5

  1. The new form will be displayed in Qt designer, various elements from the left selection panel can be dragged onto the form.

Image_6

  1. Start by adding a button, click and drag it from the left onto the form, then double click it to change the displayed text, set this so &Close (the ampersand symbol is used to provide accessibility support, which is important so that users not using a mouse can still use the application, the & selects a shortcut key, which in this case is C, so a user can either click the button, or hold alt and press C to activate the button), on the lower right-hand side property panel, change the object name to btn_Close.

Image_7

  1. Slots are used to trigger events, right click the close button and select 'Go to slot'.

Image_8

  1. Highlight the 'clicked()' slot and click OK, this will generate the mapping for this automatically in the UwxSpace class.

Image_9

  1. Here is where the code to be executed when the button is clicked goes, add in the following code which will close the dialogue when the button is clicked:
    void
    UwxSpace::on_btn_Close_clicked(
        )
    {
        //Close button clicked, close popup.
        this->close();
    }

Image_10

  1. Switch back to Qt designer for UwxSpace.ui and add 3 labels (as these are not interactive objects, names do not matter) with the texts 'Used:', 'Deleted:' and 'Free:', then add in 3 progress bars, change the properties so that no text is displayed and call them 'bar_Used', 'bar_Deleted' and 'bar_Free'.

Image_11

  1. Open UwxSpace.h and add a new function named SetupBars with 3 unsigned long parameters; ulTotal, ulFree and ulDeleted:
    public slots:
        void
        SetupBars(
            unsigned long ulTotal,
            unsigned long ulFree,
            unsigned long ulDeleted
            );

Image_12

  1. Switch to UwxSpace.cpp and add the code for setting up the bar data:
    void
    UwxSpace::SetupBars(
        unsigned long ulTotal,
        unsigned long ulFree,
        unsigned long ulDeleted
        )
    {
        //Calculate the used space
        unsigned long ulUsed = ulTotal - ulFree - ulDeleted;

        //Set the progress bars up
        ui->bar_Used->setValue(ulUsed*100/ulTotal);
        ui->bar_Deleted->setValue(ulDeleted*100/ulTotal);
        ui->bar_Free->setValue(ulFree*100/ulTotal);
    }

Image_13

  1. Go to UwxMainWindow.h and include the newly created UwxSpace.h file:
    #include "UwxSpace.h"

Image_14

  1. Switch to UwxMainWindow.cpp and create a new UwxSpace instance named gusSpaceForm:
    UwxSpace *gusSpaceForm; //Space used form

Image_15

  1. Then initialise the object in the form creation function:
    //Initialise space used form
    gusSpaceForm = new UwxSpace;

Image_16

  1. Also clear up the object by deleting it in the form deletion function:
    delete gusSpaceForm;

Image_17

  1. Inside the form creation function, where the right click menu is created, add a new option to the list after the 'Data' sub-menu named 'Space Usage'.
    gpSMenu3->addAction(new QAction("Space Usage", this));

Image_18

  1. Return to UwxMainWindow.h and add a new define for the space checking function (this is for the UwTerminalX state machine logic):
    #define MODE_SPACE_USAGE 19

Image_19

  1. In UwxMainWindow.cpp, where the right click menu handlers are, add a new else if statement to check if the selection option is 'Space Usage' and add the code that will setup the UwTerminalX state machine and send the commands to the module. In this code example, the download timer is being activated and used in-case the module times out or doesn't respond, if this happens then the error shown will be that the module didn't respond to a file download - in an actual function addition it would be best to create a new timer and use that instead of modify the existing error message output to check what state the state machine is in and output an appropriate error message instead.
    else if (qaAction->text() == "Space Usage")
    {
        //Get the space usage information from the module
        if (gspSerialPort.isOpen() == true && gbLoopbackMode == false && gbTermBusy == false)
        {
            //Not currently busy
            QByteArray baTmpBA = QString("at i 6").toUtf8();
            gspSerialPort.write(baTmpBA);
            gintQueuedTXBytes += baTmpBA.size();
            gpMainLog->WriteLogData(QString(baTmpBA).append("\n"));
            DoLineEnd();

            //We're now busy
            gbTermBusy = true;
            gchTermMode = MODE_SPACE_USAGE;
            gchTermMode2 = MODE_SPACE_USAGE;
            gchTermBusyLines = 0;
            gstrTermBusyData = tr("");
            ui->btn_Cancel->setEnabled(true);

            //Start the timeout timer (this should be adapted to use a different timer with a different error message in your own modifications, and runs if the module doesn't respond)
            gtmrDownloadTimeoutTimer.start();

            //Update display buffer
            gbaDisplayBuffer.append(baTmpBA);
            if (!gtmrTextUpdateTimer.isActive())
            {
                gtmrTextUpdateTimer.start();
            }
        }
    }

Image_20

  1. Add the code for if the user click cancel in UwxMainWindow.cpp, this will just output a message on the screen and stop the state machine:
    else if (gchTermMode == MODE_SPACE_USAGE)
    {
        //Cancel request to get module space usage information
        gtmrDownloadTimeoutTimer.stop();
        gbaDisplayBuffer.append("\n-- Space usage information request cancelled --\n");
        if (!gtmrTextUpdateTimer.isActive())
        {
            gtmrTextUpdateTimer.start();
        }
        gchTermMode = 0;
        gchTermMode2 = 0;
        gbTermBusy = false;
    }

Image_21

  1. The next code to add is when a response is received from the module in UwxMainWindow.cpp: this goes in the serial response function, as Qt has built-in support for regular expressions, it's easy to add in a matching function which will also split the data up (this also makes part 2 of this guide much simpler than manually checking all the data using string comparisons). If an invalid response is received (such as if the module doesn't support the function) then an error will be displayed instead.
    else if (gchTermMode2 == MODE_SPACE_USAGE)
    {
        //Response to space used on module
        QRegularExpression reTempRE("10\t6\t([0-9]+),([0-9]+),([0-9]+)\r");
        QRegularExpressionMatch remTempREM = reTempRE.match(baOrigData);
        if (remTempREM.hasMatch() == true)
        {
            //Got the device usage information
            bool ok; //Used to store/ignore if Qt string-to-ULong works, we already check in the regex itself so it'll only fail from a PC problem like running out of memory
            gusSpaceForm->SetupBars(remTempREM.captured(1).toULong(&ok, 10), remTempREM.captured(2).toULong(&ok, 10), remTempREM.captured(3).toULong(&ok, 10));
            gusSpaceForm->show();
        }
        else
        {
            //Invalid data received.
            QString strMessage = tr("Error whilst getting module space usage information from device.\n\nReceived: ").append(QString::fromUtf8(baOrigData));
            gpmErrorForm->show();
            gpmErrorForm->SetMessage(&strMessage);
        }
    }

Image_22

  1. If you compile and run UwTerminalX now with a BL600 or BT900 and test the function, you should see the new form appear when the response is received from the module, a blank BT900 module shows:

Image_23

  1. A BT900 module with a single file downloaded shows:

Image_24

  1. A BT900 module with the single file from the previous step deleted shows:

Image_25

  1. And a final test is done on a non-responsive device which shows the error message relating to a failed file download. This concludes the first part of the tutorial.

Image_26

Part 2:

For the second part of the tutorial, another right click action will be added so that the module's FAT and Data size can be retrieved, this will be using the same function from the first part of the tutorial.

  1. Start by opening UwxMainWindow.cpp and renaming the existing right click menu added in the first part of the tutorial to 'Space Used (Data)' and adding a new option in with the name 'Space Used (FAT)'.
    gpSMenu3->addAction(new QAction("Space Usage (Data)", this));
    gpSMenu3->addAction(new QAction("Space Usage (FAT)", this));

Image_27

  1. Update the right click selection code to use the new name in UwxMainWindow.cpp, also add in a new statement for the newly added right click option so that they both run the same code, then send 'at i 6' if the Data option was selected and 'at i 7' if the FAT option was selected.
    else if (qaAction->text() == "Space Usage (Data)" || qaAction->text() == "Space Usage (FAT)")
    {
        //Get the space usage information from the module
        if (gspSerialPort.isOpen() == true && gbLoopbackMode == false && gbTermBusy == false)
        {
            //Not currently busy
            QByteArray baTmpBA = QString("at i ").append((qaAction->text() == "Space Usage (Data)" ? "6" : "7")).toUtf8();
            gspSerialPort.write(baTmpBA);
            gintQueuedTXBytes += baTmpBA.size();
            gpMainLog->WriteLogData(QString(baTmpBA).append("\n"));
            DoLineEnd();

            //We're now busy
            gbTermBusy = true;
            gchTermMode = MODE_SPACE_USAGE;
            gchTermMode2 = MODE_SPACE_USAGE;
            gchTermBusyLines = 0;
            gstrTermBusyData = tr("");
            ui->btn_Cancel->setEnabled(true);

            //Start the timeout timer (this should be adapted to use a different timer with a different error message in your own modifications, and runs if the module doesn't respond)
            gtmrDownloadTimeoutTimer.start();

            //Update display buffer
            gbaDisplayBuffer.append(baTmpBA);
            if (!gtmrTextUpdateTimer.isActive())
            {
                gtmrTextUpdateTimer.start();
            }
        }
    }

Image_28

  1. Inside the serial data received function in UwxMainWindow.cpp, change the regular expression code to allow a 6 or 7 response and update the 'captured' functions below to incrase the offsets by 1 each to account for the new element.

Image_29

  1. If you were to run the application now then both options would work, but the form doesn't show which item it is displaying, giving no visual feedback, so we will add a new label to fix this, start by opening UwxSpace.h and adding a new public slot function named 'SetupMode' with a boolean parameter named 'bMode':
    void
    SetupMode(
        bool bMode
        );

Image_30

  1. Open UwxSpace in Qt Designer and place a new label in the centre of the form, change the object's name to label_Type and save.

Image_31

  1. Switch to UwxSpace.cpp and add the code for the SetupMode function which simply changes the label's text to 'FAT Segment' if bMode is true or 'Data Segment' if false:
    void
    UwxSpace::SetupMode(
        bool bMode
        )
    {
        if (bMode == true)
        {
            //FAT
            ui->label_Type->setText("FAT segment");
        }
        else
        {
            //Data
            ui->label_Type->setText("Data segment");
        }
    }

Image_32

  1. Go to UwxMainWindow.cpp and just before the space form is opened in the serial receive function, add in a call to the SetupMode function: if the first parameter is 6 then call it with a false parameter, else call it with a true parameter.
    else if (gchTermMode2 == MODE_SPACE_USAGE)
    {
        //Response to space used on module
        QReglarExpression reTempRE("10\t(6|7)\t([0-9]+),([0-9]+),([0-9]+)\r");
        QReularExpressionMatch remTempREM = reTempRE.match(baOrigData);
        if remTempREM.hasMatch() == true)
        {
            //Got the device usage information
            bool ok; //Used to store/ignore if Qt string-to-ULong works, we already check in the regex itself so it'll only fail from a PC problem like running out of memory
            gusSpaceForm->SetupBars(remTempREM.captured(2).toULong(&ok, 10), remTempREM.captured(3).toULong(&ok, 10), remTempREM.captured(4).toULong(&ok, 10));
            gusSpaceForm->SetupMode((remTempREM.captured(1) == "6" ? false : true));
            gusSpaceForm->show();
        }
        else
        {
            //Invalid data received.
            QString strMessage = tr("Error whilst getting module space usage information from device.\n\nReceived: ").append(QString::fromUtf8(baOrigData));
            gpmErrorForm->show();
            gpmErrorForm->SetMessage(&strMessage);
        }
    }

Image_33

  1. If you now run the application and try out the data option, it'll correctly show 'Data segment':

Image_34

  1. Likewise if you try the FAT option it'll correctly show 'FAT segment':

Image_35

  1. Now the functionality implementation is finished, it's time to clean up the GUI of the new form, start by opening UwxSpace in Qt Designer, click the form itself and in the right-hand side property editor, change windowTitle to 'Module Space Usage' - it will now show this in the title-bar instead of 'Dialog'.

Image_36

Part 3:

This section deal with creating a resizable interface for the new form; by utilising Qt's layout blocks, a GUI form can be resized and the objects will be repositions and resized automatically. Out of the scope of this tutorial is the option to add minimum sizes of objects (for example to stop text being cut off if a user tries to make a form too small) along with other properties, for information on these please refer to the Qt Documentation.

  1. Start by adding a vertical layout object to the form. This will hold all the other objects in the form. Then drag two horizontal layout objects to the form, these will hold the text labels and the bar objects.

Image_37

  1. Drag the close button to the vertical layout object, then drag the Data/FAT label to the same layout object but above the button. If it appears below the button, drag it again and position it slightly higher in the object to get it above the button (this can be a tedious process). Then, add the 3 bar objects to one of the horizontal layout objects and the descriptive labels to the other - ensure that the labels and bar objects are in the same offsets so that they will match up.

Image_38

  1. Set the Data/FAT label object's alignment to be horizontally in the centre and clear the default text, this tidies up the look further.

Image_39

  1. Drag the horizontal layout object with the bar objects to the vertical layout object, placing it above the Data/FAT label, do the same with the other horizontal layout object containing the descriptive labels, keeping it above the bar object layout object.

Image_40

  1. Select the form outline, then using the toolbar above the form designer, click the vertical layout button, this will make the whole form act as a vertical layout so when the form is resized, it will follow the behaviour of a vertical layout object and adjust accordingly.

Image_41

  1. Notice how the layout is spaced out to use all available space on the form. With the form selected, change its height and width to 300x140, the form will become much smaller and the design has adjusted to fit.

Image_42

  1. Run the application and test it with a module using one of the space usage functions, the form opens at the default 400x140 size.

Image_43

  1. Resize the popup window to check that the vertical layout system is working.

Image_44

Part 4:

This final section of the tutorial deals with updating the version information (which should be done if the code is updated, regardless if the update is a new feature or a bug fix).

  1. Open UwxMainWindow.h and search for the '#define UwVersion' line, increment the value of the version which is in the format <major>.<minor><sub-minor> where <major> is a numerical value greater than or equal to 0, <minor> is a numerical value from 0-99 and <sub-minor> is an optional alphabetical character from a-z, in this example the version number is changing from 1.01 to 1.01b.
    #define UwVersion "1.01b" //Version string 

Image_45

  1. When creating executable files for Windows, there is an extra file to adjust which controls the information in the file properties of the file, this file is not shown in Qt, therefore open Notepad (or a similar text editor) and open the 'version.rc' file from the UwTerminalX directory. From here, four changes need to be made to 'FILEVERSION x, x, x, x', 'PRODUCTVERSION x, x, x, x' and the two repeats inside the BEGIN block. The format here is '<major>, <minor>, <sub-minor>, 0' - but unlike in the previous step where a alphabetical character is used for the <sub-minor> value, in the version.rc file a numerical representation must be used, therefore the version has been changed to '1, 1, 1, 0'.

Image_46

Important note on adding additional functions

UwTerminalX by default has a limit of 19 functions that require use of the serial port, this can be changed up to a maximum of 49 but a line needs editing to allow this. Inside UwxMainWindow.cpp in the serial receive function is the line 'else if (gbTermBusy == false && gchTermMode2 > 0 && gchTermMode2 < 20)', simple increase the '< 20' line to a higher value to allow additional functions to be added.

Image_47

Reference code

For reference, there is a repository available with all the above changes applied, available under the UwTerminalX sample-addon branch.