classdef helperBLEConnectionStateModel < handle
%helperBLEConnectionStateModel Create an object for a BLE LL connection
%   CFGLLCONN = helperBLEConnectionStateModel creates an object, CFGLLCONN,
%   for a peer-to-peer connection at BLE (Bluetooth Low Energy) Link Layer
%   with default values.
%
%   CFGLLCONN = helperBLEConnectionStateModel(Name, Value) creates a BLE LL
%   connection object, CFGLLCONN, with the specified property Name set to
%   the specified Value. You can specify additional name-value pair
%   arguments in any order as (Name1, Value1, ..., NameN, ValueN).
%
%   helperBLEConnectionStateModel properties:
%
%   AccessAddress               - Unique connection address
%   InstantOffset               - Connection event instant offset
%   ChannelSelection            - Channel selection algorithm system object
%   ConnectionInterval          - Connection interval

%   Copyright 2018-2019 The MathWorks, Inc.

properties
    %AccessAddress Unique connection address
    %   Specify the access address as a 8-element character vector or
    %   string scalar denoting a 4-octet hexadecimal value. This property
    %   indicates a unique 32-bit address generated by the Link Layer for
    %   the new connection or for a periodic advertisement between two
    %   devices. The default value is '01234567'.
    AccessAddress = '01234567'
    
    %InstantOffset Connection event instant offset
    %   Specify instant offset as an integer in the range [0, 65535]. This
    %   property indicates the event count offset at which certain action
    %   needs to take place (e.g. using updated connection parameters). The
    %   default value is 10.
    InstantOffset = 10
    
    %ChannelSelection Channel selection algorithm system object
    %   Specify this property as a system object of type <a
    %   href="matlab:help('bleChannelSelection')">bleChannelSelection</a>.
    %   Selects a new data channel for each connection event using settings
    %   of this system object. The default value is an object of type
    %   'bleChannelSelection' with all properties set to their default
    %   values.
    ChannelSelection
    
    %ConnectionInterval Connection interval
    %   Specify the connection interval as an integer in the range [6,
    %   3200]. This property indicates the interval between the start of
    %   two consecutive connection events. Each unit is taken as 1.25 ms so
    %   that the resultant connection interval (ConnectionInterval*1.25) is
    %   in the range of 7.5 ms to 4.0 s. The default value is 6 (7.5 ms).
    ConnectionInterval = 6
end

