gusucode.com > vnt工具箱matlab源码程序 > vnt/vnt/+j1939/Channel.m

    classdef Channel < hgsetget & matlab.mixin.CustomDisplay
% Channel Connection to a J1939 network.
%
%   This class provides connectivity to a J1939 protocol network. It 
%   contains properties and methods to configure and use the 
%   channel on a network.
%
%   See also VNT.

% Authors: Jaremy Pyle
% Copyright 2015 The MathWorks, Inc.

properties (SetAccess = 'private')
    % DeviceVendor - The vendor of the device used to connect to the
    % network.
    DeviceVendor
    % Device - The name of the device used to connect to the network.
    Device
    % DeviceChannelIndex - The index of the channel on the device used to
    % connect to the network.
    DeviceChannelIndex
    % DeviceSerialNumber - The vendor serial number of the device used to
    % connect to the network.
    DeviceSerialNumber
    % BusSpeed - The bit rate (bps) of the network.
    BusSpeed
    % SJW - The synchronization jump width for the bit timing configuration.
    SJW
    % TSEG1 - The first time segement for the bit timing configuration.
    TSEG1
    % TSEG2 - The second time segement for the bit timing configuration.
    TSEG2
    % NumOfSamples - The number of samples taken for the bit timing
    % configuration.
    NumOfSamples
    % TransceiverName - The name of the transceiver used by the device used
    % to connect to the network.
    TransceiverName
    % InitializationAccess - A boolean value indicating if this channel
    % holds initialization rights to the device channel in use.
    InitializationAccess
    % InitialTimestamp - Indicates the time at which the channel was online.
    InitialTimestamp
    % BusStatus - The state of the controller of the device.
    BusStatus
    % Running - A boolean value indicating if the channel is started or
    % stopped.
    Running
    % ParameterGroupsReceived - The total number of parameter groups
    % received since the channel was started.
    ParameterGroupsReceived = 0;
    % ParameterGroupsTransmitted - The total number of parameter groups
    % transmitted since the channel was started.
    ParameterGroupsTransmitted = 0;
    % FilterPassList - A cell array of paramater group names and numbers
    % configured to pass through the channel. All other items are blocked.
    FilterPassList
    % FilterBlockList - A cell array of parameter group names and numbers
    % configured to be blocked by the channel. All other items are passed.
    FilterBlockList
end

properties (Dependent, SetAccess = 'private')
    % ParameterGroupsAvailable - The number of parameter groups available
    % to receive from the channel.
    ParameterGroupsAvailable
end

properties
    % TransceiverState - The current operational state of the transceiver
    % used by the device to connect to the network.
    TransceiverState
    % SilentMode - A boolean value indicating if the channel is configured
    % for active communication or listen-only mode.
    SilentMode
    % UserData - A location to store user-specific information in the
    % channel.
    UserData = [];
end

properties (SetAccess = 'private', GetAccess = 'private')
    % CANChannel - A CAN channel object used by the J1939 channel to
    % provide access to a CAN bus.
    CANChannel
    % Database - A CANdb database object that describes the network and
    % parameter groups used on the network.
    Database
    % DataBuffer - A buffer for storing parameter groups held in the
    % channel and receivable by the user.
    DataBuffer
    % TPReceiver - A map object for storing in-progress received transport
    % protocol sequences so they can be rebuilt into singular parameter
    % groups upon completion.
    TPReceiver
    % TPTransmitter - A j1949.TPTransmitter object that operates multiframe
    % transmit operations.
    TPTransmitter
    % FilterInternalPassList - An array of parameter group numbers allowed 
    % to pass through the channel. All other items are blocked.
    FilterInternalPassList
    % FilterInternalBlockList - An array of parameter group numbers that
    % are blocked by the channel. All other items are passed.
    FilterInternalBlockList
    % TimerRate - The rate of the timer used for internal processing.
    TimerRate = 0.050;
    % BufferSize - The size of the data buffer used to store
    % parameter groups in the channel.
    BufferSize = 100000;
end

properties (Transient, SetAccess = 'private', GetAccess = 'private')
    % ProcessingTimer - A internal.IntervalTimer used to run processs on
    % the channel for managing parameter groups.
    ProcessingTimer
    % ProcessingTimerListener - A listener on the timer used to trigger
    % periodic processing operations.
    ProcessingTimerListener
