classdef helperBLEMeshFriendTimer < handle 
%helperBLEMeshFriendTimer Create an object for Bluetooth mesh friend timer
%   MESHTIMER = helperBLEMeshFriendTimer creates a Bluetooth mesh friend
%   timer object, with properties and methods related to friend timer.
%
%   MESHTIMER = helperBLEMeshFriendTimer(Name, Value) creates a Bluetooth
%   mesh friend timer object 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).
%
%   helperBLEMeshFriendTimer properties:
%
%   ReceiveDelay   - Receive delay requested by the Low Power node
%   ReceiveWindow  - Receive window supported by the Friend node 
%   PollTimeout    - Initial value of the poll timeout timer set by the Low
%                    Power node

%   Copyright 2019 The MathWorks, Inc.

%#codegen

properties 
    %ReceiveDelay Receive delay requested by the Low Power node 
    %   Specify the receive delay as an integer in the range [10, 255]
    %   milliseconds. This property indicates the time between the Low
    %   Power node sending a request and listening for a response. The
    %   default value is 10 milliseconds.
    ReceiveDelay = 10
    
    %ReceiveWindow Receive window supported by the Friend node
    %   Specify the receive window as an integer in the range [1, 255]
    %   milliseconds. This property indicates the time that the Low Power
    %   node listens for a response. The default value is 5 milliseconds.
    ReceiveWindow = 5
    
    %PollTimeout Initial value of the poll timeout timer set by the Low
    %Power node
    %   Specify the poll timeout as a real value in the range [1, 345599.9]
    %   seconds. This property indicates the maximum time between two
    %   consecutive requests sent by the Low Power node. Poll timeout is a
    %   multiple of 100 milliseconds as specified by the Bluetooth Mesh
    %   Profile v1.0, Section 3.6.5.3. The default value is 2 seconds.
    PollTimeout = 2
end
    
properties (Constant, Hidden)
    %PollCount Number of times the Low Power node sends friend poll message
    %before the poll timeout timer expires
    PollCount = 10
end

properties (Hidden)
    % All the timing parameters specified in the Bluetooth mesh stack are
    % multiples of 1 millisecond. Therefore, considered the step time as 1
    % millisecond.
    StepTime = 1
    
    %Advertising Message is advertising at the Link Layer
    Advertising = false
    
    %LinkLayerDelay Delay at link layer for transport data or friend update
    %message transmission
    LinkLayerDelay = 0
end

properties (Access = private)
    %pReceiveTimer Receive timer is used to measure receive delay and
    %receive window
    pReceiveTimer = 0
    
    %pPollTimeoutTimer Poll timeout timer is used to measure time between
    %two consecutive requests sent by the Low Power node
    pPollTimeoutTimer = 0
    
    %pCheckPoints Poll timeout timer values at which the friend poll
    %message is sent by the Low Power node
    pCheckPoints
    
    %pUpdateMsg Flag for friend update message reception
    pUpdateMsg = false
    
    %pDataMsg Flag for transport data message reception
    pDataMsg = false
    
    %pPollMsg Flag for friend poll message reception
    pPollMsg = false
    
    %pTransmitInterval Transmit interval is a random value in the range of
    %receive window at which the Friend node sends response to the friend
    %poll message
    pTransmitInterval = 0
end

properties (SetAccess = private)
    %pReceiveFlag Flag to start the receive timer
    pReceiveFlag = false  
end
    
