Main Content

Create App to Display and Log Data from Serial Port GPS Device

This example shows how to create an app that connects to a serial port GPS device, reads and visualizes the data, and logs the data from the device to a file.

To run the app in this example, you need a GPS device that connects to a computer via serial port or USB virtual serial port and returns NMEA 0183 data. However, you can apply the same approach to other devices that use serial port communication.

Design App Layout

Lay out the app in App Designer to provide controls for users based on the app goals. This app contains different layout sections for the different goals:

  • Connect to device — Create a panel with components for users to configure their device connection.

  • Log data over time — Create a panel with components for users to start and stop data collection over a period of time and indicate the name of a file to store the logged data.

  • View live data in app — Create fields on the left of the app to display live information about the device connection, such as the GPS status and location. 

  • Visualize data — Create a tab group with data visualization options, such as a view of the location data on a map or of the data over time.

Running app connected to a GPS device. The app contains an option to disconnect from the device, fields with live GPS data, and option to start logging data to a file, and a plot of the GPS.

Write App Code

Write your app code to perform these key tasks based on user interactions: 

  • Connect to serial port GPS device — Use the device configuration options to connect to a device using serialport.

  • Get live data from device — Use a callback set with configureCallback to control the asynchronous behavior in the app.

  • Log data to file — Write raw and processed data to two different files within the callback.

  • Plot data in various charts — Update the live views in the app within the callback.

In addition, manage your app code to: 

  • Enable and disable app components to guide the user in the app workflow

  • Display errors in the app to alert the user to issues

Connect to Serial Port GPS Device

When a user clicks the Connect button, use the Serial port and Baud rate values to connect to the device.

Configuration panel in the app. The panel contains drop-down lists to select the serial port and baud rate, a refresh button, a connect button, and a disabled disconnect button.

First, create a private property named SerialPort to store the serialport object. You can use this property to access the object from anywhere in your code. Select Property > Private Property from the Editor tab of the App Designer toolstrip to add the property.

To connect to a device when a user clicks the Connect button, use the ConnectButtonPushed callback. In the callback, connect to the specified serial port GPS device using the serialport function and the SerialportDropDown and BaudrateDropDown values. Save the serialport object in the app.SerialPort property.

app.SerialPort = serialport(app.SerialportDropDown.Value,str2double(app.BaudrateDropDown.Value));

To disconnect the app from the serial port device when a user clicks Disconnect, use the DisconnectButtonPushed callback. In the callback, use the delete function to delete the serialport object and close the connection to the device. You cannot use the clear function to delete the object because it is stored in a property.

function DisconnectButtonPushed(app, event)
    delete(app.SerialPort)
    StopButtonPushed(app);
    setAppState(app,"DeviceSelection");
end

Get Live Data from Device

After the device is connected, you can get the live data by creating a callback that executes when the device receives a new line of data.

Create a function named serialportCallback in App Designer that handles new data from the device. This function must contain the app object as the first input argument to provide access to app properties and components. Define the callback to also accept the serialport object (obj) and serial port callback event data (evt).

function serialportCallback(app,obj,evt)
    inputline = readline(obj);
    eventtime = datetime(evt.AbsTime,Format='yyyy-MM-dd''T''HH:mm:ss.SSS');

    latestdata = processData(app,inputline,eventtime);

    if app.Logging
        logData(app,inputline,latestdata);
    end

    updateLiveView(app)
end

You can set the serialportCallback callback function to trigger whenever a terminator is available to read using the serialport configureCallback function. The configureCallback function automatically passes the source object and event data to its callback, so you can specify the callback as an anonymous function.

configureCallback(app.SerialPort,"terminator",@(obj,event) serialportCallback(app,obj,event))

Log Data to File

Users can write the data to a log file by starting and stopping the logged data collection and specifying the name of a file to store the data.

File Logging panel in the app. The panel contains a field to specify a file name, a start button, and a disabled stop button.

Write the raw and processed data in two different files:

  • Raw NMEA data — Log raw GPS sentence data line-by-line as the app receives it by using writelines.

  • Processed data — Log processed data as a comma-separated values (CSV) row after receiving all of the GPS sentence data for a single timestamp by using writecell.

Create a function named logToFile to log data in these two formats.

function logToFile(app,inputline,row)
    dt = row{1};
    writelines(string(dt)+" "+inputline,app.RawFilename,WriteMode="append")
    if app.CycleEnd
        writecell(row,app.FilenameEditField.Value,WriteMode="append")
    end
end

If your device requires an increased logging rate, consider using low-level file I/O operations instead. For more information, see Low-Level File I/O.

Plot Data in Various Charts

Visualize the data with various charts on different tabs:

  • Plot location data on a live map

  • Plot data over time on a line chart

Plot Location Data on Map

Plot the data on a map using geographic axes. Unlike other components, you must create geographic axes programmatically by calling the geoaxes function in your app startupFcn. To create a startupFcn callback, navigate to the Callbacks tab of the main app component in the Component Browser and select <add StartupFcn callback>. In your startupFcn code, store the geographic axes and plot objects in app properties.

