classdef helperBLELLConnectionEvent < handle
%helperBLELLConnectionEvent Create an object for BLE Link Layer connection
%events
%   LLCONNEVENT = helperBLELLConnectionEvent creates an object for BLE Link
%   Layer connection events, LLCONNEVENT. This object is used to maintain
%   the connection events after the device moves into 'Connection' state.
%
%   LLCONNEVENT = helperBLELLConnectionEvent(Name, Value) creates an object
%   for BLE Link Layer connection events, LLCONNEVENT, 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).
%
%   helperBLELLConnectionEvent methods:
%
%   init     - Initialize Link Layer connection event parameters
%   run      - Run Link Layer connection events
%   reset    - Reset Link Layer connection events
%
%   helperBLELLConnectionEvent properties:
%
%   ConnectionInterval  - Connection interval
%   Role                - BLE device role in piconet
%   WindowOffset        - Transmit window offset
%   WindowSize          - Transmit window size
%   MoreData            - Indication for more data
%
%   See also helperBLELLConnectionEventStatus.

%   Copyright 2018-2019 The MathWorks, Inc.

%#codegen

properties
    %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

    %Role BLE device role in piconet
    % Specify BLE device role as one of 'Master' | 'Slave'. It indicates
    % the role of BLE device at Link Layer. The default value is 'Master'.
    Role = 'Master'

    %WindowOffset Transmit window offset
    %   Specify the window offset as an integer in the range [0, Mwo],
    %   where Mwo is the lesser of 3200 and <a
    %   href="matlab:help('helperBLELLConnectionEvent.ConnectionInterval')">ConnectionInterval</a>.
    %   This property indicates the window offset after which the transmit
    %   window starts. Each unit is taken as 1.25 ms so that the resultant
    %   window offset (WindowOffset*1.25) is in the range of 0 ms to
    %   (ConnectionInterval*1.25) ms. The default value is 0.
    WindowOffset = 0

    %WindowSize Transmit window size
    %   Specify the window size as an integer in the range [1, Mws], where
    %   Mws is the lesser of 8 and (<a
    %   href="matlab:help('helperBLELLConnectionEvent.ConnectionInterval')">ConnectionInterval</a>-1).
    %   This property indicates the window size within which the Master
    %   transmits the data packet and Slave listens for the data packet
    %   after connection establishment. Each unit is taken as 1.25 ms so
    %   that the resultant window size (WindowSize*1.25) is in the range of
    %   1.25 ms to the minimum of (10 ms, ((ConnectionInterval*1.25) -
    %   1.25) ms). The default value is 1.
    WindowSize = 1

    %MoreData Indication for more data
    %   Specify this property as a scalar logical. A true value indicates
    %   that the sender has more data to send. The default value is false.
    MoreData (1, 1) logical = false
end

properties(Hidden)
    %AnchorPointOffset Offset for Master anchor point selection
    %   Specify the anchor point offset as an integer in the range of [1,
    %   WindowSize*1.25] ms, where <a
    %   href="matlab:help('helperBLELLConnectionEvent.WindowSize')">WindowSize</a>
    %   is the property of the object <a
    %   href="matlab:help('helperBLELLConnectionEvent')">helperBLELLConnectionEvent</a>.
    %   This property indicates the offset for selecting anchor point for
    %   the Master. The default value is 0.625 ms.
    AnchorPointOffset = 0.625
    
    %StepTimerUnit Time unit for single step in microseconds
    StepTimerUnit = 25
end

properties(SetAccess = private)
    %EventCounter Counter for number of connection events occurred
    EventCounter = 0

    %AnchorPointSelected Anchor point selected or not
    % This will be set to true, when an anchor point is selected for the
    % timing of all the future connection events.
    AnchorPointSelected = false
    
    %SelectNewChannel Selects a new data channel
    % This will be set to true for each connection event. New data channel
    % will be selected for each connection event.
    SelectNewChannel = false
end

