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

    classdef Channel < hgsetget
% Channel Class that implements an XCP protocol user channel.

% Authors: JDP
% Copyright 2012 The MathWorks, Inc.

properties (SetAccess = 'private')
    % SlaveName - The name of the XCP slave node to which this channel connects.
    SlaveName
    % A2LFileName - The name of the A2L file that describes the XCP target node.
    A2LFileName
    % TransportLayer - The type of transport layer used for the XCP connection.
    TransportLayer
    % TransportLayerDevice - Structure containing details on the XCP transport layer connection.
    TransportLayerDevice
end

properties
    % SeedKeyDLL - A DLL file containing the seed and key access algorithm.
    SeedKeyDLL
end

properties (SetAccess = 'private', GetAccess = 'private')
    % A2LFileObject - An xcp.A2L file object.
    A2LFileObject
    % ProtocolObject - An xcp.Protocol object that implements the XCP protocol messaging.
    ProtocolObject
    % SlaveInfo - A structure of slave information acquired from the slave.
    SlaveInfo
    % ConnectState - Stores the current slave connection state of the channel.
    ConnectState
    % MeasurementState - Stores the current state of measurement activities on the channel.
    MeasurementState
    % DAQListObject - An array of all currently configured DAQ list objects.
    DAQListObject
    % DAQData - A structure with array values of stored DAQ data for measurements.
    DAQData
    % DAQTimer - A timer object that runs the periodic DAQ tasks.
    DAQTimer
    % DAQTimerListener - Listener for the DAQ timer events.
    DAQTimerListener
    % DAQTimerRate - The rate at which to run the DAQ periodic task.
    DAQTimerRate
    % STIMListObject - An array of all currently configured STIM list objects.
    STIMListObject
    % STIMTimer - A timer object that runs the periodic STIM tasks.
    STIMTimer
    % STIMTimerListener - Listener for the STIM timer events.
    STIMTimerListener    
    % STIMTimerRate - The rate at which to run the STIM periodic task.
    STIMTimerRate
    % SeedKeyDLLFullPath - Full path to the seed and key access DLL file.
    SeedKeyDLLFullPath
end

properties (Dependent, SetAccess = 'private', GetAccess = 'private')
    % NumberOfDAQLists - The number of configured DAQ lists.
    NumberOfDAQLists
    % NumberOfSTIMLists - The number of configured STIM lists.
    NumberOfSTIMLists
    % NumberOfMeasurementLists - The number of configured DAQ and STIM lists.
    NumberOfMeasurementLists
end


