Ground-Side-Telemetry

Qt Creator

All ground side telemetry code is written in Qt Creator, using C++. The entire project is under the .pro file called PilotManager. To get started, install anything needed for C++, then install Qt. Install Qt 5.15.2 MSVC 2019 using custom installation. Then, Install MS Build Tools for C++, download, and click “Desktop development with C++”. Ensure that MSVC 2019 is selected in the bottom right checkbox. Clone the repo using this command:

git clone https://github.com/UWARG/Ground_Side_Telemetry.git

Once you open the PilotManager.pro file, you should be good to go. First, you should test the program by clicking run, or the big green arrow at the bottom left of Qt. A new window should pop up with some buttons and text (shown below). This indicates that the program works, and you can now exit the window.

The overall objective of the ground station code is to communicate with the drone in the sky. Devices called xbees are plugged into the drone and ground station (one into each), and these two devices communicate wirelessly. To do this, the drone takes in results of a QR code scan, and the xbee from the plane will send the information serially to the xbee at the ground station. The information in the xbees will then be converted to bytes, so the ground station will receive an array of bytes. The ground station code decodes the bytes into an array of characters that can be read by humans as a message.

Graphical User Interface (GUI)

One of the main reasons Qt Creator is used as the IDE for the ground station code is its ability to display a simple and editable GUI as you ran above. Currently, the GUI is used to display the decoded message. A button called testUpdateWidget was added at the top, near the right of the GUI to test the output of the updateWidget function. This is the function that decodes an array of bytes and prints the decoded message. When clicked, the decoded message is displayed under it, and contains information split into three sections: “Info:”, “Date:”, and “Time:”.

Important Qt Keywords used in Groundstation Code

setNum(): Sets a text label on the GUI to a number in the brackets

QByteArray: Data type, used to initialize an array that holds bytes in each index

uint: Unsigned integer, can store any number between 0-255 (unsigned means no negative numbers)

qDebug(): Function used to output text or variables to the console (Application Output) at the bottom of Qt

QDataStream: See link attached to line 143 in code that explains logic for this line. lines 143-145 store two bytes containing message length info into a short.

quint8: Unsigned byte. Integer used to store a byte.

QString: Stores a string of 16-bit QChars. Think of this like a normal string, but they have to be initilized differently (see line 194).

quint16: Unsigned 16-bit integer.

setText(): sets the text label on the GUI to show info passed in the brackets.

 

Mainwindow.ui

The “mainwindow.ui” file under the “Forms\src\mainwindow” folder displays the user interface editor that appears when the program is run. Double clicking “mainwindow.ui” shows the UI editor and includes test labels, text editors, spin boxes, layouts, and buttons that can be added to the main window. The left side panel includes the list of buttons and functions that can be added to the main window by dragging and dropping them onto the UI editor.

If you click on a button or label, you can view and change its properties in the coloured box at the bottom right of Qt. As well, buttons can be connected to functions that run when the button is clicked. To do this, select the button you wish to connect, right click on the button, select “Go to Slot”, select “clicked()”, and “Ok”. If there is no existing function connected to that button already, a new function will be created in mainwindow.cpp that runs when the button is clicked. If there is already an existing function connected, Qt will jump to that function in mainwindow.cpp.

To exit the UI editor, simply click on the “Edit’ tab on the leftmost sidebar, near the top. This will take you to the original folder containing the project. Note that it first jumps to the XML file of the UI.

 

Array of Bytes

The byte is a unit of information that consists of 8 bits. In the computer, every 8 bits (1 byte) can store one character, such as ‘A’, ‘e’, ‘#’, etc.

In the current code, for testing purposes, an array of bytes is initialized instead of being passed through from the plane (the array will be passed by the plane in the competition). Note that the “0x” before each byte indicates that the data stored in each index is indeed a byte. This array is declared when the “testUpdateWidget” button is clicked, and the array is then passed as a parameter into the updateWidget function. The example format for the array of bytes is shown below, as a transmit frame. Keep in mind that every two characters represent one byte, and contents of the frame in each section may vary. Since this is an array, 7E is stored in index 0, 00 is stored in index 1, 19 is stored in index 2, 10 is stored in index 3, and so on.

7E | 00 19 | 10 | 01 | 00 13 A2 00 41 B1 6D 1C | FF FE | 00 | 00 | 53 45 4E 54 20 46 52 4F 4D 20 42 | D1