properties (Hidden)
    %ConnectionEvent LL connection event object
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBLELLConnectionEvent')">helperBLELLConnectionEvent</a>.
    %   The Link Layer connection events are managed using settings of this
    %   object. The default value is an object of type
    %   'helperBLELLConnectionEvent' with all properties set to their
    %   default values.
    ConnectionEvent
    
    %ConnectionEventOffset Offset for starting the connection events
    % Specify the connection event offset as an integer in milliseconds.
    % This is the offset after which connection events of this Link Layer
    % connection starts. The default value is 0.
    ConnectionEventOffset = 0
    
    %IsChannelListUpdated Flags to determine whether the channel
    %list is updated or not.
    IsChannelListUpdated
    
    %ChannelMap Updated channel map
    % Column represents a channel number from 1 to 37. The value 0
    % represents 'bad channel' and 1 represents 'good channel'.
    ChannelMap
    
    %LastNRxStatus Status of the recent N (i.e RxStatusCount) received
    %packets
    % LastNRxStatus is a two-dimensional array. LastNRxStatus(channelNum,
    % i) contains information about the status of previous 'i-th' reception
    % in channel 'channelNum'. The received packet status value 0
    % represents 'Failed' and 1 represents 'Success'.
    LastNRxStatus
    
    %RxIdx The current receiving packet index in LastNRxStatus array
    % RxIdx is a vector. RxIdx(channelNum) represents the
    % index in channel 'channelNum'.
    RxIdx
    
    %ChannelBusyStatus Status of the recent N (i.e EDIndicationsCount)
    %energy detection (ED) indications
    % ChannelBusyStatus is a two-dimensional array.
    % ChannelBusyStatus(channelNum, i) contains information about status
    % the of previous 'i-th' energy detection indication in channel
    % 'channelNum'. The channel busy status value 0 represents 'Busy' and 1
    % represents 'Idle'.
    ChannelBusyStatus
    
    %ChannelBusyStatusIdx The current channel busy status index in
    %ChannelBusyStatus array
    % ChannelBusyStatusIdx is a vector. ChannelBusyStatusIdx(channelNum)
    % represents the index in channel 'channelNum'.
    ChannelBusyStatusIdx
    
    %TotalRxCount Counter to indicate the number of packets received 
    %from the start of simulation
    % TotalRxCount is a vector. TotalRxCount(channelNum) represents the
    % overall packets received in channel 'channelNum'.
    TotalRxCount
    
    %TotalRxFailureCount Counter to indicate the number of reception
    %failures from the start of simulation
    % TotalRxFailureCount is a vector. TotalRxFailureCount(channelNum)
    % represents the overall reception failures in channel 'channelNum'.
    TotalRxFailureCount
    
    %ChannelMapUpdateFlag Indicates whether the Master has already sent a
    %channel map update packet or not. True indicates the channel map
    %packet is transmitted to Slave and false indicates response is
    %received from the Slave for the transmitted 'channel map update
    %indication' packet.
    ChannelMapUpdateFlag
    
    %RetransmitFlag Indicates whether the packet is acknowledged or not.
    %This is set to true when a packet is transmitted. This flag is set to
    %false only when the packet is acknowledged.
    RetransmitFlag
    
    %ResponseReceivedFlag Indicates whether the response packet of the
    %previous transmission is received or not. This is set to true when a
    %response packet is received. This flag is reset to false when a packet
    %is transmitted.
    ResponseReceivedFlag
    
    %SequenceCounter Incremental counter for assigning sequence number in
    %transmitting packet.
    SequenceCounter
    
    %NextExpectedSequenceCounter Incremental counter for assigning next
    %expected sequence number in transmitting packet.
    NextExpectedSequenceCounter
    
    %InitialExchange Initial packet exchange
    InitialExchange
    
    %PreviousTxPacket Previous transmitted packet. It is used when
    %retransmission is required.
    PreviousTxPacket = struct('LLPDU',[], ...
                              'AccessAddress','', ...
                              'RateIndex','', ...
                              'ChannelIndex',-1)
    
    %CurrentChannel Current channel being used for transmission or
    %reception.
    CurrentChannel
    
    %ConnectionTerminated Indicates whether the connection has been
    %terminated or not.
    ConnectionTerminated
    
    %Instant Indicates the connection event count at which channel map has
    %to be updated.
    Instant
    
    %ClassificationInterval interval to classify the channels
    ClassificationInterval
    
    %CRCInitialization Initial value for CRC calculation
    CRCInitialization = '012345'
    
    %NewChannelMap Indicates the new channel map sent or received in the
    %channel map update packet. (per each peer)
    NewChannelMap
    
    %SendResponse Response packet has to be sent or not
    SendResponse = false
    
    %ResponsePacket Response packet
    ResponsePacket = []
    
    %PollBadChannels Energy poll in bad channels
    PollBadChannels = false
    
    %ChannelClassificationCount Number of times channels are classified
    ChannelClassificationCount = 0
    
    %ClassificationStats Channel classification statistics
    ClassificationStats
    
    %ChannelMapStats Channel map statistics after classification
    ChannelMapStats
end

properties(Constant, Hidden)
    % Number of data channels
    DataChannelsCount = 37
    
    % Maximum times channel classification statistics will be collected
    MaxClassifications = 65535
end

properties(Access = private)
    %pOffsetTimer Timer for connection events offset in microseconds
    pOffsetTimer = 0
    
    %pClassificationTimer decrementing counter of channel classification
    %interval
    pClassificationTimer
end

methods
    % Constructor
    function obj = helperBLEConnectionStateModel(varargin)
        if (mod(nargin, 2) ~= 0)
            error('Invalid property/value pair arguments.');
        end
        obj.ChannelSelection = bleChannelSelection;
        for idx = 1:2:nargin
            obj.(char(varargin{idx})) = varargin{idx+1};
        end
    end
    
    % For auto completion
    function v = set(obj, prop)
        v = obj.([prop, 'Values']);
    end
    
    function set.AccessAddress(obj, value)
        ble.internal.validateHex(value, 8, 'AccessAddress');
        obj.AccessAddress = char(value);
    end
    
    function set.InstantOffset(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            'integer', '>=', 0, '<=', 65535}, mfilename, 'InstantOffset');
        obj.InstantOffset = value;
    end
    
    function set.ChannelSelection(obj, value)
        validateattributes(value, {'bleChannelSelection'}, {'scalar'}, ...
            mfilename, 'ChannelSelection');
        obj.ChannelSelection = value;
    end
    
    function set.ConnectionInterval(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            'integer', '>=', 6, '<=', 3200}, mfilename, 'ConnectionInterval');
        obj.ConnectionInterval = value;
    end