methods
    function obj = helperBLEMeshFriendTimer(varargin)
        % Name-value pairs
        for idx = 1:2:nargin
            obj.(char(varargin{idx})) = varargin{idx+1};
        end
    end
    
    % Set receive delay. Refer Mesh Profile v1.0.1 of Bluetooth
    % Specification | Section 3.6.5.3
    function set.ReceiveDelay(obj, value)
        validateattributes(value, {'numeric'}, {'integer', '>=', 10, '<=', ...
            255}, mfilename, 'ReceiveDelay');
        obj.ReceiveDelay = value;
    end
   
    % Set receive window. Refer Mesh Profile v1.0.1 of Bluetooth
    % Specification | Section 3.6.5.4
    function set.ReceiveWindow(obj, value)
        validateattributes(value, {'numeric'}, {'integer', '>=', 1, '<=', ...
            255}, mfilename, 'ReceiveWindow');
        obj.ReceiveWindow = value;
    end
    
    % Set poll timeout. Refer Mesh Profile v1.0.1 of Bluetooth Specification
    % | Section 3.6.5.3
    function set.PollTimeout(obj, value)
        % Poll timeout is a multiple of 100 milliseconds. Converting the
        % input value into multiple of 100 milliseconds.
        value = round(value, 1);
        validateattributes(value, {'numeric'}, {'real', '>=', 1, '<=', ...
            345599.9}, mfilename, 'PollTimeout');
        obj.PollTimeout = value;
    end
    
    % Initialize the timers and call pollCheckPoints function
    function initialize(obj)
        % Initialize receive timer and poll timeout timer
        obj.pReceiveTimer = 0;
        obj.pPollTimeoutTimer = obj.PollTimeout*1000;
        
        % Call CheckPoints function
        obj.pollCheckPoints();
    end
    
    % Create poll check points
    function pollCheckPoints(obj)
        % Convert poll timeout from seconds to milliseconds
        pollTimeoutMS = obj.PollTimeout*1000;
        % Initialize check points with its maximum size
        obj.pCheckPoints = zeros(1, obj.PollCount);
        % Create check points based on poll count and Friendship timing
        % parameters. If the Low Power node does not receive a response
        % within the receive window, it is recommended to resend the friend
        % poll message 3 times. Refer Mesh Profile v1.0.1 of Bluetooth
        % Specification | Section 3.6.6.4.2
        if (pollTimeoutMS > obj.PollCount *(obj.ReceiveDelay + ...
                obj.ReceiveWindow))
            % Time interval for each check point
            val = floor(pollTimeoutMS/obj.PollCount);
            for idx = 1: obj.PollCount
                obj.pCheckPoints(idx) = pollTimeoutMS - (idx-1)*val;
            end
        else
            error(['Configure ReceiveDelay (%d), ReceiveWindow (%d) and ' ...
                'PollTimeout (%d) values such that ' ...
                '"PollTimeout (in milliseconds) > %d*(ReceiveDelay+ReceiveWindow)"'], ...
                obj.ReceiveDelay, obj.ReceiveWindow, ...
                obj.PollTimeout, obj.PollCount);
        end
    end
    
    % Provides the state of Low Power node based on receive timer
    function state = runReceiveLPNTimer(obj)
        % Idle state
        state = 0;
        
        % Update the receive timer
        obj.pReceiveTimer = obj.pReceiveTimer + obj.StepTime;
        
        % Low power node is in idle state until the receive timer reaches
        % the receive delay. Refer Mesh Profile v1.0.1 of Bluetooth
        % Specification | Section 3.6.6.3.2
        if obj.pReceiveTimer <= obj.ReceiveDelay
            % Idle state
            state = 0;
        % Low power node is in listen state for the duration of receive
        % window. The receive window starts after receive delay. Refer Mesh
        % Profile v1.0.1 of Bluetooth Specification | Section 3.6.6.3.2
        elseif obj.pReceiveTimer > obj.ReceiveDelay &&  obj.pReceiveTimer < ...
                (obj.ReceiveDelay + obj.ReceiveWindow)
            if obj.pUpdateMsg == true || obj.pDataMsg == true
                % Idle state
                state = 0;
            else
                % Listen state
                state = 1;
            end
        elseif obj.pReceiveTimer == (obj.ReceiveDelay + obj.ReceiveWindow)
            obj.pReceiveTimer = 0;
            if obj.pUpdateMsg == true || obj.pDataMsg == true
                % Idle state
                state = 0;
            else
                % Listen state
                state = 1;
            end
            obj.pReceiveFlag = false;
        end
    end
    
    % Provides the state of Low Power node based on poll timeout timer and
    % receive timer
    function state = runLPNTimer(obj, inCTL, moreData)
        % Idle state
        state = 0;
        resetFlag = false;
        
        % Start the receive timer when Low Power node is not advertising
        if obj.pReceiveFlag && ~obj.Advertising
            state = obj.runReceiveLPNTimer();
        end
        
        if inCTL == -1
            % Poll timeout timer is not expired
            if (obj.pPollTimeoutTimer > 0)
                % Low Power node sends friend poll message
                if (obj.pUpdateMsg == false) && (obj.pDataMsg == false)
                    for idx = 1: obj.PollCount
                        % Poll timeout timer is equal to one of the poll
                        % check points to transmit the friend poll message
                        if (obj.pPollTimeoutTimer == obj.pCheckPoints(idx))
                            % Transmit state
                            state = 2;
                            obj.pReceiveFlag = true;
                            obj.Advertising = true;
                        end
                    end
                % Low power node receives the transport data message
                elseif (obj.pDataMsg == true)
                    % Low power node waits until the receive timer
                    % reaches the sum of receive delay and receive
                    % window
                    if ~obj.pReceiveFlag
                        % Reset poll timeout timer before transmitting
                        % friend poll message
                        obj.pPollTimeoutTimer = obj.PollTimeout*1000;
                        resetFlag = true;
                        obj.pDataMsg = false;
                    end
                % Low power node receives the friend update message
                elseif (obj.pUpdateMsg == true)
                    % Low power node should continue sending friend poll
                    % messages until it receives a friend update message
                    % with the MD field set to 0. Refer Mesh Profile v1.0.1
                    % of Bluetooth Specification | Section 3.6.6.4.2
                    if moreData == 0
                        for idx = 1:obj.PollCount
                            % Reset poll timeout timer before transmitting
                            % friend poll message or when it reaches one.
                            if (obj.pPollTimeoutTimer == obj.pCheckPoints(idx)+1) ...
                                    || (obj.pPollTimeoutTimer == 1)
                                obj.pPollTimeoutTimer = obj.PollTimeout*1000;
                                obj.pUpdateMsg = false;
                                resetFlag = true;
                            end
                        end
                    else
                        % Low power node waits until the receive timer
                        % reaches the sum of receive delay and receive
                        % window
                        if ~obj.pReceiveFlag
                            % Reset poll timeout timer before transmitting
                            % friend poll message
                            obj.pPollTimeoutTimer = obj.PollTimeout*1000;
                            obj.pUpdateMsg = false;
                            resetFlag = true;
                        end
                    end
                end
            % Poll timeout timer is expired
            else
                % Low power node re-syncs with the Friend node when
                % termination state is reached
                state = 3;
                obj.pPollTimeoutTimer = obj.PollTimeout*1000;
                resetFlag = true;
            end
        % Low power node receives transport data message
        elseif inCTL == 0
            obj.pDataMsg = true;
        % Low power node receives friend update message
        else
            obj.pUpdateMsg = true;
        end
        
        % Update the poll timeout timer only when it is not reset
        if ~resetFlag
            obj.pPollTimeoutTimer = obj.pPollTimeoutTimer - obj.StepTime;
        end
    end
    
    % Provides the state of Friend node based on receive timer
    function state= runReceiveFNTimer(obj)
        % Listen state
        state = 1;
        
        % Update the receive timer
        obj.pReceiveTimer = obj.pReceiveTimer + obj.StepTime;
        
        % Friend node is in transmission state when the receive timer
        % reaches the sum of receive delay and transmit interval
        if obj.pReceiveTimer == obj.ReceiveDelay + obj.pTransmitInterval
            % Transmit state
            state = 2;
        elseif obj.pReceiveTimer >= obj.ReceiveDelay + obj.ReceiveWindow
            obj.pPollMsg = false;
            obj.pReceiveTimer = 0;
            obj.pReceiveFlag = false;
        end
    end
    
    % Provides the state of Friend node based on poll timeout timer and   
    % receive timer
    function state = runFNTimer(obj, inCTL)
        % Listen state
        state = 1;
        resetFlag = false;
        
        % Start the receive timer when Friend node is not advertising
        if obj.pReceiveFlag
            state = runReceiveFNTimer(obj);
        end
        
        % Poll timeout timer is not expired
        if obj.pPollTimeoutTimer > 0
            % Friend node receives friend poll message
            if inCTL == 1 && ~obj.pPollMsg
                % Reset poll timeout timer after receiving the friend poll
                % message. Refer Mesh Profile v1.0.1 of Bluetooth
                % Specification | Section 3.6.6.1
                obj.pPollMsg = true;
                obj.pPollTimeoutTimer = obj.PollTimeout*1000;
                resetFlag = true;
                obj.pReceiveFlag = true;
                % Assign a random value in the range of receive window to
                % transmit transport data or friend update message
                if obj.LinkLayerDelay == 0
                    obj.pTransmitInterval = obj.ReceiveDelay + randi([1 obj.ReceiveWindow]);
                else
                    obj.pTransmitInterval = obj.ReceiveDelay + obj.LinkLayerDelay;
                end
            end
        % Poll timeout timer is expired
        else
            % Friend node re-syncs with the Low Power node when termination
            % state is reached
            state = 3;
            obj.pPollTimeoutTimer = obj.PollTimeout*1000;
            resetFlag = true;
        end
        
        % Update the poll timeout timer only when it is not reset
        if ~resetFlag
            obj.pPollTimeoutTimer = obj.pPollTimeoutTimer - obj.StepTime;
        end
    end
end
end