The breakdown of the transit frame is below:

  • 7E is the Start Delimiter and is always used to signify the start of the message.

  • 00 19 indicates the Message Length, starting from the next byte (10) to the end of the actual message (42). 00 19 is in hexadecimal, so the length is the integer when 00 19 is converted to decimal. In this example 00 19 converted to decimal is 25. Hexadecimal calculator: https://www.rapidtables.com/convert/number/hex-to-decimal.html

  • 10 indicates the Frame Type. 10 represents a transmit frame, 90 for receive frame, and 8B indicates a transmit response frame.

  • 01 is for the Frame ID. This is optional but allows for ordering frames with their corresponding responses.

  • 00 13 A2 00 41 B1 6D 1C is the 64-bit address. This is the transmit frame destination or a receive frame source.

  • FF FE represents the 16-bit address, and FF FE is always used as the default.

  • 00 is the Broadcast Radius, which is the limit on how many times the message should hop before giving up if not reached target. Leaving it at 00 means no limit.

  • 00 represents Options and will be left constant at 00.

  • 53 45 4E 54 20 46 52 4F 4D 20 42 is the actual message, and this tells includes the info, date, and time. Since the length of the message is variable, the number of bytes making up the message is not known. To get characters to form a message, the hexadecimal bytes in the frame need to be converted to ascii text. This can be done here: https://www.rapidtables.com/convert/number/hex-to-ascii.html

  • D1 represents the Checksum. This is the number of bytes from the frame type to the checksum, including the frame type but not including the checksum.  

Each section is stored separately and output to the console using qDebug() to check proper storage and output of the data (qDebug() is like cout()). Other than the message, bytes are converted to decimal and stored in integer data types. The message is converted to ascii characters and stored in a QString. Note that the start delimiter and the frame type is checked for accuracy and used to determine if the frame is transit or response before the rest of the frame is read and stored.

 

The Message: 53 45 4E 54 20 46 52 4F 4D 20 42

The message portion of the frame is to be converted to ascii characters, then split into three sections: info, date, and time. The format of the date will always follow a MMDDYY format, taking up 6 characters, equivalent to 6 bytes. The time format follows HHMM and takes up 4 bytes. The rest of the message is stored as info. In the example frame, the message contains “SENT FROM B”. When converted and split into the three sections, the GUI prints “Info: S”, “Date: ENT FR”, “Time: OM B”. This confirms that the program is correctly storing the date with 6 characters, the time with 4 characters, and the remaining character in info. In an array sent from the drone, the message will be much longer (110 bytes).

The message is stored as a new array of bytes, then converted to ascii and printed to the console as a QString. Substrings of the QString message are taken, and the three sections are displayed on the GUI using the setText() method.

Response Frame

In the on_testUpdateWidget_clicked() function, a second example array is declared below the example transit frame array. This is the example response frame, and it has a format like before, but without the message. Since this frame has a different format than the transmit frame, the Frame Type (8B) will be checked by the program initially and a different case will be run to fit the format of the response frame (See line 156 and 250). Here, the response frame is declared for testing the example. To test this array instead of the response frame, simply pass the response frame array name into the parameter instead when you call updateWidget. The point of a response frame is to respond to every transmit frame sent. That is, every time a transmit frame is sent, a response frame is sent to the sender that contains information on the transmit frame. Along with previous data, this includes new information like retry count, delivery status, success/failure, and discovery status. The breakdown of an example transmit frame is shown below:

7E | 00 07 | 8B | 01 | FF FE | 00 | 00 | 00 | 76

  • 7E is the Start Delimiter, this will always be 7E.

  • 00 07 is the length of the message in hexadecimal.

  • 8B is the Frame Type, the same conventions follow as stated above.

  • 01 is the Frame ID and is identical to above description.

  • FF FE is the 16-bit address, and is the default value (unchanged)

  • 00 is the TX Retry Count, which is the number of times that the message we resent before success/failure.

  • 00 is the delivery status. 00 represent a successful delivery, and anything else indicates a failed delivery

  • 00 is the Discovery Status. 00 indicates success, 25 indicates root not found (destination not found)

  • 76 represents the Checksum, the number of bytes between message length and checksum (not including checksum).

Note that response frames are only generated when a transmit frame is sent. These response frames act as a “reply” to the transmit frame sender, and response frames are always automatically generated. Think of it as a “call” with a guaranteed “response”, where a transmit frame is the “call” by a human sender, and a response frame will always respond back to the sender with details on the “call”.