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