end

methods
    function initialize(obj, classificationConfig)
        % Initialize IsChannelListUpdated flag. This is used by the Master
        % device to send the updated channels list to Slave
        obj.IsChannelListUpdated = false;
        
        % Initialize channel map. True indicates 'good channel', False
        % indicates 'bad channel'
        obj.ChannelMap = true(1, obj.DataChannelsCount);

        % Initialize the status of the last 'n' receptions to -1. These
        % receptions are per data channel. Value 1 indicates success, 0
        % indicates failure and -1 indicates not yet received
        obj.LastNRxStatus = ...
            zeros(obj.DataChannelsCount, classificationConfig.RxStatusCount)-1;

        % Initialize per channel RxIdx to 1
        obj.RxIdx = ones(1, obj.DataChannelsCount);
        
        % Initialize the status of the channel busy status to -1. These
        % status are per data channel. Value 1 indicates channel is busy, 0
        % indicates channel is idle and -1 indicates status not yet
        % observed
        obj.ChannelBusyStatus = ...
            zeros(obj.DataChannelsCount, classificationConfig.EDIndicationsCount)-1;
        
        % Initialize per channel ChannelBusyStatusIdx to 1
        obj.ChannelBusyStatusIdx = ones(1, obj.DataChannelsCount);
        
        % Initialize per channel TotalRxCount to 0
        obj.TotalRxCount = zeros(1, obj.DataChannelsCount);
        
        % Initialize per channel TotalRxFailureCount to 0
        obj.TotalRxFailureCount = zeros(1, obj.DataChannelsCount);
        
        % Initialize ChannelMapUpdateFlag to false.
        obj.ChannelMapUpdateFlag = false;
        
        % Initialize RetransmitFlag to false. True indicates retransmit the
        % last packet and false indicates do not retransmit
        obj.RetransmitFlag = false;
        
        % Initialize ResponseReceivedFlag to false. True indicates the
        % response is received and False indicates response is not received
        obj.ResponseReceivedFlag = false;
        
        % Initialize SequenceCounter to 0
        obj.SequenceCounter = 0;
        
        % Initialize NextExpectedSequenceCounter 0
        obj.NextExpectedSequenceCounter = 0;
        
        % Initialize InitialExchange true
        obj.InitialExchange = true;
        
        % Initialize PreviousTxPacket to empty link layer packet structure
        obj.PreviousTxPacket = struct('LLPDU',[], ...
                                      'AccessAddress','', ...
                                      'RateIndex','', ...
                                      'ChannelIndex',-1);

        % Initialize CurrentChannel to -1
        obj.CurrentChannel = -1;

        % Initialize ConnectionTerminated to 0
        obj.ConnectionTerminated = 0;
        
        % Initialize the connection event offset timer
        obj.pOffsetTimer = obj.ConnectionEventOffset;
        
        % Initialize the instant
        obj.Instant = -1;
        
        % Initialize the classification interval and timer
        obj.ClassificationInterval = classificationConfig.ClassificationInterval;
        obj.pClassificationTimer = obj.ClassificationInterval;
        
        % Initialize NewChannelMap to true. True indicates 'good channel'
        % and false indicates 'bad channel'
        obj.NewChannelMap = true(1, obj.DataChannelsCount);
        
        % Initialize SendResponse
        obj.SendResponse = false;
        
        % Initialize ResponsePacket
        obj.ResponsePacket = [];
        
        % Initialize PollBadChannels
        obj.PollBadChannels = false;
        
        % Initialize ChannelClassificationCount
        obj.ChannelClassificationCount = 0;
        
        % Initialize ClassificationStats
        obj.ClassificationStats = zeros(obj.MaxClassifications, ...
            obj.DataChannelsCount, classificationConfig.RxStatusCount)*-1;
        
        % Initialize ChannelMapStats
        obj.ChannelMapStats = zeros(obj.MaxClassifications, obj.DataChannelsCount)*-1;
    end
    
    function state = updateConnection(obj, packetInfo, classificationConfig)
        % Initialize
        state = helperBLELLConnectionEventStatus.Idle;
        
        % Wait for offset timer to start connection events
        if obj.pOffsetTimer > 0 %expires
            obj.pOffsetTimer = obj.pOffsetTimer - obj.ConnectionEvent.StepTimerUnit;
        else
            state = run(obj.ConnectionEvent, packetInfo);
        end
        
        if obj.ConnectionEvent.SelectNewChannel
            % Selects a new data channel
            obj.CurrentChannel = obj.ChannelSelection()+1;
            obj.PollBadChannels = true;
            
            if strcmp(obj.ConnectionEvent.Role, 'Master')
                % Resets the classification timer by one connection event
                if (obj.pClassificationTimer == 0)
                    obj.pClassificationTimer = obj.ClassificationInterval;
                    
                    if obj.ChannelClassificationCount >= 65535
                        obj.ChannelClassificationCount = 0; % Resets the classification counter
                    end
                    
                    % Update channel classification count
                    obj.ChannelClassificationCount = ...
                        obj.ChannelClassificationCount + 1;
                    
                    obj.ClassificationStats(obj.ChannelClassificationCount, :, :) = obj.LastNRxStatus;
                    
                    classifyChannels(classificationConfig, obj);
                    obj.IsChannelListUpdated = true;
                    
                    obj.ChannelMapStats(obj.ChannelClassificationCount, :) = obj.ChannelMap;
                end
                
                % Decrement the classification timer
                obj.pClassificationTimer = obj.pClassificationTimer - 1;
            end
        end
    end
    
    function llPacket = generatePacket(obj, packetType)
        %generatePacket generate and returns a link layer packet
        %corresponding to the given link layer packet type packetType
        
        llPacket = struct('LLPDU',[], ...
            'AccessAddress',obj.AccessAddress, ...
            'RateIndex','', ...
            'ChannelIndex',obj.CurrentChannel);
        
        % Set the retransmit and response flags
        obj.RetransmitFlag = true;
        obj.ResponseReceivedFlag = false;
        
        switch(packetType)
            case 'Empty'
                % Empty Link-Layer packet
                cfg = ...
                    bleLLDataChannelPDUConfig(...
                    'LLID','Data (continuation fragment/empty)', ...
                    'SequenceNumber',obj.SequenceCounter, ...
                    'NESN',obj.NextExpectedSequenceCounter, ...
                    'CRCInitialization',obj.CRCInitialization);
                llPacket.LLPDU = bleLLDataChannelPDU(cfg);
                
            case 'Random'
                % Link-Layer packet with random payload bits
                
                % Payload from upper layers
                payload = randi([0 1], 24, 1);
                
                cfg = ...
                    bleLLDataChannelPDUConfig(...
                    'LLID', ...
                    'Data (start fragment/complete)', ...
                    'SequenceNumber',obj.SequenceCounter, ...
                    'NESN',obj.NextExpectedSequenceCounter, ...
                    'CRCInitialization',obj.CRCInitialization);
                llPacket.LLPDU = bleLLDataChannelPDU(cfg, payload);
                
            case 'ChannelMapUpdate'
                % Channel update packet
                
                % Updated channel map
                chanMap = obj.ChannelMap;
                
                controlCfg = bleLLControlPDUConfig('Opcode','Channel map indication', ...
                    'UsedChannels',(find(chanMap)-1), ...
                    'Instant',obj.Instant);
                
                cfg = ...
                    bleLLDataChannelPDUConfig('LLID','Control', ...
                    'SequenceNumber',obj.SequenceCounter, ...
                    'NESN',obj.NextExpectedSequenceCounter, ...
                    'CRCInitialization',obj.CRCInitialization, ...
                    'ControlConfig',controlCfg);
                llPacket.LLPDU = bleLLDataChannelPDU(cfg);
        end
    end
    
    function packet = updateCounters(obj, packet)
        % Decode packet
        [~, cfg, payload] = bleLLDataChannelPDUDecode(packet, ...
            obj.CRCInitialization);
        
        % Update counters
        cfg.SequenceNumber = obj.SequenceCounter;
        cfg.NESN = obj.NextExpectedSequenceCounter;
        
        % Generate packet of same payload with updated counters
        packet = bleLLDataChannelPDU(cfg, payload);
    end
end
end