app.MapViewAxes = geoaxes(app.MapGrid);
app.MapViewAxes.Layout.Row = 1;
app.MapViewAxes.Layout.Column = [1 5];
app.MapViewPlot = geoplot(app.MapViewAxes,NaN,NaN,"r.-");

To update the plot with new data, update the LatitudeData and LongitudeData properties of the geographic plot in the updateLiveView function.

set(app.MapViewPlot,"LatitudeData",latdata,"LongitudeData",longdata);

Plot Data over Time

To display data over a time span, store the data in an app property and update this property as the app receives new data. For example, to plot the most recent 10 minutes of GPS location data, create an app property named DataTable. In a processData function, create and store a new row of data whenever the app receives new data from the device.

newrow = {dt}
...
newrow = {newrow{1}, app.FixType, app.Latitude, app.Longitude, app.Satellites, app.Altitude, app.HDOP, app.Course, app.Speed, app.UTCDate, app.UTCTime};
app.DataTable = [app.DataTable; newrow];

Read the DataTable property in the updatePlotView function to display the data.

function updatePlotView(app)
    filter = seconds(datetime("now")-app.DataTable.ComputerTime) < app.XDataDropDown.Value;
    x = app.DataTable.ComputerTime(filter);
    y = app.DataTable{filter,app.YDataDropDown.Value};
    set(app.Plot,"XData",x,"YData",y);
end

Optimize Graphics Performance

To optimize performance and reduce the time spent updating app graphics objects, update only the currently displayed graphics objects based on the open tab in the tab group. Use switch/case statements in the updateLiveView function to decide which graphics objects to update.

switch app.TabGroup.SelectedTab
    case "MapViewTab"
        updateMapView(app,app.Logging);
    case "PlotViewTab"
        updatePlotView(app);
    case "NMEALogTab"
        updateNMEALog(app);
end

Enable and Disable Components

You can enable and disable certain components of the app depending on where the user is in the workflow. For example, when the user is selecting the device, you can enable the Serial port drop-down component, Baud rate drop-down component, and Connect button. However, once the device is connected, you can disable those components and enable the Disconnect button.

When a user interacts with this app, there are a few states that the app can appear in.

  • Device selection — The app is not connected to a device. Users can configure and connect to a device. Other options are disabled.

  • Device connected — The app is connected to a device. Users can view the live data, interact with the visualizations, or disconnect from the device.

  • Waiting for data — The app is waiting for new data from the connected device, and users can view the existing data.

  • Data Acquisition — The app is processing and logging new data from the connected device, and users can view the new data.

To control which components are enabled, create a helper function named setAppState that sets the Enable property of the components.

function setAppState(app,state) 
    switch state 
        case "DeviceSelection" 
            app.SerialportDropDown.Enable = "on"; 
            app.ConnectButton.Enable = "on";
            app.DisconnectButton.Enable = "off";
            ... 
        case "Connected" 
            ... 
        case "WaitForData" 
            ... 
        case "Logging" 
            ... 
    end 
end

Call this helper function when you want to change the enabled buttons, such as after successfully connecting to a GPS device. To avoid a delay between setting the Enable property of a component and when the component appears as enabled or disabled in the app, use drawnow.

function ConnectButtonPushed(app, event)
    setAppState(app, "Connected");
    drawnow
    ...
end

Display Errors in App

Set up in-app alerts to notify users of errors that occur while they use the app, rather than showing these errors in the Command Window. Wrap operations susceptible to user input errors within try/catch statements. For example, if an error occurs during device connection, use an alert dialog box to inform the app user of the error.

try
    app.SerialPort = serialport(app.SerialportDropDown.Value,str2double(app.BaudrateDropDown.Value));
catch ME
    uialert(app.UIFigure,ME.message,"Serialport Connection Error",Interpreter="html")
    setAppState(app,"DeviceSelection");
    return
end

Additionally, you can use the uialert function to enforce app expectations. For instance, to ensure app users log only to .txt files, use uialert to simulate an error and flag the restriction.

[filepath, name, ext] = fileparts(filename);
if ~strcmpi(ext,".txt")
    uialert(app.UIFigure,"File must be in .TXT format","File Log Error")
    ext = ".txt";
end

Run App

To run the app, click the Run button in App Designer. In the app, select a GPS device connected to your computer via serial port or USB virtual serial port. Then click Connect. To start logging to a file, specify a file name and use the Start and Stop buttons to collect the data.

Load the logged data into MATLAB using readtimetable.

data = readtimetable("GPS_data.txt")

Run App Without GPS Device

If you do not have a physical GPS device, an alternative method for running the app requires two serial ports or virtual serial ports connected to each other via a null modem cable. Use one port to simulate a GPS device by transmitting a raw log data file and the other port in a separate MATLAB session to receive data using the app.

From a separate MATLAB session, open the attached replayGPSLogData script for editing. In the script, specify the serial port to use and the raw log data file name. Then, run the script.

edit replayGPSLogData

See Also

Related Topics