properties(Access = private)
    %pEventTimer Timer for connection events in microseconds
    pEventTimer = 0

    %pState State of the device at the current instance
    pState = helperBLELLConnectionEventStatus.Idle

    %pIFSTimer Timer for inter-frame space timer
    pIFSTimer = 0

    %pInitialTimer Initial sleep timer
    pInitialTimer = 0

    %pEventTime Duration of a connection event
    pEventTime = 0

    %pCRCFailCount Counter for CRC failed packets
    pCRCFailCount = 0

    %pWaitForResp Waiting for response or not
    pWaitForResp = false

    %pGiveResp Need to send response or not
    pGiveResp = false
    
    %pWindowOffset Transmit window offset in microseconds
    pWindowOffset = 0
    
    %pWindowSize Transmit window offset in microseconds
    pWindowSize = 0
    
    %pCounterUpdate Event counter updated or not
    pCounterUpdate = false
    
    %pListenSteps Step counter in listening state
    pListenSteps = 0;
end

properties(Constant, Hidden)
    %WindowDelay Time unit for single step in microseconds
    % WindowDelay shall be 1250 us when connection is established using
    % Link Layer advertising channel PDU (Connection indication).
    WindowDelay = 1250

    %T_IFS Time interval between two consecutive packets on the same
    %channel index
    T_IFS = 150 %us

    RoleValues = {'Master', 'Slave'};
    
    ListenSteps = 3 % Listen time = 3 steps (3*StepTimerUnit)
end

methods
    % Constructor
    function obj = helperBLELLConnectionEvent(varargin)
        % Apply constructor name value pairs:
        for idx = 1 : 2 : nargin
            obj.(varargin{idx}) = varargin{idx+1};
        end
    end

    % For auto completion:
    function v = set(obj, prop)
        v = obj.([prop, 'Values']);
    end

    function set.ConnectionInterval(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            'integer', '>=', 6, '<=', 3200}, mfilename, 'ConnectionInterval');
        obj.ConnectionInterval = value;
    end

    function set.Role(obj, value)
        obj.Role = validatestring(value, obj.RoleValues, mfilename, 'Role');
    end

    function set.WindowSize(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            'integer', '>=', 1, '<=', 8}, mfilename, 'WindowSize');
        obj.WindowSize = value;
    end

    function set.WindowOffset(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            'integer', '>=', 0, '<=', 3200}, mfilename, 'WindowOffset');
        obj.WindowOffset = value;
    end
    
    function set.AnchorPointOffset(obj, value)
        validateattributes(value, {'double'}, {'scalar', '>=', 0, '<=', 10}, ...
            mfilename, 'AnchorPointOffset');
        obj.AnchorPointOffset = value;
    end
end

methods(Hidden)
    function status = validateWindowSize(obj)
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 4.5.3
        if (obj.ConnectionInterval <= 8)
            status = (obj.WindowSize <= (obj.ConnectionInterval-1));
        else
            status = (obj.WindowSize <= 8);
        end
    end

    function status =  validateWindowOffset(obj)
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 4.5.3
        status = (obj.WindowOffset <= obj.ConnectionInterval);
    end

    function status = validateAnchorPointOffset(obj)
        status = (obj.AnchorPointOffset <= (obj.WindowSize*1.25));
    end
    
    function updateEventCount(obj, eventCounter)
        % Validate event counter
        validateattributes(eventCounter, {'numeric'}, ...
            {'scalar', 'integer', '>=', 0, '<=', 65535}, mfilename, ...
            'eventCounter');
        
        obj.EventCounter = eventCounter;
        obj.pCounterUpdate = true;
        if (eventCounter >= 1)
            obj.AnchorPointSelected = true;
        end
    end

    function anchorPointSelected(obj)
        obj.AnchorPointSelected = true;
    end
end