end

methods
    
    function obj = Channel(database, varargin)
    % Channel Construct a connection to a J1939 network.

        % Validate the input argument count.
        narginchk(3, 4);

        % Validate the database argument.
        if ~isa(database, 'can.Database') || ~database.Usable
            error(message('vnt:J1939:InvalidDatabase'));
        end
        
        % Ensure that the supplied database is for a J1939 network.
        if ~database.DatabaseTypeJ1939
            error(message('vnt:J1939:DatabaseNotJ1939'));
        end
             
        % Set the database object into the channel.
        obj.Database = database;
        
        % Set the CAN channel construction parameters.
        obj.DeviceVendor = varargin{1};
        obj.Device = varargin{2};
        % Adjust for difference in CAN channel constructor for different
        % vendors. Some do not have a channel index argument.
        if nargin == 4
            obj.DeviceChannelIndex = varargin{3};
        else
            obj.DeviceChannelIndex = [];
        end
        
        % Create a CAN channel.
        if isempty(obj.DeviceChannelIndex)
            obj.CANChannel = canChannel(obj.DeviceVendor, obj.Device);
        else
            obj.CANChannel = canChannel(obj.DeviceVendor, obj.Device, obj.DeviceChannelIndex);
        end
        
        % Initialize the transport protocol data buffer.
        obj.TPReceiver = containers.Map();
        
        % Create objects to seed the data buffer using the first 
        % definition found in the database. It does not matter what we use,
        % it just matters that we fill the buffer with J1939 paramater
        % group objects to reserve the memory.
        bufferData = j1939.ParameterGroup(obj.Database, obj.Database.MessageInfo(1).Name);
        obj.DataBuffer = vnt.internal.DataBuffer(obj.BufferSize, bufferData);
    end
    
    function configBusSpeed(obj, varargin)
    % configBusSpeed Set the bit timing of a J1939 channel.
    %
    %   configBusSpeed(CHANNEL, BUSSPEED) sets the speed of the CHANNEL
    %   to BUSSPEED in a direct form that uses default bit timing
    %   calculation factors.
    %
    %   configBusSpeed(CHANNEL, BUSSPEED, SJW, TSEG1, TSEG2, NUMBEROFSAMPLES)
    %   sets the speed of the CHANNEL to BUSSPEED using specified bit
    %   timing calculation factors in an advanced form.
    %
    %   Inputs:
    %       BUSSPEED -  The speed of a network in bits per second.
    %       SJW - The Synchronization Jump Width which helps define the
    %           length of a bit on a network.
    %       TSEG1 - Time Segment 1 which defines the section before
    %           a bit is sampled on a network.
    %       TSEG2 - Time Segment 2 which defines the section after
    %           a bit is sampled on a network.
    %       NUMBEROFSAMPLES - The count of samples used for determining
    %           the bit state of a network.
    %
    %   Note that unless you have specific timing requirements provided
    %   for your network, it is recommended to use the direct form of
    %   configBusSpeed. Also note that bit timing can only be set when
    %   the channel is offline and has initialization access to the device.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       configBusSpeed(ch, 250000)
    %       configBusSpeed(ch, 500000, 1, 4, 3, 1)
    %
    %   See also VNT.

        % Check the argument count.
        if ((nargin ~= 2) && (nargin ~= 6))
            error(message('vnt:Channel:IncorrectArgumentCount'));
        end
        
        % Pass through to the CAN channel.
        if ~isempty(obj.CANChannel)
            if nargin == 2
                obj.CANChannel.configBusSpeed(varargin{1});
            else
                obj.CANChannel.configBusSpeed(varargin{1}, varargin{2}, ...
                    varargin{3}, varargin{4}, varargin{5});
            end
        end
    end
    
    function start(obj)
    % start Start a connection to a J1939 bus.
    %
    %   start(CHANNEL) activates the CHANNEL on the bus to send and
    %   receive messages. The CHANNEL will go offline if stop is called 
    %   or it is cleared from the memory.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       start(ch)
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(1, 1);

        % Check if the channel is already online, and return immediately if
        % so. No need to error on this condition.
        if obj.Running
            return;
        end
    
        % Clear the contents of the data buffer.
        obj.DataBuffer.wipe();

        % Initialize the transport protocol data buffer.
        obj.TPReceiver = containers.Map();

        % Start the CAN channel.
        obj.CANChannel.start();
        
        % Create and start a timer for processing data. 
        obj.ProcessingTimer = internal.IntervalTimer(obj.TimerRate);
        obj.ProcessingTimerListener = event.listener( ...
            obj.ProcessingTimer, ...
            'Executing',  ...
            @obj.updateProcessing);
        start(obj.ProcessingTimer);
    end
    
    function stop(obj)
    % stop Stop a connection to a J1939 bus.
    %
    %   stop(CHANNEL) deactivates the CHANNEL on the bus. The CHANNEL will 
    %   also stop running if it is cleared.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       start(ch)
    %       stop(ch)
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(1, 1);

        % Check if the channel is already offline, and return immediately if
        % so. No need to error on this condition.
        if ~obj.Running
            return;
        end
    
        % Check the state of the timer.
        if ~isempty(obj.ProcessingTimer)
            % Stop the timer.
            stop(obj.ProcessingTimer);
            % Clear the timer.
            obj.ProcessingTimer = [];
            obj.ProcessingTimerListener = [];
        end

        % Clear the CAN channel.
        obj.CANChannel.stop();
    end
    
    function out = receive(obj, count)
    % receive Receive parameter groups from a J1939 bus.
    %
    %   PG = receive(CHANNEL, COUNT) returns received PG(s) equal to or 
    %   less than COUNT from the CHANNEL.
    %
    %   COUNT specifies the maximum numer of parameter groups to receive. It
    %   must be a nonzero, positive value or Inf to indicate return all
    %   available parameter groups. If less parameter groups are available 
    %   to be received than COUNT specifies, the amount currently available will be
    %   returned. If no parameter groups are available to receive, an empty 
    %   array is returned.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       start(ch)
    %       pg = receive(ch, Inf)
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(2, 2);

        % Validate that count is a numeric, integer, positive value or inf.
        if count ~= inf
            if ~(isnumeric(count) && (rem(count, 1) == 0) && (count >= 1))
                error(message('vnt:J1939:InvalidPGsRequested'));
            end
        end
            
        % If no parameter groups are available, return empty.
        if obj.ParameterGroupsAvailable == 0
            out = j1939.ParameterGroup.empty();
            return;
        end
        
        % Retrieve the requested items from the data buffer.
        out = obj.DataBuffer.pop(count);
        
        % Increment the receive counter.
        obj.ParameterGroupsReceived = obj.ParameterGroupsReceived + numel(out);
    end
    
    function transmit(obj, parameterGroups)
    % transmit Send paramter groups onto a J1939 bus.
    %
    %   transmit(CHANNEL, PG) sends the PG(s) onto the bus via the CHANNEL.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       start(ch)
    %       pg = j1939ParameterGroup(db, 'MyParameterGroup')
    %       transmit(ch, pg)
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(2, 2);

        % Error if the channel is not online.
        if ~obj.Running
            error(message('vnt:J1939:ChannelNotOnline'));
        end
        
        % Transmitting is not allowed in Silent mode
        if obj.SilentMode
            error(message('vnt:J1939:ChannelNotInNormalMode'));
        end
        
        % Validate the parameter group input. The argument is validated with
        % direct checks due to performance gains over validateAttributes.
        if ~isa(parameterGroups, 'j1939.ParameterGroup') || ~isvector(parameterGroups)
            error(message('vnt:J1939:InvalidParameterGroup'));
        end
        
        % Protect against interleaving of multiframe PGs and single frame
        % PGs within the input.
        if numel(parameterGroups) > 1
            for ii = 1:numel(parameterGroups)
                if numel(parameterGroups(ii).Data) > 8
                    error(message('vnt:J1939:MultiframePGTransmitNotIndependent'));
                end
            end
        end
        
        % Check if this is a parameter group requiring use of the transport
        % protocol to send.
        if (numel(parameterGroups) == 1) && (numel(parameterGroups.Data) > 8)
            % Use the transport protocol methods to send this parameter
            % group in a multiframe sequence.
            obj.tpTransmit(parameterGroups);
        else
            try
                % Structurize the entire parameter group array ahead of using it. This
                % speeds up the transmit processing by negating the many slow
                % property references required as they are all now made on the
                % structure fields.
                pgStruct = [parameterGroups.PrivateRawStruct];
                % Send the parameter groups.
                obj.CANChannel.transmitRaw(pgStruct);
            catch err
                error(message('vnt:J1939:ChannelUnableToTransmit'));
            end
        end

        % Increment the transmit counter.
        obj.ParameterGroupsTransmitted = obj.ParameterGroupsTransmitted + numel(parameterGroups);
    end
    
    function discard(obj)
    % discard Discard available parameter groups on the J1939 channel.
    %
    %   discard(CHANNEL) deletes all parameter groups available on the CHANNEL.
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       start(ch)
    %       discard(ch)
    %
    %   See also VNT.
        
        % Validate the input argument count.
        narginchk(1, 1);

        % Clear the contents of the data buffer.
        obj.DataBuffer.wipe();

        % Initialize the transport protocol data buffer.
        obj.TPReceiver = containers.Map();
        
        % Pass through to the CAN channel.
        if ~isempty(obj.CANChannel)
            obj.CANChannel.discard();
        end
    end
    
    function filterAllowOnly(obj, value)
    % filterAllowOnly Set the parameter group pass filter for specific groups.
    %
    %   filterAllowOnly(CHANNEL, PGNAME) configures the parameter group filter
    %   on CHANNEL to pass only the groups indicated by PGNAME. PGNAME may be 
    %   a single string or a cell array of strings. 
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       filterAllowOnly(ch, 'PG1');
    %       filterAllowOnly(ch, {'PG1' 'PG2'});
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(2, 2);
        
        % Verify that the channel is offline to configure filtering.
        obj.errorIfOnline();
        
        % Process the input to get both the names and numbers of the
        % parameter groups for the filter.
        [pgnNameList, pgnNumericList] = filterProcessValues(obj, value);
        
        % Clear the existing filters.
        obj.filterClear();
        
        % Set the internal filter.
        obj.FilterInternalPassList = pgnNumericList;
        
        % Set the filter list property visibile to the user.
        for ii = 1:numel(pgnNumericList)
            % Build the property value.
            obj.FilterPassList{end+1} = sprintf('%s (%d)', pgnNameList{ii}, pgnNumericList(ii));
        end
    end
    
    function filterBlockOnly(obj, value)
    % filterBlockOnly Set the parameter group block filter for specific groups.
    %
    %   filterBlockOnly(CHANNEL, PGNAME) configures the parameter group filter
    %   on CHANNEL to block only the groups indicated by PGNAME. PGNAME may be 
    %   a single string or a cell array of strings. 
    %     
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       filterBlockOnly(ch, 'PG1');
    %       filterBlockOnly(ch, {'PG1' 'PG2'});
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(2, 2);

        % Verify that the channel is offline to configure filtering.
        obj.errorIfOnline();
        
        % Process the input to get both the names and numbers of the
        % parameter groups for the filter.
        [pgnNameList, pgnNumericList] = filterProcessValues(obj, value);
        
        % Clear the existing filters.
        obj.filterClear();
        
        % Set the internal filter.
        obj.FilterInternalBlockList = pgnNumericList;
        
        % Set the filter list property visibile to the user.
        for ii = 1:numel(pgnNumericList)
            % Build the property value.
            obj.FilterBlockList{end+1} = sprintf('%s (%d)', pgnNameList{ii}, pgnNumericList(ii));
        end
    end
    
    function filterAllowAll(obj)
    % filterAllowAll Open the parameter group filters on the channel.
    %
    %   filterAllowAll(CHANNEL) opens all parameter group filters on the 
    %   CHANNEL. All parameter groups will thus be receivable. 
    %
    %   Example:
    %       db = canDatabase('MyDatabase.dbc')
    %       ch = j1939Channel(db, 'Vector', 'CANCaseXL 1', 1)
    %       filterAllowAll(ch);
    %
    %   See also VNT.

        % Validate the input argument count.
        narginchk(1, 1);

        % Verify that the channel is offline to configure filtering.
        obj.errorIfOnline();
        
        % Clear the existing filters.
        obj.filterClear();
    end

    
    function value = get.DeviceSerialNumber(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('DeviceSerialNumber');
    end
    
    function value = get.BusSpeed(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('BusSpeed');
    end
    
    function value = get.SJW(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('SJW');
    end
    
    function value = get.TSEG1(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('TSEG1');
    end
    
    function value = get.TSEG2(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('TSEG2');
    end
    
    function value = get.NumOfSamples(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('NumOfSamples');
    end
    
    function value = get.TransceiverName(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('TransceiverName');
    end
    
    function value = get.TransceiverState(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('TransceiverState');
    end
    
    function set.TransceiverState(obj, value)
        % Write the property value to the CAN channel.
        obj.writePropertyToCANChannel('TransceiverState', value);
    end
    
    function value = get.InitializationAccess(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('InitializationAccess');
    end
    
    function value = get.InitialTimestamp(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('InitialTimestamp');
    end
    
    function value = get.SilentMode(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('SilentMode');
    end
    
    function set.SilentMode(obj, value)
        % Write the property value to the CAN channel.
        obj.writePropertyToCANChannel('SilentMode', value);
    end
    
    function value = get.BusStatus(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('BusStatus');
    end
    
    function value = get.Running(obj)
        % Read the property value from the CAN channel.
        value = obj.readPropertyFromCANChannel('Running');
    end
    
    function value = get.ParameterGroupsAvailable(obj)
        % Get the value of this property from the count of items currently
        % available on the underlying data buffer.
        value = obj.DataBuffer.Count;
    end
    
end

methods (Hidden)
    
    function delete(obj)
    % delete Delete the J1939 channel object.
        
        % Check the state of the timer.
        if ~isempty(obj.ProcessingTimer)
            % Stop the timer.
            stop(obj.ProcessingTimer);
            % Clear the timer.
            obj.ProcessingTimer = [];
            obj.ProcessingTimerListener = [];
        end
        
        % Check the state of the CAN channel.
        if ~isempty(obj.CANChannel)
            % Stop the CAN channel if it is running.
            if obj.CANChannel.Running
                obj.CANChannel.stop();
            end
            % Clear the CAN channel.
            obj.CANChannel = [];
        end
    end
    
    function updateProcessing(obj, ~, ~)
    % updateProcessing Timer callback function for internal processing.
    %
    %   This method is the primary processing function of the channel. It
    %   is called from the main timer at a fixed rate to perform incoming
    %   message and parameter group handling and processing. It runs any
    %   regular required J1939 protocol activity as well.
        
        % Get all available messages from the CAN channel.
        rxData = obj.CANChannel.receiveRaw();
        % Return immediately if nothing was received.
        if isempty(rxData)
            return;
        end
        
        % Perform all required extra processing.
        for ii = 1:numel(rxData)
            % Get the individual parameter elements from the identifier.
            [~, sourceAddress, destinationAddress, pgn] = ...
                mexJ1939Utility('getSubsetForConstruction', rxData(ii).ID);
            % Add the PGN to the structure for filtering purposes.
            rxData(ii).PGN = pgn;
            
            % Perform processing on any protocol-specific PGNs.
            switch pgn
                case j1939.Utility.TPConnectionManagementPGN
                    obj.tpProcessCM(rxData(ii), sourceAddress, destinationAddress);
                case j1939.Utility.TPDataTransferPGN
                    obj.tpProcessDT(rxData(ii), sourceAddress, destinationAddress);
           end
        end
        
        % Handle any complete multiframe parameter groups.
        multiframePGs = obj.tpCheckForCompletions();
        % Combine the regular and multiframe PGs and sort them according to
        % the timestamps. When we convert to PG objects, all the entries
        % will be in proper chronological order.
        if ~isempty(multiframePGs)
            rxData = [rxData multiframePGs];
            timestamps = [rxData.Timestamp];
            [~, sortedIndex] = sort(timestamps);
            rxData = rxData(sortedIndex);
        end
        
        % Apply the message filtering capability.
        rxData = obj.filterApply(rxData);

        % Before converting to parameter group objects and setting into the
        % data buffer, make sure that we still have values to handle. It is
        % possible that the filter may have blocked all of the received input.
        if ~isempty(rxData)
            % Convert the received data into J1939 parameter group objects.
            newPGs = j1939.ParameterGroup(obj.Database, rxData);
            % Store them in the data buffer.
            obj.DataBuffer.push(newPGs);
        end
    end

end

methods (Access = protected)
    
    function group = getPropertyGroups(obj) %#ok<MANU>
    % getPropertyGroups Build custom display for the class.
    %
    %   This method is used from the matlab.mixin.CustomDisplay class to
    %   perform custom command line display configuration. It presents
    %   properties as logical groups for a more usable display.
    
        % Build the property sections for display.
        title1 = sprintf('Device Information:\n   -------------------');
        plist1 = {'DeviceVendor', 'Device', 'DeviceChannelIndex', 'DeviceSerialNumber'};
        title2 = sprintf('Data Details:\n   -------------');
        plist2 = {'ParameterGroupsAvailable', 'ParameterGroupsReceived', ...
            'ParameterGroupsTransmitted', 'FilterPassList', 'FilterBlockList'};
        title3 = sprintf('Channel Information:\n   --------------------');
        plist3 = {'Running', 'BusStatus', 'InitializationAccess', 'InitialTimestamp', ...
            'SilentMode', 'TransceiverName', 'TransceiverState', 'BusSpeed', ...
            'SJW', 'TSEG1', 'TSEG2', 'NumOfSamples'};
        title4 = sprintf('Other Information:\n   ------------------');
        plist4 = {'UserData'};

        % Set the property groups.
        group(1) = matlab.mixin.util.PropertyGroup(plist1, title1);
        group(2) = matlab.mixin.util.PropertyGroup(plist2, title2);
        group(3) = matlab.mixin.util.PropertyGroup(plist3, title3);
        group(4) = matlab.mixin.util.PropertyGroup(plist4, title4);
    end

end

methods (Access = 'private')
    
    function [pgnNameList, pgnNumericList] = filterProcessValues(obj, value)
    % filterProcessValues Build filter setting input to usable values.
    %
    %   This method is used to process filter set input (for both pass and
    %   block filters) and parse it fully into parameter groups names and
    %   numbers. These resulting arrays of names and numbers are what we
    %   need to actually configure the filter activity for the channel.
        
        % Do processing for names.  
        if ischar(value) || iscell(value)
            % For a single message convert to cell array as further
            % processing requires cell array input.
            if ~iscell(value)
                value = {value};
            end
            
            % Loop through the cell array of names and process them.
            pgnNameList = value;
            pgnNumericList = [];
            for ii = 1:numel(pgnNameList)
                % Look up the parameter group in the database.
                pgInfo = privateMessageInfoByName(obj.Database, pgnNameList{ii});
                
                % Verify the parameter group definition exists in the database.
                if isempty(pgInfo)
                    error(message('vnt:J1939:ParameterGroupNotFound', pgnNameList{ii}));
                end
                
                % Add this PGN to the filter list.
                pgnNumericList = [pgnNumericList pgInfo.J1939.PGN]; %#ok<AGROW>
            end
        else
            error(message('vnt:J1939:InvalidFilterSetting'));
        end
    end 
    
    function itemsToFilter = filterApply(obj, itemsToFilter)
    % filterApply Run the parameter group filtering check.
    %
    %   This method is used to apply the filtering as configured on the
    %   channel to incoming parameter group messages. It takes an array of
    %   messages as input and returns a filtered array as output,
    %   discarding any items that did not get through the filter.
        
        % Create a logical index array for filtering parameter groups.
        filterIndex = false(1, numel(itemsToFilter));
        
        % Action if the pass filter is set.
        if ~isempty(obj.FilterInternalPassList)
            % Apply each entry in the pass filter.
            for ii = 1:numel(obj.FilterInternalPassList)
                % Generate a logical index for each item in the pass list
                % so we know which entries should be marked for removal.
                % Combine the logical index arrays for each entry in the
                % pass list filter to determine the final filterable array.
                filterIndex = filterIndex | ([itemsToFilter.PGN] == obj.FilterInternalPassList(ii));
            end
            % Reverse the filter index list because this is a pass filter.
            % The index tells which items to keep, so we invert it to
            % determine which items to discard.
            filterIndex = ~filterIndex;
        end

        % Action if the block filter is set.
        if ~isempty(obj.FilterInternalBlockList)
            % Apply each entry in the block filter.
            for ii = 1:numel(obj.FilterInternalBlockList)
                % Generate a logical index for each item in the block list
                % so we know which entries should be marked for removal.
                % Combine the logical index arrays for each entry in the
                % block list filter to determine the final filterable array.
                filterIndex = filterIndex | ([itemsToFilter.PGN] == obj.FilterInternalBlockList(ii));
            end
        end
        
        % Commit the filter by removing any parameter groups that should
        % not pass. If no filter was set, this action will have no effect.
        itemsToFilter(filterIndex) = [];
    end
    
    function filterClear(obj)
    % filterClear Resets all filter settings.
    %
    %   This method is used to fully open and reset all of the filter
    %   setting properties of the channel.
        
        % Clear the existing filters.
        obj.FilterPassList = cell(0);
        obj.FilterBlockList = cell(0);
        obj.FilterInternalPassList = [];
        obj.FilterInternalBlockList = [];
    end
        
    function tpProcessCM(obj, msgRx, sourceAddress, destinationAddress)
    % tpProcessCM Handle a TP connection management message.
    %
    %   This method is used to process transport protocol connection
    %   management messages. These messages are used in J1939 to start
    %   multiframe transfer sequences as well as flow control them.
        
        % Switch on the first data byte, which is the
        % connection management control byte.
        switch msgRx.Data(1)
            case {j1939.Utility.TPBroadcastAnnounceControlByte, j1939.Utility.TPRequestToSendControlByte}
                % Get a map key for this CM packet.
                tpKey = j1939.Utility.tpCreateReceiverKey(sourceAddress, destinationAddress);
                % Create a new TP PG object for this connection.
                tpPG = j1939.TPReceiver(obj.Database, msgRx);
                % Store the object in the TP data buffer map.
                obj.TPReceiver(tpKey) = tpPG;
                
            case j1939.Utility.TPClearToSendControlByte
                % Check if the channel is engaged in any multiframe
                % peer-to-peer transmission.
                if ~isempty(obj.TPTransmitter)
                    obj.TPTransmitter.processCTS(msgRx, sourceAddress, destinationAddress);
                end
                
                % Check for an active TP receiver for this message.
                tpReceiver = obj.tpGetReceiver(destinationAddress, sourceAddress);
                % Dispatch to the receiver if it exists.
                if ~isempty(tpReceiver)
                    tpReceiver.processCTS(msgRx);
                end
                
            case j1939.Utility.TPAcknowledgmentControlByte
                % Check if the channel is engaged in any multiframe
                % peer-to-peer transmission.
                if ~isempty(obj.TPTransmitter)
                    obj.TPTransmitter.processAck(msgRx, sourceAddress, destinationAddress);
                end
            
                % Check for an active TP receiver for this message.
                tpReceiver = obj.tpGetReceiver(destinationAddress, sourceAddress);
                % Dispatch to the receiver if it exists.
                if ~isempty(tpReceiver)
                    tpReceiver.completeParameterGroup(msgRx.Timestamp);
                end
                
            case j1939.Utility.TPConnectionAbortControlByte
                % Check if the channel is engaged in any multiframe
                % peer-to-peer transmission.
                if ~isempty(obj.TPTransmitter)
                    obj.TPTransmitter.processAbort(msgRx, sourceAddress, destinationAddress);
                end
            
                % Get a TP receiver key for this message.
                tpKey = j1939.Utility.tpCreateReceiverKey(destinationAddress, sourceAddress);
                % Check if this message is part of a transport protocol 
                % sequence that we are receiving.
                if obj.TPReceiver.isKey(tpKey)
                    % If so, remove it. The sequence has been aborted.
                    obj.TPReceiver.remove(tpKey);
                end
        end
    end
    
    function tpProcessDT(obj, msgRx, sourceAddress, destinationAddress)
    % tpProcessDT Handle a TP data transfer message.
    %
    %   This method is used to process transport protocol data transfer
    %   messages. It directs them to the appropriate open TP sequence
    %   handler based on the source and destination of the message.
        
        % Check for an active TP receiver for this message.
        tpReceiver = obj.tpGetReceiver(sourceAddress, destinationAddress);
        % Dispatch to the receiver if it exists.
        if ~isempty(tpReceiver)
            tpReceiver.processDT(msgRx);
        end
    end
    
    function multiframePGs = tpCheckForCompletions(obj)
    % tpCheckForCompletions Check and process completed TP receptions
    %
    %   This method is used to look for and finish processing of completed
    %   transport protocol receptions. Once all of the messages comprising
    %   a multiframe sequence are complete, they need to be consolidated
    %   together into a final parameter group.
        
        % Default to empty output.
        multiframePGs = [];
        
        % Get all of the current processing multiframe sequences and
        % iterate through them to check status.
        tpPGsAll = obj.TPReceiver.values();
        for jj = 1:numel(tpPGsAll)
            % Inspect each entry for completion.
            if tpPGsAll{jj}.isComplete()
                % Extract the completed parameter group.
                multiframePGs = [multiframePGs tpPGsAll{jj}.CompletePG]; %#ok<AGROW>
                
                % Get a map key for this transport protocol
                % data buffer entry.
                tpKey = j1939.Utility.tpCreateReceiverKey( ...
                    tpPGsAll{jj}.SourceAddress, ...
                    tpPGsAll{jj}.DestinationAddress);
                
                % Remove this entry from the data buffer now
                % that it is fully processed.
                obj.TPReceiver.remove(tpKey);
            end
        end
    end
    
    function out = tpGetReceiver(obj, sourceAddress, destinationAddress)
    % tpGetReceiver Retrieves a specific TP receiver object.
    %
    %   This method is used to check the TP receiver map for a specific key
    %   based on the source and destination addresses provided. If an entry
    %   for this key exists, that item is returned. If not, then empty is
    %   returned.
        
        % Get a TP receiver key for the provided input.
        tpKey = j1939.Utility.tpCreateReceiverKey(sourceAddress, destinationAddress);
        
        % Check if this key is part of a transport protocol sequence that 
        % we are receiving.
        if obj.TPReceiver.isKey(tpKey)
            % Get the transport protocol receiver entry to which
            % this packet belongs.
            out = obj.TPReceiver(tpKey);
        else
            % If the key is void, then return empty.
            out = [];
        end
    end
    
    function tpTransmit(obj, parameterGroup)
    % tpTransmit Transmit a parameter group via the transport protocol.
    %
    %   This method is used to send a parameter group onto the network
    %   using the J1939 transport protocol. This means the data is larger
    %   than 8 bytes and requires multiple CAN messages to complete the
    %   data transfer.
        
        % Create a transmitter object.
        obj.TPTransmitter = j1939.TPTransmitter(obj.CANChannel, parameterGroup);

        % Check if this is a broadcast transmission.
        if obj.TPTransmitter.isBroadcast()
            % Send the broadcast.
            obj.TPTransmitter.transmitBAM();
        else
            % Send as a peer-to-peer transmission.
            obj.TPTransmitter.transmitP2P();
        end
        
        % Clear the object.
        obj.TPTransmitter = [];
    end
    
    function value = readPropertyFromCANChannel(obj, name)
    % readPropertyFromCANChannel Get property value from the CAN channel.
    %
    %   This method returns the value of the property in the "name"
    %   argument as read from the underlying CAN channel object.
        
        if isempty(obj.CANChannel)
            value = [];
        else
            value = obj.CANChannel.(name);
        end
    end
    
    function writePropertyToCANChannel(obj, name, value)
    % writePropertyToCANChannel Set property value to the CAN channel.
    %
    %   This method sets the value of the property in the "name"
    %   argument into the underlying CAN channel object.
        
        if ~isempty(obj.CANChannel)
            obj.CANChannel.(name) = value;
        end
    end
    
    function errorIfOnline(obj)
    % errorIfOnline Throws an error if the channel is online.
    %
    %   This method is used to trigger an error if the channel is online.
    %   It is used by user visible methods to ensure proper channel state
    %   for certain operations.
        
        % Check if the channel is online, and error if so.
        if obj.Running
            error(message('vnt:J1939:ChannelNotOffline'));
        end
    end
    
end

methods (Static, Hidden)
    
    function obj = loadobj(obj)
    % loadobj Load a J1939 channel object from memory.
    end
    
end

end