methods
    
    function obj = Channel(a2l, transportLayer, varargin)
    % Channel Creates and returns an XCP Channel object.
    %
    %   OBJ = Channel(A2L_OBJECT, 'CAN', DEVICE_VENDOR, DEVICE,
    %   DEVICE_CHANNEL_INDEX) creates an XCP channel OBJ connected to the
    %   slave device described by the A2L file A2L_OBJECT. The
    %   TRANSPORT_LAYER is specified as 'CAN' in order to use a CAN bus to
    %   communicate with the slave. Additional arguments are necessary to
    %   define the CAN device through which to access the network. The
    %   DEVICE_VENDOR must be specified as a string, the DEVICE also as a
    %   string, and the DEVICE_CHANNEL_INDEX as a numeric value.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(4, 5);
        
        % Validate the A2L argument.
        validateattributes(a2l, {'xcp.A2L'}, {'scalar', 'nonempty'}, ...
            'xcp.Channel', 'A2L file object', 1);
        
        % Convert the transport layer argument to all upper case since CAN
        % is the only supported option, and we always want that name as a
        % string represented in the object in upper case.
        transportLayer = upper(transportLayer);
        % Validate the transport layer argument. 
        validatestring(transportLayer, {'CAN'}, ...
            'xcp.Channel', 'transport layer', 2);
        
        % Validate the variable arguments.
        validateattributes(varargin{1}, {'char'}, {'nonempty'}, ...
            'xcp.Channel', 'transport layer device vendor', 3);
        validateattributes(varargin{2}, {'char'}, {'nonempty'}, ...
            'xcp.Channel', 'transport layer device', 4);
        if nargin == 5
            validateattributes(varargin{3}, {'numeric'}, {'nonempty', 'integer', 'real', 'finite', 'nonnan', 'positive'}, ...
                'xcp.Channel', 'transport layer device channel index', 5);
        end
        
        % Set the base property values from the input arguments.
        obj.A2LFileObject = a2l;
        obj.A2LFileName = a2l.FileName;
        obj.SlaveName = a2l.SlaveName;
        obj.TransportLayer = transportLayer;
        obj.TransportLayerDevice.Vendor = varargin{1};
        obj.TransportLayerDevice.Device = varargin{2};
        if nargin == 5
            obj.TransportLayerDevice.ChannelIndex = varargin{3};
        else
            obj.TransportLayerDevice.ChannelIndex = [];
        end
        
        % Initialize internal states.
        obj.ConnectState = false;
        obj.MeasurementState = false;
        
        % Initialize the measurement list properties.
        obj.DAQListObject = xcp.MeasurementList.empty();
        obj.DAQData = [];
        obj.DAQTimerRate = 0.025;
        obj.STIMListObject = xcp.MeasurementList.empty();
        obj.STIMTimerRate = 0.010;
    end
     
    function delete(obj)
    % delete Clean up and remove the object.
    %
    %   delete(OBJ) deletes the XCP channel object OBJ. Any activity is
    %   stopped and connection to a slave module is disconnected.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       delete(channelObj);
    %
    %   See also VNT.
        
        % Disconnect from the slave if the connection is active.
        if obj.isConnected()
            obj.disconnect();
        end
    end
    
    
    function connect(obj)
    % connect Starts an active connection to the slave module.
    %
    %   connect(OBJ) actively connects the XCP channel object OBJ to the slave
    %   module that was specified during creation of the object. The
    %   required XCP commands necessary to establish a connection are sent
    %   to the slave and any applicable responses are received and
    %   processed.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);
        
        % Validate current state conditions.
        obj.errorWhenConnected();

        % Build a protocol object for communicating with the slave.
        try
            obj.ProtocolObject = xcp.Protocol( ...
                obj.A2LFileObject, ...
                obj.TransportLayer, ...
                obj.TransportLayerDevice.Vendor, ...
                obj.TransportLayerDevice.Device, ...
                obj.TransportLayerDevice.ChannelIndex);
        catch err
            error(message('vnt:XCP:UnableToCreateProtocolObject'));
        end
        
        % Connect to the slave.
        [~, resource, commModeBasic, maxCTO, maxDTO] = ...
            obj.ProtocolObject.connect(xcp.Utility.NORMAL_MODE);
        % Store the returned information in this channel.
        obj.SlaveInfo.Resource = resource;
        obj.SlaveInfo.CommModeBasic = commModeBasic;
        obj.SlaveInfo.MaxCTO = maxCTO;
        obj.SlaveInfo.MaxDTO = maxDTO;
        
        % Query for current slave status.
        [~, currentSessionStatus, currentResourceProtectionStatus, sessionConfigurationID] = ...
            obj.ProtocolObject.getStatus();
        % Store the returned information in this channel.
        obj.SlaveInfo.CurrentSessionStatus = currentSessionStatus;
        obj.SlaveInfo.CurrentResourceProtectionStatus = currentResourceProtectionStatus;
        obj.SlaveInfo.SessionConfigurationID = sessionConfigurationID;
        
        % If DAQ is available and locked, unlock it.
        if obj.SlaveInfo.Resource.DAQ && obj.SlaveInfo.CurrentResourceProtectionStatus.DAQ
            obj.unlockResource('DAQ');
        end
        
        % If STIM is available and locked, unlock it.
        if obj.SlaveInfo.Resource.STIM && obj.SlaveInfo.CurrentResourceProtectionStatus.STIM
            obj.unlockResource('STIM');
        end
        
        % If CAL is available and locked, unlock it.
        if obj.SlaveInfo.Resource.CAL && obj.SlaveInfo.CurrentResourceProtectionStatus.CAL
            obj.unlockResource('CAL');
        end
        
        % Modify the internal state.
        obj.ConnectState = true;
    end
    
    function disconnect(obj)
    % disconnect Stops an active connection to the slave module.
    %
    %   disconnect(OBJ) disconnects the XCP channel object OBJ from the slave
    %   module that was specified during creation of the object. The
    %   required XCP commands necessary to close a connection are sent
    %   to the slave and any applicable responses are received and
    %   processed.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       disconnect(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);
        
        % Validate current state conditions.
        obj.errorWhenDisconnected();

        % If measurements are active, stop them.
        if obj.isMeasurementRunning()
            obj.stopMeasurement();
        end

        % If the channel is connected, disconnect it.
        if obj.isConnected()
            obj.ProtocolObject.disconnect();
            obj.ProtocolObject = [];
        end
        
        % Modify the internal state.
        obj.ConnectState = false;
    end
    
    
    function createMeasurementList(obj, resource, eventName, measurementNames)
    % createMeasurementList Configure a DAQ or STIM list on the XCP channel.
    %
    %   createMeasurementList(OBJ, RESOURCE, EVENT_NAME, MEASUREMENT_NAMES)
    %   configures a dynamic DAQ or STIM list on the XCP channel object
    %   OBJ. The RESOURCE must be specified as a string to denote this a
    %   'DAQ' or 'STIM' list. The EVENT_NAME is also a string that is the
    %   name of the event on which to configure this list. Measurements are
    %   specified as MEASUREMENT_NAMES as a string or cell array of strings
    %   to define which measurements are configured for this list.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       createMeasurementList(channelObj, 'STIM', 'Event2', {'Measurement1', 'Measurement1'});
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(4, 4);

        % Validate current state conditions.
        obj.errorWhenDisconnected();
        obj.errorWhenMeasurementActive();
        
        % Force the direction argument to be all caps because that is how
        % we want the values represented inside the channel.
        resource = upper(resource);
        % Validate the resource argument.
        validatestring(resource, {'DAQ', 'STIM'}, ...
            'xcp.createMeasurementList', 'direction', 2);

        % Validate the event argument.
        validatestring(eventName, obj.A2LFileObject.Events, ...
            'xcp.createMeasurementList', 'event name', 3);

        % Create a cell array out of the measurement names argument. This
        % argument may be sent as a single string, but we always reference
        % this argument as a cell array.
        if ~iscell(measurementNames)
            measurementNames = {measurementNames};
        end
        
        % Validate each entry in the measurements argument.
        for ii = 1:numel(measurementNames)
            validatestring(measurementNames{ii}, obj.A2LFileObject.Measurements, ...
                'xcp.createMeasurementList', 'measurement names', 4);
        end
        
        % Verify that the requested resource is an available resource on
        % this slave. For example, error if STIM was requested but STIM is
        % not supported by the slave.
        if ~obj.SlaveInfo.Resource.(resource)
            error(message('vnt:XCP:ResourceNotAvailableBySlave', resource));
        end
        
        % Verify that the specified event is not already in use for the
        % given resource. We only allow an event to be used once for DAQ
        % and once for STIM at a time.
        for ii = 1:obj.(['NumberOf' resource 'Lists'])
            % Check through each list already configured for the given
            % resource type to see if the list is using the requested event.
            if strcmpi(eventName, obj.([resource 'ListObject'])(ii).EventInfo.Name)
                error(message('vnt:XCP:EventAlreadyUsedForThisResource', eventName, resource));
            end
        end
        
        % Verify that the specified measurements are not already in use for
        % the given resource. We only allow a measurement to be used once
        % for DAQ and once for STIM at a time.
        for ii = 1:numel(measurementNames)
            % Check each resource list.
            for jj = 1:obj.(['NumberOf' resource 'Lists'])
                % Use logical indexing to check for the measurement.
                if any(strcmpi(measurementNames{ii}, obj.([resource 'ListObject'])(jj).MeasurementNames))
                    error(message('vnt:XCP:MeasurementAlreadyUsedForThisResource', measurementNames{ii}, resource));
                end
            end
        end
        
        % Get the detailed event information from the A2L file.
        eventInfo = obj.A2LFileObject.getEventInfo(eventName);
        
        % Validate that this event supports the specified resource. Some
        % events can be used for both STIM and DAQ while others only
        % support one option.
        if ~strcmpi(resource, eventInfo.Direction) && ~strcmpi(eventInfo.Direction, 'DAQ_STIM')
            error(message('vnt:XCP:EventDirectionNotSupported', eventInfo.Name, resource));
        end
        
        % If this is a STIM list, verify that the specified event is a
        % periodic/time-based event. Only time-based events are supported for
        % STIM in the current implementation, but the A2L file may have
        % non-periodic events configured as possible for STIM.
        if strcmpi(resource, 'STIM') && eventInfo.ChannelTimeCycleInSeconds == 0
            error(message('vnt:XCP:EventNotPeriodic', eventInfo.Name, resource));
        end
        
        % Get the detailed measurement information from the A2L file for
        % each specified measurement.
        measurementInfo = struct([]);
        for ii = 1:numel(measurementNames)
            measurementInfo = [measurementInfo obj.A2LFileObject.getMeasurementInfo(measurementNames{ii})]; %#ok<AGROW>
        end
        
        % Verify that each of the specified measurements are smaller in
        % size than the maximum allowed by the slave.
        for ii = 1:numel(measurementNames)
            if measurementInfo(ii).SizeInBytes > obj.A2LFileObject.DAQInfo.MaxODTEntrySize
                error(message('vnt:XCP:MeasurementExceedsMaximumODTEntrySize', ...
                    measurementNames{ii}, ...
                    obj.A2LFileObject.DAQInfo.MaxODTEntrySize));
            end
        end
        
        % Create a new measurement list for the specified resource.
        obj.([resource 'ListObject']) = [obj.([resource 'ListObject']) ...
            xcp.MeasurementList(obj.A2LFileObject, resource, eventInfo, measurementInfo)];
    end
    
    function freeMeasurementLists(obj)
    % freeMeasurementLists Remove all DAQ and STIM lists from the XCP channel.
    %
    %   freeMeasurementLists(OBJ) deletes all configured DAQ and STIM lists
    %   configured on the XCP channel object OBJ.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       freeMeasurementLists(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);

        % Validate current state conditions.
        obj.errorWhenMeasurementActive();
        obj.errorWhenNoMeasurementListsConfigured();
        
        % Erase the stored measurement lists.
        obj.DAQListObject = xcp.MeasurementList.empty();
        obj.STIMListObject = xcp.MeasurementList.empty();
    end
    
    function viewMeasurementLists(obj)
    % viewMeasurementLists View the configured DAQ and STIM lists.
    %
    %   viewMeasurementLists(OBJ) displays information to the command line
    %   about all of the currently configured DAQ and STIM lists on the XCP
    %   channel object OBJ.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       viewMeasurementLists(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);

        % Validate current state conditions.
        obj.errorWhenNoMeasurementListsConfigured();
        
        % Put the measurement lists together to configure them.
        measurementLists = [obj.DAQListObject obj.STIMListObject];

        % Display information for all configured measurements lists.
        fprintf(1, '\n');
        for ii = 1:obj.NumberOfMeasurementLists
            % Display basic list information.
            fprintf(1, '%s List #%d', measurementLists(ii).Resource, ii);
            if measurementLists(ii).EventInfo.ChannelTimeCycleInSeconds == 0
                fprintf(1, ' using the "%s" event ', measurementLists(ii).EventInfo.Name);
            else
                fprintf(1, ' using the "%s" event @ %f seconds ', ...
                    measurementLists(ii).EventInfo.Name, ...
                    measurementLists(ii).EventInfo.ChannelTimeCycleInSeconds);
            end
            fprintf(1, 'and the following measurements:\n');
            
            % Construct a disp table to display the measurements.
            measurementTable = internal.DispTable();
            measurementTable.ColumnSeparator = ' | ';
            measurementTable.ShowHeader = false;
            measurementTable.Indent = 3;
            measurementTable.addColumn('Measurements');
            for jj = 1:measurementLists(ii).NumberOfMeasurements
                measurementTable.addRow(measurementLists(ii).MeasurementNames{jj});
            end
            disp(measurementTable);
            
            % Display extra white space.
            fprintf(1, '\n');
        end
    end
    
    
    function startMeasurement(obj)
    % startMeasurement Start all configured DAQ and STIM lists.
    %
    %   startMeasurement(OBJ) actively starts all configured DAQ and STIM
    %   lists on the XCP channel object OBJ. DAQ lists begin acquiring
    %   data values from the slave module. STIM lists begin transmitting
    %   data values to the slave module.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       startMeasurement(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);
        
        % Validate current state conditions.
        obj.errorWhenDisconnected();
        obj.errorWhenMeasurementActive();
        obj.errorWhenNoMeasurementListsConfigured();
        
        % Clear existing DAQ data.
        obj.DAQData = [];
        
        % Put the measurement lists together to configure them.
        measurementLists = [obj.DAQListObject obj.STIMListObject];
        
        % Free all existing measurement lists.
        obj.ProtocolObject.freeDAQ();
        
        % Configure for the required number of lists.
        obj.ProtocolObject.allocDAQ(obj.NumberOfMeasurementLists);
        
        % Configure the ODTs for each list. All ODTs must be
        % configured before continuing to add ODT entries and
        % measurements.
        for ii = 1:obj.NumberOfMeasurementLists
            % Configure this list for the required number of ODTs.
            obj.ProtocolObject.allocODT( ...
                ii - 1, ... % List number adjusted for zero index.
                measurementLists(ii).NumberOfODTs);
        end
        
        % Configure the ODT entries for each ODT on each list. All
        % ODT entries must be configured before measurements are added
        % to the entries.
        for ii = 1:obj.NumberOfMeasurementLists
            % Loop to configure the ODT entries for each ODT.
            for jj = 1:measurementLists(ii).NumberOfODTs
                % Configure the ODT entries.
                obj.ProtocolObject.allocODTEntry( ...
                    ii - 1, ... % List number adjusted for zero index.
                    jj - 1, ... % ODT number adjusted for zero index.
                    measurementLists(ii).ODTObject(jj).NumberOfODTEntries);
            end
        end
        
        % Add the measurements to the appropriate list, ODT, and
        % ODT entry.
        for ii = 1:obj.NumberOfMeasurementLists
            % Loop through each ODT to add measurements.
            for jj = 1:measurementLists(ii).NumberOfODTs
                % Set the DAQ pointer to prepare for linking the
                % measurement information with the DAQ list, ODT, and
                % ODT entry.
                obj.ProtocolObject.setDAQPtr( ...
                    ii - 1, ... % List number adjusted for zero index.
                    jj - 1, ... % ODT number adjusted for zero index.
                    xcp.Utility.FIRST_ODT_ENTRY);
                
                % Loop to write each measurement to their ODT entries.
                for kk = 1:measurementLists(ii).ODTObject(jj).NumberOfODTEntries
                    % Write the measurement information to the ODT.
                    obj.ProtocolObject.writeDAQ( ...
                        xcp.Utility.IGNORE_BIT_OFFSET, ...
                        measurementLists(ii).ODTObject(jj).ODTEntryInfo(kk).SizeInBytes, ...
                        measurementLists(ii).ODTObject(jj).ODTEntryInfo(kk).ECUAddressExtension, ...
                        measurementLists(ii).ODTObject(jj).ODTEntryInfo(kk).ECUAddress);

                    % If this measurement list is a DAQ list, then add 
                    % an entry for this measurement to the DAQ data.
                    if strcmpi(measurementLists(ii).Resource, 'DAQ')
                        obj.DAQData.(measurementLists(ii).ODTObject(jj).ODTEntryInfo(kk).Name) = [];
                    end
                end
            end
        end
        
        % Configure the lists for starting.
        for ii = 1:obj.NumberOfMeasurementLists
            % Set the measurement list mode.
            obj.ProtocolObject.setDAQListMode( ...
                measurementLists(ii).ListMode, ...
                ii - 1, ... % List number adjusted for zero index.
                measurementLists(ii).EventInfo.ChannelNumber, ...
                xcp.Utility.NO_TRANSMISSION_RATE_PRESCALER, ...
                xcp.Utility.LET_SLAVE_MANAGE_PRIORITY);

            % Prepare the list to start.
            [~, firstNumber] = obj.ProtocolObject.startStopDAQList( ...
                xcp.Utility.SET_SYNCHRONIZED_START_STOP, ...
                ii - 1); % List number adjusted for zero index.

            % Set the absolute list number for the list.
            measurementLists(ii).Number = ii - 1;
            % Set the numbers for the ODTs in the list.
            measurementLists(ii).setODTNumbers(firstNumber);
        end

        % Configure STIM messages for each STIM list.
        for ii = 1:obj.NumberOfSTIMLists
            obj.STIMListObject(ii).configureSTIMMessageObjects();
        end
        
        % Turn on the lists.
        obj.ProtocolObject.startStopSynch(xcp.Utility.SYNCH_START_SELECTED_LISTS);
        
        % Configure and start a timer to process DAQ messages if DAQ is
        % being used as a resource.
        if ~isempty(obj.DAQListObject)
            obj.DAQTimer = internal.IntervalTimer(obj.DAQTimerRate);
            obj.DAQTimerListener = event.listener( ...
                obj.DAQTimer, ...
                'Executing', ...
                @(~, ~)(obj.executeDAQTimerCallback));
            start(obj.DAQTimer);
        end

        % Configure and start a timer to process STIM messages if STIM is
        % being used as a resource.
        if ~isempty(obj.STIMListObject)
            obj.STIMTimer = internal.IntervalTimer(obj.STIMTimerRate);
            obj.STIMTimerListener = event.listener( ...
                obj.STIMTimer, ...
                'Executing', ...
                @(~, ~)(obj.executeSTIMTimerCallback));
            start(obj.STIMTimer);
        end
        
        % Modify the internal state.
        obj.MeasurementState = true;
    end
    
    function stopMeasurement(obj)
    % stopMeasurement Stop all configured DAQ and STIM lists.
    %
    %   stopMeasurement(OBJ) stops all configured DAQ and STIM
    %   lists on the XCP channel object OBJ. DAQ lists stop acquiring
    %   data values from the slave module. STIM lists stop transmitting
    %   data values to the slave module.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       startMeasurement(channelObj);
    %       stopMeasurement(channelObj);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(1, 1);

        % Validate current state conditions.
        obj.errorWhenDisconnected();
        obj.errorWhenMeasurementInactive();
        
        % Stop and remove the STIM timer if in use.
        if ~isempty(obj.STIMTimer)
            stop(obj.STIMTimer);
            obj.STIMTimer = [];
        end
        
        % Turn off all of the lists.
        obj.ProtocolObject.startStopSynch(xcp.Utility.SYNCH_STOP_ALL_LISTS);
        
        % Stop and remove the DAQ timer if in use.
        if ~isempty(obj.DAQTimer)
            stop(obj.DAQTimer);
            obj.DAQTimer = [];
        end
        
        % Modify the internal state.
        obj.MeasurementState = false;
    end
    
    
    function data = readDAQListData(obj, measurementName, varargin)
    % readDAQListData Read samples of the specified measurement from a DAQ list.
    %
    %   DATA = readDAQListData(OBJ, MEASUREMENT_NAME) returns all 
    %   acquired DAQ list data from the XCP channel object OBJ for the 
    %   specified MEASUREMENT_NAME. 
    %
    %   DATA = readDAQListData(OBJ, MEASUREMENT_NAME, COUNT) returnes  
    %   acquired DAQ list data from the XCP channel object OBJ for the 
    %   specified MEASUREMENT_NAME. In this case, the quantity of data
    %   values to read is given as COUNT. That number of values will be
    %   returned or less if fewer samples are available.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       startMeasurement(channelObj);
    %       data = readDAQListData(channelObj, 'Measurement1', 10);
    %       data = readDAQListData(channelObj, 'Measurement1');
    %
    %   See also VNT.
        
        % Perform an argument count check.
        narginchk(2, 3);

        % Validate the measurement name argument.
        validatestring(measurementName, obj.A2LFileObject.Measurements, ...
            'xcp.readDAQListData', 'measurement name', 2);
        % Validate the number of samples argument if it exists.
        if nargin == 3
            validateattributes(varargin{1}, {'numeric'}, ...
                {'nonempty', 'integer', 'real', 'finite', 'nonnan', 'positive'}, ...
                'xcp.readDAQListData', 'number of samples', 3);
        end
        
        % If the internal DAQ data is empty, then return empty as no data
        % is yet available.
        if isempty(obj.DAQData)
            data = [];
            return;
        end
        
        % Read the number of samples currently available.
        try 
            sampleCount = numel(obj.DAQData.(measurementName));
        catch err
            % If the measurement is not in the DAQ data, then it has not
            % been added to a DAQ list, so error.
            error(message('vnt:XCP:MeasurementNotConfiguredForDAQ', measurementName));
        end
        
        % If no samples are available, then return empty.
        if sampleCount == 0
            data = [];
            return;
        end
        
        % If a sample count was provided and that value is less than the
        % number of samples available, then read only that quantity.
        if nargin == 3 && varargin{1} < sampleCount
            sampleCount = varargin{1};
        end
        
        % Return the data.
        data = obj.DAQData.(measurementName)(1:sampleCount);
        % Clear the read data from the object.
        obj.DAQData.(measurementName)(1:sampleCount) = [];
    end
    
    function writeSTIMListData(obj, measurementName, newValue)
    % writeSTIMListData Write a new value of the specified measurement to a STIM list.
    %
    %   writeSTIMListData(OBJ, MEASUREMENT_NAME, NEW_VALUE) writes a
    %   NEW_VALUE to the STIM list measurement MEASUREMENT_NAME on the XCP
    %   channel object OBJ.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       startMeasurement(channelObj);
    %       writeSTIMListData(channelObj, 'Measurement1', newValue);
    %
    %   See also VNT.

        % Perform an argument count check.
        narginchk(3, 3);        

        % Validate the measurement name argument.
        validatestring(measurementName, obj.A2LFileObject.Measurements, ...
            'xcp.writeSTIMListData', 'measurement name', 2);
        
        % Validate the value argument.
        validateattributes(newValue, {'numeric'}, ...
            {'nonempty', 'real', 'finite', 'nonnan'}, ...
            'xcp.writeSTIMListData', 'new value', 3);
        
        % Pack the data value.
        for ii = 1:obj.NumberOfSTIMLists
            % Try to write the value to each STIM list.
            result = obj.STIMListObject(ii).writeSTIMData(measurementName, newValue);
            
            % Check the result. If the write worked, then return.
            if result
                return;
            end
        end
        
        % If no write was made, error as the measurement was not found to
        % be configured for STIM.
        error(message('vnt:XCP:MeasurementNotConfiguredForSTIM', measurementName));        
    end

    
    function value = readSingleValue(obj, measurementName)
    % readSingleValue Read a sample of the specified measurement from direct memory.
    %
    %   VALUE = readSingleValue(OBJ, MEASUREMENT_NAME) acquires a single
    %   VALUE for the specified MEASUREMENT_NAME through the XCP channel
    %   object OBJ. This action is performed via direct read from memory on
    %   the slave module.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       value = readSingleValue(channelObj, 'Measurement1');
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(2, 2);

        % Validate the measurement name argument.
        validatestring(measurementName, obj.A2LFileObject.Measurements, ...
            'xcp.readSingleValue', 'measurement name', 2);

        % Validate current state conditions.
        obj.errorWhenDisconnected();
        
        % Get the measurement information from the A2L file.
        info = obj.A2LFileObject.getMeasurementInfo(measurementName);
        
        % Set the memory transfer address for this measurement.
        obj.ProtocolObject.setMTA(info.ECUAddressExtension, info.ECUAddress);
        
        % Upload the data value from the slave.
        response = obj.ProtocolObject.upload(info.SizeInBytes);

        % Decode and return the measurement value.
        firstByte = 2;
        lastByte = firstByte + info.SizeInBytes - 1;
        value = xcp.Utility.unpackMeasurement(info, response(firstByte:lastByte));
    end
    
    function writeSingleValue(obj, measurementName, newValue)
    % writeSingleValue Write a new value of the specified measurement to direct memory.
    %
    %   writeSingleValue(OBJ, MEASUREMENT_NAME, NEW_VALUE) writes a single
    %   NEW_VALUE for the specified MEASUREMENT_NAME through the XCP channel
    %   object OBJ. This action is performed via direct write to memory on
    %   the slave module.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       writeSingleValue(channelObj, 'Measurement1', newValue);
    %
    %   See also VNT.
    
        % Perform an argument count check.
        narginchk(3, 3);

        % Validate the measurement name argument.
        validatestring(measurementName, obj.A2LFileObject.Measurements, ...
            'xcp.writeSingleValue', 'measurement name', 2);

        % Validate the value argument.
        validateattributes(newValue, {'numeric'}, ...
            {'nonempty', 'real', 'finite', 'nonnan'}, ...
            'xcp.writeSingleValue', 'new value', 3);
        
        % Validate current state conditions.
        obj.errorWhenDisconnected();

        % Get the measurement information from the A2L file.
        info = obj.A2LFileObject.getMeasurementInfo(measurementName);
        
        % Convert the data value into bytes suitable for download.
        byteValue = xcp.Utility.packMeasurement(info, newValue);

        % Set the memory transfer address for this measurement.
        obj.ProtocolObject.setMTA(info.ECUAddressExtension, info.ECUAddress);
        % Download the bytes to the slave.
        obj.ProtocolObject.download(info.SizeInBytes, byteValue);
    end

    
    function result = isConnected(obj)
    % isConnected - Returns a boolean value to indicate active connection to the slave.
    %
    %   RESULT = isConnected(OBJ) returns a boolean RESULT indicating true
    %   if the XCP channel object OBJ is actively connected to the slave
    %   module, otherwise it returns false.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       result = isConnected(channelObj);
    %
    %   See also VNT.
    
        % Return an indication based off the internal state property.
        result = obj.ConnectState;
    end
    
    function result = isMeasurementRunning(obj)
    % isMeasurementRunning - Returns a boolean value to indicate active measurement activity.
    %
    %   RESULT = isMeasurementRunning(OBJ) returns a boolean RESULT indicating 
    %   true if the XCP channel object OBJ is actively running DAQ and/or STIM
    %   measurement lists with the slave module, otherwise it returns false.
    %
    %   Example:
    %       a2lObj = xcpA2L('myFile.a2l');
    %       channelObj = xcpChannel(a2lObj, 'CAN', 'Vector', 'CANcaseXL 1', 1);
    %       connect(channelObj);
    %       createMeasurementList(channelObj, 'DAQ', 'Event1', 'Measurement1');
    %       startMeasurement(channelObj);
    %       result = isMeasurementRunning(channelObj);
    %
    %   See also VNT.
    
        % Return an indication based off the internal state property.
        result = obj.MeasurementState;
    end

