gusucode.com > vnt工具箱matlab源码程序 > vnt/vnt/+can/Message.m
classdef Message < hgsetget & ... vnt.internal.mixin.message.Extractions & ... vnt.internal.mixin.message.SignalAccess % Message Handle to a CAN message. % % This class represents a CAN message. It contains properties to % act as both a simple raw CAN message as well as a physical message % using CAN database files providing a signals based interface. The methods % provided by the class allow for manipulation of CAN data as well as % various analysis assisting functionality. % % See also VNT. % Authors: JDP % Copyright 2008-2015 The MathWorks, Inc. properties (Dependent, SetAccess = 'private') % ID - The numeric CAN identifier of the message. ID % Extended - A logical value indicating if the message ID is % standard or extended. Extended % Name - The symbolic name of the message. Name % Database - A handle to a CAN database file containing message and % signal information for this message. Database % Error - A logical value indicating if the message is an error % frame or a valid CAN message. Error end properties (Dependent) % Remote - A logical value indicating if the message is a remote % frame or a regular CAN message. Remote % Timestamp - The hardware logged time of reception for the message. Timestamp % Data - CAN message data array. Data % Signals - A structure of the signals contained within the % message with a named field for each signal. Signals end properties (SetAccess = 'private', GetAccess = 'private') % Version - Indicates the version number of the class for % determining the difference between current and out of date % versions of message objects. % % Version 3.0: R2015a: Removed PrivateStruct internal structure % architecture due to increases in MATLAB performance. % Version 2.0: R2011a: Version with dynamic properties removed % entirely. % Version 1.0: R2010a: Version with dynamic properties % in a default disabled state for deprecation. Includes other % performance oriented changes such as the single % PrivateStruct design. % R2009a - R2009b: No Version property used. Class was built with % internal Private properties for each public dependent % property. Version end properties % UserData - Storage for custom information. UserData end % Internal property private storage. Used because some properties are % related in their set/get operations and to bypass the overhead of % validation when referencing properties internally. properties (Hidden, SetAccess = protected) PrivateID PrivateExtended PrivateError PrivateRemote PrivateTimestamp PrivateData PrivateDatabase PrivateName end properties (Hidden, Dependent, SetAccess = protected) PrivateRawStruct end events % DataChanged - This event fires when data on the message changes. % This includes changes to the Data property as well as whenever % a physical signal value is changed. DataChanged end methods function obj = Message(varargin) % Message Construct a CAN message. % Build the message directly from a structure. % MSG = Message(STRUCT) % MSG = Message(STRUCT, DATABASE) if (nargin == 1 || nargin == 2) && isstruct(varargin{1}) % Check if this is a Simulink logged structure of arrays. If % so, it must be converted into an array of structures before % turning the data into CAN message objects. if isfield(varargin{1}, 'Length') % Get the structure of arrays into a local variable. msgStructOfArrays = varargin{1}; % Preallocate the message structures. msgStruct(numel(msgStructOfArrays.ID)) = struct( ... 'ID', [], 'Extended', [], 'Remote', [], ... 'Error', [], 'Timestamp', [], 'Data', []); % Loop through and copy the relevant data into structures. for ii = 1:numel(msgStructOfArrays.ID) msgStruct(ii).ID = msgStructOfArrays.ID(ii); msgStruct(ii).Extended = msgStructOfArrays.Extended(ii); msgStruct(ii).Remote = msgStructOfArrays.Remote(ii); msgStruct(ii).Error = msgStructOfArrays.Error(ii); msgStruct(ii).Timestamp = msgStructOfArrays.Timestamp(ii); % Set the message data by using the Length field to % truncate the data array as needed. if msgStructOfArrays.Length(ii) == 0 msgStruct(ii).Data = uint8([]); else msgStruct(ii).Data = msgStructOfArrays.Data(:, ii)'; msgStruct(ii).Data = ... msgStruct(ii).Data(1:msgStructOfArrays.Length(ii)); end end else % Copy the regular structure input to a local variable. msgStruct = varargin{1}; end % Create message objects equal in count to the number of % structures received which will be configured and % returned from the constructor. obj = can.Message.newarray(1, numel(msgStruct)); % Create the physical message fields initialized as blank. msgStruct(end).Database = []; msgStruct(end).Name = ''; [msgStruct.Name] = deal(''); % If a database was provided, we need to look up each message % and populate physical message fields accordingly. if nargin == 2 % Loop through each message. for ii = 1:numel(msgStruct) % Query the database. msgInfo = privateMessageInfoByID( ... varargin{2}, ... msgStruct(ii).ID, ... msgStruct(ii).Extended); % Check for found information. if ~isempty(msgInfo) % Set the physical properties. msgStruct(ii).Database = varargin{2}; msgStruct(ii).Name = msgInfo.Name; end end end % Load the structure field values into the object. [obj.PrivateID] = msgStruct.ID; [obj.PrivateExtended] = msgStruct.Extended; [obj.PrivateError] = msgStruct.Error; [obj.PrivateRemote] = msgStruct.Remote; [obj.PrivateTimestamp] = msgStruct.Timestamp; [obj.PrivateData] = msgStruct.Data; [obj.PrivateDatabase] = msgStruct.Database; [obj.PrivateName] = msgStruct.Name; % Create a single message object from database information. % MSG = Message(DATABASE, NAME) elseif nargin == 2 % Copy the input to local variables. database = varargin{1}; msgName = varargin{2}; % Validate the database argument. validateattributes(database, {'can.Database'}, ... {'nonempty', 'scalar'}, ... 'Message', 'DATABASE'); % Validate the name argument. validateattributes(msgName, {'char'}, ... {'nonempty', 'row'}, ... 'Message', 'NAME'); % Check for the message in the database. msgInfo = privateMessageInfoByName(database, msgName); % Error on an unfound message. if isempty(msgInfo) error(message('vnt:Message:MessageNotFoundInDatabase', msgName)); end % Do not allow creation of a CAN message object using database % information if the length of the data is defined as >8 bytes. if msgInfo.Length > 8 error(message('vnt:Message:InvalidMessageDefinitionLength')); end % Set the object properties to complete construction. obj.PrivateID = uint32(msgInfo.ID); obj.PrivateExtended = msgInfo.Extended; obj.PrivateError = false; obj.PrivateRemote = false; obj.PrivateTimestamp = 0; obj.PrivateData = zeros(1, msgInfo.Length, 'uint8'); obj.PrivateDatabase = database; obj.PrivateName = msgInfo.Name; % Create a single message object from raw information. % MSG = Message(ID, EXTENDED, DATALENGTH) elseif nargin == 3 % Get the message information from the input. id = varargin{1}; extended = varargin{2}; dataLength = varargin{3}; % Validate the ID argument. validateattributes(id, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', ... 'nonnegative', 'nonsparse', 'real', 'scalar'}, ... 'Message', 'ID'); % Validate the extended argument. validateattributes(extended, {'logical'}, ... {'nonempty', 'scalar'}, ... 'Message', 'EXTENDED'); % Validate the maximum value of the ID based on the type. if extended maxID = 536870911; else maxID = 2047; end validateattributes(id, {'numeric'}, {'<=', maxID}, ... 'Message', 'ID'); % Validate the dataLength argument. validateattributes(dataLength, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', ... 'nonnegative', 'nonsparse', 'real', 'scalar', '<=', 8}, ... 'Message', 'DATALENGTH'); % Set the object properties to complete construction. obj.PrivateID = uint32(id); obj.PrivateExtended = extended; obj.PrivateError = false; obj.PrivateRemote = false; obj.PrivateTimestamp = 0; obj.PrivateData = zeros(1, dataLength, 'uint8'); obj.PrivateDatabase = []; obj.PrivateName = ''; else % Use nargchk to error on incorrect number of arguments. narginchk(3,3); end % Set the object version number. [obj.Version] = deal(3.0); end function out = get.ID(obj) out = obj.PrivateID; end function out = get.Extended(obj) out = obj.PrivateExtended; end function out = get.Error(obj) out = obj.PrivateError; end function out = get.Remote(obj) out = obj.PrivateRemote; end function out = get.Timestamp(obj) out = obj.PrivateTimestamp; end function out = get.Data(obj) out = obj.PrivateData; end function out = get.Database(obj) out = obj.PrivateDatabase; end function out = get.Name(obj) out = obj.PrivateName; end function out = get.PrivateRawStruct(obj) out.ID = obj.PrivateID; out.Extended = obj.PrivateExtended; out.Error = obj.PrivateError; out.Remote = obj.PrivateRemote; out.Timestamp = obj.PrivateTimestamp; out.Data = obj.PrivateData; end function set.Remote(obj, value) % Validate the new value. validateattributes(value, {'logical'}, ... {'nonempty', 'scalar'}, ... 'set.Remote', 'VALUE'); % If remote is being set to true, set the message data to all % zeros as data has no relevance to remote frames. if value obj.PrivateData = zeros(1, numel(obj.PrivateData), 'uint8'); end % Set the property. obj.PrivateRemote = value; end function set.Timestamp(obj, value) % Validate the new value. validateattributes(value, {'numeric'}, ... {'finite', 'nonempty', 'nonnan', 'nonnegative', ... 'nonsparse', 'real', 'scalar'}, ... 'set.Timestamp', 'VALUE'); % Set the property. obj.PrivateTimestamp = value; end function set.Database(obj, value) % Check for empty input. if isempty(value) % An empty input means that the user is trying to remove % the physical database information from this message. % Reinitialize the physical properties to their raw defaults. obj.PrivateDatabase = []; obj.PrivateName = ''; return; end % Validate the new value. validateattributes(value, {'can.Database'}, ... {'nonempty', 'scalar'}, ... 'set.Database', 'VALUE'); if ~isvalid(value) error(message('vnt:Message:InvalidDatabase')); end % Get the message information from the database file. msgInfo = privateMessageInfoByID(value, obj.PrivateID, obj.PrivateExtended); if isempty(msgInfo) % No message definition exists for this message. Clear % the physical properties. obj.PrivateDatabase = []; obj.PrivateName = ''; return; end % Store new property values. obj.PrivateDatabase = value; obj.PrivateName = msgInfo.Name; % Fix the length of the message data to ensure it matches % the length defined in the database. if numel(obj.PrivateData) > msgInfo.Length % Cut off the extra data bytes. obj.PrivateData = obj.PrivateData(1:msgInfo.Length); elseif numel(obj.PrivateData) < msgInfo.Length % Pad the extra data bytes onto the message. for ii = 1:(msgInfo.Length - numel(obj.PrivateData)) obj.PrivateData = [obj.PrivateData 0]; end end end function set.Data(obj, value) % If input is empty, set the Data immediately to empty and % return. No validation or other processing is required. if isempty(value) obj.PrivateData = []; return; end % Validate the new value for proper type. validateattributes(value, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', 'nonnegative', ... 'nonsparse', 'real', 'row'}, ... 'set.Data', 'VALUE'); % Validate the new value for proper length. if (numel(value) < 0) || (numel(value) > 8) error(message('vnt:Message:InvalidDataLength')); end % Extra validation is required when the object is physical. if ~strcmpi(obj.PrivateName, '') % Verify the length of the new Data against the current % length of the message data. The user is not allowed to % change the length of a message that is physical. if numel(value) ~= numel(obj.PrivateData) error(message('vnt:Message:MismatchedDataLength')); end end % Check if this is a remote frame. if obj.PrivateRemote % If the message is configured as a remote frame, set % the data to an array of the same length as the input; % however, set the data values to all zero. obj.PrivateData = zeros(1, numel(value), 'uint8'); else % Set the property while casting to uint8 type. obj.PrivateData = uint8(value); end % Fire the data changed event. notify(obj, 'DataChanged'); end function out = get.Signals(obj) % Use the mixin function to get the signal values. out = obj.getSignalValues(); end function set.Signals(obj, value) % Use the mixin function to set the signal values. obj.setSignalValues(value); % Fire the data changed event. notify(obj, 'DataChanged'); end function attachDatabase(obj, database) % attachDatabase Attach or remove a database on messages. % % attachDatabase(MESSAGE, DATABASE) attaches a DATABASE to the % MESSAGE(s). This forces the MESSAGE(s) to interpret % themselves in physical form so that a signals based % interaction with MESSAGE data may be used. % % attachDatabase(MESSAGE, []) clears an attached DATABASE from % the MESSAGE(s). This forces the MESSAGE(s) to interpret % themselves in raw form. % % Note that if MESSAGE is an array, the DATABASE will be % attached to each entry in the array. Also note that if % a MESSAGE is not found in the database, the Database property % of the MESSAGE will still be set; however, the message will % present like a raw message only. % % Examples: % database = canDatabase('TestDatabase.dbc') % message = receive(channel, Inf) % attachDatabase(message, database) % % See also VNT. % Loop through the array of messages received as input. for ii = 1:numel(obj) % Set the Database property of the object to the value % received as input. Note that we use the regular Database % property and not the Private storage because we want % the database input to be validated before being used. obj(ii).Database = database; end end function pack(obj, value, startBit, signalSize, byteOrder) % pack Load a raw signal value into message data. % % pack(MESSAGE, VALUE, STARTBIT, SIGNALSIZE, BYTEORDER) takes % a set of input parameters to load raw signal VALUE(s) into MESSAGE(s). % % Inputs: % MESSAGE - The message in which to pack the signal. % VALUE - The raw signal value to pack. % STARTBIT - The signal's starting bit location in the message data % with a value between 0 and 63. The STARTBIT represents the % least significant bit position in the CAN data of the signal. % SIGNALSIZE - The signal's length in bits with a value between 1 and 64. % BYTEORDER - The signal's endian format as 'LittleEndian' or % 'BigEndian'. This is often referenced as Intel for % 'LittleEndian' and Motorola for 'BigEndian'. % % Note that MESSAGE can be an array of CAN messages. When given as an % array, the raw signal value is packed into each entry in the array % individually. These signal values are then returned as an array of % equal size to MESSAGE. The VALUE array is ordered the same as in the % original MESSAGE array. Each MESSAGE in the array must have the % same definition. % % Example: % pack(message, -25, 0, 16, 'LittleEndian') % % See also VNT. % Check the argument count. narginchk(5,5); % Validate the matching size of the message and value arguments. if numel(obj) ~= numel(value) error(message('vnt:Message:MessageToValueCountMismatch')); end % Validate the argument startBit. validateattributes(startBit, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', 'nonnegative', ... 'nonsparse', 'real', 'scalar', ... '<', (numel(obj(1).PrivateData) * 8)}, ... 'pack', 'STARTBIT'); % Validate the argument signalSize. validateattributes(signalSize, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', 'positive', ... 'real', 'scalar', '<=', 64}, ... 'pack', 'SIGNALSIZE'); % Validate the argument byteOrder. byteOrder = validatestring(byteOrder, ... {'LittleEndian', 'BigEndian'}, ... 'pack', 'BYTEORDER'); % Recast the type of any signed or floating point types as an % appropriately sized unsigned type to use MATLAB bit get and set % functions. switch class(value) % Do nothing for unsigned integer types. case {'uint8', 'uint16', 'uint32', 'uint64'} % Cast all signed integer types to unsigned type of corresponding % size using typecast to preserve the original bit field. case 'int8' value = typecast(value, 'uint8'); case 'int16' value = typecast(value, 'uint16'); case 'int32' value = typecast(value, 'uint32'); case 'int64' value = typecast(value, 'uint64'); % All single values are treated as 32 bit floating point numbers. % They are typecasted to uint32 to preserve the original bit field. case 'single' value = typecast(value, 'uint32'); % Not all double types are automatically intended to be floating % point numbers. Doubles with a signal size of 64 bits will be % treated as floating point values. Doubles with a signal size of 1 % to 63 will be treated as integer values. case 'double' % For doubles that may truly be integer values, first perform % a signed integer cast. if (signalSize < 64) value = int64(value); end % Typecast all doubles into 64 bit unsigned integers to % preserve the exact bit field. value = typecast(value, 'uint64'); otherwise % Error on any unsupported types. error(message('vnt:Message:InvalidValueType')); end % Check the endian configuration. bigEndian = strcmpi(byteOrder, 'BigEndian'); % Loop to pack all messages. for i = 1:numel(obj) % Copy the Data property into temporary storage. This temp Data is % what is packed. In case of error, the original Data is not % partially overwritten. tmpData = obj(i).PrivateData; % Set the first target bit as the start bit. Add one to adjust % indexing from zero based to one based. targetBit = startBit + 1; % Perform packing operations on each bit of the signal. for j = 1:signalSize try % Determine the byte position and bit position within the % byte to write. targetByteIndex = ceil((targetBit) / 8); targetBitIndex = (targetBit) - (targetByteIndex - 1) * 8; % Get the bit from the value to pack. bitValue = bitget(value(i), j); % Write the bit into the message data. tmpData(targetByteIndex) = bitset(tmpData(targetByteIndex), targetBitIndex, bitValue); % Set the next target bit based on the byte ordering. if bigEndian && (targetBitIndex == 8) % When hitting a byte boundary, the target bit needs % to jump across the boundary and through to the least % significant bit of the next byte. This is a jump of % 15 bits in length. This big jump is only for % Big Endian values. targetBit = targetBit - 15; else % Otherwise, when Little Endian or when not against a % byte boundary, just index to the next bit in the % byte. targetBit = targetBit + 1; end catch err % On error, the function tried a bit access that was not % possible given the parameters of the signal. error(message('vnt:Message:SignalDefinitionError')); end end % When gone this far, the pack operation was a success, so set the % the Data property back into the object. obj(i).PrivateData = tmpData; end end function value = unpack(obj, startBit, signalSize, byteOrder, dataType) % unpack Unload a raw signal value from message data. % % VALUE = unpack(MESSAGE, STARTBIT, SIGNALSIZE, BYTEORDER, DATATYPE) % takes a set of input parameters to unload raw signal VALUE(s) from the % MESSAGE(s) and returns them as output. % % Inputs: % MESSAGE - The CAN message(s) from which to unpack the signal(s). % STARTBIT - The signal's starting bit location in the message data % with a value between 0 and 63. The STARTBIT represents the % least significant bit position in the CAN data of the signal. % SIGNALSIZE - The signal's length in bits with a value between 1 and 64. % BYTEORDER - The signal's endian format as 'LittleEndian' or % 'BigEndian'. This is often referenced as Intel for % 'LittleEndian' and Motorola for 'BigEndian'. % DATATYPE - The desired type of the returned VALUE as 'int8', % 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', % 'uint64', 'single', or 'double'. % % Note that MESSAGE can be an array of CAN messages. When given as an % array, the raw signal value is unpacked from each entry in the array % individually. These signal values are then returned as an array of % equal size to MESSAGE. The VALUE array is ordered the same as in the % original MESSAGE array. Each MESSAGE in the array must have the % same definition. % % Example: % value = unpack(message, 56, 64, 'BigEndian', 'double') % % See also VNT. % Check the argument count. narginchk(5,5); % Validate the argument startBit. validateattributes(startBit, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', 'nonnegative', ... 'nonsparse', 'real', 'scalar', ... '<', (numel(obj(1).PrivateData) * 8)}, ... 'unpack', 'STARTBIT'); % Validate the argument signalSize. validateattributes(signalSize, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', 'positive', ... 'real', 'scalar', '<=', 64}, ... 'unpack', 'SIGNALSIZE'); % Validate the byteOrder argument. byteOrder = validatestring(byteOrder, ... {'LittleEndian', 'BigEndian'}, ... 'unpack', 'BYTEORDER'); % Validate the argument dataType. dataType = validatestring(dataType, ... {'uint8', 'int8', 'uint16', 'int16', 'uint32', 'int32', ... 'uint64', 'int64', 'single', 'double'}, ... 'unpack', 'DATATYPE'); % Initialize the return value to 0. Also make value an array of the % appropriate size equal to the length of the provided message array. value = zeros(1, numel(obj)); % Cast the type of the return value to an appropriately sized uint type % so that the bit set and get functions will operate on it. Also, set a % signed bit variable here depending on the type. The value represent % the maximum number of bits in the data type for sign extension % purposes. Zero indicates no sign bits are possible. switch dataType case 'uint8' value = cast(value, 'uint8'); signBits = 0; case 'int8' value = cast(value, 'uint8'); signBits = 8; case 'uint16' value = cast(value, 'uint16'); signBits = 0; case 'int16' value = cast(value, 'uint16'); signBits = 16; case {'uint32', 'single'} value = cast(value, 'uint32'); signBits = 0; case 'int32' value = cast(value, 'uint32'); signBits = 32; case {'uint64', 'double'} value = cast(value, 'uint64'); signBits = 0; case 'int64' value = cast(value, 'uint64'); signBits = 64; end % Check the endian configuration. bigEndian = strcmpi(byteOrder, 'BigEndian'); % Loop and extract the signal value for each message provided. for i = 1:numel(obj) % OPTIMIZATION: Store the Data property of the message object in a % local variable to make access faster during the bit get operation. tmpData = obj(i).PrivateData; % Set the first target bit as the start bit. Add one to adjust % indexing from zero based to one based. targetBit = startBit + 1; % Perform unpacking operations on each bit of the signal. for j = 1:signalSize try % Determine the byte position and bit position within the % byte to read. targetByteIndex = ceil((targetBit) / 8); targetBitIndex = (targetBit) - (targetByteIndex - 1) * 8; % Get the bit from the message data. bitValue = bitget(tmpData(targetByteIndex), targetBitIndex); % Write the bit into the signal value if the bit is 1. We % do not need to write 0 as the output value is initialized % to 0 already. if bitValue value(i) = bitset(value(i), j, bitValue); end % Set the next target bit based on the byte ordering. if bigEndian && (targetBitIndex == 8) % When hitting a byte boundary, the target bit needs % to jump across the boundary and through to the least % significant bit of the next byte. This is a jump of % 15 bits in length. This big jump is only for % Big Endian values. targetBit = targetBit - 15; else % Otherwise, when Little Endian or when not against a % byte boundary, just index to the next bit in the % byte. targetBit = targetBit + 1; end catch err % On error, the function tried a bit access that was not % possible given the parameters of the signal. error(message('vnt:Message:SignalDefinitionError')); end end % When unpacking signed numbers that do no fill the size of the % output data type container, the sign bit may need to be extended. if signBits % If the value of the most significant bit is 1, then extend it % to the length of the storage container. We do not need to % extend 0 as the bit values in the container are already 0. if bitget(value(i), signalSize) % Loop over the number of sign bits required. for k = signBits:-1:(signalSize + 1) % Set the bit to extend the sign. value(i) = bitset(value(i), k, 1); end end end end % Do a final typecast to the desired return type. Typecast is used to % arrive at the proper type without changing the actual bit field. value = typecast(value, dataType); end function [extracted, remainder] = extractAll(obj, varargin) % extractAll Returns all occurrences of the specified CAN message(s). % % [EXTRACTED, REMAINDER] = extractAll(MESSAGE, NAME) parses the given % array MESSAGE and returns all messages found with the matched NAME(s). % % [EXTRACTED, REMAINDER] = extractAll(MESSAGE, ID, EXTENDED) parses the % given array MESSAGE and returns all messages found with the matched % ID/EXTENDED value(s). % % Inputs: % MESSAGE - The messages to parse. % NAME - The name(s) of messages to extract as a string. % ID - The numeric CAN identifier(s) to extract. % EXTENDED - Boolean value(s) indicating true if the ID is extended, % otherwise false if the ID is standard. % % Outputs: % EXTRACTED - A CAN message array containing all found instances of % the given message(s). If no matches were found, then EXTRACTED % will return as empty. % REMAINDER - A CAN message array containing all messages from the % original MESSAGE array not matching the specified input. % % Note that NAME can be given as a cell array. ID/EXTENDED can be % given as vectors. For example, if NAME is passed as {'Msg1' 'Msg2'}, % extractAll returns every instance of both messages as found in % the MESSAGE array. When ID is provided as input, then the EXTENDED % argument must be provided also as a vector of the same length as ID. % % Also note that REMAINDER is an optional output. If a variable for % REMAINDER is not specified, only the extracted messages are returned. % % Examples: % [msg1, remainder] = extractAll(message, 'Msg1') % msg1 = extractAll(message, 'Msg1') % [msgOut, remainder] = extractAll(message, {'Msg1' 'Msg2'}) % [msg3000, remainder] = extractAll(message, 3000, true) % [msgOut, remainder] = extractAll(message, [200 5000], [false true]) % % See also VNT. % Check the argument count. narginchk(2,3); % Check for extraction by name. Note that when the user gives the % messages to extract by name, it can be either a string or a cell % array. A string represents the user trying to extract a single % message by name. If the user wants to extract multiple messages, then % the input will be a cell array of strings. if nargin == 2 % Validate the input. validateattributes(varargin{1}, {'char', 'cell'}, ... {'nonempty', 'row'}, ... 'extractAll', 'NAME'); % Take the message names for extraction from the input. if ischar(varargin{1}) % When input is a single string, set it as a cell array to % match how we use it with how multiple input operates. desiredNames = {varargin{1}}; %#ok<CCAT1> else % Validate that each entry in the cell is a string. for ii = 1:length(varargin{1}) validateattributes(varargin{1}{ii}, {'char'}, ... {'nonempty', 'row'}, ... 'extractAll', 'NAME'); end % When already a cell array, just take it right from varargin. desiredNames = varargin{1}; end % Run the extraction using the mixin method. [extracted, remainder] = obj.doExtractAllByName(desiredNames); return; end % Take the ID information to extract from the input. ID = varargin{1}; extended = varargin{2}; % Validate the input. validateattributes(ID, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', ... 'nonnegative', 'nonsparse', 'real', 'row'}, ... 'extractAll', 'ID'); validateattributes(extended, {'logical'}, ... {'nonempty', 'row'}, ... 'extractAll', 'EXTENDED'); % Validate the matching size of the ID and extended arguments. if (numel(ID) ~= numel(extended)) error(message('vnt:Message:MismatchedIDToExtendedCount')); end % Get ID and Extended properties of the objects. msgID = [obj.PrivateID]; msgExtended = [obj.PrivateExtended]; % Build the initial logical index array as false logicals. logicalMatches = false(1, numel(obj)); % Loop through each ID/Extended requested for extraction. for idi = 1:numel(ID) % Get the logical indexes for the message. The existing % found indexes are OR'ed with the new ones as the full listing % of requested messages is built. The comparison for new indexes to % include checks for a match on both the ID and Extended values. logicalMatches = logicalMatches |... ((ID(idi) == msgID) & (extended(idi) == msgExtended)); end % Set the output. If no matches were found, extracted will inherently % return as empty. extracted = obj(logicalMatches); remainder = obj(~logicalMatches); end function extracted = extractRecent(obj, varargin) % extractRecent Returns the most recent occurrence of the specified message(s). % % EXTRACTED = extractRecent(MESSAGE) parses the array MESSAGE and % returns the most recent instance of each unique CAN message found % in the array. % % EXTRACTED = extractRecent(MESSAGE, NAME) parses the array MESSAGE and % returns the most recent instance of each message found with % the matched NAME(s). % % EXTRACTED = extractRecent(MESSAGE, ID, EXTENDED) parses the array % MESSAGE and returns the most recent instance of each message % found with the matched ID/EXTENDED values. % % Inputs: % MESSAGE - The messages to parse. % NAME - The name(s) of messages to extract as a string. % ID - The numeric CAN identifier(s) to extract. % EXTENDED - Boolean value(s) indicating true if the ID is extended, % otherwise false if the ID is standard. % % Outputs: % EXTRACTED - CAN message(s) which are the most recent occurrence of % the requested message(s) based on the Timestamp. If no matches % were found, then EXTRACTED will return as empty. % % Note that NAME can be given as a cell array. ID/EXTENDED can be % given as vectors. For example, if NAME is passed as {'Msg1' 'Msg2'}, % extractAll returns every instance of both messages as found in % the MESSAGE array. When ID is provided as input, then the EXTENDED % argument must be provided also as a vector of the same length as ID. % % Examples: % msgAll = extractRecent(message) % msg1 = extractRecent(message, 'Msg1') % msgOut = extractRecent(message, {'Msg1' 'Msg2'}) % msg3000 = extractRecent(message, 3000, true) % msgOut = extractRecent(message, [400, 5000], [false true]) % % See also VNT. % Check the argument count. narginchk(1,3); % Check for extraction by name. Note that when the user gives the % messages to extract by name, it can be either a string or a cell % array. A string represents the user trying to extract a single % message by name. If the user wants to extract multiple messages, then % the input will be a cell array of strings. if nargin == 2 % Validate the input. validateattributes(varargin{1}, {'char', 'cell'}, ... {'nonempty', 'row'}, ... 'extractRecent', 'NAME'); % Take the message names for extraction from the input. if ischar(varargin{1}) % When input is a single string, set it as a cell array to % match how we use it with how multiple input operates. desiredNames = {varargin{1}}; %#ok<CCAT1> else % Validate that each entry in the cell is a string. for ii = 1:length(varargin{1}) validateattributes(varargin{1}{ii}, {'char'}, ... {'nonempty', 'row'}, ... 'extractRecent', 'NAME'); end % When already a cell array, just take it right from varargin. desiredNames = varargin{1}; end % Run the extraction using the mixin method. extracted = obj.doExtractRecentByName(desiredNames); return; end % Get the ID and extended values. msgID = [obj.PrivateID]; msgExtended = [obj.PrivateExtended]; % Check for extraction of the most recent of every unique message. if nargin == 1 % Initialize the ID/Extended search list as empty. ID = []; extended = logical([]); % Find all unique ID/Extended in the message array. for mi = 1:numel(obj) % Obtain and check the logical indexes for the currently % indexed message for matches to the list of already found % messages. if (~any((msgID(mi) == ID) & (msgExtended(mi) == extended))) % Add the newly found message to the output. ID(end + 1) = msgID(mi); %#ok<AGROW> extended(end + 1) = msgExtended(mi); %#ok<AGROW> end end % Check for extraction by specific ID/Extended. elseif nargin == 3 % Take the ID information to extract from the input. ID = varargin{1}; extended = varargin{2}; % Validate the input. validateattributes(ID, {'numeric'}, ... {'finite', 'integer', 'nonempty', 'nonnan', ... 'nonnegative', 'nonsparse', 'real', 'row'}, ... 'extractRecent', 'ID'); validateattributes(extended, {'logical'}, ... {'nonempty', 'row'}, ... 'extractRecent', 'EXTENDED'); % Validate the matching size of the ID and extended arguments. if (numel(ID) ~= numel(extended)) error(message('vnt:Message:MismatchedIDToExtendedCount')); end end % Initialize the output in case no messages are found. extracted = can.Message.empty(); % Loop through each ID/Extended requested for extraction. for idi = 1:numel(ID) % Using logical indexing, obtain matches of all messages having % matching ID/Extended properties. msgByID = obj((ID(idi) == msgID) & (extended(idi) == msgExtended)); % 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.PrivateTimestamp]; % 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 extracted = extractTime(obj, startTime, endTime) % extractTime Returns all messages having occurred within a time period. % % EXTRACTED = extractTime(MESSAGE, STARTTIME, ENDTIME) parses the % array MESSAGE and returns all messages found to be within % the time period bounded inclusively by STARTTIME and ENDTIME. % % Inputs: % MESSAGE - The messages to parse. % STARTTIME - The beginning of the time range specified in seconds. % ENDTIME - The end of the time range specified in seconds. % % Outputs: % EXTRACTED - An array of CAN messages containing all % messages having timestamps within the specified time range. If % no messages are found within the time range, EXTRACTED returns % as empty. % % Note that the time range specified must be given in increasing order % from STARTTIME to ENDTIME. Inf is also an accepted value for ENDTIME % to specify the largest available time. 0 is the earliest accepted % STARTTIME. % % Examples: % msgRange = extractTime(message, 5, 10.5) % msgRange = extractTime(message, 0, 60) % msgRange = extractTime(message, 150, Inf) % % See also VNT. % Check the argument count. narginchk(3,3); % Validate startTime and endTime. validateattributes(startTime, {'numeric'}, ... {'finite', 'nonempty', 'nonnan', 'nonnegative', ... 'nonsparse', 'real', 'scalar'}, ... 'extractTime', 'STARTTIME'); validateattributes(endTime, {'numeric'}, ... {'nonempty', 'nonnan', 'nonnegative', 'nonsparse', ... 'real', 'scalar'}, ... 'extractTime', 'ENDTIME'); % Validate that the start time is earlier than the end time. if (startTime > endTime) error(message('vnt:Message:InvalidTimeRange')); end % Run the extraction using the mixin method. extracted = obj.doExtractByTime(startTime, endTime); end end methods (Access = protected) function validity = isSignalValid(obj, signalIndex) %#ok<INUSD> % isSignalValid Verifies signal integrity. % % This method is used to perform any required signal integrity % checking needed when reading or writing signals values. % In the case of a CAN message, no action is necessary. % No validation is required. Always return as valid. validity = true; end end methods (Hidden) function delete(obj) %#ok<INUSD> end end methods (Static, Hidden) function obj = loadobj(obj) % loadobj Load a message object from memory. % No Version property means this object is from R2009a or R2009b. if ~isfield(obj, 'Version') obj = rebuildObject( ... obj.ID, ... obj.Extended, ... obj.Error, ... obj.Remote, ... obj.Timestamp, ... obj.Data, ... obj.Database, ... obj.Name, ... []); % Next check for a version 3 object. Version 3 first existed in % R2015a. It would be nicer to check for this first for % performance, but we can't use the Version property value % without checking if it exists first anyway. elseif obj.Version == 3 obj = rebuildObject( ... obj.PrivateID, ... obj.PrivateExtended, ... obj.PrivateError, ... obj.PrivateRemote, ... obj.PrivateTimestamp, ... obj.PrivateData, ... obj.PrivateDatabase, ... obj.PrivateName, ... obj.UserData); % The otherwise case would mean a Version 1 or 2 object. Both of % these versions load the same way as they both employ the % PrivateStruct design. else % The UserData property was added in R2012a. This case handles % objects from R2010a through R2014b though. So we need to % check for the existence of this property before using it. if isfield(obj, 'UserData') obj = rebuildObject( ... obj.PrivateStruct.ID, ... obj.PrivateStruct.Extended, ... obj.PrivateStruct.Error, ... obj.PrivateStruct.Remote, ... obj.PrivateStruct.Timestamp, ... obj.PrivateStruct.Data, ... obj.PrivateStruct.Database, ... obj.PrivateStruct.Name, ... obj.UserData); else obj = rebuildObject( ... obj.PrivateStruct.ID, ... obj.PrivateStruct.Extended, ... obj.PrivateStruct.Error, ... obj.PrivateStruct.Remote, ... obj.PrivateStruct.Timestamp, ... obj.PrivateStruct.Data, ... obj.PrivateStruct.Database, ... obj.PrivateStruct.Name, ... []); end end function newObj = rebuildObject(ID, extended, error, remote, ... timestamp, data, database, name, userData) % rebuildObject Creates a new message object from loaded values. % % This function does the actual loaded values to new object % conversion. It makes a new message object, either physical or % raw, and populates all of the properties. % Check the database status of the loaded input. if ~isempty(database) && database.Usable % If the database is present and usable, then use it to create % a new message object for this message. newObj = can.Message(database, name); else % Otherwise, use the raw message information to construct a new % object. newObj = can.Message(ID, extended, numel(data)); end % Load the other propery values into the new object. newObj.PrivateError = error; newObj.PrivateRemote = remote; newObj.PrivateTimestamp = timestamp; newObj.PrivateData = data; newObj.UserData = userData; % Set the new object as the output and return. obj = newObj; end end end end