gusucode.com > vnt工具箱matlab源码程序 > vnt/vntguis/+can/Tool.m
classdef (Hidden) Tool < handle % Tool Class that implements the VNT CAN Tool. % % The Tool class contains a full implementation of the CAN Tool feature. % It contains properties and methods to use the tool. The class is hidden % to prevent direct user access. We expects user to access Tool through % our factory function. % % See also VNT. % % Copyright 2010-2016 The MathWorks, Inc. properties % BusSpeed - Stores the desired bus speed value provided by the user. BusSpeed % Channel - A handle to the channel to which the tool is currently % connected when runnning. Channel % ChannelInfoAll - An array of structures containing constructor % parameters to build a channel for all available channels. ChannelInfoAll % ChannelInfoCurrent - A structure containing constructor parameters to % build a channel for the current selection. ChannelInfoCurrent % Database - A handle to a database object to use for message decoding. Database % FigureWindow - Handle for the figure window represented by this class. FigureWindow % MaximumMessageCount - The maximum number of messages to appear in the % tool display. MaximumMessageCount = 2500; % MenuChildren - Handles to all of the child options on the menu bars. MenuChildren % MenuConfigure - The menu bar for configuring the tool. MenuConfigure % MenuFile - The menu bar for operating the tool. MenuFile % MenuHelp - The menu bar for accessing tool help. MenuHelp % MenuRun - The menu bar for running the tool. MenuRun % MenuView - The menu bar for customizing tool view options. MenuView % MessageTable - Handle to the primary message table of the tool. MessageTable % MessageTableCellData - A cell array that contains the data currently % shown on the tool's table. MessageTableCellData % MessageTableHandle - A handle to the primary message table. MessageTableHandle % MessageLog - An array of message objects that is the current % complete message history logged by the tool. MessageLog % MessageLogPaused - An array of message objects that is the % frozen set of messages shown on pause. MessageLogPaused % MessageLogUnique - An array of message objects that contains % only the most recent instance of each unique message to appear in % the tool since last start. MessageLogUnique % MessageLogUniquePaused - An array of message objects that % contains a frozen set of unique messages shown on pause. MessageLogUniquePaused % MsgFilterStd - Handle to a message filter object for standard IDs. % This object stores filter settings for the given ID type. MsgFilterStd % MsgFilterExt - Handle to a message filter object for extended IDs. % This object stores filter settings for the given ID type. MsgFilterExt % StateNumberFormat - Indicates what numerical format the tool is % using to display information. StateNumberFormat % StateShowUniqueMessages - Indicates the current state of the show % unique message option. StateShowUniqueMessages % StateTool - Indicates the current run state of the tool. StateTool % Toolbar - A toolbar for operating the tool. Toolbar % ToolbarButtons - A structure of handles to toolbar buttons. ToolbarButtons % UpdateTimer - The timer controlling when the display is updated. UpdateTimer % MsgInfoMapStd - A map of standard message IDs to message information % stored locally for faster access. MsgInfoMapStd % MsgInfoMapExt - A map of extended message IDs to message information % stored locally for faster access. MsgInfoMapExt end properties (Constant) % ALLOWED_MESSAGE_COUNT_MAX - The maximum count of messages that % the tool allows the user to display. ALLOWED_MESSAGE_COUNT_MAX = 5000; % ALLOWED_MESSAGE_COUNT_MIN - The minimum count of messages that % the tool allows the user to display. ALLOWED_MESSAGE_COUNT_MIN = 100; % FIGURE_HEIGHT - The height of the figure in pixels. FIGURE_HEIGHT = 670; % FIGURE_WIDTH - The width of the figure in pixels. FIGURE_WIDTH = 600; % FILTERSPEC_DATABASE - The file filter specification to use when % browsing for CANdb files. FILTERSPEC_DATABASE = {'*.dbc', 'CANdb Database (*.dbc)'}; % FORMAT_HEXADECIMAL - Indicates that the tool is displaying % hexadecimal format values. FORMAT_HEXADECIMAL = 0; % FORMAT_DECIMAL - Indicates that the tool is displaying decimal % format values. FORMAT_DECIMAL = 1; % SIGNAL_TABLE_COLUMN_NAMES - Columns names for signal tables. SIGNAL_TABLE_COLUMN_NAMES = {'Signal Name' 'Physical Value'}; % STATE_STARTED - Indicates that the tool is started. STATE_STARTED = 0; % STATE_PAUSED - Indicates that the tool is paused. STATE_PAUSED = 1; % STATE_STOPPED - Indicates that the tools is stopped. STATE_STOPPED = 2; % TABLE_BLANK_ROW - A cell array of values that creates a blank row on % the tool's table. TABLE_BLANK_ROW = {' ', ' ', ' ', ' ', ' '}; % UPDATE_RATE - The rate at which the tool display is updated once % started. Units are in seconds. UPDATE_RATE = 0.500; % EMPTY_MSG_STRUCT - Defines default empty structure for messages. EMPTY_MSG_STRUCT = struct('ID', {}, 'Timestamp', {}, 'Data', {}, ... 'Extended', {}, 'Remote', {}, 'Error', {}); end methods function obj = Tool() % Check for no access to any devices on the system to prevent % launching of CAN Tool. info = can.HardwareInfo(); if isempty(info.VendorInfo) % Error if no devices are available. error(message('vnt:Tool:NoDevicesAvailable')); end % Create the figure according to the defined properties. pos = get(0, 'ScreenSize'); obj.FigureWindow = figure( ... 'CloseRequestFcn', @obj.closeFigure, ... 'MenuBar', 'none', ... 'Name', 'Vehicle CAN Bus Monitor', ... 'NextPlot', 'new', ... 'NumberTitle', 'off', ... 'Position', [(pos(3) - obj.FIGURE_WIDTH) / 2, ... (pos(4) - obj.FIGURE_HEIGHT) / 2, ... obj.FIGURE_WIDTH, obj.FIGURE_HEIGHT], ... 'ResizeFcn', @obj.resizeFigure, ... 'ToolBar', 'none', ... 'Units', 'pixels', ... 'Visible', 'off'); % Add the menu bar for tool operation. obj.MenuFile = uimenu( ... obj.FigureWindow, ... 'Label', 'File'); obj.MenuChildren.Save = uimenu(obj.MenuFile, ... 'Accelerator', 'S', ... 'Callback', @obj.save, ... 'Label', 'Save Messages'); obj.MenuChildren.Clear = uimenu(obj.MenuFile, ... 'Accelerator', 'X', ... 'Callback', @obj.clear, ... 'Label', 'Clear Messages'); obj.MenuChildren.Exit = uimenu(obj.MenuFile, ... 'Accelerator', 'Q', ... 'Callback', @obj.exit, ... 'Label', 'Exit', ... 'Separator', 'on'); % Add the menu bar for configuration. obj.MenuConfigure = uimenu( ... obj.FigureWindow, ... 'Label', 'Configure'); obj.MenuChildren.Channel = uimenu(obj.MenuConfigure, ... 'Label', 'Channel', ... 'CreateFcn', @obj.initChannels); obj.MenuChildren.BusSpeed = uimenu(obj.MenuConfigure, ... 'Label', 'Bus Speed...', ... 'Callback', @obj.configBusSpeed); obj.MenuChildren.Filtering = uimenu(obj.MenuConfigure, ... 'Label', 'Message Filters...', ... 'Callback', @obj.configFiltering); obj.MenuChildren.Database = uimenu(obj.MenuConfigure, ... 'Label', 'Database...', ... 'Callback', @obj.configDatabase); % Add the menu bar for run control. obj.MenuRun = uimenu( ... obj.FigureWindow, ... 'Label', 'Run'); obj.MenuChildren.Start = uimenu(obj.MenuRun, ... 'Accelerator', 'R', ... 'Callback', @obj.start, ... 'Label', 'Start', ... 'Separator', 'on'); obj.MenuChildren.Pause = uimenu(obj.MenuRun, ... 'Accelerator', 'P', ... 'Callback', @obj.pause, ... 'Label', 'Pause'); obj.MenuChildren.Stop = uimenu(obj.MenuRun, ... 'Accelerator', 'T', ... 'Callback', @obj.stop, ... 'Label', 'Stop'); % Add the menu bar for view control. obj.MenuView = uimenu( ... obj.FigureWindow, ... 'Label', 'View'); obj.MenuChildren.MaximumMessageCount = uimenu(obj.MenuView, ... 'Label', 'Maximum Message Count...', ... 'Callback', @obj.configMaximumMessageCount); obj.MenuChildren.NumberFormat = uimenu(obj.MenuView, ... 'Label', 'Number Format'); uimenu(obj.MenuChildren.NumberFormat, ... 'Callback', @obj.changeNumberFormat, ... 'Label', 'Hexadecimal', ... 'Checked', 'on'); uimenu(obj.MenuChildren.NumberFormat, ... 'Callback', @obj.changeNumberFormat, ... 'Label', 'Decimal'); obj.MenuChildren.ShowUniqueMessages = uimenu(obj.MenuView, ... 'Accelerator', 'U', ... 'Callback', @obj.showUniqueMessages, ... 'Checked', 'off', ... 'Label', 'Show Unique Messages'); % Add the menu bar for help. obj.MenuHelp = uimenu( ... obj.FigureWindow, ... 'Label', 'Help'); obj.MenuChildren.ToolHelp = uimenu(obj.MenuHelp, ... 'Label', 'Documentation', ... 'Callback', @obj.launchToolHelp); obj.MenuChildren.ToolAbout = uimenu(obj.MenuHelp, ... 'Label', 'About Vehicle Network Toolbox', ... 'Callback', @obj.launchToolAbout); % Add the toolbar. obj.Toolbar = uitoolbar(obj.FigureWindow); obj.ToolbarButtons.Start = uipushtool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'Start.png')), ... 'TooltipString', 'Start', ... 'ClickedCallback', @obj.start); obj.ToolbarButtons.Pause = uipushtool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'Pause.png')), ... 'TooltipString', 'Pause', ... 'ClickedCallback', @obj.pause); obj.ToolbarButtons.Stop = uipushtool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'Stop.png')), ... 'TooltipString', 'Stop', ... 'ClickedCallback', @obj.stop); obj.ToolbarButtons.Save = uipushtool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'Save.png')), ... 'TooltipString', 'Save Messages', ... 'ClickedCallback', @obj.save, ... 'Separator', 'on'); obj.ToolbarButtons.Clear = uipushtool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'Clear.png')), ... 'TooltipString', 'Clear Messages', ... 'ClickedCallback', @obj.clear); obj.ToolbarButtons.ShowUniqueMessages = uitoggletool( ... obj.Toolbar, ... 'CData', imread(fullfile(toolboxdir('vnt'), 'vntguis', 'ShowUnique.png')), ... 'TooltipString', 'Show Unique Messages', ... 'ClickedCallback', @obj.showUniqueMessages, ... 'Separator', 'on'); % Build a table on the tool. obj.MessageTable = internal.ui.widget.TablePanelController( ... obj.TABLE_BLANK_ROW, ... {'Timestamp', 'ID', 'Name', 'Length', 'Data'}, ... [false false false false false], ... 'QETestMessageTable', ... 'Hierarchical'); [~, obj.MessageTableHandle] = ... javacomponent(obj.MessageTable.getJavaComponent(), ... [0, 0, obj.FIGURE_WIDTH, obj.FIGURE_HEIGHT], ... obj.FigureWindow); % Initialize the signal tables with blank information. clearSignalTableData(obj); % Set initial tool states. obj.StateTool = obj.STATE_STOPPED; obj.StateNumberFormat = obj.FORMAT_HEXADECIMAL; obj.StateShowUniqueMessages = false; % Create message filter objects. obj.MsgFilterStd = can.MessageFilter('Standard'); obj.MsgFilterExt = can.MessageFilter('Extended'); % Initialize the internal message logs. obj.MessageLog = obj.EMPTY_MSG_STRUCT; obj.MessageLogPaused = obj.EMPTY_MSG_STRUCT; obj.MessageLogUnique = obj.EMPTY_MSG_STRUCT; obj.MessageLogUniquePaused = obj.EMPTY_MSG_STRUCT; % Make figure visible. set(obj.FigureWindow, 'Visible', 'on'); % Update the figure. updateFigure(obj); end function closeFigure(obj, ~, ~) % closeFigure Prepares the tool to close. % % This function is called when the user clicks close on the tool % window. It cleans up the object and removes the display window. % Clear the timer. if ~isempty(obj.UpdateTimer) % Stop the timer. if obj.UpdateTimer.Running stop(obj.UpdateTimer); end % Delete the timer. delete(obj.UpdateTimer); end obj.UpdateTimer = []; % Clear the channel. obj.Channel = []; % Clear the internal message logs. obj.MessageLog = []; obj.MessageLogUnique = []; obj.MessageLogPaused = []; obj.MessageLogUniquePaused = []; % Remove the figure. delete(obj.FigureWindow); obj.FigureWindow = []; end function exit(obj, ~, ~) % exit Closes the tool. % % This function responds to user closing of the tool. % Close the figure to shut down the tool. close(obj.FigureWindow); end function resizeFigure(obj, ~, ~) % resizeFigure Manages figure on resize operation. % % This function redraws the figure controls to match any window % resize events. % Get the new position information. pos = get(obj.FigureWindow, 'Position'); % Resize the message table to completely fill the figure. javacomponent(obj.MessageTable.getJavaComponent(), ... [0, 0, pos(3), pos(4)], ... obj.FigureWindow); % Draw now to force the resize. drawnow limitrate; end function initChannels(obj, handle, ~) % initChannels Create function for channel options. % % This function is called on launch of the tool to build the % list of devices and channels. % Get hardware information for all devices on the system. hwInfo = canHWInfo(); % If no devices were found, error. if isempty(hwInfo.VendorInfo) return; end % Initialize the constructor information structure. obj.ChannelInfoAll = struct([]); % Build the channel options. for vi = 1:numel(hwInfo.VendorInfo) for ci = 1:numel(hwInfo.VendorInfo(vi).ChannelInfo) % Build a string representing this channel. switch hwInfo.VendorInfo(vi).VendorName case {'Kvaser', 'Vector','MathWorks'} selectionString = [hwInfo.VendorInfo(vi).VendorName ' ' ... hwInfo.VendorInfo(vi).ChannelInfo(ci).Device ' ' ... '(Channel ' num2str(hwInfo.VendorInfo(vi).ChannelInfo(ci).DeviceChannelIndex) ')']; case {'NI', 'PEAK-System'} selectionString = [hwInfo.VendorInfo(vi).VendorName ' ' ... hwInfo.VendorInfo(vi).ChannelInfo(ci).DeviceType ' ' ... '(' hwInfo.VendorInfo(vi).ChannelInfo(ci).Device ')']; end % Set the string as available to the user. uimenu(handle, ... 'Label', selectionString, ... 'Callback', @obj.changeChannel); % Build a corresponding structure containing the information for % each CAN channel as needed to construct it later. This is done % for efficiency rather than parsing the values later out of the % selection strings. index = numel(obj.ChannelInfoAll) + 1; obj.ChannelInfoAll(index).Vendor = ... hwInfo.VendorInfo(vi).VendorName; obj.ChannelInfoAll(index).Device = ... hwInfo.VendorInfo(vi).ChannelInfo(ci).Device; obj.ChannelInfoAll(index).DeviceChannelIndex = ... hwInfo.VendorInfo(vi).ChannelInfo(ci).DeviceChannelIndex; if strcmpi(obj.ChannelInfoAll(index).Vendor, 'NI') || ... strcmpi(obj.ChannelInfoAll(index).Vendor, 'PEAK-System') obj.ChannelInfoAll(index).DeviceType = ... hwInfo.VendorInfo(vi).ChannelInfo(ci).DeviceType; else obj.ChannelInfoAll(index).DeviceType = ''; end end end % Set the active channel to the first option in the list as the % default case by checking it. hInfo = get(handle); set(hInfo.Children(end), 'Checked', 'on'); % Set the current information structure to reference the active % channel as well. obj.ChannelInfoCurrent.Vendor = obj.ChannelInfoAll(1).Vendor; obj.ChannelInfoCurrent.Device = obj.ChannelInfoAll(1).Device; obj.ChannelInfoCurrent.DeviceChannelIndex = ... obj.ChannelInfoAll(1).DeviceChannelIndex; obj.ChannelInfoCurrent.DeviceType = ... obj.ChannelInfoAll(1).DeviceType; end function changeChannel(obj, handle, ~) % changeChannel Callback function for changing channel selection. % % This function is called when the user selects a new device channel. % It will ensure only the selected channel is checked active, % internally configure the tool to use the new channel, and take % care of any visible management required on the tool. % If the user is reselecting the already set option, do nothing. if strcmpi(get(handle, 'Checked'), 'on') return; end % If the tool is started, prompt to stop in order to allow % configuration of this option. if promptForCancel(obj) return; end % Get the info on the menu option. hInfo = get(obj.MenuChildren.Channel); % Uncheck all of the suboptions. set(hInfo.Children, 'Checked', 'off'); % Check the currently selected option. set(handle, 'Checked', 'on'); % Set the new channel's constructor info using logical % indexing to find the current channel. Note that the order of the % channel info array is opposite of the numbering used by the menu % system, so the array must be flipped for proper referencing. index = flipud(hInfo.Children) == handle; obj.ChannelInfoCurrent.Vendor = obj.ChannelInfoAll(index).Vendor; obj.ChannelInfoCurrent.Device = obj.ChannelInfoAll(index).Device; obj.ChannelInfoCurrent.DeviceChannelIndex = ... obj.ChannelInfoAll(index).DeviceChannelIndex; obj.ChannelInfoCurrent.DeviceType = ... obj.ChannelInfoAll(index).DeviceType; % Clear all message information in the tool. clearMessages(obj); % Update the figure. updateFigure(obj); end function configBusSpeed(obj, ~, ~) % configBusSpeed Allows changing of the bus speed. % % This function provides the interface that allows the user to change % the bus speed of the currently selected channel. It will launch a % bus speed specific configuration window and listen for any changes % made there. % If the tool is started, prompt to stop in order to allow % configuration of this option. if promptForCancel(obj) return; end % Construct a temporary channel for validation. if strcmpi(obj.ChannelInfoCurrent.Vendor, 'NI') || ... strcmpi(obj.ChannelInfoCurrent.Vendor, 'PEAK-System') try channel = canChannel(obj.ChannelInfoCurrent.Vendor, obj.ChannelInfoCurrent.Device); catch err %#ok<NASGU> % Throw an error describing the case that bus speed is not % currently settable because we cannot open a channel to % validate a new value. text = getString(message('vnt:Tool:BusSpeedNotConfigurable')); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'BusSpeedNotConfigurable'); uiwait(e); return; end else channel = canChannel( ... obj.ChannelInfoCurrent.Vendor, ... obj.ChannelInfoCurrent.Device, ... obj.ChannelInfoCurrent.DeviceChannelIndex); end % Check for initialization access as without it, we do not allow % configuring the bus speed. if ~channel.InitializationAccess % Throw an error describing the init access situation. text = getString(message('vnt:Tool:BusSpeedNoInitAccess')); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'BusSpeedNoInitAccess'); uiwait(e); return; end % Build an input dialog box. text = getString(message('vnt:Tool:BusSpeedInputDialogText')); header = getString(message('vnt:Tool:BusSpeedInputDialogHeader')); numLines = 1; % Set the default configuration value for the dialog. if isempty(obj.BusSpeed) % If no desired bus speed value exists, populate the default % setting with the value currently defined on the channel. defaultAnswer = {num2str(channel.BusSpeed)}; else % Otherwise, use the desired value. defaultAnswer = {num2str(obj.BusSpeed)}; end % Query for user input. answerStr = inputdlg(text, header, numLines, defaultAnswer); % Check for an empty answer indicating that the user closed or % cancelled the dialog box. We have nothing more to do then. if isempty(answerStr) return; end % Convert the answer string to a number. answer = str2double(answerStr); % Validate that the user entered a valid number. if isnan(answer) throwInvalidBusSpeedError(obj, answerStr); return; end % Compare the new value against the currently configured value. If % they are equal, we do not need to validate as we already know the % value is good. Just set the desired option and exit. if answer == channel.BusSpeed obj.BusSpeed = answer; return; end % Validate the new bus speed by setting it in the temporary channel. try % If the set succeeded, we have a validated bus speed. configBusSpeed(channel, answer); obj.BusSpeed = answer; catch err %#ok<NASGU> throwInvalidBusSpeedError(obj, answerStr); return; end function throwInvalidBusSpeedError(obj, answerStr) %#ok<INUSD> % throwInvalidBusSpeedError Throws a configuration error. % % This local function is used to throw errors when the % configuration option is not set correctly. % Clear the validation channel. channel = []; % Error. text = getString(message('vnt:Tool:BusSpeedInvalidValue')); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'BusSpeedInvalidValue'); uiwait(e); % Relaunch the configuration dialog. configBusSpeed(obj); end end function configFiltering(obj, ~, ~) % configFiltering Allows changing of message filtering settings. % % This function presents the user with a figure allowing for % configuration of standard and extended CAN message identifier % filters. % If the tool is started, prompt to stop in order to allow % configuration of this option. if promptForCancel(obj) return; end % Launch a filtering configuration window. filterWindow = can.ToolFilter( ... obj.MsgFilterStd, ... obj.MsgFilterExt, ... get(obj.FigureWindow, 'Position')); % Wait until the window is closed. uiwait(filterWindow.FigureWindow); end function configDatabase(obj, ~, ~) % configDatabase Allows changing of the option. % % This function presents the user with a file selection dialog box % allowing them to choose a CANdb database file to be used for % message/signal decoding by the tool. % If the tool is started, prompt to stop in order to allow % configuration of this option. if promptForCancel(obj) return; end % Launch a file dialog for the user to select the database file. [fName, fPath] = uigetfile(obj.FILTERSPEC_DATABASE); % Exit if no database file was selected. if fName == 0 return; end % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); try % Open the database file and set it on the object. obj.Database = canDatabase([fPath fName]); catch err %#ok<NASGU> % If opening the file fails, inform the user. text = getString(message('vnt:Tool:DatabaseUnableToOpen')); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'DatabaseUnableToOpen'); uiwait(e); % Relaunch the configuration dialog. configDatabase(obj); end end function set.Database(obj, value) % set.Database Set the property. % % This function is a custom setter for the Database property. It sets % the received handle to a database objects and forces any changes % required in the tool as a result of adding database decoding. % % NOTE: Having this logic in a custom setter is not inherently % necessary. In some respects, it is better left in the function that % responds to the user inputting a database from the file dialog. % This code was relocated here for testing purposes. It allows the % tool tests to directly set a database object into the tool without % the limitations of needing to interact with the file dialog window. % Set the property value. obj.Database = value; % Initialize the message information maps. obj.MsgInfoMapStd = containers.Map( ... 'KeyType', 'double', 'ValueType', 'any'); %#ok<MCSUP> obj.MsgInfoMapExt = containers.Map( ... 'KeyType', 'double', 'ValueType', 'any'); %#ok<MCSUP> % Reset the table data. updateTableMessageLog(obj); end function configMaximumMessageCount(obj, ~, ~) % configMaximumMessageCount Allows changing of the option. % % This function provides the interface that allows the user to change % an option of the tool. It will launch a configuration window and % listen for any changes made there. % Build an input dialog box. text = getString(message('vnt:Tool:MaximumMessageCountDialogText', ... obj.ALLOWED_MESSAGE_COUNT_MIN, ... obj.ALLOWED_MESSAGE_COUNT_MAX)); header = getString(message('vnt:Tool:MaximumMessageCountDialogHeader')); numLines = 1; defaultAnswer = {num2str(obj.MaximumMessageCount)}; % Query for user input. answerStr = inputdlg(text, header, numLines, defaultAnswer); % Check for an empty answer indicating that the user closed or % cancelled the dialog box. We have nothing more to do then. if isempty(answerStr) return; end % Convert the answer string to a number. answer = str2double(answerStr); try % Validate the input. validateattributes(answer, {'numeric'}, ... {'positive', '<=', obj.ALLOWED_MESSAGE_COUNT_MAX, ... '>=', obj.ALLOWED_MESSAGE_COUNT_MIN, 'integer', 'scalar'}); % Set the new value. obj.MaximumMessageCount = answer; % Trim the message logs. if numel(obj.MessageLog) > obj.MaximumMessageCount obj.MessageLog = obj.MessageLog(1:obj.MaximumMessageCount); end if numel(obj.MessageLogPaused) > obj.MaximumMessageCount obj.MessageLogPaused = obj.MessageLogPaused(1:obj.MaximumMessageCount); end % Update the message table for the change in view only if the % tool is not started. When started, the change will happen % automatically on periodic update of the display. if obj.StateTool ~= obj.STATE_STARTED updateTableMessageLog(obj); end catch err %#ok<NASGU> % Error. text = getString(message('vnt:Tool:MaximumMessageCountInvalidValue', ... obj.ALLOWED_MESSAGE_COUNT_MIN, ... obj.ALLOWED_MESSAGE_COUNT_MAX)); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'MaximumMessageCountInvalidValue'); uiwait(e); % Relaunch the configuration dialog. configMaximumMessageCount(obj); return; end end function changeNumberFormat(obj, handle, ~) % changeNumberFormat Callback function for changing number format. % % This function is called when the user changes the number format % used in the display of the tool (for example from hex to dec). It % will update the number format control, set the internal tool state, % and manage any visible changes required on the tool. % If the user is reselecting the already set option, do nothing. if strcmpi(get(handle, 'Checked'), 'on') return; end % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); % Get the info on the menu option. hInfo = get(obj.MenuChildren.NumberFormat); % Set the new number format value on the tool. switch get(handle, 'Label') case 'Hexadecimal' obj.StateNumberFormat = obj.FORMAT_HEXADECIMAL; case 'Decimal' obj.StateNumberFormat = obj.FORMAT_DECIMAL; end % Uncheck all of the suboptions. set(hInfo.Children, 'Checked', 'off'); % Check the currently selected option. set(handle, 'Checked', 'on'); % Update the message table for the change in view. updateTableMessageLog(obj); end function showUniqueMessages(obj, ~, ~) % showUniqueMessages Callback function for changing the show unique option. % % This function is called when the show unique option is changed. It % will set the internal state on the tool and manage any visible % changes required for the table. % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); % Adjust the show unique message state and controls. if obj.StateShowUniqueMessages obj.StateShowUniqueMessages = false; set(obj.MenuChildren.ShowUniqueMessages, 'Checked', 'off'); set(obj.ToolbarButtons.ShowUniqueMessages, 'State', 'off'); else obj.StateShowUniqueMessages = true; set(obj.MenuChildren.ShowUniqueMessages, 'Checked', 'on'); set(obj.ToolbarButtons.ShowUniqueMessages, 'State', 'on'); end % Update the message table for the change in show unique messages. updateTableMessageLog(obj); end function launchToolHelp(obj, ~, ~) %#ok<INUSD> % launchToolHelp Shows tool help. % % This function launches tool help. % Show the help for CAN Tool. helpview(fullfile(docroot, 'toolbox', 'vnt', 'vnt.map'), 'cantool'); end function launchToolAbout(obj, ~, ~) %#ok<INUSD> % launchToolAbout Shows the About dialog. % % This function launches a dialog box showing information about the % toolbox. % Build a display string with toolbox version information. verInfo = ver('vnt'); text = getString(message('vnt:Tool:AboutMessageBoxText', ... verInfo.Name, ... verInfo.Version, ... verInfo.Release)); header = getString(message('vnt:Tool:AboutMessageBoxHeader')); % Display a message dialog with the about information. msgbox(text, header, 'modal'); end function start(obj, ~, ~) % start Callback function for starting the tool. % % This function is called when tool is started. It will build and % activate the currently selected channel as well as manage the % internal tool state and visible changes required on start. Note % that start can also mean resume if start is engaged when the tool % is paused. % Add a protection against double push of the start button. This % can happen due to lag and if allowed results in double timers % and problems operating the tool. if obj.StateTool == obj.STATE_STARTED return; end % Check if we are resuming (i.e. restarting from pause). if obj.StateTool == obj.STATE_PAUSED % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); % Set the new tool state. obj.StateTool = obj.STATE_STARTED; updateFigure(obj); % Update the message table to restore to the an active state % from the pause state. updateTableMessageLog(obj); return; end % Clear any messages. clearMessages(obj); % Build a Channel connected to the currently selected device. switch obj.ChannelInfoCurrent.Vendor case {'Vector', 'Kvaser','MathWorks'} obj.Channel = canChannel( ... obj.ChannelInfoCurrent.Vendor, ... obj.ChannelInfoCurrent.Device, ... obj.ChannelInfoCurrent.DeviceChannelIndex); case {'NI', 'PEAK-System'} try obj.Channel = canChannel( ... obj.ChannelInfoCurrent.Vendor, ... obj.ChannelInfoCurrent.Device); catch err % If the channel fails to create due to another active % connection already existing, inform the user with a % dialog box. if strcmpi(err.identifier, 'vnt:Channel:ActiveConnectionExists') text = getString(message('vnt:Tool:ActiveConnectionExists')); e = errordlg(text, 'Error', 'modal'); set(e, 'Tag', 'ActiveConnectionExists'); uiwait(e); return; else % Rethrow any other error. rethrow(err); end end end % If a database is configured for the tool, set it on the channel. if ~isempty(obj.Database) obj.Channel.Database = obj.Database; end % Check the desired bus speed against the actual value % currently set on the channel. if obj.BusSpeed ~= obj.Channel.BusSpeed try % Setting the bus speed is done in a try/catch to protect the % figure from an improper value. No action is taken on error. % The bus speed will revert back to its previous state. configBusSpeed(obj.Channel, obj.BusSpeed); catch err %#ok<NASGU> % Warn that we cannot change the bus speed. text = getString(message('vnt:Tool:BusSpeedUnableToSetOnStart', obj.Channel.BusSpeed)); w = errordlg(text, 'Warning', 'modal'); set(w, 'Tag', 'BusSpeedUnableToSetOnStart'); uiwait(w); end end % Set up any custom filters for the channel. can.Tool.setMessageFilter(obj.Channel, obj.MsgFilterStd); can.Tool.setMessageFilter(obj.Channel, obj.MsgFilterExt); % Start the channel. start(obj.Channel); % Configure and start a timer for updating the message table. obj.UpdateTimer = timer(... 'Period', obj.UPDATE_RATE,... 'ExecutionMode', 'fixedSpacing', ... 'TimerFcn', @obj.updateDisplay); start(obj.UpdateTimer); % Set the new tool state. obj.StateTool = obj.STATE_STARTED; updateFigure(obj); end function pause(obj, ~, ~) % pause Callback function for pausing the tool. % % This function is called the tool is paused. It will freeze the % message display with the current values and manage the internal % state of the tool. % Set the new tool state. obj.StateTool = obj.STATE_PAUSED; updateFigure(obj); % Copy the current message logs into paused logs. The reason for this % is because messages will continue to process behind the scenes even % when the display is paused. If a user then saves messages or changes % show unique messages, we want them to save or view messages in the % snapshot of time when they hit the pause button. Unpausing will allow % a return to the "real time" view of message traffic. obj.MessageLogPaused = obj.MessageLog; obj.MessageLogUniquePaused = obj.MessageLogUnique; % Update the table to show signal tables. updateTableMessageLog(obj); end function stop(obj, ~, ~) % stop Callback function for stopping the tool. % % This function is called when the tool is stopped. It will shut down % update operations and release the channel. Note that we do not % clear messages though. They must be kept so the user can % view/analyze them. % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); % Set the new tool state. obj.StateTool = obj.STATE_STOPPED; updateFigure(obj); % Clear the timer. stop(obj.UpdateTimer); delete(obj.UpdateTimer); obj.UpdateTimer = []; % Clear the channel. obj.Channel = []; % Update the table to show signal tables. updateTableMessageLog(obj); end function save(obj, ~, ~) % save Saves the messages stored in the tool to a file. % % This function saves the currently logged messages to file. % Get the file information of the current folder. files = dir; % Get the names of the files in the current folder. fileNames = {files.name}; % Find any message logs in the file list. fileMatches = strmatch('VNT CAN Log', fileNames); fileMatchCount = numel(fileMatches); % Set the log file enumeration into the file name. if fileMatchCount == 0 % Set a blank enumeration if no log files currently exist. saveFileString = ''; else % Get the logs files from the full list. logFiles = files(fileMatches); % Get the matched file names. logFileNames = {logFiles.name}; % Loop through each log file and extract its enumeration value. for fi = 1:numel(logFileNames) % Use the parentheses as a token to get to the enum. [~, enumString] = strtok(char(logFileNames(fi)), '('); % Before extracting the enum value, check that the string was % parsable with the parentheses token. if isempty(enumString) % If the string is empty, this is the first log file present. enumValues(fi) = 1; %#ok<AGROW> else % Otherwise, get the value from the string. enumValues(fi) = str2double(enumString(:, 2:end - 5)); %#ok<AGROW> end end % Get the highest current enum value and add one to get the new % value for the new log file. newEnumValue = max(enumValues) + 1; % Set the new value into a string for the file name. saveFileString = [' (' num2str(newEnumValue) ')']; end % Complete building of the log file name. logFileName = ['VNT CAN Log' saveFileString]; % Give the user a file save dialog with the determined name as % the default save file name. [fName, fPath] = uiputfile({[logFileName '.mat']}, 'Save'); % If the user cancels the file selection dialog, then cancel the % save operation. if fName == 0 return; end % Set the final save file name and path based on the user selection. logFileName = [fPath fName]; % Set the cursor mode. set(obj.FigureWindow, 'Pointer', 'watch'); drawnow limitrate; cleanupObj = onCleanup(@()set(obj.FigureWindow, 'Pointer', 'arrow')); % Check the pre-save state of the figure. if obj.StateTool == obj.STATE_PAUSED % When paused, save the messages frozen on the display. saveMessageLog = obj.MessageLogPaused; else % Otherwise save the active log. saveMessageLog = obj.MessageLog; end % Reverse the order of the message log to save. We store messages % inside the tool with the most recent first. We normally keep them % with the most recent as the last index though. This is how they % should be saved. saveMessageLog = saveMessageLog(end:-1:1); % Convert to the structures to objects to save. if isempty(obj.Database) saveMessageLog = can.Message(saveMessageLog); %#ok<NASGU> else saveMessageLog = can.Message(saveMessageLog, obj.Database); %#ok<NASGU> end % Save the message log to the current working directory. save(logFileName, 'saveMessageLog'); end function clear(obj, ~, ~) % clear Clears all messages. % % This function clears all message information from the tool. % Delegate the clear messages method. clearMessages(obj); % Update the figure controls. This is done primarily to control % the state of the save and clear controls. With message % information removed from the tool, both of these controls should % be disabled. The control function will take care of this as well % as other special state management. updateFigure(obj); end function updateDisplay(obj, ~, ~) % updateDisplay Timer callback function to update the display. % % This function is the processing function for CAN Tool. It runs off % the timer when the tool is active to receive messages and update % the display. % Exit immediately if there is no channel to use. It can happen % on close/delete of the tool where the timer callback will run % as the tool is being deconstructed. if isempty(obj.Channel) return; end % Return immediately if no messages are available. if obj.Channel.MessagesAvailable == 0 return; end % Get all available messages from the channel. msgRx = receiveRaw(obj.Channel); % Enable the clear controls when messages are received. set(obj.MenuChildren.Clear, 'Enable', 'On'); set(obj.ToolbarButtons.Clear, 'Enable', 'On'); % Flip the order of the new message array. We do this to put the % most recent messages in the tool on top of the list. msgRx = msgRx(end:-1:1); % Perform an extract recent. This is done whether we are in unique % display mode or not in order to maximize efficiency. The % extraction is performed on the results of the last extraction % plus the new messages received instead of the entire log to save % time. It is preferable to do smaller extractions each time % interval than to risk doing large extractions occasionally which % would stutter the figure. obj.MessageLogUnique = obj.extractRecent([obj.MessageLogUnique msgRx]); % Add the new messages onto the front of the message log. obj.MessageLog = [msgRx obj.MessageLog]; % The log must be clipped if the total count of existing messages % plus the newly received ones are over the log size threshold. obj.MessageLog = obj.MessageLog(1:min(end, obj.MaximumMessageCount)); % Return without updating the display when paused. if obj.StateTool == obj.STATE_PAUSED return; end % Display on the table based on the setting of show unique messages. if obj.StateShowUniqueMessages % Update the table with a full rewrite. updateTableMessageLog(obj); else % PERFORMANCE: It would be cleanest and easiest here to just use % the updateTableMessageLog method for all cases; however, when % the tool is running and showing all messages, we can not % afford the time to reconvert every message every iteration. % If more new messages have arrived than will fit on the table, % we can discard the extra messages. msgRx = msgRx(1:min(end, obj.MaximumMessageCount)); % Create the cell data for the new messages. newTableData = obj.convertMessageObjectsToTable(msgRx); % Concatenate the new messages with the messages already in % the table to make a single list with the newest messages on % top for proper order. obj.MessageTableCellData = cat(1, newTableData, obj.MessageTableCellData); % Cut off any messages in the concatenated table over the maximum % threshold. We remove from the bottom of the array to get rid % of the oldest messages first. obj.MessageTableCellData = obj.MessageTableCellData( ... 1:min(size(obj.MessageTableCellData, 1), obj.MaximumMessageCount), :); % Set the data into the table. setTableData(obj.MessageTable, obj.MessageTableCellData, uint16(1)); end % Force a redraw on the figure to update the table. drawnow limitrate; end function clearMessages(obj) % clearMessages Removes all message history from the tool. % % This function is used to delete any currently stored messages % history from the tool. This includes both the internally stored % messages logs as well as clearing the visible message table. % Clear the internal message logs. obj.MessageLog = obj.EMPTY_MSG_STRUCT; obj.MessageLogUnique = obj.EMPTY_MSG_STRUCT; obj.MessageLogPaused = obj.EMPTY_MSG_STRUCT; obj.MessageLogUniquePaused = obj.EMPTY_MSG_STRUCT; % Clear any existing signal table data. clearSignalTableData(obj); % Clear the message table by setting blank information into it. obj.MessageTableCellData = []; setTableData(obj.MessageTable, obj.TABLE_BLANK_ROW, uint16(1)); end function msgCell = convertMessageObjectsToTable(obj, msg) % convertMessageObjectsToTable Converts message object to table cell data. % % This function extracts displayed message information from the % message objects and converts it into cell array format for display % on the table. % Check for empty and return a blank set of data if so. if isempty(msg) msgCell = obj.TABLE_BLANK_ROW; return; end % Call to the MEX helper function to process the message structure % to string table data conversion. This is done for performance. % The call differs whether a database is being used or not as with % a database we get the message names. if isempty(obj.Database) msgCell = mexCANToolTableConverter( ... msg, ... obj.StateNumberFormat, ... false); else msgCell = mexCANToolTableConverter( ... msg, ... obj.StateNumberFormat, ... true, ... obj.Database.Path, ... obj.Database.HandleName); end end function updateTableMessageLog(obj) % updateTableMessageLog Displays the correct current message log. % % This function determines which message log is currently viewable on % the tool table given the current state of the tool. It will then % update the table accordingly. % The correct message log to display on the table is dependent on % whether the tool is paused or not and also whether the show % unique messages option is active or not. if obj.StateTool == obj.STATE_PAUSED if obj.StateShowUniqueMessages % Unique + paused. targetLog = obj.MessageLogUniquePaused; % Update the signal table data. setSignalTableData(obj, targetLog); else % Normal + Paused. targetLog = obj.MessageLogPaused; % Update the signal table data. setSignalTableData(obj, targetLog); end else if obj.StateShowUniqueMessages % Unique + Started. targetLog = obj.MessageLogUnique; % Update the signal table data. setSignalTableData(obj, targetLog); else % Normal + Started. targetLog = obj.MessageLog; % Update the signal table data if the tool is stopped. if obj.StateTool == obj.STATE_STOPPED setSignalTableData(obj, targetLog); else clearSignalTableData(obj); end end end % Turn the new target message log into table cell data. obj.MessageTableCellData = ... convertMessageObjectsToTable(obj, targetLog); % Set the data on the table. Note that the final argument indicates % the first table row for the data and must be casted for the % interface to work. setTableData(obj.MessageTable, obj.MessageTableCellData, uint16(1)); end function setSignalTableData(obj, msg) % setSignalTableData Builds new signal tables. % % This function builds tables of signal information that are child % tables of each row containing the decoded signal data for the % message contained in a given row. % Do nothing if no database is present. if isempty(obj.Database) return; end % Set the column names. columnNames = obj.SIGNAL_TABLE_COLUMN_NAMES; % Build a blank table for rows without signal data. blankData = cell(1, 2); blankData{1, 1} = ' '; blankData{1, 2} = ' '; % Build the cell data for each message. for ii = 1:numel(msg) % Look up the message. msgInfo = obj.getMessageInfo(msg(ii)); % If there is no signal data to set, send a blank table. if isempty(msgInfo) % Add the blank signal data to the table. addChildData(obj.MessageTable, ii, columnNames, blankData); continue; end % Get the signal names for this message. signalNames = msgInfo.Signals; % Initialize the cell data to the appropriate signal number. cellData = cell(numel(signalNames), 2); % PERFORMANCE: Get the signals structure from the message % object into temporary storage for faster access. [~, ~, signalValues] = obj.Database.getSignalValueAll(msgInfo.Name, msg(ii).Data); % Loop through the signals and add to the cell data. We % maintain a separate index for loading versus looping in order % to get the signals to show up in proper alphabetical order. idx = 1; for jj = numel(signalNames):-1:1 % Load the signal name. cellData{idx, 1} = signalNames{jj}; % Load the value. cellData{idx, 2} = sprintf('%f', signalValues.(signalNames{jj})); % Increment the loading index. idx = idx + 1; end % Add the signal data to the table. addChildData(obj.MessageTable, ii, columnNames, cellData); end end function clearSignalTableData(obj) % clearSignalTableData Blanks out signal table data. % % This function is used to write blanks to all of the signal table % data in the tool. % Build the blank table. cellData = cell(1, 2); cellData{1, 1} = 'Unavailable'; cellData{1, 2} = 'Unavailable'; % Loop through each table entry and blank it. for ii = 1:obj.MaximumMessageCount % Add the blank signal data to the table. addChildData(obj.MessageTable, ii, obj.SIGNAL_TABLE_COLUMN_NAMES, cellData); end end function updateFigure(obj) % updateFigure Reconfigures the tool figure in response to state change. % % This function is used to modify the current active/inactive status % of the tool. This includes enabling or disabling menu and/or % toolbar controls as well managing the text shown on the figure % header. % Enable/disable some controls based on message count. if isempty(obj.MessageLog) % Disable save. set(obj.MenuChildren.Save,'Enable', 'Off'); set(obj.ToolbarButtons.Save,'Enable', 'Off'); % Disable clear. set(obj.MenuChildren.Clear,'Enable', 'Off'); set(obj.ToolbarButtons.Clear,'Enable', 'Off'); else % Enable save. set(obj.MenuChildren.Save,'Enable', 'On'); set(obj.ToolbarButtons.Save,'Enable', 'On'); % Enable clear. set(obj.MenuChildren.Clear,'Enable', 'On'); set(obj.ToolbarButtons.Clear,'Enable', 'On'); end % Enable/disable controls based on the tool state. switch obj.StateTool case obj.STATE_STARTED set(obj.MenuChildren.Start, 'Enable', 'Off'); set(obj.MenuChildren.Pause, 'Enable', 'On'); set(obj.MenuChildren.Stop, 'Enable', 'On'); set(obj.ToolbarButtons.Start, 'Enable', 'Off'); set(obj.ToolbarButtons.Pause, 'Enable', 'On'); set(obj.ToolbarButtons.Stop, 'Enable', 'On'); % Disable save. set(obj.MenuChildren.Save,'Enable', 'Off'); set(obj.ToolbarButtons.Save,'Enable', 'Off'); case obj.STATE_PAUSED set(obj.MenuChildren.Start, 'Enable', 'On'); set(obj.MenuChildren.Pause, 'Enable', 'Off'); set(obj.MenuChildren.Stop, 'Enable', 'On'); set(obj.ToolbarButtons.Start, 'Enable', 'On'); set(obj.ToolbarButtons.Pause, 'Enable', 'Off'); set(obj.ToolbarButtons.Stop, 'Enable', 'On'); case obj.STATE_STOPPED set(obj.MenuChildren.Start, 'Enable', 'On'); set(obj.MenuChildren.Pause, 'Enable', 'Off'); set(obj.MenuChildren.Stop, 'Enable', 'Off'); set(obj.ToolbarButtons.Start, 'Enable', 'On'); set(obj.ToolbarButtons.Pause, 'Enable', 'Off'); set(obj.ToolbarButtons.Stop, 'Enable', 'Off'); end % Build a new header string for the tool. It is expected that prior % to calling this method that the current channel info structure % has been populated with new information. switch obj.ChannelInfoCurrent.Vendor case {'Kvaser', 'Vector','MathWorks'} headerStr = sprintf('Vehicle CAN Bus Monitor | %s %s (Channel %d)', ... obj.ChannelInfoCurrent.Vendor, ... obj.ChannelInfoCurrent.Device, ... obj.ChannelInfoCurrent.DeviceChannelIndex); case {'NI', 'PEAK-System'} headerStr = sprintf('Vehicle CAN Bus Monitor | %s %s (%s)', ... obj.ChannelInfoCurrent.Vendor, ... obj.ChannelInfoCurrent.DeviceType, ... obj.ChannelInfoCurrent.Device); end % Add additional information based on tool state. switch obj.StateTool case obj.STATE_STARTED headerStr = [headerStr ' | Started']; headerStr = sprintf([headerStr ' - %d kbps'], ... obj.Channel.BusSpeed / 1000); case obj.STATE_PAUSED headerStr = [headerStr ' | Paused']; headerStr = sprintf([headerStr ' - %d kbps'], ... obj.Channel.BusSpeed / 1000); case obj.STATE_STOPPED headerStr = [headerStr ' | Stopped']; end % Set the new information into the header. set(obj.FigureWindow, 'Name', headerStr); drawnow limitrate; end function out = promptForCancel(obj) % promptForCancel Queries the user to cancel an operation based on state. % % This function prompts the user for input on whether to stop the % tool if that is required to perform some action. % Initialize the output to ensure operation continues as the % default case. This covers the tool to act appropriately when % this method is called and the tool is already stopped. out = false; % If the tool not stopped, prompt if the user would like to stop. if obj.StateTool ~= obj.STATE_STOPPED % Prompt the user. text = getString(message('vnt:Tool:PromptForCancelDialogText')); buttonName = questdlg(text, 'Continue?', 'Yes', 'No', 'No'); % Check the user input. if strcmpi(buttonName, 'Yes') % Stop the tool if requested. stop(obj); out = false; else % Return true as the user chose to cancel the action and % leave the tool running instead of forcing the tool to % stop. out = true; end end end function extracted = extractRecent(obj, msg) % extractRecent Returns the most recent occurrence of the specified message(s). % % This method takes an array of message structures and returns an % array of message structures that is a reduced set. The reduced set % contains only one entry for each unique ID found in the input. % Get the ID and extended values. msgDetails = [msg.ID]'; msgDetails(:,2) = [msg.Extended]'; % Find all unique ID/Extended in the message array. msgU = unique(msgDetails, 'rows'); % Initialize the output in case no messages are found. extracted = obj.EMPTY_MSG_STRUCT; % Loop through each ID/Extended requested for extraction. for idi = 1:size(msgU, 1) % Using logical indexing, obtain matches of all messages having % matching ID/Extended properties. msgByID = msg((msgU(idi,1) == msgDetails(:,1)) & (msgU(idi,2) == msgDetails(:,2))); % Note that an implementation was tested using findobj in place of % logical indexing; however, logical indexing proved to be much % faster when extracting with a large number of target ID/Extended % values.The findobj code is as follows: % msgByID = findobj(obj, 'ID', uint32(ID(idi)), 'Extended', extended(idi)); % Check for matches. if ~isempty(msgByID) % Get the timestamp values. timestamps = [msgByID.Timestamp]; % Get the latest occurring message of the matches. latestMessage = msgByID(timestamps == max(timestamps)); % Set the actual extracted message as the last one in % the array. This addresses an issue should multiple % messages have the same timestamp because they were % received faster than the resolution of the timestamp. % This is a possible scenario with virtual channels. extracted(end + 1) = latestMessage(end); %#ok<AGROW> end end end function info = getMessageInfo(obj, msg) % getMessageInfo Get database message information for a message. % % This method returns a messsage information structure for the input % message structure representing a message. It does a database search % for the information if the information is not already stored % locally in the tool. % Use try/catch for better performance than isKey for the map. try % Check the appropriate map depending on if the message has a % standard or extended ID. if msg.Extended info = obj.MsgInfoMapExt(msg.ID); else info = obj.MsgInfoMapStd(msg.ID); end catch err %#ok<NASGU> % The message information was not already in the map, so look % it up in the database. info = obj.Database.privateMessageInfoByID(msg.ID, msg.Extended); % Add the information to the appropriate map. if msg.Extended obj.MsgInfoMapExt(msg.ID) = info; else obj.MsgInfoMapStd(msg.ID) = info; end end end end methods (Static) function setMessageFilter(channel, filterSettings) % setMessageFilter Configures filtering settings on a channel. % % This method configures filter settings according to the given input % on the channel also provided as input. Depending on the state of % the filter, the channel is configured accordingly. % Set the filter based on the configured state. switch filterSettings.State case can.FilterStates.AllowAll channel.filterAllowAll(filterSettings.Type); case can.FilterStates.BlockAll channel.filterBlockAll(filterSettings.Type); case can.FilterStates.AllowOnly channel.filterAllowOnly(filterSettings.AllowOnlyNumeric, filterSettings.Type); end end end end