end


methods (Access = 'private')
    
    function executeDAQTimerCallback(obj)
    % executeDAQTimerCallback A timer callback function that runs periodic DAQ tasks.
    
        % If there is no logged information at the protocol level, then exit.
        if numel(obj.ProtocolObject.DAQData) == 0
            return;
        end
        
        % Copy the information to a local variable to process it.
        localData = obj.ProtocolObject.DAQData;
        % Delete the copied information in the protocol object.
        obj.ProtocolObject.DAQData(1:numel(localData)) = [];
        
        % Loop through and process each received entry.
        for ii = 1:numel(localData)
            % Find the ODT in the DAQ lists that matches the ODT of the
            % received message.
            switch obj.A2LFileObject.DAQInfo.IdentificationFieldType
                case xcp.Utility.IDENTIFICATION_FIELD_TYPE_ABSOLUTE
                    % When absolute ODT numbering is used, we not know the
                    % list number at this time, so we need to search
                    % through them all until we find the right ODT.
                    for jj = 1:obj.NumberOfDAQLists
                        % Search the list for the ODT, and break when found.
                        ODTObject = obj.DAQListObject(jj).getODTObject(localData{ii}(1));
                        if ~isempty(ODTObject)
                            break;
                        end
                    end
                    
                case xcp.Utility.IDENTIFICATION_FIELD_TYPE_RELATIVE_BYTE
                    % For relative byte identification fields, we know the
                    % list number and the relative ODT number. Both are
                    % byte values.
                    ODTObject = obj.DAQListObject(1 + localData{ii}(2)).getODTObject(localData{ii}(1));
                    
                case xcp.Utility.IDENTIFICATION_FIELD_TYPE_RELATIVE_WORD
                    % For relative word identification fields, again we
                    % know both the list number and relative ODT number.
                    % The ODT number is a byte. The list number is a word.
                    listNumber = 1 + xcp.Utility.unpackParameter( ...
                        obj.A2LFileObject.ProtocolLayerInfo.ByteOrder, ...
                        xcp.Utility.BYTES_IN_WORD, ...
                        localData{ii}(2:3));
                    ODTObject = obj.DAQListObject(listNumber).getODTObject(localData{ii}(1));
                    
                case xcp.Utility.IDENTIFICATION_FIELD_TYPE_RELATIVE_WORD_ALIGNED
                    % For relative word aligned identification fields, again we
                    % know both the list number and relative ODT number.
                    % The ODT number is a byte. The list number is a word,
                    % but it is located in different byte positions.
                    listNumber = 1 + xcp.Utility.unpackParameter( ...
                        obj.A2LFileObject.ProtocolLayerInfo.ByteOrder, ...
                        xcp.Utility.BYTES_IN_WORD, ...
                        localData{ii}(3:4));
                    ODTObject = obj.DAQListObject(listNumber).getODTObject(localData{ii}(1));
            end
            
            % Set the byte index to begin reading data after the
            % identification field.
            currentByte = 1 + xcp.Utility.MAP_IDENTIFICATION_FIELD_TYPE_BYTES_USED( ...
                obj.A2LFileObject.DAQInfo.IdentificationFieldType);
            
            % Loop through and read all of the measurements in this ODT.
            for jj = 1:ODTObject.NumberOfODTEntries
                % Use the measurement information to read the value.
                firstByte = currentByte;
                lastByte = firstByte + ODTObject.ODTEntryInfo(jj).SizeInBytes - 1;
                data = xcp.Utility.unpackMeasurement(ODTObject.ODTEntryInfo(jj), localData{ii}(firstByte:lastByte));

                % Set this new data into the DAQ data arrays.
                 obj.DAQData.(ODTObject.ODTEntryInfo(jj).Name) = ...
                     [obj.DAQData.(ODTObject.ODTEntryInfo(jj).Name) data];

                % Increment the current byte counter to index the next ODT
                % Entry in the message data.
                currentByte = currentByte + ODTObject.ODTEntryInfo(jj).SizeInBytes;
            end
        end
    end
    
    function executeSTIMTimerCallback(obj)
    % executeSTIMTimerCallback A timer callback function that runs periodic STIM tasks.

        % Loop through each STIM list.
        for ii = 1:obj.NumberOfSTIMLists
            % Subtract the STIM timer rate from the counter.
            obj.STIMListObject(ii).STIMCounter = obj.STIMListObject(ii).STIMCounter - obj.STIMTimerRate;
            
            % If the counter has gone negative then it is time for this
            % list to be transmitted.
            if obj.STIMListObject(ii).STIMCounter <= 0
                % Transmit the message for all ODTs in the list.
                obj.ProtocolObject.TransportLayerObject.transmit([obj.STIMListObject(ii).ODTObject.STIMMessageObject]);
                % Reset the counter.
                obj.STIMListObject(ii).STIMCounter = obj.STIMListObject(ii).STIMPeriod;
            end
        end
    end

    function unlockResource(obj, resource)
    % unlockResource Unlocks a slave with seed and key security access.
    %
    %   resource - A string indicating which resource to unlock.
        
        % Check that a seed and key DLL file is configured.
        if isempty(obj.SeedKeyDLLFullPath)
            error(message('vnt:XCP:NoSeedKeyDLLFileConfigured', resource));
        end
        
        % Call for the first part of the seed.
        [~, seedLength, seedValue] = obj.ProtocolObject.getSeed( ...
            xcp.Utility.FIRST_PART_OF_SEED, ...
            xcp.Utility.(['RESOURCE_' resource]));
        % Store the number of seed bytes read so far.
        seedBytesRead = numel(seedValue);
        
        % Get the additional parts of the seed value if necessary.
        while seedBytesRead ~= seedLength
            % Call for additional seed bytes.
            [~, ~, seedValueAdditional] = obj.ProtocolObject.getSeed( ...
                xcp.Utility.REMAINING_PART_OF_SEED, ...
                xcp.Utility.(['RESOURCE_' resource]));
            % Put the seed bytes together.
            seedValue = [seedValue seedValueAdditional]; %#ok<AGROW>
            % Add to the number of seed bytes read.
            seedBytesRead = seedBytesRead + numel(seedValueAdditional);
        end
        
        % Call the seed and key DLL MEX interface to turn the seed into a key.
        [status, keyLength, keyValue] = mexXCPSeedKey( ...
            'skGetKeyFromSeed', ...
            obj.SeedKeyDLLFullPath, ...
            xcp.Utility.(['RESOURCE_' resource]), ...
            seedLength, ...
            seedValue);
        
        % Verify that the key value was obtained.
        if status ~= xcp.Utility.SEED_KEY_SUCCESS
            error(message('vnt:XCP:KeyGenerationFailed', resource, xcp.Utility.MAP_SEED_KEY_RESPONSE_TO_NAME(status)));
        end        
        
        % Use the key value to unlock the resource.
        keyBytesRemainingToSend = keyLength;
        while keyBytesRemainingToSend > 0
            % Determine which key value bytes to send.
            if keyBytesRemainingToSend <= xcp.Utility.MAX_KEY_BYTES_PER_CTO
                % Send all available bytes.
                keyBytesToSend = keyValue;
                % Clear the key value.
                keyValue = [];
            else
                % Send the maximum number of bytes that fit into the CTO.
                keyBytesToSend = keyValue(1:xcp.Utility.MAX_KEY_BYTES_PER_CTO);
                % Clear those bytes in the key value.
                keyValue(1:xcp.Utility.MAX_KEY_BYTES_PER_CTO) = [];
            end
                
            % Issue an unlock command.
            [~, protectionStatus] = obj.ProtocolObject.unlock(keyBytesRemainingToSend, keyBytesToSend);
            
            % Adjust the number of remaining key bytes to send.
            keyBytesRemainingToSend = keyBytesRemainingToSend - numel(keyBytesToSend);
        end
        
        % Verify that the resource was unlocked.
        if bitand(protectionStatus, xcp.Utility.(['RESOURCE_' resource]))
            error(message('vnt:XCP:ResourceDidNotUnlock', resource));
        end
    end
    
    
    function errorWhenConnected(obj)
    % errorWhenConnected Throws an error if the channel is connected.
    
        % Check the state.
        if obj.isConnected()
            % Throw an error to prevent operation by the calling method.
            error(message('vnt:XCP:ChannelConnected'));
        end
    end
    
    function errorWhenDisconnected(obj)
    % errorWhenDisconnected Throws an error if the channel is disconnected.
    
        % Check the state.
        if ~obj.isConnected()
            % Throw an error to prevent operation by the calling method.
            error(message('vnt:XCP:ChannelDisconnected'));
        end
    end
    
    function errorWhenMeasurementActive(obj)
    % errorWhenMeasurementActive Throws an error if measurements are active.
    
        % Check the state.
        if obj.isMeasurementRunning()
            % Throw an error to prevent operation by the calling method.
            error(message('vnt:XCP:MeasurementActive'));
        end
    end
    
    function errorWhenMeasurementInactive(obj)
    % errorWhenMeasurementInactive Throws an error if measurements are inactive.
    
        % Check the state.
        if ~obj.isMeasurementRunning()
            % Throw an error to prevent operation by the calling method.
            error(message('vnt:XCP:MeasurementInactive'));
        end
    end
    
    function errorWhenNoMeasurementListsConfigured(obj)
    % errorWhenNoMeasurementListsConfigured Throws an error if no lists are configured.
        
        % Check if any DAQ or STIM lists are currently configured.
        if isempty(obj.DAQListObject) && isempty(obj.STIMListObject)
            % Throw an error to prevent operation by the calling method.
            error(message('vnt:XCP:NoMeasurementsListsConfigured'));
        end
    end
   
end


methods % Custom get/set methods for properties. 
    
    function value = get.NumberOfDAQLists(obj)
        value = numel(obj.DAQListObject);
    end
    
    function value = get.NumberOfSTIMLists(obj)
        value = numel(obj.STIMListObject);
    end
    
    function value = get.NumberOfMeasurementLists(obj)
        value = numel(obj.DAQListObject) + numel(obj.STIMListObject);
    end
    
    function set.SeedKeyDLL(obj, newValue)
        % Validate the new value argument.
        validateattributes(newValue, {'char'}, {'nonempty'});

        % Get the full path to the seed and key DLL file.
        [status, fileInfo, ~] = fileattrib(newValue);
        % Make sure the file exists.
        if status == 0
            error(message('vnt:XCP:SeedKeyDLLFileNotFound'));
        end
        
        % Set the property value.
        obj.SeedKeyDLL = newValue;
        % Also set a reference to the full path to the file.
        obj.SeedKeyDLLFullPath = fileInfo.Name; %#ok<MCSUP>
    end

end % Custom get/set methods for properties. 
    
end