classdef CruiseControlTestUI < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure                        matlab.ui.Figure
        ChannelConfigurationMenu        matlab.ui.container.Menu
        SelectCANChannel                matlab.ui.container.Menu
        controllerOutputsPlotPanel      matlab.ui.container.Panel
        tspeedPlot                      matlab.ui.control.UIAxes
        engagedPlot                     matlab.ui.control.UIAxes
        driverInputsPanel               matlab.ui.container.Panel
        vehicleSpeedSlider              matlab.ui.control.Slider
        LabelNumericEditField2          matlab.ui.control.Label
        vehicleSpeedNumericEditField    matlab.ui.control.NumericEditField
        calibrationsPanel               matlab.ui.container.Panel
        LabelNumericEditField           matlab.ui.control.Label
        holdRateNumericEditField        matlab.ui.control.NumericEditField
        Label                           matlab.ui.control.Label
        incDecNumericEditField          matlab.ui.control.NumericEditField
        Label2                          matlab.ui.control.Label
        maxTargetSpeedNumericEditField  matlab.ui.control.NumericEditField
        Label3                          matlab.ui.control.Label
        minTargetSpeedNumericEditField  matlab.ui.control.NumericEditField
        plottingPanel                   matlab.ui.container.Panel
        LabelDropDown                   matlab.ui.control.Label
        plotTimeWindowDropDown          matlab.ui.control.DropDown
        cruisePowerCheckBox             matlab.ui.control.CheckBox
        brakeOnOffCheckBox              matlab.ui.control.CheckBox
        cruiseCoastSetCheckBox          matlab.ui.control.CheckBox
        cruiseAccelResumeCheckBox       matlab.ui.control.CheckBox
        Label4                          matlab.ui.control.Label
        SimulationControlPanel          matlab.ui.container.Panel
        SimStartStopButton              matlab.ui.control.StateButton
        StartSimLabel                   matlab.ui.control.Label
        ChannelLogControls              matlab.ui.container.Panel
        canLoggingStartStopButton       matlab.ui.control.StateButton
        saveLoggedDataButton            matlab.ui.control.Button
        StartLoggingLabel               matlab.ui.control.Label
        SaveloggedDataLabel             matlab.ui.control.Label
        ChannelReplayControlPanel       matlab.ui.container.Panel
        canReplayStartStopButton        matlab.ui.control.StateButton
        loadCANLogDataButton            matlab.ui.control.Button
        LoadloggedDataLabel             matlab.ui.control.Label
        StartReplayLabel                matlab.ui.control.Label
    end

    
    properties (Access = private)
        receiveCANmsgsTimer      % Timer object for receiving CAN data
        tspeedFb                 % Target vehicle speed feedback
        tspeedFbMinSpeedLine     % yline horizontal plot line object for min speed line
        tspeedFbMaxSpeedLine     % yline horizontal plot line object for max speed line
        tspeedLine               % tspeed plot line handle
        engagedFb                % Cruise Control on/off status
        engagedFbLine            % engaged plot line handle
        mdl                      % Test harness model
        blkCruisePower           % Handle to Cruise On/Off block in test harness model
        blkBrake                 % Handle to Brake applied block in test harness model
        blkVehicleSpeed          % Handle to Vehicle target speed block in test harness model
        blkCoastSet              % Handle to Coast set switch block in test harness model
        blkAccelRes              % Handle to Accel/Resume switch block in test harness model
        plotTimeWidth            % Width of plots in seconds
        canChannelInfo           % Table of info about available CAN channels including Vendor, Device, and Channel #
        canChannelObj            % CAN channel object
        numConstructors          % Number of possible CAN channel object constructor strings - depends on connected hardware
        availableCANChannelsForDisplay     % String array of available CAN channels to populate CAN Channel list box
        canChannelConstructorSelected   % Selected CAN channel constructor from list of available CAN Channel Constructors
        canChannelDeviceSelected % Selected CAN channel Device and DeviceMenu strings from list of available CAN channel Devices
        canChannelIndex          % index of selected CAN channel from list of available CAN channels
        cruiseControlCmdMessage  % Cruise control command message object for transmission to cruise control application
        canLogChannelObj         % CAN channel object for logging CAN data
        canLogMsgBuffer          % Variable to hold logged CAN message data
    end
    
    methods (Access = private)
        
        % receiveCANmsgsTimerCallback Timer callback function for GUI updating
        function receiveCANmsgsTimerCallback(app)
            
            try
                
                % receive available CAN messages
                msg = receive(app.canChannelObj, Inf, 'OutputFormat', 'timetable');
                % msg = receive(app.canChannelObj, Inf);
                
                % update Cruise Control Feedback CAN message data
                newFbData = getCruiseCtrlFBCANmessage(app, msg);
                
                if ~newFbData
                    return;
                end
                
                % Update target speed and engaged plots with latest data from CAN bus
                updatePlots(app);
                
            catch err
                disp(err.message)
            end             % try/catch
            
        end                 % receiveCANmsgsTimerCallback
        
        
        function newFbData = getCruiseCtrlFBCANmessage(app, msg)
            
            % exit if no messages were received as there is nothing to update.
            if isempty(msg)
                newFbData = false;
                return;
            end
            
            % extract signals from all CruiseCtrlFB messages
            cruiseCtrlFBSignals = canSignalTimetable(msg, "CruiseCtrlFB");
            
            % if no messages then just return as there is nothing to do
            if isempty(cruiseCtrlFBSignals)
                newFbData = false;
                return;
            end
            
            if ~isempty(cruiseCtrlFBSignals)
                
                % received new Cruise Control Feedback messages, so create time series from CAN signal data
                
                % save the Target Speed feedback signal
                if isempty(app.tspeedFb)     % check if target speed feedback property has been initialized
                    app.tspeedFb = cell(2,1);
                    
                    % it appears Simulink.SimulationData.Dataset class is not
                    % compatible with MATLAB Compiler, so change the way we store data
                    % from a Dataset format to cell array.
                    
                    % save target speed actual data
                    app.tspeedFb = timeseries(cruiseCtrlFBSignals.F02_TargetSpeed, seconds(cruiseCtrlFBSignals.Time),...
                        'Name','CruiseControlTargetSpeed');
                    
                else  % add to existing data
                    
                    % save target speed actual data
                    app.tspeedFb = timeseries([app.tspeedFb.Data; cruiseCtrlFBSignals.F02_TargetSpeed], ...
                        [app.tspeedFb.Time; seconds(cruiseCtrlFBSignals.Time)],'Name','CruiseControlTargetSpeed');
                    
                end
                
                % save the Cruise Control Engaged actual signal
                if isempty(app.engagedFb)    % check if Cruise engaged property has been initialized
                    app.engagedFb = cell(2,1);
                    
                    % it appears Simulink.SimulationData.Dataset class is not
                    % compatible with MATLAB Compiler, so change the way we store data
                    % from a Dataset format to cell array.
                    
                    % save cruise engaged command data
                    app.engagedFb = timeseries(cruiseCtrlFBSignals.F01_Engaged,seconds(cruiseCtrlFBSignals.Time),...
                        'Name','CruiseControlEngaged');
                    
                    
                else  % add to existing logsout
                    
                    % save cruise engaged command data
                    app.engagedFb = timeseries([app.engagedFb.Data; cruiseCtrlFBSignals.F01_Engaged], ...
                        [app.engagedFb.Time; seconds(cruiseCtrlFBSignals.Time)],'Name','CruiseControlEngaged');
                    
                end
                
                newFbData = true;
            end
            
        end             % getCruiseCtrlFBCANmessage()
        
        
        function setAxesLimitsAndTitles(app, XLim0,timeWindow)
            % set X axis limits
            app.tspeedPlot.XLim = [XLim0, XLim0+timeWindow];
            app.engagedPlot.XLim = [XLim0, XLim0+timeWindow];
            
            % update X axis ticks
            XTick = app.tspeedPlot.XTick;
            XTick = [XTick(1):timeWindow/10:XTick(end)];
            % the tick mode is set to "auto" and it always displays 10 ticks for whatever time range I pick.  Looks like I don't need
            % to adjust the tics if auto mode always displays 10 ticks.
            
            %app.UIAxes.XTick = XTick;
            %app.tspeedPlot.XGrid = 'on';
            %app.engagedPlot.XGrid = 'on';
            %app.tspeedPlot.XTickLabel = '';
            %app.engagedPlot.XTickLabel = '';
            
            % set Y axis limits
            app.tspeedPlot.YLim = [0, 100];
            app.engagedPlot.YLim = [-0.1 1.1];
            
            % set Y axis ticks
            app.tspeedPlot.YTick = [0;20;40;60;80;100];
            %app.tspeedPlot.YGrid = 'on';
            app.engagedPlot.YTick = [0;1];
            %app.engagedPlot.YGrid = 'on';
            
            % add axis annotations
            xlabel(app.tspeedPlot,'Time (sec)');
            ylabel(app.tspeedPlot,'Velocity (mph)');
            title(app.tspeedPlot, 'Cruise Target Speed')
            xlabel(app.engagedPlot,'Time (sec)');
            ylabel(app.engagedPlot,'On/Off (1/0)');
            title(app.engagedPlot, 'Cruise Engaged')
            
        end     % setAxesLimitsAndTitles
        
        
        function setUpPlotGridAndHoldStatus(app)
            grid(app.tspeedPlot, 'on');
            hold(app.tspeedPlot,'on');
            
            grid(app.engagedPlot, 'on');
            hold(app.engagedPlot,'on');
            
            % initialize handles to the tspeed and engaged plot lines
            % set handle visibility to off so calls to cla() don't delete these handles
            app.tspeedLine = plot(app.tspeedPlot, nan, nan,'b', 'HandleVisibility', 'off');     % initialize handle to tspeed plot line
            app.engagedFbLine = plot(app.engagedPlot, nan, nan,'b', 'HandleVisibility', 'off');  % initialize handle to tspeed plot line
        end
        
        function initializeMaxMinSpeedPlotLines(app)
            % initialize tspeed plot max and min speed line plot elements
            % set handle visibility to off so calls to cla() don't delete these handles
            app.tspeedFbMinSpeedLine = yline(app.tspeedPlot, app.minTargetSpeedNumericEditField.Value,'r--','HandleVisibility', 'off');
            app.tspeedFbMaxSpeedLine = yline(app.tspeedPlot, app.maxTargetSpeedNumericEditField.Value, 'r--','HandleVisibility', 'off');
        end
        
        function updatePlots(app)
            
            timeWindow = app.plotTimeWidth;
            
            time = app.tspeedFb.Time;
            
            endTime = time(end);
            startTime = max((endTime-timeWindow),0);
            
            % find the array index correpsonding to startTime
            [~, startNdx] = min(abs(time-startTime));
            
            if isempty(startNdx)
                startNdx = 1;
            end
            
            % extract the timeWindow of sample points from the time vector
            time = time(startNdx:end);
            
            % update the target speed plot using the line handle
            app.tspeedLine.XData = time;
            app.tspeedLine.YData = app.tspeedFb.Data(startNdx:end);     % target speed actual
                        
            % update the engaged plot using the line handle
            app.engagedFbLine.XData = time;
            app.engagedFbLine.YData = app.engagedFb.Data(startNdx:end);
            
            % reset axis limits based on current time window
            setAxesLimitsAndTitles(app, startTime,timeWindow);
            
            % delete old data that preceeds the current time window
            if endTime - timeWindow > 0         % data in buffer longer than timeWindow, ok to delete old data points
                app.tspeedFb = delsample(app.tspeedFb, 'Index', [1:(startNdx-1)]);
                app.engagedFb = delsample(app.engagedFb, 'Index', [1:(startNdx-1)]);
            end
            
            % display length of buffered data in seconds
            % timeInfo = get(app.motorPositionFb, 'TimeInfo');
            
            % app.LengthOfBufferedMessagesNumericEditField.Value = timeInfo.End - timeInfo.Start;  
        end
        
        
        function calValueAllowed = updateCalibrationValue(app, calValueRequested, calValueMax, calValueMin)
            % check to see if calibration value requested is > max allowed
            if calValueRequested > calValueMax
                message = sprintf('Calibration parameter max value exceeded\n Using max value of %i instead', ...
                    calValueMax);
                % raise a warning dialog window to alert the user
                uialert(app.UIFigure, message, 'Calibration Max/Min value check', 'Icon','warning')
                
                % return clipped calibration value
                calValueAllowed = calValueMax;
                
                % check to see if calibration value requested is < min allowed
            elseif calValueRequested < calValueMin
                message = sprintf('Calibration parameter min value exceeded\n Using min value of %i instead', ...
                    calValueMin);
                % raise a warning dialog window to alert the user
                uialert(app.UIFigure, message, 'Calibration Max/Min value check', 'Icon','warning')
                
                % return clipped calibration value
                calValueAllowed = calValueMin;
                
                % calibration value is within max/min limites so pass on requested value unaltered
            else
                calValueAllowed = calValueRequested;
            end
            
        end
    
        function setConnectionToCalibrationBlocks(app)
            % app.mdl = 'CruiseControl_VNT_AdHoc_Harness';
            app.mdl = bdroot;
            app.blkCruisePower = [app.mdl '/Inputs/Baseline/CruiseOnOff_Val'];
            app.blkBrake = [app.mdl '/Inputs/Baseline/Brake_Val'];
            app.blkVehicleSpeed = [app.mdl '/Inputs/Baseline/Speed_Val'];
            app.blkCoastSet = [app.mdl '/Inputs/Baseline/CoastSetSw_Val'];
            app.blkAccelRes = [app.mdl '/Inputs/Baseline/AccelResSw_Val'];
        end
        
        
        function setupCANTransmitMessages(app)
            % create a CAN message container 
            app.cruiseControlCmdMessage = canMessage(app.canChannelObj.Database, 'CruiseCtrlCmd');
            
            % fill the message container with signals and assign values to each signal
            app.cruiseControlCmdMessage.Signals.S01_CruiseOnOff = logical2Numeric(app, app.cruisePowerCheckBox.Value);
            app.cruiseControlCmdMessage.Signals.S02_Brake =  logical2Numeric(app, app.brakeOnOffCheckBox.Value);
            app.cruiseControlCmdMessage.Signals.S03_VehicleSpeed =  app.vehicleSpeedSlider.Value;
            app.cruiseControlCmdMessage.Signals.S04_CoastSetSw =  logical2Numeric(app, app.cruiseCoastSetCheckBox.Value);
            app.cruiseControlCmdMessage.Signals.S05_AccelResSw =  logical2Numeric(app, app.cruiseAccelResumeCheckBox.Value);
            
            % set up periodic transmission of this CAN message.  Actual transmission starts/stops with CAN channel start/stop
            transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'On', 0.1);
        end
        
        
        function setupInitialVals(app)
            % set radio button initial values
            app.brakeOnOffCheckBox.Value = false;
            app.cruisePowerCheckBox.Value = true;
            app.cruiseCoastSetCheckBox.Value = false;
            app.cruiseAccelResumeCheckBox.Value = false;
            
            % set initial target vehicle speed slider and display values
            app.vehicleSpeedSlider.Value = 60;
            app.vehicleSpeedNumericEditField.Value = app.vehicleSpeedSlider.Value;
            
           if evalin('base','exist(''maxtspeed'',''var'')')   % check to see if parameter data has been loaded to workspace
               % set app calibration initial values by reading model base workspace parameter values
               app.maxTargetSpeedNumericEditField.Value = evalin('base', 'maxtspeed.Value');
               app.minTargetSpeedNumericEditField.Value = evalin('base', 'mintspeed.Value');
               app.incDecNumericEditField.Value = evalin('base', 'incdec.Value');
               app.holdRateNumericEditField.Value = evalin('base', 'holdrate.Value');
           else
               message = sprintf('Calibration parameter values not loaded\n Please run the script ''CruiseControl_dd''');
               % raise a error dialog window to alert the user
               uialert(app.UIFigure, message, 'Calibration data loaded check', 'Icon','error')
           end
               
        end
        
        
        function updateVehicleSpeedSliderAndEditFieldValues(app)
            % update value displayed on slider to be an integer
            app.vehicleSpeedSlider.Value =round(app.vehicleSpeedSlider.Value);
            % update CAN message transmitted to ECU
            app.cruiseControlCmdMessage.Signals.S03_VehicleSpeed =  app.vehicleSpeedSlider.Value;
            % update vehicle speed numeric edit field with value entered from vehicle speed slider
            app.vehicleSpeedNumericEditField.Value = app.vehicleSpeedSlider.Value;
            
        end
        
        function updateVehicleSpeedEditFieldAndSliderValues(app)
            % update value displayed on edit field to be an integer
            app.vehicleSpeedNumericEditField.Value =round(app.vehicleSpeedNumericEditField.Value);
            % update CAN message transmitted to ECU
            app.cruiseControlCmdMessage.Signals.S03_VehicleSpeed =  app.vehicleSpeedNumericEditField.Value;
            % update vehicle speed slider value with value entered on numeric edit field
            app.vehicleSpeedSlider.Value = app.vehicleSpeedNumericEditField.Value;
            
        end
        
        
        function findTestbenchModel(app)
            % hard code name of model to use with the UI.  Could expand this to a
            % model selection UI using uigetfile() for a more general solution
            app.mdl = 'CruiseControlTestbench';
            
            % if the selected model isn't already open, open it
            if ~bdIsLoaded(app.mdl)
                open_system(app.mdl)
            end
        end
        
        
        function result = logical2Numeric(~, logicalValue)
            if logicalValue == true
                result = 1;
            else
                result = 0;
            end
        end
        
        
        function startSimApplication(app, index)
            % start the model running on the desktop
            
            % check to see if hardware or virtual CAN channel is selected, otherwise
            % do nothing
            if app.canChannelInfo.DeviceModel(index) == "Virtual"
                % check to see if the model is loaded before trying to run
                if bdIsLoaded(app.mdl)
                    % model is loaded, now check to see if it is already running
                    if ~strcmp('running',get_param(app.mdl,'SimulationStatus'))
                        % model is not already running, so start it
                        % flush the CAN Receive message buffers
                        app.tspeedFb = [];   % delsample(app.motorPositionFb, 'Index', [1, (startNdx-1)]);
                        app.engagedFb = [];
                        
                        % clear figure window
                        cla(app.tspeedPlot)
                        cla(app.engagedPlot)
                        
                        % start the CAN channels and update timer if it isn't already running
                        startCANChannel(app);
                        
                        % start the model
                        set_param(app.mdl, 'SimulationCommand', 'start');
                        
                        % set the sim start/stop button icon to the stop icon indicating the model has
                        % been successfully started and is ready to be stopped at the next
                        % button press
                        app.SimStartStopButton.Icon = "IconEnd.png";
                        app.StartSimLabel.Text = "Stop Sim";
                    else
                        % model is already running, inform the user
                        warnStr = sprintf('Warning: Model %s is already running', app.mdl);
                        warndlg(warnStr, 'Warning');
                    end
                else
                    % model is not yet loaded, so warn the user
                    warnStr = sprintf('Warning: Model %s is not loaded\nPlease load the model and try again', app.mdl);
                    % opts = struct('WindowStyle', 'modal');
                    warndlg(warnStr, 'Warning');
                end
            end
            
        end
        
        
        function stopSimApplication(app, index)
            % stop the model running on the desktop
            try
                % check to see if hardware or virtual CAN channel is selected
                if app.canChannelInfo.DeviceModel(index) == "Virtual"
                    % virtual channel selected, so issue a stop command to the 
                    % the simulation, even if it is already stopped
                    set_param(app.mdl, 'SimulationCommand', 'stop')

                    % stop the CAN channels and update timer
                    stopCANChannel(app);
                end
                
                % set the sim start/stop button text to Start indicating the model has
                % been successfully stopped and is ready to start again at the next
                % button press
                app.SimStartStopButton.Icon = "IconPlay.png";
                app.StartSimLabel.Text = "Start Sim";
            catch
                % do nothing at the moment
            end
            
        end
        
        
        function startCANLogging(app)
            startCANLogChannel(app);
            
            % update the button icon and label
            app.canLoggingStartStopButton.Icon = 'IconEnd.png';
            app.StartLoggingLabel.Text = "Stop Logging";
        end
        
        function stopCANLogging(app)
            % stop the CAN Log channel
            stop(app.canLogChannelObj);
            
            % get the messages from the CAN log message queue 
            retrieveLoggedCANMessages(app);
            
            % update the button icon and label
            app.canLoggingStartStopButton.Icon = 'IconPlay.png';
            app.StartLoggingLabel.Text = "Start Logging";
        end
        
        function startCANReplay(app)
            startPlaybackOfLoggedCANData(app);
            
            % update the button icon and label
            app.canReplayStartStopButton.Icon = 'IconEnd.png';
            app.StartReplayLabel.Text = "Stop Replay";
        end
        
        function stopCANReplay(app)
            stopPlaybackOfLoggedCANData(app);
            
            % update the button icon and label
            app.canReplayStartStopButton.Icon = 'IconPlay.png';
            app.StartReplayLabel.Text = "Start Replay";
        end
        
        function setupCANChannel(app)
            
            % open CAN database file
            db = canDatabase('CruiseControl.dbc');
            
            % create a CAN channel for sending and receiving messages
            app.canChannelObj = eval(app.canChannelConstructorSelected);
            
            % attach CAN database to channel for received message decoding
            app.canChannelObj.Database = db;
            
            % set the baud rate (can only do this if the UI has channel initialization access)
            if app.canChannelObj.InitializationAccess
                configBusSpeed(app.canChannelObj, 500000);
            end
            
        end         % setupCANchan()
        
        
        function startCANChannel(app)
            % start the CAN channel if it isn't already running
            try
                if ~app.canChannelObj.Running
                    start(app.canChannelObj);
                end
            catch
                % do nothing
            end
            
            % start the CAN receive processing timer - check to see if it is already running.  This allows us to change CAN channels
            % with or without starting and stopping the model running on the real time target.
            if strcmpi(app.receiveCANmsgsTimer.Running, 'off')
                start(app.receiveCANmsgsTimer);
            end
            
        end
        
        
        function stopCANChannel(app)
            % stop the CAN channel
            stop(app.canChannelObj);
            
            % stop the CAN message processing timer
            stop(app.receiveCANmsgsTimer);
            
        end
        
        
        function getAvailableCANChannelInfo(app)
            % get a table containing all available CAN channels and devices
            app.canChannelInfo = canChannelList;
            
            % format CAN channel information for display on the UI
            app.availableCANChannelsForDisplay = formatCANChannelEntryForDisplay(app);
            
            % save the number of available constructors
            app.numConstructors = numel(app.canChannelInfo.Vendor);
            
        end
        
        
        function initializeCANChannelSelections(app)
            % get the available CAN channels
            getAvailableCANChannelInfo(app);
            
            % pick default CAN channel constructor properties
            app.canChannelIndex = 1;
            app.canChannelConstructorSelected = formatCANChannelConstructor(app, app.canChannelIndex);
            app.canChannelDeviceSelected = formatCANChannelDeviceStr(app, app.canChannelIndex);
            
            % update simulation model with initialized CAN Channel selection
            updateModelWithSelectedCANChannel(app, app.canChannelIndex);
        end
        
    
        function canChannelConstructor = formatCANChannelConstructor(app, index)
            canChannelConstructor = "canChannel(" + "'" + app.canChannelInfo.Vendor(index) + "'" + ", " + "'" + app.canChannelInfo.Device(index) + "'" + ", " + app.canChannelInfo.Channel(index) + ")";
        end
        
    
        function canChannelDeviceStr = formatCANChannelDeviceStr(app, index)
            canChannelDeviceStr = app.canChannelInfo.Vendor(index) + " " + app.canChannelInfo.Device(index) + " " + "(Channel " + app.canChannelInfo.Channel(index) + ")";
        end
        
    
        function updateModelWithSelectedCANChannel(app, index)
            % check to see if we are using a virtual CAN channel and whether the model is loaded
            if app.canChannelInfo.DeviceModel(index) == "Virtual" && bdIsLoaded(app.mdl)
                % using a virtual channel
                
                % find path to CAN configuration block
                canConfigPath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
                    'FollowLInks', 'on', 'Name', 'CAN Configuration');
                
                % find path to CAN transmit block
                canTransmitPath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
                    'FollowLInks', 'on', 'Name', 'CAN Transmit');
                
                % find path to CAN receive block
                canReceivePath = find_system(app.mdl,'Variants', 'AllVariants', 'LookUnderMasks', 'all',...
                    'FollowLInks', 'on', 'Name', 'CAN Receive');
                
                % push the selected CAN channel into the simulation model CAN Configuration block
                set_param(canConfigPath{1}, 'Device', app.canChannelDeviceSelected);
                set_param(canConfigPath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
                set_param(canConfigPath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
                
                % push the selected CAN channel into the simulation model CAN Recieve block
                set_param(canReceivePath{1}, 'Device', app.canChannelDeviceSelected);
                set_param(canReceivePath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
                set_param(canReceivePath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
                
                % push the selected CAN channel into the simulation model CAN Transmit block
                set_param(canTransmitPath{1}, 'Device', app.canChannelDeviceSelected);
                set_param(canTransmitPath{1}, 'DeviceMenu', app.canChannelDeviceSelected);
                set_param(canTransmitPath{1}, 'ObjConstructor', app.canChannelConstructorSelected);
            end
        end
        
        
        function displayText = formatCANChannelEntryForDisplay(app)
            % pre-allocate Execution Context column for canChannelInfo table
            ExecutionContext = repelem(string(missing), height(app.canChannelInfo), 1);
            
            % add the pre-allocated Execution Context column to the canChannelInfo table
            app.canChannelInfo = addvars(app.canChannelInfo, ExecutionContext, 'NewVariableNames', 'ExecutionContext');
            
            % determine if the execution context is "Desktop" or "RealTime"
            app.canChannelInfo.ExecutionContext(app.canChannelInfo.DeviceModel == "Virtual") = "Desktop";
            app.canChannelInfo.ExecutionContext(app.canChannelInfo.DeviceModel ~= "Virtual") = "Realtime";
            
            % format CAN channel list for display
            displayText = app.canChannelInfo.Vendor + " " + app.canChannelInfo.Device + ", Channel: " + app.canChannelInfo.Channel + " " + "(" + app.canChannelInfo.ExecutionContext + ")";
        end
        
        
        function setModelStartStopButtonEnableStatus(app)
            
            if(app.canChannelInfo.DeviceModel(app.canChannelIndex) == "Virtual")
                % using a Vector virtual channel, so talk to model on host, not on SLRT
                app.SimStartStopButton.Enable = 'on';
            else
                % using a hardware CAN channel, so talk to model on real time target, not host
                app.SimStartStopButton.Enable = 'off';
            end
            
            if isdeployed
                app.SimStartStopButton.Enable = 'off';
            end
            
        end
        
        
        function setupCANLogChannel(app)
            % open CAN database file
            db = canDatabase('CruiseControl.dbc');
            
            % create a CAN channel for sending and receiving messages
            app.canLogChannelObj = eval(app.canChannelConstructorSelected);
            
            % attach CAN database to channel for received message decoding
            app.canLogChannelObj.Database = db;
            
            % set the baud rate (can only do this if the UI has channel initialization access)
            if app.canLogChannelObj.InitializationAccess
                configBusSpeed(app.canLogChannelObj, 500000);
            end
        end
        
        
        function startCANLogChannel(app)
            % start the CAN Log channel if it isn't already running
            try
                if ~app.canLogChannelObj.Running
                    start(app.canLogChannelObj);
                end
            catch
                % do nothing
            end
            
        end
        
        
        function stopCANLogChannel(app)
            % stop the CAN Log channel
            stop(app.canLogChannelObj);
            
        end
        
        
        function retrieveLoggedCANMessages(app)
            try
                % receive available CAN message
                % initialize buffer to make sure it is empty
                app.canLogMsgBuffer = [];
                
                % receive available CAN messages
                msg = receive(app.canLogChannelObj, Inf, 'OutputFormat', 'timetable');
                
                % fill the buffer with the logged Cruise Control Command CAN message data
                app.canLogMsgBuffer = msg(msg.Name == "CruiseCtrlCmd", :);
                
            catch err
                disp(err.message)
            end             % try/catch
            
        end
        
        
        function flushCANFbMsgQueue(app)
            app.tspeedFb = [];
            app.engagedFb = [];
            
        end
        
        function savedLoggedCANDataToFile(app)
            % raise dialog box to prompt user for a CAN log file to store the logged data
            [FileName,PathName] = uiputfile('*.mat','Select a .MAT file to store logged CAN data');
            if FileName ~= 0
                % user did not cancel the file selection operation, so OK to save
                % convert the CAN log data from Timetable to struct of arrays so the data is compatible
                % with the VNT Simulink Replay block.
                canLogStructOfArrays = canMessageReplayBlockStruct(app.canLogMsgBuffer);
                % canLog = app.canLogMsgBuffer;
                save(fullfile(PathName, FileName), 'canLogStructOfArrays');
                % clear the buffer after saving it
                app.canLogMsgBuffer = [];
            end
            
        end

        
        function loadLoggedCANDataFromFile(app)
            % raise dialog box to prompt user for a CAN log file to load
            [FileName,PathName] = uigetfile('*.mat','Select a CAN log file to load');
            
            figure(app.UIFigure)        % return focus to main UI after dlg closes
            
            if FileName ~= 0
                % user did not cancel the file selection operation, so OK to load
                % make sure the message buffer is empty before loading in the logged CAN data
                app.canLogMsgBuffer = [];
                
                % upload the saved message data from the selected file
                canLogMsgStructOfArrays = load(fullfile(PathName, FileName), 'canLogStructOfArrays');
                
                % convert the saved message data into timetables for the replay command
                app.canLogMsgBuffer = canMessageTimetable(canLogMsgStructOfArrays.canLogStructOfArrays);
            end
            
        end
        
    
        function startPlaybackOfLoggedCANData(app)
            % turn off periodic transmission of CruiseCtrCmd CAN message from UI controls.
            transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'Off');
            
            % stop the UI CAN channel so we can instead use if for playback
            stopCANChannel(app)
            
            % flush the existing CAN messages stored for plotting
            flushCANFbMsgQueue(app)
            
            % clear the existing plots
            cla(app.tspeedPlot)
            cla(app.engagedPlot)
            
            % start the CAN Channel and replay the logged CAN message data
            startCANChannel(app)
            
            % replay the logged CAN data on the UI CAN Channel
            replay(app.canChannelObj, app.canLogMsgBuffer);            
        end
        
    
        function stopPlaybackOfLoggedCANData(app)
            % stop the playback CAN channel
            stopCANChannel(app)
            
            % flush the existing CAN messages stored for plotting
            flushCANFbMsgQueue(app)
            
            % clear the existing plots
            cla(app.tspeedPlot)
            cla(app.engagedPlot)
            
            % re-enable periodic transmission of CruiseCtrCmd CAN message from UI controls.
            transmitPeriodic(app.canChannelObj, app.cruiseControlCmdMessage, 'On', 0.1);
            
            % restart the CAN Channel from/To UI
            startCANChannel(app)
            
        end
        
    
    end
    
    
    methods (Access = public)
        
        function close(app)
            % clean up and close the UI
            closeUI(app)
            
        end
        
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app)
            
            % initialize radio button values
            setupInitialVals(app)
            
            % Setting up plot Displays
            setUpPlotGridAndHoldStatus(app)
            
            % initialize plot time width property
            plotTimeWindowWidth(app);
            
            % set axis limits
            setAxesLimitsAndTitles(app, 0, app.plotTimeWidth)
            
            % set initial tspeed plot max and min speed lines
            initializeMaxMinSpeedPlotLines(app)
                       
            % initialize a handle to the model running on the host PC
            try
                % app.mdl = bdroot;
                findTestbenchModel(app)
            catch
                % do nothing
            end
            
            % initialize CAN channel type string - assume first channel from list of available channels at startup
            initializeCANChannelSelections(app)
            
            % initialize start/stop button states
            app.SimStartStopButton.Value = 0;
            app.canLoggingStartStopButton.Value = 0;
            app.canReplayStartStopButton.Value = 0;
            
            % initialize the model and real time button enables based on type of CAN channel device: Virtual or Hardware
            setModelStartStopButtonEnableStatus(app)
            
            % setup the CAN channel
            setupCANChannel(app);
            
            % setup the CAN Log channel
            setupCANLogChannel(app);
            
            % setup periodic transmission of CAN messages to target
            setupCANTransmitMessages(app)
            
            % create a timer to receive CAN msgs
            app.receiveCANmsgsTimer = timer('Period', 0.5,...
                                            'ExecutionMode', 'fixedSpacing', ...
                                            'TimerFcn', @(~,~)receiveCANmsgsTimerCallback(app));
            
            % initialize a handle to the model running on the host PC
            try
                % app.mdl = bdroot;
                findTestbenchModel(app)
            catch
                % do nothing
            end
            
            % if deployed, disable the simulation start/stop buttons
            if isdeployed
                app.startSimButton.Enable = 'off';
                app.stopSimButton.Enable = 'off';
            end
        end

        % Value changed function: brakeOnOffCheckBox
        function brakeOnOffCheckBoxValueChanged(app, event)
            value = app.brakeOnOffCheckBox.Value;
            app.cruiseControlCmdMessage.Signals.S02_Brake =  logical2Numeric(app, value);
        end

        % Close request function: UIFigure
        function closeUI(app, event)
            % clean up CAN receive timer objects when UI window is closed
            try
                stop(app.receiveCANmsgsTimer);
                delete(app.receiveCANmsgsTimer);
            catch
                % do nothing
            end
            
            % stop and delete the CAN channel
            try
                stop(app.canChannelObj)
                delete(app.canChannelObj)
            catch
                % do nothing
            end
            
            delete(app)
        end

        % Value changed function: cruiseAccelResumeCheckBox
        function cruiseAccelResumeCheckBoxValueChanged(app, event)
            value = app.cruiseAccelResumeCheckBox.Value;
            app.cruiseControlCmdMessage.Signals.S05_AccelResSw =  logical2Numeric(app, value);
        end

        % Value changed function: cruiseCoastSetCheckBox
        function cruiseCoastSetCheckBoxValueChanged(app, event)
            value = app.cruiseCoastSetCheckBox.Value;
            app.cruiseControlCmdMessage.Signals.S04_CoastSetSw =  logical2Numeric(app, value);
        end

        % Value changed function: cruisePowerCheckBox
        function cruisePowerCheckBoxValueChanged(app, event)
            value = app.cruisePowerCheckBox.Value;
            app.cruiseControlCmdMessage.Signals.S01_CruiseOnOff = logical2Numeric(app, value);
        end

        % Value changed function: holdRateNumericEditField
        function holdRateNumericEditFieldChanged(app, event)
            % adjust so the edit field only uses integer values
            app.holdRateNumericEditField.Value = floor(app.holdRateNumericEditField.Value);
            
            % check to make sure the requested calibration value does not violate max/min limits
            calValueAllowed = updateCalibrationValue(app, app.holdRateNumericEditField.Value, ...
                                                     evalin('base','holdrate.Max'), ...
                                                     evalin('base','holdrate.Min'));
            
            % update calibration value in base workspace
            evalin('base',['holdrate.Value = ', num2str(calValueAllowed)]);
            
            % force model to pick up the change in calibration value
            set_param(app.mdl,'SimulationCommand', 'update');
            
            % update calibration value in UI edit field to reflect clipped value
            app.holdRateNumericEditField.Value = calValueAllowed;
  
        end

        % Value changed function: incDecNumericEditField
        function incDecNumericEditFieldChanged(app, event)
            % adjust so the edit field only uses integer values
            app.incDecNumericEditField.Value = floor(app.incDecNumericEditField.Value);
             
            % check to make sure the requested calibration value does not violate max/min limits
            calValueAllowed = updateCalibrationValue(app, app.incDecNumericEditField.Value, ...
                                                     evalin('base','incdec.Max'), ...
                                                     evalin('base','incdec.Min'));
            
            % update calibration value in base workspace
            evalin('base',['incdec.Value = ', num2str(calValueAllowed)]);
            
            % force model to pick up the change in calibration value
            set_param(app.mdl,'SimulationCommand', 'update');
            
            % update calibration value in UI edit field to reflect clipped value
            app.incDecNumericEditField.Value = calValueAllowed;
            
        end

        % Button pushed function: loadCANLogDataButton
        function loadCANLogDataButtonButtonPushed(app, event)
            loadLoggedCANDataFromFile(app)
        end

        % Value changed function: maxTargetSpeedNumericEditField
        function maxTargetSpeedNumericEditFieldChanged(app, event)
            % adjust so the edit field only uses integer values
            app.maxTargetSpeedNumericEditField.Value = floor(app.maxTargetSpeedNumericEditField.Value);
            
            % check to make sure the requested calibration value does not violate max/min limits
            calValueAllowed = updateCalibrationValue(app, app.maxTargetSpeedNumericEditField.Value, ...
                                                     evalin('base','maxtspeed.Max'), ...
                                                     evalin('base','maxtspeed.Min'));
            
            % update calibration value in base workspace
            evalin('base',['maxtspeed.Value = ', num2str(calValueAllowed)]);
            
            % force model to pick up the change in calibration value
            set_param(app.mdl,'SimulationCommand', 'update');
            
            % update calibration value in UI edit field to reflect clipped value
            app.maxTargetSpeedNumericEditField.Value = calValueAllowed;
            
            % update value of max speed line on the UI plot
            app.tspeedFbMaxSpeedLine.Value = calValueAllowed;
            
        end

        % Value changed function: minTargetSpeedNumericEditField
        function minTargetSpeedNumericEditFieldChanged(app, event)
            % adjust so the edit field only uses integer values
            app.minTargetSpeedNumericEditField.Value = floor(app.minTargetSpeedNumericEditField.Value);
            
            % check to make sure the requested calibration value does not violate max/min limits
            calValueAllowed = updateCalibrationValue(app, app.minTargetSpeedNumericEditField.Value, ...
                                                     evalin('base','mintspeed.Max'), ...
                                                     evalin('base','mintspeed.Min'));
            
            % update calibration value in base workspace
            evalin('base',['mintspeed.Value = ', num2str(calValueAllowed)]);
            
            % force model to pick up the change in calibration value
            set_param(app.mdl,'SimulationCommand', 'update');
            
            % update calibration value in UI edit field to reflect clipped value
            app.minTargetSpeedNumericEditField.Value = calValueAllowed;
            
            % update value of max speed line on the UI plot
            app.tspeedFbMinSpeedLine.Value = calValueAllowed;
            
        end

        % Value changed function: plotTimeWindowDropDown
        function plotTimeWindowWidth(app, event)
            app.plotTimeWidth = str2num(app.plotTimeWindowDropDown.Value);         
        end

        % Button pushed function: saveLoggedDataButton
        function saveLoggedDataButtonButtonPushed(app, event)
            savedLoggedCANDataToFile(app);
        end

        % Value changed function: vehicleSpeedNumericEditField
        function vehicleSpeedNumericEditFieldChanged(app, event)
            updateVehicleSpeedEditFieldAndSliderValues(app)
        end

        % Value changed function: vehicleSpeedSlider
        function vehicleSpeedSliderValueChanged(app, event)
            updateVehicleSpeedSliderAndEditFieldValues(app)
        end

        % Menu selected function: SelectCANChannel
        function selectCANChannel_callback(app, event)
            % get user selected CAN channel
            getAvailableCANChannelInfo(app)
            
            % display the list of available channels in a dialog box for the user to select from
            [index, tf] = listdlg('Name', 'CAN Channel Selection','PromptString', 'Select a CAN Channel',...
                'ListString', app.availableCANChannelsForDisplay, 'SelectionMode', 'single', ...
                'ListSize', [300, 200]);
            
            figure(app.UIFigure)        % return focus to main UI after listdlg closes
            
            if tf        % user made a selection
                % save the index of the selected CAN channel for other functions to use
                app.canChannelIndex = index;
                
                % format the CAN channel constructor.  This string will be used to create a CAN channel object
                % that will be stored in the app UI, as well as update the VNT blocks in the Simulink model with
                % the user selected CAN channel.
                app.canChannelConstructorSelected = formatCANChannelConstructor(app, app.canChannelIndex);
                
                % format the CAN channel Device and DeviceMenu strings.  These strings, along with the channel
                % constructor will be used to update the VNT blocks in the Simulink model with the user selected
                % CAN channel.
                app.canChannelDeviceSelected = formatCANChannelDeviceStr(app, app.canChannelIndex);
                
                % set model start/stop button enable status
                setModelStartStopButtonEnableStatus(app)
                
                % stop and delete existing CAN channel
                try
                    stopCANChannel(app);
                    
                    % stop the CAN Log channel
                    stop(app.canLogChannelObj);
                    
                    delete(app.canChannelObj)
                    delete(app.canLogChannelObj)
                catch
                    % do nothing
                end
                
                % create new channel
                setupCANChannel(app);
                setupCANLogChannel(app);
                
                % update the simulation model with the new CAN channel selection
                updateModelWithSelectedCANChannel(app, app.canChannelIndex);
                
                % setup periodic transmission of CAN messages to target
                setupCANTransmitMessages(app)
                
            else         % user did not make a selection, so just return and use previous selection
                return;
            end
            
        end

        % Value changed function: SimStartStopButton
        function SimStartStopButton_callback(app, event)
            value = app.SimStartStopButton.Value;
            if value == 1           % button was pressed
                startSimApplication(app, app.canChannelIndex);
            else
                stopSimApplication(app, app.canChannelIndex);
            end
        end

        % Value changed function: canLoggingStartStopButton
        function canLoggingStartStopButton_callback(app, event)
            value = app.canLoggingStartStopButton.Value;
            if value == 1           % button was pressed
                startCANLogging(app);
            else
                stopCANLogging(app);
            end
        end

        % Value changed function: canReplayStartStopButton
        function canReplayStartStopButton_callback(app, event)
            value = app.canReplayStartStopButton.Value;
            if value == 1       % button was pressed
                startCANReplay(app);
            else
                stopCANReplay(app);
            end
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create UIFigure and hide until all components are created
            app.UIFigure = uifigure('Visible', 'off');
            app.UIFigure.Position = [50 50 1000 699];
            app.UIFigure.Name = 'CruiseControlTestUI';
            app.UIFigure.CloseRequestFcn = createCallbackFcn(app, @closeUI, true);

            % Create ChannelConfigurationMenu
            app.ChannelConfigurationMenu = uimenu(app.UIFigure);
            app.ChannelConfigurationMenu.Text = 'Channel Configuration';

            % Create SelectCANChannel
            app.SelectCANChannel = uimenu(app.ChannelConfigurationMenu);
            app.SelectCANChannel.MenuSelectedFcn = createCallbackFcn(app, @selectCANChannel_callback, true);
            app.SelectCANChannel.Text = 'Select CAN Channel';

            % Create controllerOutputsPlotPanel
            app.controllerOutputsPlotPanel = uipanel(app.UIFigure);
            app.controllerOutputsPlotPanel.Title = 'CONTROLLER OUTPUTS';
            app.controllerOutputsPlotPanel.Position = [261 145 720 445];

            % Create tspeedPlot
            app.tspeedPlot = uiaxes(app.controllerOutputsPlotPanel);
            title(app.tspeedPlot, 'Cruise Target Speed')
            xlabel(app.tspeedPlot, 'X')
            ylabel(app.tspeedPlot, 'Y')
            app.tspeedPlot.PlotBoxAspectRatio = [4.21052631578947 1 1];
            app.tspeedPlot.GridAlpha = 0.15;
            app.tspeedPlot.MinorGridAlpha = 0.25;
            app.tspeedPlot.Box = 'on';
            app.tspeedPlot.XGrid = 'on';
            app.tspeedPlot.YGrid = 'on';
            app.tspeedPlot.Position = [21 215 690 210];

            % Create engagedPlot
            app.engagedPlot = uiaxes(app.controllerOutputsPlotPanel);
            title(app.engagedPlot, 'Cruise Engaged')
            xlabel(app.engagedPlot, 'X')
            ylabel(app.engagedPlot, 'Y')
            app.engagedPlot.PlotBoxAspectRatio = [4.81203007518797 1 1];
            app.engagedPlot.GridAlpha = 0.15;
            app.engagedPlot.MinorGridAlpha = 0.25;
            app.engagedPlot.Box = 'on';
            app.engagedPlot.XGrid = 'on';
            app.engagedPlot.YGrid = 'on';
            app.engagedPlot.Position = [21 14 690 191];

            % Create driverInputsPanel
            app.driverInputsPanel = uipanel(app.UIFigure);
            app.driverInputsPanel.Title = 'DRIVER INPUTS';
            app.driverInputsPanel.Position = [31 390 210 200];

            % Create vehicleSpeedSlider
            app.vehicleSpeedSlider = uislider(app.driverInputsPanel);
            app.vehicleSpeedSlider.ValueChangedFcn = createCallbackFcn(app, @vehicleSpeedSliderValueChanged, true);
            app.vehicleSpeedSlider.Position = [24 131 150 3];
            app.vehicleSpeedSlider.Value = 30;

            % Create LabelNumericEditField2
            app.LabelNumericEditField2 = uilabel(app.driverInputsPanel);
            app.LabelNumericEditField2.HorizontalAlignment = 'right';
            app.LabelNumericEditField2.VerticalAlignment = 'top';
            app.LabelNumericEditField2.Position = [28 156 80 15];
            app.LabelNumericEditField2.Text = 'Vehicle Speed';

            % Create vehicleSpeedNumericEditField
            app.vehicleSpeedNumericEditField = uieditfield(app.driverInputsPanel, 'numeric');
            app.vehicleSpeedNumericEditField.Limits = [0 Inf];
            app.vehicleSpeedNumericEditField.ValueChangedFcn = createCallbackFcn(app, @vehicleSpeedNumericEditFieldChanged, true);
            app.vehicleSpeedNumericEditField.Position = [125 152 50 22];

            % Create calibrationsPanel
            app.calibrationsPanel = uipanel(app.UIFigure);
            app.calibrationsPanel.Title = 'CALIBRATIONS';
            app.calibrationsPanel.Position = [31 250 210 130];

            % Create LabelNumericEditField
            app.LabelNumericEditField = uilabel(app.calibrationsPanel);
            app.LabelNumericEditField.VerticalAlignment = 'top';
            app.LabelNumericEditField.Position = [58 11 66 15];
            app.LabelNumericEditField.Text = 'Hold Rate';

            % Create holdRateNumericEditField
            app.holdRateNumericEditField = uieditfield(app.calibrationsPanel, 'numeric');
            app.holdRateNumericEditField.Limits = [0 Inf];
            app.holdRateNumericEditField.ValueChangedFcn = createCallbackFcn(app, @holdRateNumericEditFieldChanged, true);
            app.holdRateNumericEditField.Position = [126 7 68 22];
            app.holdRateNumericEditField.Value = 5;

            % Create Label
            app.Label = uilabel(app.calibrationsPanel);
            app.Label.VerticalAlignment = 'top';
            app.Label.Position = [37 36 87 15];
            app.Label.Text = 'Inc/Decrement';

            % Create incDecNumericEditField
            app.incDecNumericEditField = uieditfield(app.calibrationsPanel, 'numeric');
            app.incDecNumericEditField.Limits = [0 Inf];
            app.incDecNumericEditField.ValueChangedFcn = createCallbackFcn(app, @incDecNumericEditFieldChanged, true);
            app.incDecNumericEditField.Position = [126 32 68 22];
            app.incDecNumericEditField.Value = 1;

            % Create Label2
            app.Label2 = uilabel(app.calibrationsPanel);
            app.Label2.VerticalAlignment = 'top';
            app.Label2.Position = [18 61 108 15];
            app.Label2.Text = 'Max Target Speed';

            % Create maxTargetSpeedNumericEditField
            app.maxTargetSpeedNumericEditField = uieditfield(app.calibrationsPanel, 'numeric');
            app.maxTargetSpeedNumericEditField.Limits = [0 Inf];
            app.maxTargetSpeedNumericEditField.ValueChangedFcn = createCallbackFcn(app, @maxTargetSpeedNumericEditFieldChanged, true);
            app.maxTargetSpeedNumericEditField.Position = [126 57 68 22];
            app.maxTargetSpeedNumericEditField.Value = 90;

            % Create Label3
            app.Label3 = uilabel(app.calibrationsPanel);
            app.Label3.VerticalAlignment = 'top';
            app.Label3.Position = [18 86 97 15];
            app.Label3.Text = 'Min Target Speed';

            % Create minTargetSpeedNumericEditField
            app.minTargetSpeedNumericEditField = uieditfield(app.calibrationsPanel, 'numeric');
            app.minTargetSpeedNumericEditField.Limits = [0 Inf];
            app.minTargetSpeedNumericEditField.ValueChangedFcn = createCallbackFcn(app, @minTargetSpeedNumericEditFieldChanged, true);
            app.minTargetSpeedNumericEditField.Position = [126 82 68 22];
            app.minTargetSpeedNumericEditField.Value = 20;

            % Create plottingPanel
            app.plottingPanel = uipanel(app.UIFigure);
            app.plottingPanel.Title = 'PLOTTING';
            app.plottingPanel.Position = [31 145 210 50];

            % Create LabelDropDown
            app.LabelDropDown = uilabel(app.plottingPanel);
            app.LabelDropDown.HorizontalAlignment = 'right';
            app.LabelDropDown.VerticalAlignment = 'top';
            app.LabelDropDown.Position = [43 8 74 15];
            app.LabelDropDown.Text = 'Time Window';

            % Create plotTimeWindowDropDown
            app.plotTimeWindowDropDown = uidropdown(app.plottingPanel);
            app.plotTimeWindowDropDown.Items = {'10', '20', '50', '100'};
            app.plotTimeWindowDropDown.ValueChangedFcn = createCallbackFcn(app, @plotTimeWindowWidth, true);
            app.plotTimeWindowDropDown.Position = [123 6 70 20];
            app.plotTimeWindowDropDown.Value = '20';

            % Create cruisePowerCheckBox
            app.cruisePowerCheckBox = uicheckbox(app.UIFigure);
            app.cruisePowerCheckBox.ValueChangedFcn = createCallbackFcn(app, @cruisePowerCheckBoxValueChanged, true);
            app.cruisePowerCheckBox.Text = 'Cruise On/Off';
            app.cruisePowerCheckBox.Position = [103 464 92 16];

            % Create brakeOnOffCheckBox
            app.brakeOnOffCheckBox = uicheckbox(app.UIFigure);
            app.brakeOnOffCheckBox.ValueChangedFcn = createCallbackFcn(app, @brakeOnOffCheckBoxValueChanged, true);
            app.brakeOnOffCheckBox.Text = 'Brake On/Off';
            app.brakeOnOffCheckBox.Position = [103 442 88 16];

            % Create cruiseCoastSetCheckBox
            app.cruiseCoastSetCheckBox = uicheckbox(app.UIFigure);
            app.cruiseCoastSetCheckBox.ValueChangedFcn = createCallbackFcn(app, @cruiseCoastSetCheckBoxValueChanged, true);
            app.cruiseCoastSetCheckBox.Text = 'Coast/Set';
            app.cruiseCoastSetCheckBox.Position = [103 420 72 16];

            % Create cruiseAccelResumeCheckBox
            app.cruiseAccelResumeCheckBox = uicheckbox(app.UIFigure);
            app.cruiseAccelResumeCheckBox.ValueChangedFcn = createCallbackFcn(app, @cruiseAccelResumeCheckBoxValueChanged, true);
            app.cruiseAccelResumeCheckBox.Text = 'Accel/Res';
            app.cruiseAccelResumeCheckBox.Position = [103 398 74 16];

            % Create Label4
            app.Label4 = uilabel(app.UIFigure);
            app.Label4.VerticalAlignment = 'top';
            app.Label4.FontSize = 48;
            app.Label4.FontWeight = 'bold';
            app.Label4.Position = [132 606 745 63];
            app.Label4.Text = 'CAN Enabled Open Loop Test UI';

            % Create SimulationControlPanel
            app.SimulationControlPanel = uipanel(app.UIFigure);
            app.SimulationControlPanel.Title = 'Simulation Control';
            app.SimulationControlPanel.Position = [261 42 108 89];

            % Create SimStartStopButton
            app.SimStartStopButton = uibutton(app.SimulationControlPanel, 'state');
            app.SimStartStopButton.ValueChangedFcn = createCallbackFcn(app, @SimStartStopButton_callback, true);
            app.SimStartStopButton.Icon = 'IconPlay.png';
            app.SimStartStopButton.IconAlignment = 'center';
            app.SimStartStopButton.Text = '';
            app.SimStartStopButton.BackgroundColor = [0.9412 0.9412 0.9412];
            app.SimStartStopButton.Position = [37 31 34 30];

            % Create StartSimLabel
            app.StartSimLabel = uilabel(app.SimulationControlPanel);
            app.StartSimLabel.Position = [26 10 55 22];
            app.StartSimLabel.Text = 'Start Sim';

            % Create ChannelLogControls
            app.ChannelLogControls = uipanel(app.UIFigure);
            app.ChannelLogControls.Title = 'Channel Log Control';
            app.ChannelLogControls.Position = [547 42 204 89];

            % Create canLoggingStartStopButton
            app.canLoggingStartStopButton = uibutton(app.ChannelLogControls, 'state');
            app.canLoggingStartStopButton.ValueChangedFcn = createCallbackFcn(app, @canLoggingStartStopButton_callback, true);
            app.canLoggingStartStopButton.Icon = 'IconPlay.png';
            app.canLoggingStartStopButton.Text = '';
            app.canLoggingStartStopButton.BackgroundColor = [0.9412 0.9412 0.9412];
            app.canLoggingStartStopButton.Position = [31 31 36 30];

            % Create saveLoggedDataButton
            app.saveLoggedDataButton = uibutton(app.ChannelLogControls, 'push');
            app.saveLoggedDataButton.ButtonPushedFcn = createCallbackFcn(app, @saveLoggedDataButtonButtonPushed, true);
            app.saveLoggedDataButton.Icon = 'IconBrowse.png';
            app.saveLoggedDataButton.BackgroundColor = [0.9412 0.9412 0.9412];
            app.saveLoggedDataButton.Position = [132 31 36 30];
            app.saveLoggedDataButton.Text = '';

            % Create StartLoggingLabel
            app.StartLoggingLabel = uilabel(app.ChannelLogControls);
            app.StartLoggingLabel.Position = [10 10 77 22];
            app.StartLoggingLabel.Text = 'Start Logging';

            % Create SaveloggedDataLabel
            app.SaveloggedDataLabel = uilabel(app.ChannelLogControls);
            app.SaveloggedDataLabel.Position = [100 11 99 21];
            app.SaveloggedDataLabel.Text = 'Save logged Data';

            % Create ChannelReplayControlPanel
            app.ChannelReplayControlPanel = uipanel(app.UIFigure);
            app.ChannelReplayControlPanel.Title = 'Channel Replay Control';
            app.ChannelReplayControlPanel.Position = [766 42 212 89];

            % Create canReplayStartStopButton
            app.canReplayStartStopButton = uibutton(app.ChannelReplayControlPanel, 'state');
            app.canReplayStartStopButton.ValueChangedFcn = createCallbackFcn(app, @canReplayStartStopButton_callback, true);
            app.canReplayStartStopButton.Icon = 'IconPlay.png';
            app.canReplayStartStopButton.Text = '';
            app.canReplayStartStopButton.BackgroundColor = [0.9412 0.9412 0.9412];
            app.canReplayStartStopButton.Position = [157 31 32 30];

            % Create loadCANLogDataButton
            app.loadCANLogDataButton = uibutton(app.ChannelReplayControlPanel, 'push');
            app.loadCANLogDataButton.ButtonPushedFcn = createCallbackFcn(app, @loadCANLogDataButtonButtonPushed, true);
            app.loadCANLogDataButton.Icon = 'IconFindFiles.png';
            app.loadCANLogDataButton.BackgroundColor = [0.9412 0.9412 0.9412];
            app.loadCANLogDataButton.Position = [41 31 37 30];
            app.loadCANLogDataButton.Text = '';

            % Create LoadloggedDataLabel
            app.LoadloggedDataLabel = uilabel(app.ChannelReplayControlPanel);
            app.LoadloggedDataLabel.Position = [10 10 100 22];
            app.LoadloggedDataLabel.Text = 'Load logged Data';

            % Create StartReplayLabel
            app.StartReplayLabel = uilabel(app.ChannelReplayControlPanel);
            app.StartReplayLabel.Position = [137 10 72 22];
            app.StartReplayLabel.Text = 'Start Replay';

            % Show the figure after all components are created
            app.UIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = CruiseControlTestUI

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.UIFigure)

            % Execute the startup function
            runStartupFcn(app, @startupFcn)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end