methods(Access = private)
    % Validate inputs to the run method
    function validateRunInputs(~, packetInfo)
        % Validate packetInfo
        validateattributes(packetInfo, {'struct'}, {'scalar'}, mfilename, ...
            'packetInfo');

        % Validate packetInfo structure fields
        fieldStatus = zeros(1, 2);
        fields = {'MoreData', 'CRCFail'};
        for idx = 1:2
            fieldStatus(idx) = isfield(packetInfo, fields{idx});
        end

        if (~all(fieldStatus))
            error(['Input "packetInfo" to the step function must be a ' ...
                'structure with "MoreData" and "CRCFail" fields.']);
        end

        % Validate packetInfo.MoreData
        validateattributes(packetInfo.MoreData, {'numeric'}, ...
            {'scalar', 'integer', '>=', -1, '<=', 1}, mfilename, ...
            'packetInfo.MoreData');

        % Validate packetInfo.CRCFail
        validateattributes(packetInfo.CRCFail, {'numeric', 'logical'}, ...
            {'scalar', 'integer', '>=', -1, '<=', 1}, mfilename, ...
            'packetInfo.CRCFail');
    end
end

methods
    % Init functionality
    function init(obj)
        validateWindowOffset(obj);
        validateWindowSize(obj);
        validateAnchorPointOffset(obj);
        obj.pEventTime = obj.ConnectionInterval*1250; %us
        obj.pWindowOffset = obj.WindowOffset * 1250; %us
        obj.pWindowSize = obj.WindowSize * 1250; %us
    end

    % Reset functionality
    function reset(obj)
        % Reset configuration
        if ~obj.pCounterUpdate
            obj.EventCounter = 0;
            obj.SelectNewChannel = false;
            obj.AnchorPointSelected = false;
        end
        obj.pEventTimer = 0;
        obj.pState = helperBLELLConnectionEventStatus.Idle;
        obj.pIFSTimer = 0;
        obj.pInitialTimer = 0;
        obj.pCRCFailCount = 0;
        obj.pWaitForResp = false;
        obj.pGiveResp = false;
        obj.pListenSteps = 0;
    end

    % Run functionality
    function curState = run(obj, packetInfo)
        % Initialize the state to 'Idle'
        curState = helperBLELLConnectionEventStatus.Idle;

        if obj.pCounterUpdate
            % Set SelectNewChannel to true, to select a new data channel
            % for next connection event
            obj.SelectNewChannel = true;
            isAnchorPoint = true;
        else
            % Initialize SelectNewChannel
            obj.SelectNewChannel = false;
            isAnchorPoint = false;
        end

        if ~obj.AnchorPointSelected
            % Transmit window starts after (windowDelay + windowOffset)
            if (obj.pInitialTimer < (obj.WindowDelay + obj.pWindowOffset))
                % Update the device state
                obj.pState = curState;

                % Update initial timer
                obj.pInitialTimer = obj.pInitialTimer + obj.StepTimerUnit;
                return;
            elseif (obj.pInitialTimer == (obj.WindowDelay + obj.pWindowOffset))
                if (strcmp(obj.Role, 'Slave'))
                    obj.pEventTimer = obj.pEventTimer + obj.StepTimerUnit;
                    
                    % Listen for data packet
                    obj.SelectNewChannel = true;
                    curState = helperBLELLConnectionEventStatus.Listen;
                    
                    % Update the device state
                    obj.pState = curState;
                    
                    % Update initial timer
                    obj.pInitialTimer = obj.pInitialTimer + obj.StepTimerUnit;
                    return;
                end
            end
            if strcmp(obj.Role, 'Master')
                % Anchor point for the Master is defined by
                % "AnchorPointOffset"
                anchorPoint = (obj.AnchorPointOffset*1000); % converting into microseconds
                if (obj.pEventTimer == anchorPoint)
                    % Anchor point selected
                    obj.AnchorPointSelected = true;
                    obj.SelectNewChannel = true;
                    isAnchorPoint = true;
                    obj.pCRCFailCount = 0;
                end
            else
                curState = obj.pState;
                if (obj.pState == helperBLELLConnectionEventStatus.Listen) && ...
                        (packetInfo.MoreData ~= -1) && (packetInfo.CRCFail ~= -1)
                    % Anchor point selected
                    obj.AnchorPointSelected = true;
                    isAnchorPoint = true;
                    obj.pCRCFailCount = 0;
                end
                if (obj.pEventTimer == obj.pWindowSize)
                    curState = helperBLELLConnectionEventStatus.Idle;
                end
                if (obj.pEventTimer == obj.pEventTime)
                    % Reset the timer and update the event counter after
                    % connection interval
                    obj.pEventTimer = 0;
                    obj.EventCounter = obj.EventCounter + 1;
                    obj.SelectNewChannel = true;

                    % Listen for data packet
                    curState = helperBLELLConnectionEventStatus.Listen;
                end
            end
            % Update the device state
            obj.pState = curState;

            % Update event timer
            obj.pEventTimer = obj.pEventTimer + obj.StepTimerUnit;
            if ~obj.AnchorPointSelected
                return;
            end
        end

        % Reset the timer and update the event counter after connection
        % interval
        if (obj.pEventTimer == obj.pEventTime)
            obj.EventCounter = obj.EventCounter + 1;
            obj.pEventTimer = 0;
            isAnchorPoint = true;
            obj.pCRCFailCount = 0;
            obj.SelectNewChannel = true;
        end

        if isAnchorPoint || obj.pCounterUpdate
            obj.pCounterUpdate = false;
            if strcmp(obj.Role, 'Master')
                % Transmit data packet
                curState = helperBLELLConnectionEventStatus.Transmit;
                obj.pWaitForResp = true;
                obj.pGiveResp = false;

                % Start inter-frame space timer
                obj.pIFSTimer = obj.T_IFS;
            else
                if ((obj.pState == helperBLELLConnectionEventStatus.Listen)) && ...
                        (packetInfo.MoreData ~= -1) && (packetInfo.CRCFail ~= -1)

                    obj.pGiveResp = true;

                    % Start inter-frame space timer
                    obj.pIFSTimer = obj.T_IFS;

                    if ((obj.MoreData) || (packetInfo.MoreData))
                        % Connection event has sufficient time to exchange
                        % more data
                        if ((2*obj.T_IFS + obj.pEventTimer) <= obj.pEventTime)
                            obj.pWaitForResp = true;
                        else
                            fprintf(['Connection event does not have sufficient time ' ...
                                'to exchange more data. Exchange the data in the next connection event.']);
                        end
                    else
                        obj.pWaitForResp = false;
                    end
                    if (packetInfo.CRCFail)
                        obj.pCRCFailCount = obj.pCRCFailCount + 1;
                    else
                        obj.pCRCFailCount = 0;
                    end
                    curState = helperBLELLConnectionEventStatus.Idle;
                else
                    % Note: Slave latency is not considered. So, Slave
                    % should respond to Master in each connection event.

                    % Listen for data packet until receives from the Master
                    curState = helperBLELLConnectionEventStatus.Listen;
                end
            end
        else
            if obj.pIFSTimer > 0
                curState = helperBLELLConnectionEventStatus.Idle;
                obj.pIFSTimer = obj.pIFSTimer - obj.StepTimerUnit;
            end
            if strcmp(obj.Role, 'Master')
                if (obj.pIFSTimer == 0)
                    if (obj.pWaitForResp) && (obj.pState == helperBLELLConnectionEventStatus.Idle)
                        curState = helperBLELLConnectionEventStatus.Listen;
                        obj.pListenSteps = obj.ListenSteps;
                    elseif (obj.pGiveResp)
                        curState = helperBLELLConnectionEventStatus.Transmit;
                        obj.pGiveResp = false;

                        % Start inter-frame space timer
                        obj.pIFSTimer = obj.T_IFS;
                        obj.pWaitForResp = true;
                    else
                        curState = helperBLELLConnectionEventStatus.Idle;
                    end
                end
                if (obj.pState == helperBLELLConnectionEventStatus.Listen)
                    if (packetInfo.MoreData ~= -1) && (packetInfo.CRCFail ~= -1)
                        if ((obj.MoreData) || (packetInfo.MoreData))
                            % Connection event has sufficient time to
                            % exchange more data
                            if ((2*obj.T_IFS + obj.pEventTimer) <= obj.pEventTime)
                                obj.pGiveResp = true;
                                % Start inter-frame space timer
                                obj.pIFSTimer = obj.T_IFS;
                            else
                                fprintf(['Connection event does not have sufficient time ' ...
                                    'to exchange more data. Exchange the data in the next connection event.']);
                            end
                        else
                            obj.pGiveResp = false;
                        end
                        if (packetInfo.CRCFail)
                            obj.pCRCFailCount = obj.pCRCFailCount + 1;
                        else
                            obj.pCRCFailCount = 0;
                        end
                        curState = helperBLELLConnectionEventStatus.Idle;
                        obj.pWaitForResp = false;
                        obj.pListenSteps = 0;
                    else
                        if (obj.pListenSteps > 0)
                            obj.pListenSteps = obj.pListenSteps - 1;
                            curState = helperBLELLConnectionEventStatus.Listen;
                        else
                            curState = helperBLELLConnectionEventStatus.Idle;
                            obj.pWaitForResp = false;
                        end
                    end
                end
            else
                if (obj.pIFSTimer == 0)
                    if (obj.pGiveResp)
                        curState = helperBLELLConnectionEventStatus.Transmit;
                        obj.pGiveResp = false;
                        if (obj.pWaitForResp)
                            % Start inter-frame space timer
                            obj.pIFSTimer = obj.T_IFS;
                        end
                    elseif (obj.pWaitForResp)
                        curState = helperBLELLConnectionEventStatus.Listen;
                    else
                        curState = helperBLELLConnectionEventStatus.Idle;
                    end
                end
                if ((obj.pState == helperBLELLConnectionEventStatus.Listen))
                    if (packetInfo.MoreData ~= -1) && (packetInfo.CRCFail ~= -1)
                        obj.pGiveResp = true;

                        % Start inter-frame space timer
                        obj.pIFSTimer = obj.T_IFS;

                        if ((obj.MoreData) || (packetInfo.MoreData))
                            % Connection event has sufficient time to
                            % exchange more data
                            if ((2*obj.T_IFS + obj.pEventTimer) <= obj.pEventTime)
                                obj.pWaitForResp = true;
                            else
                                fprintf(['Connection event does not have sufficient time ' ...
                                    'to exchange more data. Exchange the data in the next connection event.']);
                            end
                        else
                            obj.pWaitForResp = false;
                        end
                        if (packetInfo.CRCFail)
                            obj.pCRCFailCount = obj.pCRCFailCount + 1;
                        else
                            obj.pCRCFailCount = 0;
                        end
                        curState = helperBLELLConnectionEventStatus.Idle;
                    else
                        % Note: Slave latency is not considered. So, Slave
                        % should respond to Master in each connection
                        % event.

                        % Listen, until it receives data packet from the
                        % Master
                        curState = helperBLELLConnectionEventStatus.Listen;
                    end
                end
            end
            % Two consecutive packets received with an invalid CRC within a
            % connection event shall close the event.
            if (obj.pCRCFailCount >= 2)
                curState = helperBLELLConnectionEventStatus.Idle;
                obj.pCRCFailCount = 0;
                obj.pWaitForResp = false;
                obj.pGiveResp = false;
                fprintf(['Closing the current "connection event" as two ' ...
                    'consecutive packets are received with an invalid CRC.']);
            end
        end

        % Update the device state
        obj.pState = curState;

        % Update event timer
        obj.pEventTimer = obj.pEventTimer + obj.StepTimerUnit;
    end
end
end
