classdef helperBluetoothBaseband < handle
%helperBluetoothBaseband Create an object for Bluetooth baseband layer
%   BTBB = helperBluetoothBaseband creates an object for Bluetooth baseband
%   layer for controlling and managing the physical and logical links
%   created between the Bluetooth devices.
%
%   BTBB = helperBluetoothBaseband(Name, Value) creates an object for
%   Bluetooth baseband layer 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).
%
%   helperBluetoothBaseband properties:
%
%   Role    - Role of the Bluetooth device in a piconet

%   Copyright 2020 The MathWorks, Inc.

%#codegen

properties
    %Role Role of the Bluetooth device in a piconet
    %   Specify the role as 'Master' | 'Slave'. This property indicates the
    %   role of the Bluetooth device at baseband layer. The default value
    %   is 'Master'.
    Role = 'Master'
end

properties (Constant)
    % Master can support up to 7 connections with the slaves
    MaxPiconetSize = 7
    
    % Packet types transmitted over ACL logical transport
    ACLPacketTypes = {'DM1', 'DH1', 'DM3', 'DH3', 'DM5', 'DH5'}
    
    % SCO packet types supported
    SCOPacketTypes = {'HV1', 'HV2', 'HV3'}
    
    % Common packet types supported
    CommonPacketTypes = {'POLL', 'NULL'}
end

% Constant, Hidden properties
properties (Constant, Hidden)
    RoleValues = {'Master', 'Slave'}
    
    % Slots | Maximum information bytes (excluding the payload header)
    % ACLPacketInfo is associated with ACLPacketTypes
    ACLPacketInfo = [1, 17; ...
                     1, 27; ...
                     3, 121; ...
                     3, 183; ...
                     5, 224; ...
                     5, 339]
               
    % Slots | Maximum information bytes (excluding the payload header)
    % SCOPacketInfo is associated with SCOPacketTypes
    SCOPacketInfo = [1 10; ...
                     1 20; ...
                     1 30]

    % Slots in the CommonPacketInfo associated with CommonPacketTypes
    CommonPacketInfo = [1, 1]

    % Maximum size of the information bytes (data from the higher layers)
    % in octets
    MaxPayloadSize = 400
end

properties (Hidden)
    % The properties TxData and RxData are structures containing these
    % fields:
    %       LTAddr          - Logical transport address of an active
    %                         destination Slave for a packet in a
    %                         Master-to-Slave or Slave-to-Master
    %                         transmission slot. It is a scalar, integer
    %                         value.
    %       PacketType      - Type of Bluetooth packet. The value of this
    %                         field is a scalar or a character vector
    %                         containing one of these: {'NULL', 'POLL',
    %                         'HV1', 'HV2', 'HV3', 'DM3', 'DM1', 'DH1',
    %                         'DM5', 'DH3', 'DH5'}.
    %       Payload         - Payload bits from the baseband layer. It is a
    %                         binary column vector.
    %       PayloadLength   - Number of payload bytes. It is a scalar,
    %                         integer value.
    %       LLID            - Logical link identifier
    %       SEQN            - 1-bit sequence number for transmission
    %       ARQN            - 1-bit acknowledgement for previous 
    %                         transmission
    %       IsValid         - Logical flag indicating whether the received
    %                         packet is valid or not based on the packet
    %                         header error check (HEC) and cyclic
    %                         redundancy check (CRC).
    
    %TxData Data to be transmitted from the baseband layer
    TxData
    
    %RxData Data to be received by the baseband layer
    RxData
end

properties (SetAccess = private, Hidden)
    %State State of the device
    % 0 - Idle
    % 1 - Tx
    % 2 - Rx
    State = 0
    
    %DecodedPayload Decoded payload passed to the higher layers
    DecodedPayload = zeros(1, 0)
    
    %ChannelIndex Channel index for transmitting or receiving a packet
    ChannelIndex = -1
    
    %WhitenInit Data whitening initialization vector used by PHY for
    %whitening process
    WhitenInit = zeros(7, 1)
    
    %SendRequest Flag to specify when to send a request to PHY
    SendRequest = false
end

% Read-only properties
properties (SetAccess = private)
    %NumLTAddrs Total number of LT addresses used by this device
    NumLTAddrs = 0
    
    %LTAddresses Logical transport addresses of each logical link present
    %in the baseband layer
    LTAddresses = zeros(1, 7)
    
    % Total number of ACL links created by this device
    NumACLLinks = 0
    
    %NumSCOLinks Total number of SCO links created by this device
    NumSCOLinks = 0
    
    % The following properties are Baseband layer statistics
    
    %TxPackets Total number of packets transmitted from the baseband layer
    TxPackets = 0
    
    %RxPackets Total number of packets received by the baseband layer
    RxPackets = 0
    
    %RxFailedPackets Total number of packets received by the baseband layer
    %with failed CRC/HEC
    RxFailedPackets = 0
    
    %TxACLPackets Total number of packets transmitted by each ACL logical
    %transport
    TxACLPackets = zeros(7, 1)
    
    %TxACLOneSlotPackets Total number of one-slot packets transmitted by
    %each ACL logical transport
    TxACLOneSlotPackets = zeros(7, 1)
    
    %TxACLThreeSlotPackets Total number of three-slot packets transmitted
    %by each ACL logical transport
    TxACLThreeSlotPackets = zeros(7, 1)
    
    %TxACLFiveSlotPackets Total number of five-slot packets transmitted by
    %each ACL logical transport
    TxACLFiveSlotPackets = zeros(7, 1)
    
    %RetransmittedACLPackets Number times ACL packets are retransmitted by
    %each ACL logical transport
    RetransmittedACLPackets = zeros(7, 1)
    
    %RxACLPackets Total number of packets received by each ACL logical
    %transport
    RxACLPackets = zeros(7, 1)
    
    %RxACLOneSlotPackets Total number of one-slot packets received by each
    %ACL logical transport
    RxACLOneSlotPackets = zeros(7, 1)
    
    %RxACLThreeSlotPackets Total number of three-slot packets received by
    %each ACL logical transport
    RxACLThreeSlotPackets = zeros(7, 1)
    
    %RxACLFiveSlotPackets Total number of five-slot packets received by
    %each ACL logical transport
    RxACLFiveSlotPackets = zeros(7, 1)
    
    %TxSCOPackets Total number of packets transmitted by each SCO logical
    %transport
    TxSCOPackets = zeros(7, 1)
    
    %RxSCOPackets Total number of packets received by each SCO logical
    %transport
    RxSCOPackets = zeros(7, 1)
    
    %ACKedSCOPackets Total number of packets successfully acknowledged on
    %each SCO logical transport
    ACKedSCOPackets = zeros(7, 1)
    
    %ACKedACLPackets Total number of packets successfully acknowledged on
    %each ACL logical transport
    ACKedACLPackets = zeros(7, 1)
    
    %DroppedPackets Total number of packets dropped by the baseband layer
    %due to mismatched LT address or unsupported LT address (0) or
    %unsupported packet type is received
    DroppedPackets = 0
    
    %DroppedLMPMessages Total number of LMP messages dropped by the
    %baseband layer due to decoding failures or unsupported received LMP
    %messages
    DroppedLMPMessages = 0
    
    %ChannelClassificationStats Statistics of the channel classification on
    %each logical transport address
    ChannelClassificationStats
    
    %ChannelClassificationCount Channel classification count on each
    %logical transport address
    ChannelClassificationCount = zeros(1, 7)
    
    %ClassificationTotalCount Count for the number of channel classifications
    %updated in this device
    ClassificationTotalCount = 0
end

% Private properties
properties (Access = private)
    %pLinkConfigs Objects for Master-Slave logical links configuration
    %   Specify this property as a vector of type <a
    %   href="matlab:help('helperBluetoothLogicalTransports')">helperBluetoothLogicalTransports</a>.
    %   Each element in the vector specifies the configuration of logical
    %   links between two Bluetooth devices. If the <a
    %   href="matlab:help('helperBluetoothBaseband.Role')">Role</a> is
    %   Slave, then this property must be a scalar value. The maximum size
    %   of this property is 7. The default value is an object of type
    %   'helperBluetoothLogicalTransports' with all properties set to their
    %   default values.
    pLinkConfigs
    
    %pLinkIndex Index to the link configuration array specifying the number
    %of active links
    pLinkIndex = 1
    
    %pSlotTimer Object for managing the Bluetooth slot timer
    pSlotTimer
    
    %pPrevLTAddr LT address identifying the previous transmission at Master
    %and the previous reception at Slave
    pPrevLTAddr = 0
    
    %pPrevLinkType Link type identifying the previous transmission at
    %Master and the previous reception at Slave
    % 0 - Unknown
    % 1 - ACL
    % 2 - SCO
    pPrevLinkType = 0
    
    %pDataSlots Number of slots required for current transmission or
    %reception
    pDataSlots = 0
    
    %pData Data to be passed between baseband and PHY layers
    pData
    
    %pSCOTimers Timers for each SCO logical transport along with the LT
    %addresses
    % Each row represents LT address and the corresponding SCO timer in
    % slots
    pSCOTimers = ones(3, 2)*-1
    
    %pACLTimers Timers for each ACL logical transport along with the LT
    %addresses
    % Each row represents LT address and the corresponding ACL timer in
    % slots
    pACLTimers = ones(7, 2)*-1
    
    %pPayload Current payload transmitting from the higher layers
    pPayload
    
    %pPayloadLength Length of the current transmitting payload in bits
    pPayloadLength = 0
    
    %pSendPoll Flag to identify when to send a poll packet from the Master
    %to Slave
    pSendPoll = false
    
    %pAckReceived Flag to acknowledge the transmission of a packet
    pAckReceived = true
    
    %pUpdateClassification Update the classification of channels in the
    %Bluetooth frequency hopping object
    pUpdateClassification = false(1, 7)
    
    %pMaster Flag to specify whether the role is master or not
    pMaster = true
end

% Public methods
methods
    % Constructor
    function obj = helperBluetoothBaseband(varargin)
        % Initialize link configurations (maximum number of logical links
        % within a piconet is 7)
        obj.pLinkConfigs = {helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports ...
            helperBluetoothLogicalTransports};
        
        % To support codegen for variable sized values
        coder.varsize('basebandData.PacketType', [1 3]);
        
        % Initialize the slot timer object
        obj.pSlotTimer = helperBluetoothSlotTimer;
        octetLen = 8; % Number of bits in single octet
        % Initialize the data between PHY and baseband layers
        basebandData = struct('LTAddr', 0, ... % Logical transport address
                           'PacketType', blanks(0), ... % Packet type
                           'Payload', zeros(obj.MaxPayloadSize * octetLen, 1), ... % Payload
                           'PayloadLength', 0, ... % Payload size
                           'LLID', [0; 0], ... % Logical link identifier
                           'SEQN', 0, ... % Sequence number
                           'ARQN', 0, ... % Acknowledgement flag
                           'IsValid', false); % Flag for CRC and HEC check
        obj.pData = basebandData;
        % Initialize the Tx and Rx data
        obj.TxData = obj.pData;
        obj.RxData = obj.pData;
        % Initialize the payload
        obj.pPayload = zeros(obj.MaxPayloadSize * octetLen, 1);
        % Initialize the channel classification statistics
        % LTAddrs (1) | Count (1) | Channel map (79) 
        obj.ChannelClassificationStats = zeros(100, 81);
        
        % Name-value pairs
        for idx = 1:2:nargin
            obj.(varargin{idx}) = varargin{idx+1};
        end
    end
    
    % Auto-completion
    function v = set(obj, prop)
        v = obj.([prop, 'Values']);
    end
    
    % Validate Bluetooth device role
    function set.Role(obj, value)
        obj.Role = validatestring(value, obj.RoleValues, mfilename, 'Role');
        % Update the master flag
        obj.pMaster = strcmpi(obj.Role, 'Master'); %#ok<MCSUP>
    end
end

methods
    function addLink(obj, linkConfig)
    %addLink Add a new logical link
    %
    %   addLink(OBJ, LINKCONFIG) adds a new logical link between this
    %   device and the peer device using the link configuration,
    %   LINKCONFIG.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   LINKCONFIG is an object of type helperBluetoothLogicalTransports.

        % ACL and SCO operate on single logical transport address.
        
        % Check whether there are any logical links with the given LT
        % address
        if any(linkConfig.PrimaryAddress == obj.LTAddresses)
            error(['Failed to add the given link. ' ...
                'Device already had a logical link with the ' ...
                'LT address ' num2str(linkConfig.PrimaryAddress) '.']);
        end
        
        primaryLTAddr = linkConfig.PrimaryAddress;
        
        % Enable SCO logical link
        if linkConfig.SynchronousLink
            % Get active SCO intervals already added
            scoIntervals = getActiveSCOIntervals(obj);
            if ~any(scoIntervals == linkConfig.SCOInterval)
                % Update the number of SCO links and the corresponding
                % timers
                obj.NumSCOLinks = obj.NumSCOLinks + 1;
                obj.pSCOTimers(obj.NumSCOLinks, :) = [primaryLTAddr 1];
            else
                error(['Failed to add the given link. ' ...
                    'Device already had an SCO link with the same configuration.']);
            end
        end
        
        % Add link to the baseband layer
        obj.pLinkConfigs{obj.pLinkIndex} = linkConfig;
        
        % Update the primary LT addresses (ACL/SCO)
        obj.LTAddresses(obj.NumLTAddrs+1) = primaryLTAddr;
        
        % Update the number of ACL links and the corresponding
        % timers
        obj.NumACLLinks = obj.NumACLLinks + 1;
        obj.pACLTimers(obj.NumACLLinks, :) = ...
            [primaryLTAddr linkConfig.PollInterval+1];
        
        % Update the link index and number of links
        obj.pLinkIndex = obj.pLinkIndex + 1;
        obj.NumLTAddrs = obj.NumLTAddrs + 1;
    end
    
    function updateReceptionSlots(obj, pktType)
    %updateReceptionSlots Update the number of reception slots
    %
    %   updateReceptionSlots(OBJ, PACKETTYPE) updates the number of slots
    %   required to receive the packet with the PACKETTYPE.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   PACKETTYPE is a character vector, one of 'DM1', 'DH1', 'DM3',
    %   'DH3', 'DM5', 'DH5', 'POLL', 'NULL', 'HV1', 'HV2' or 'HV3'.
    
        % Get the required slots for processing the received packet
        if any(strcmpi(obj.ACLPacketTypes, pktType))
            slots = obj.ACLPacketInfo(strcmpi(obj.ACLPacketTypes, pktType), 1);
        elseif any(strcmpi(obj.SCOPacketTypes, pktType))
            slots = obj.SCOPacketInfo(strcmpi(obj.SCOPacketTypes, pktType), 1);
        else
            slots = obj.CommonPacketInfo(strcmpi(obj.CommonPacketTypes, pktType));
        end
        
        if isempty(slots)
            % Reset the slots and the response flag for an unknown packet
            obj.pDataSlots = 0;
        else
            % Update the reception slots
            obj.pDataSlots = slots;
        end
    end
    
    function nextInvokeTime = run(obj, elapsedTime)
    %run Runs the baseband layer of the Bluetooth device
    %
    %   This function performs the following operations: 
    %   1. Maintains logical links (ACL/SCO) between the device and peer
    %   device
    %   2. Transmit and receive Bluetooth baseband packets
    %
    %   NEXTINVOKETIME = run(OBJ, ELAPSEDTIME) performs the baseband layer
    %   functionality of Bluetooth device by updating the elapsed time and
    %   returns the time to run the baseband again.
    %
    %   NEXTINVOKETIME is the time (in microseconds) after which the RUN
    %   function must be invoked again.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   ELAPSEDTIME is the time elapsed in microseconds between two
    %   successive calls of this function.
    
        % Initialize the parameters
        inData = obj.RxData;
        obj.TxData = obj.pData;
        obj.SendRequest = false;
        obj.DecodedPayload = zeros(1, 0);
        
        % Run the slot timer module to identify the slot start
        [nextInvokeTime, slotStart] = runSlotTimer(obj.pSlotTimer, elapsedTime);
        
        % Return, if the baseband has no logical transports to transmit or
        % receive the data
        if obj.NumLTAddrs == 0
            return;
        end
        
        % Start point of a new slot
        if slotStart
            % Update the running timers in the baseband layer
            updateTimers(obj);
            
            % No active transmission or reception
            if obj.pDataSlots == 0
                % Derive the slot type (even/odd)
                slotEven = mod(obj.pSlotTimer.NumSlots, 2) == 0;
        
                if obj.pMaster
                    % Even slot is for Master transmission
                    if slotEven
                        % Tx state
                        obj.State = 1;
                        masterTx(obj);
                        % Odd slot is for Master reception
                    else
                        % Idle state, if no packet has transmitted in the
                        % previous master Tx slot
                        obj.State = 0;
                        
                        % Update the Rx request only when the Master has
                        % transmitted in the previous slot
                        if (obj.pPrevLTAddr ~= 0)
                            % Listen state
                            obj.State = 2;
                            % Get the active logical link configuration and
                            % update the Rx request to PHY
                            ltHandle = getLTHandle(obj, obj.pPrevLTAddr);
                            updateRxRequest(obj, ltHandle);
                        end
                    end
                else
                    % Even slot is for Slave reception
                    if slotEven
                        % Rx state
                        obj.State = 2;
                        % Get the active logical link configuration and
                        % update the Rx request to PHY
                        ltHandle = obj.pLinkConfigs{1};
                        updateRxRequest(obj, ltHandle);
                    % Odd slot is for Slave transmission
                    else
                        % Tx state
                        obj.State = 1;
                        slaveTx(obj);
                    end
                end
            end
        end
        
        % Listening for incoming packets and received a valid packet
        if (obj.State == 2) && (~isempty(inData.PacketType))
            % Update the total number of received packets
            obj.RxPackets = obj.RxPackets + 1;
            
            % Master reception
            if obj.pMaster
                masterRx(obj, inData);
            % Slave reception
            else
                slaveRx(obj, inData);
            end
        end
        % Update the number of transmitted packets
        if ~isempty(obj.TxData.PacketType)
            obj.TxPackets = obj.TxPackets + 1;
        end
        % Reset the Rx data
        obj.RxData = obj.pData;
    end
    
    function flag = pushACLData(obj, ltAddr, payload)
    %pushACLData Push payload from the higher layers into ACL logical
    %transport queue associated with the given LT address
    %
    %   FLAG = pushACLData(OBJ, LTADDR, PAYLOAD) pushes the payload,
    %   PAYLOAD, from the higher layers into the ACL logical transport
    %   queue with the address, LTADDR. The function returns a FLAG
    %   indicating whether the push operation is successful or not.
    %
    %   FLAG is boolean value to identify whether the push operation is
    %   successful or not.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   LTADDR is the logical transport address, specified as a
    %   positive integer.
    %
    %   PAYLOAD is the payload from the higher layers, specified as a
    %   binary column vector.
        
        % Fetch the LT handle having the given LT address
        ltHandle = getLTHandle(obj, ltAddr);
        
        % Push the payload into ACL queue
        flag = enqueue(ltHandle.ACLQueue, payload);
    end
    
    function flag = pushSCOData(obj, ltAddr, payload)
    %pushSCOData Push payload from the higher layers into SCO logical
    %transport queue associated with the given LT address
    %
    %   FLAG = pushSCOData(OBJ, LTADDR, PAYLOAD) pushes the payload,
    %   PAYLOAD, from the higher layers into the SCO logical transport
    %   queue with the address, LTADDR. The function returns a FLAG
    %   indicating whether the push operation is successful or not.
    %
    %   FLAG is boolean value to identify whether the push operation is
    %   successful or not.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   LTADDR is the logical transport address, specified as a
    %   positive integer.
    %
    %   PAYLOAD is the payload from the higher layers, specified as a
    %   binary column vector.
        
        % Fetch the LT handle having the given LT address
        ltHandle = getLTHandle(obj, ltAddr);
        
        % Push the payload into ACL queue
        flag = enqueue(ltHandle.SCOQueue, payload);
    end
    
    function ltAddrs = aclLinkAddress(obj)
    %aclLinkAddress Return ACL logical transport address which can accept
    %data from the higher-layers
    %
    %   LTADDRS = aclLinkAddress(OBJ) returns the ACL logical transport
    %   addresses which can accept data from the higher-layers, if the
    %   respective queue is not full.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   LTADDRS is an array of ACL logical transport addresses, each
    %   address is specified as a positive integer.
    
        % Initialize
        ltAddrs = zeros(1, 7);
        aclIdx = 1;
        
        % Check the status of ACL queues of all the ACL logical transports
        for idx = 1:obj.NumLTAddrs
            if ~isFull(obj.pLinkConfigs{idx}.ACLQueue)
                ltAddrs(aclIdx) = obj.pLinkConfigs{idx}.PrimaryAddress;
                aclIdx = aclIdx + 1;
            end
        end
        
        % Return the ACL LT addresses
        ltAddrs = ltAddrs(1:aclIdx-1);
    end
    
    function ltAddrs = scoLinkAddress(obj)
    %scoLinkAddress Return SCO logical transport address which can accept
    %data from the higher-layers
    %
    %   LTADDRS = scoLinkAddress(OBJ) returns the SCO logical transport
    %   addresses which can accept data from the higher-layers, if the
    %   respective queue is not full.
    %
    %   OBJ is an object of type helperBluetoothBaseband.
    %
    %   LTADDRS is an array of SCO logical transport addresses, each
    %   address is specified as a positive integer.
    
        % Initialize
        ltAddrs = zeros(1, 7);
        scoIdx = 1;
        
        % Check the status of SCO queues of all the SCO logical transports
        for idx = 1:obj.NumLTAddrs
            if obj.pLinkConfigs{idx}.SynchronousLink && ~isFull(obj.pLinkConfigs{idx}.SCOQueue)
                ltAddrs(scoIdx) = obj.pLinkConfigs{idx}.PrimaryAddress;
                scoIdx = scoIdx + 1;
            end
        end
        
        % Return the SCO LT addresses
        ltAddrs = ltAddrs(1:scoIdx-1);
    end
end

methods (Access = private)
    function scoIntervals = getActiveSCOIntervals(obj)
    %getActiveSCOIntervals Get active SCO intervals
    
        % Initialize
        scoIntervals = zeros(1, 3);
        scoIndex = 1;
        
        % Get the active SCO intervals
        for idx = 1:obj.pLinkIndex - 1
            if obj.pLinkConfigs{idx}.SynchronousLink
                scoIntervals(scoIndex) = obj.pLinkConfigs{idx}.SCOInterval;
            end
        end
    end
    
    function ltHandle = getLTHandle(obj, ltAddr)
    %getLTHandle Get the LT configuration handle corresponding to the given
    %LT address
    
        ltIndex = 0;
        for idx = 1:obj.NumLTAddrs
            if (obj.pLinkConfigs{idx}.PrimaryAddress == ltAddr)
                ltIndex = idx;
                break;
            end
        end
        % Fetch the LT handle having the given LT address
        ltHandle = obj.pLinkConfigs{ltIndex};
    end
    
    function [slotsRequired, packetType] = getRequiredSlots(obj, payloadSize)
    %getRequiredSlots Get the number of required slots for the given
    %payload size on ACL logical transport
    
        % Initialize
        slotsRequired = 0;
        packetType = blanks(0);
        
        % Get the required slots for the given payload size
        for idx = 1:numel(obj.ACLPacketTypes)
            if payloadSize <= obj.ACLPacketInfo(idx, 2)
                % Get the number of slots required to transmit the payload
                % with given size
                slotsRequired = obj.ACLPacketInfo(idx, 1);
                % Packet type used for transmitting the payload with given
                % size
                packetType = obj.ACLPacketTypes{idx};
                break;
            end
        end
    end
    
    function updateTimers(obj)
    %updateTimers Update all the running timers in the device
        if obj.NumACLLinks > 0
            % Update the ACL timers for all non-zero timers
            obj.pACLTimers(obj.pACLTimers(1:obj.NumACLLinks, 2) > 0, 2) = ...
                obj.pACLTimers(obj.pACLTimers(1:obj.NumACLLinks, 2) > 0, 2) - 1;
            
            % Update the classification timer for each logical link
            for idx = 1:obj.NumACLLinks
                obj.pLinkConfigs{idx}.ClassificationTimer = obj.pLinkConfigs{idx}.ClassificationTimer + 1;
            end
        end
        
        if obj.NumSCOLinks > 0
            % Update the SCO timers
            obj.pSCOTimers(1:obj.NumSCOLinks, 2) = obj.pSCOTimers(1:obj.NumSCOLinks, 2) - 1;
        end
        
        % Data is transmitting or receiving
        if obj.pDataSlots > 0
            % Update the transmission/reception slots
            obj.pDataSlots = obj.pDataSlots - 1;
        end
    end
    
    function updateRxRequest(obj, ltHandle)
    %updateRxRequest Update the Rx request from baseband to PHY

        % Channel classification needs to be updated in frequency
        % hopping algorithm
        if obj.pUpdateClassification(ltHandle.PrimaryAddress)
            % AFH instant reached
            if obj.pSlotTimer.Clock >= ltHandle.AFHInstant
                % Update the channel classification in the frequency hopping object
                ltHandle.FrequencyHop.UsedChannels = (find(ltHandle.ChannelClassification.ChannelMap)-1);
                obj.pUpdateClassification(ltHandle.PrimaryAddress) = false;
                updateClassification(obj, ltHandle);
            end
        end
        % Enable sending requests from baseband to PHY
        requestToPHY(obj, ltHandle);
    end
    
    function requestToPHY(obj, ltActive)
    %requestToPHY Enable the flag and update the data when a request needs
    %to be sent to PHY
    
        % Calculate the data whitening initialization vector
        inputClock = comm.internal.utilities.de2biBase2RightMSB(obj.pSlotTimer.Clock, 28);
        whitenInitialization = [inputClock(2:7)'; 1];
        obj.WhitenInit = whitenInitialization;
        obj.ChannelIndex = nextHop(ltActive.FrequencyHop, obj.pSlotTimer.Clock);

        % Update the flag to send request from baseband to PHY
        obj.SendRequest = true;
    end
    
    function transmitACLData(obj, ltActive)
    %transmitACLData Transmit the data from the active ACL logical
    %transport
    
        octetLen = 8; % Number of bits in single octet
    
        % Channel classification needs to be updated in frequency
        % hopping algorithm
        if obj.pUpdateClassification(ltActive.PrimaryAddress)
            % AFH instant reached
            if obj.pSlotTimer.Clock >= ltActive.AFHInstant
                % Update the channel classification in the frequency hopping object
                ltActive.FrequencyHop.UsedChannels = (find(ltActive.ChannelClassification.ChannelMap)-1);
                obj.pUpdateClassification(ltActive.PrimaryAddress) = false;
                updateClassification(obj, ltActive);
            end
        end
        
        % Classify the channels into good or bad for each classification
        % interval at the Master
        if (obj.pSlotTimer.Clock ~= 0) && obj.pMaster && ...
                (ltActive.ClassificationTimer >= ltActive.ChannelClassification.ClassificationInterval)
            
            classifyChannels(ltActive.ChannelClassification);
            ltActive.ChannelsClassified = true;
            ltActive.ClassificationTimer = 0;
        end
        
        % Fetch the number of available slots for an ACL data exchange
        remTimers = obj.pSCOTimers(:,2);
        minSlots = min(remTimers(remTimers ~= -1));
        if isempty(minSlots)
            availableSlots = inf;
        else
            availableSlots = minSlots;
        end
        
        % Check for availability of any payload for retransmissions
        if (obj.pPayloadLength == 0)
            % Channels are classified, transmit the updated channel map
            if ltActive.ChannelsClassified && ~ltActive.ClassificationSent
                % Generate LMP set AFH PDU for sending the channel map to
                % the Slave
                lmpPDU = generateClassificationPDU(ltActive);
                
                % Update the payload and payload length with LMP PDU
                obj.pPayloadLength = numel(lmpPDU);
                obj.pPayload(1:obj.pPayloadLength) = lmpPDU;
                ltActive.ACLSEQN = double(~ltActive.ACLSEQN);
                obj.TxData.SEQN = ltActive.ACLSEQN;
                obj.TxData.LLID = [1; 1]; % LMP message
                ltActive.ChannelsClassified = false;
                
                % Set the flag to specify the transmission of channel
                % classification
                ltActive.ClassificationSent = true;
            elseif ~isEmpty(ltActive.ACLQueue)
                % Get the data from the selected logical transport
                [~, aclData] = dequeue(ltActive.ACLQueue);
                % Update the payload and payload length with ACL data
                obj.pPayloadLength = numel(aclData);
                obj.pPayload(1:obj.pPayloadLength) = aclData;
                obj.TxData.LLID = [0;1]; % Start of an L2CAP message or no fragmentation
                ltActive.ACLSEQN = double(~ltActive.ACLSEQN);
                obj.TxData.SEQN = ltActive.ACLSEQN;
            end
        else
            % Update the number of times ACL packets are retransmitted
            obj.RetransmittedACLPackets(ltActive.PrimaryAddress) = ...
                obj.RetransmittedACLPackets(ltActive.PrimaryAddress) + 1;
        end
        payloadSize = obj.pPayloadLength/octetLen; % in bytes
        
        % Received payload from the higher layers
        if payloadSize > 0
            % Get the required number of slots for transmitting the payload
            [slotsRequired, type] = getRequiredSlots(obj, payloadSize);
            
            % Slots are sufficient for packet transmission
            if (availableSlots > slotsRequired)
                obj.TxData.PacketType = type;
                obj.TxData.Payload = obj.pPayload;
                obj.TxData.PayloadLength = payloadSize; % in bytes
                
                % If ACK response is pending for previous reception, send
                % it along with the current data transmission
                if ltActive.RespFlag
                    obj.TxData.ARQN = ltActive.RecvACLStatus;
                    ltActive.RespFlag = false;
                end
                % Update the slots required for transmission
                obj.pDataSlots = slotsRequired;
                % Waiting for the acknowledgement
                obj.pAckReceived = false;
            end
        end
        
        % Transmit the acknowledgement as Null packet, if there is no other
        % data and an acknowledgement transmission is pending
        if isempty(obj.TxData.PacketType) && ltActive.RespFlag
            obj.TxData.PacketType = 'Null';
            obj.TxData.ARQN = ltActive.RecvACLStatus;
            obj.TxData.PayloadLength = 0;
            ltActive.RespFlag = false;
        end
        
        % If there is nothing to transmit and a poll is pending to get
        % response, send poll message
        if isempty(obj.TxData.PacketType) && (obj.pSendPoll)
            obj.TxData.PacketType = 'Poll';
            % Update the slots required for transmission
            obj.pDataSlots = 1;
            % Update the SEQN and ARQN as '0' for poll packets
            obj.TxData.SEQN = 0;
            obj.TxData.ARQN = 0;
            obj.TxData.PayloadLength = 0;
            % Waiting for the acknowledgement
            obj.pAckReceived = false;
        end
        
        if ~isempty(obj.TxData.PacketType)
            % Reset the ACL poll timer
            obj.pACLTimers(...
                obj.pACLTimers(:, 1) == ltActive.PrimaryAddress, 2) = ltActive.PollInterval;
            obj.pPrevLTAddr = ltActive.PrimaryAddress;
            obj.pPrevLinkType = 1; % ACL
            obj.TxData.LTAddr = ltActive.PrimaryAddress;
            obj.pSendPoll = false;
            
            % Update the number of Tx ACL packets
            obj.TxACLPackets(ltActive.PrimaryAddress) = ...
                obj.TxACLPackets(ltActive.PrimaryAddress) + 1;
            
            % Update the number of Tx ACL one-slot packets
            if any(strcmpi(obj.TxData.PacketType, {'DM1', 'DH1', 'Poll', 'Null'}))
                obj.TxACLOneSlotPackets(ltActive.PrimaryAddress) = ...
                    obj.TxACLOneSlotPackets(ltActive.PrimaryAddress) + 1;
            % Update the number of Tx ACL three-slot packets
            elseif any(strcmpi(obj.TxData.PacketType, {'DM3', 'DH3'}))
                obj.TxACLThreeSlotPackets(ltActive.PrimaryAddress) = ...
                    obj.TxACLThreeSlotPackets(ltActive.PrimaryAddress) + 1;
            % Update the number of Tx ACL five-slot packets
            elseif any(strcmpi(obj.TxData.PacketType, {'DM5', 'DH5'}))
                obj.TxACLFiveSlotPackets(ltActive.PrimaryAddress) = ...
                    obj.TxACLFiveSlotPackets(ltActive.PrimaryAddress) + 1;
            end
            
            % Enable sending requests from baseband to PHY
            requestToPHY(obj, ltActive);
        end
    end
    
    function receiveACLData(obj, inData)
    %receiveACLData Process the received ACL data
        
        % Fetch the active Slave logical transport
        ltActive = getLTHandle(obj, inData.LTAddr);
        
        % Update the received ACL packet status
        ltActive.RecvACLStatus = double(inData.IsValid);

        % Update the number of Rx ACL one-slot packets
        if any(strcmpi(inData.PacketType, {'DM1', 'DH1', 'Poll', 'Null'}))
            obj.RxACLOneSlotPackets(ltActive.PrimaryAddress) = ...
                obj.RxACLOneSlotPackets(ltActive.PrimaryAddress) + 1;
            % Update the number of Rx ACL three-slot packets
        elseif any(strcmpi(inData.PacketType, {'DM3', 'DH3'}))
            obj.RxACLThreeSlotPackets(ltActive.PrimaryAddress) = ...
                obj.RxACLThreeSlotPackets(ltActive.PrimaryAddress) + 1;
            % Update the number of Rx ACL five-slot packets
        elseif any(strcmpi(inData.PacketType, {'DM5', 'DH5'}))
            obj.RxACLFiveSlotPackets(ltActive.PrimaryAddress) = ...
                obj.RxACLFiveSlotPackets(ltActive.PrimaryAddress) + 1;
        else
            % Drop the packets, if the packet type is unknown or not
            % supported
            obj.DroppedPackets = obj.DroppedPackets + 1;
        end
        
        % Update the number of Rx ACL packets
        obj.RxACLPackets(ltActive.PrimaryAddress) = ...
            obj.RxACLPackets(ltActive.PrimaryAddress) + 1;
        
        % Update the acknowledgement of the previously transmitted packet
        if ~strcmpi(inData.PacketType, 'Poll') && inData.ARQN
            % Received the acknowledgement
            obj.pAckReceived = true;
            obj.pPayloadLength = 0;
            % Update the number of ACL packets successfully acknowledged
            obj.ACKedACLPackets(ltActive.PrimaryAddress) = ...
                obj.ACKedACLPackets(ltActive.PrimaryAddress) + 1;
        else
            % Acknowledgement failed, need to retransmit the packet
            obj.pAckReceived = false;
        end
        
        % Received ACK for channel classification LMP message
        if ltActive.ClassificationSent && inData.ARQN
            % Set the flag to use the channel classification
            obj.pUpdateClassification(ltActive.PrimaryAddress) = true;
            ltActive.ClassificationSent = false;
        end
        
        % Received a poll packet
        if strcmpi(inData.PacketType, 'Poll')
            % Send acknowledgement
            ltActive.RespFlag = true;
        % Received a null packet
        elseif strcmpi(inData.PacketType, 'Null')
            % Doesn't require any acknowledgement
            ltActive.RespFlag = false;
        else
            % Check for the SEQN field, same payload received again
            if (ltActive.RecvACLSEQN ~= -1) && (ltActive.RecvACLSEQN == inData.SEQN)
                % No need to process the payload
                obj.DecodedPayload = zeros(1, 0);
            else
                ltActive.RecvACLSEQN = inData.SEQN;
                octetLen = 8; % Number of bits in single octet
                % Process the newly received payload
                obj.DecodedPayload = inData.Payload(1:inData.PayloadLength*octetLen);
            end
            
            % Send the acknowledgement
            ltActive.RespFlag = true;
            
            % Received LMP message
            if all(inData.LLID == [1; 1]) && ~isempty(obj.DecodedPayload)
                % Decode the received LMP PDU
                [status, ~, lmpCfg] = helperBluetoothLMPPDUDecode(obj.DecodedPayload);
                % Failed to decode the received LMP message
                if status ~= 0
                    % Update the number of LMP messages dropped
                    obj.DroppedLMPMessages = obj.DroppedLMPMessages + 1;
                else
                    % Update the received channel classification
                    if strcmpi(lmpCfg.PDUType, 'LMP set AFH')
                        ltActive.AFHInstant = lmpCfg.AFHInstant;
                        % Set the flag to use the channel classification
                        obj.pUpdateClassification(ltActive.PrimaryAddress) = true;
                        
                        % Received AFH set message which is older than
                        % current clock
                        if ltActive.AFHInstant <= obj.pSlotTimer.Clock
                            % Update the channel map in frequency hopping algorithm
                            ltActive.FrequencyHop.UsedChannels = (find(lmpCfg.UsedChannels)-1);
                            obj.pUpdateClassification(ltActive.PrimaryAddress) = false;
                            updateClassification(obj, ltActive);
                            ltActive.AFHInstant = 0;
                        end
                    else
                        % Update the number of LMP messages dropped
                        obj.DroppedLMPMessages = obj.DroppedLMPMessages + 1;
                    end
                end
            end
        end
    end
    
    function transmitSCOData(obj, ltActive)
    %transmitSCOData Transmit SCO data over logical link
    
        octetLen = 8; % Number of bits in single octet
        
        % No SCO data is available for transmission
        if isEmpty(ltActive.SCOQueue)
            obj.pDataSlots = 0;
            return;
        end
        
        % Enable sending requests from baseband to PHY
        requestToPHY(obj, ltActive);
        
        % Fetch SCO data from the queue
        [~, payload] = dequeue(ltActive.SCOQueue);
        payloadSize = numel(payload); % in bits
        obj.TxData.Payload(1:payloadSize) = payload;
        obj.TxData.PayloadLength = payloadSize/octetLen; % in bytes
        
        obj.TxData.PacketType = ltActive.SCOPacketType;
        % SCO packets doesn't have sequence number, it uses the old
        % sequence number (In this case, it is the ACL sequence number)
        obj.TxData.SEQN = ltActive.ACLSEQN;
        obj.TxData.ARQN = ltActive.RecvSCOStatus;
        obj.TxData.LTAddr = ltActive.PrimaryAddress;
        
        % Update the slots required for transmission
        obj.pDataSlots = 1;
        
        % Reset SCO packet status. This will be updated when receiving SCO
        % packet
        ltActive.RecvSCOStatus = 0;
        
        % Update the previous LT address and previous link type
        obj.pPrevLTAddr = ltActive.PrimaryAddress;
        obj.pPrevLinkType = 2; % SCO
        
        % Update the number of Tx SCO packets
        obj.TxSCOPackets(ltActive.PrimaryAddress) = ...
            obj.TxSCOPackets(ltActive.PrimaryAddress) + 1;
    end
    
    function receiveSCOData(obj, inData)
    %receiveSCOData Receive data over SCO logical link
    
        octetLen = 8; % Number of bits in single octet
    
        % Fetch the active Slave logical transport
        ltActive = getLTHandle(obj, inData.LTAddr);
        
        % SCO packet is valid and passed FEC check
        ltActive.RecvSCOStatus = 1;
        
        % Update the number of Rx SCO packets
        obj.RxSCOPackets(ltActive.PrimaryAddress) = ...
            obj.RxSCOPackets(ltActive.PrimaryAddress) + 1;

        % Process the newly received payload
        obj.DecodedPayload = inData.Payload(1:inData.PayloadLength*octetLen);

        % Received acknowledgement for the previously transmitted packet
        if inData.ARQN
            % Update the number of SCO packets successfully acknowledged
            obj.ACKedSCOPackets(ltActive.PrimaryAddress) = ...
                obj.ACKedSCOPackets(ltActive.PrimaryAddress) + 1;
        end
    end
    
    function linkType = getLinkType(obj, pktType)
    %getLinkType Get the link type based on the packet type
    
        % Initialize
        % 0 - Unknown
        % 1 - ACL
        % 2 - SCO
        linkType = 0;
        
        % Check for ACL packet types
        if any(strcmpi(obj.ACLPacketTypes, pktType)) || ...
                any(strcmpi(obj.CommonPacketTypes, pktType))
            linkType = 1; % ACL
        elseif any(strcmpi(obj.SCOPacketTypes, pktType))
            linkType = 2; % SCO
        end
    end
    
    function updateClassification(obj, ltActive)
    %updateClassification Update the classification statistics of the given
    %logical link
    
        % Get the classification count
        curCount = obj.ChannelClassificationCount(ltActive.PrimaryAddress);
        
        % Update the total number of classifications
        obj.ClassificationTotalCount = obj.ClassificationTotalCount + 1;
        
        % Update the channel classification statistics as LTAddrs (1) |
        % Count (1) | Channel map (79). Size is specified in brackets.
        obj.ChannelClassificationStats(obj.ClassificationTotalCount, 1) = ltActive.PrimaryAddress;
        obj.ChannelClassificationStats(obj.ClassificationTotalCount, 2) = curCount + 1;
        obj.ChannelClassificationStats(obj.ClassificationTotalCount, 3:end) = double(ltActive.ChannelClassification.ChannelMap);
        obj.ChannelClassificationCount(ltActive.PrimaryAddress) = obj.ChannelClassificationCount(ltActive.PrimaryAddress) + 1;
    end
    
    function masterTx(obj)
    %masterTx Master transmission
        % Check for the reserved SCO slots
        if any(obj.pSCOTimers(:, 2) == 0)
            % Fetch the reserved SCO slot LT address
            ltAddr = obj.pSCOTimers((obj.pSCOTimers(:, 2) == 0), 1);
            
            % Fetch the active Slave logical transport
            ltActive = getLTHandle(obj, ltAddr(1));
            
            % Transmit the voice data over SCO link
            transmitSCOData(obj, ltActive);
            
            % Reset the SCO interval
            obj.pSCOTimers((obj.pSCOTimers(:, 1) == ltAddr(1)), 2) = ...
                obj.pLinkConfigs{obj.LTAddresses(:) == ltAddr(1)}.SCOInterval;
            
        % Slot is available for ACL transmission
        else
            % Fetch one ACL logical transport with expired
            % poll interval
            aclTimer = obj.pACLTimers(:, 2) == 0;
            
            % Check for the ACL slots
            if any(aclTimer)
                % Fetch the ACL slot LT address if the poll
                % interval is expired
                ltAddrs = obj.pACLTimers(aclTimer, 1);
                % Consider the first LT address when more than
                % one poll timer is expired at once
                ltAddr = ltAddrs(1);
                
                % Master must transmit at least one packet for
                % every poll interval
                
                % if there is no data at the logical transport,
                % set the poll flag as true to send the poll
                % packet
                obj.pSendPoll = true;
                
            % Previously transmitted packet is not acknowledged
            elseif ~obj.pAckReceived
                ltAddr = obj.pPrevLTAddr;
            else
                % Get a logical transport address randomly,
                % from which packet needs to be transmitted
                ltIndex = randi([1 obj.NumACLLinks]);
                ltAddr = obj.LTAddresses(ltIndex);
            end
            
            % Get the active Slave logical transport
            ltActive = getLTHandle(obj, ltAddr);
            
            % Transmit the data from the active ACL logical
            % transport
            transmitACLData(obj, ltActive);
        end
    end
    
    function slaveTx(obj)
    %slaveTx Slave transmission
        % No packet is received in the previous slot for Slave
        % reception and no reserved slot is available for transmission
        if (obj.pPrevLTAddr == 0)
            return;
        end
    
        % Get the logical transport address for
        % transmitting a packet
        ltAddr = obj.pPrevLTAddr;
        
        % Get the active Slave logical transport
        ltActive = getLTHandle(obj, ltAddr);
        
        if (obj.pPrevLinkType == 1) % ACL
            % Transmit packet from the selected LT address
            transmitACLData(obj, ltActive);
        elseif (obj.pPrevLinkType == 2) % SCO
            % Transmit packet from the selected LT address
            transmitSCOData(obj, ltActive);
        end
        
        % Reset the LT address of the previously received
        % logical transport and the previous link type
        obj.pPrevLTAddr = 0;
        obj.pPrevLinkType = 0;
    end
    
    function masterRx(obj, inData)
    %masterRx Master reception
        % Fetch the active Slave logical transport
        ltActive = getLTHandle(obj, obj.pPrevLTAddr);
        
        % Update the number of packets received with invalid
        % CRC/HEC
        if ~inData.IsValid
            obj.RxFailedPackets = obj.RxFailedPackets + 1;
            return;
        end
        
        % Check the received LT address
        if (inData.LTAddr ~= obj.pPrevLTAddr) || (inData.LTAddr == 0)
            % Drop the packet received from the incorrect LT address or
            % drop the packets with LT address with 0 (not supported)
            
            obj.DroppedPackets = obj.DroppedPackets + 1;
            
            % Update the Rx status as failed on the channel. This
            % is used when classifying the channels into good or
            % bad channels
            updateChannelRxStatus(ltActive.ChannelClassification, ...
                obj.ChannelIndex, 0);
            return;
        end
        
        % Update the Rx status on the channel. This is used when
        % classifying the channels into good or bad channels.
        updateChannelRxStatus(ltActive.ChannelClassification, ...
            obj.ChannelIndex, inData.IsValid);
        
        if (obj.pPrevLinkType == 1)
            % Receive packets from the previous ACL LT address
            receiveACLData(obj, inData);
        elseif (obj.pPrevLinkType == 2)
            % Receive packets from the previous SCO LT address
            receiveSCOData(obj, inData);
        end
    end
    
    function slaveRx(obj, inData)
    %slaveRx Slave reception
        
        % Update the number of packets received with invalid
        % CRC/HEC
        if ~inData.IsValid
            obj.RxFailedPackets = obj.RxFailedPackets + 1;
            return;
        end
        
        % Drop the packets with LT address with 0 (not supported)
        if inData.LTAddr == 0
            obj.DroppedPackets = obj.DroppedPackets + 1;
            return;
        end
        
        % Match the received LT address with the available LT
        % addresses
        if ~(any(inData.LTAddr == obj.LTAddresses(1:obj.NumLTAddrs)))
            % Drop the packet which is not destined for the
            % current baseband layer
            obj.DroppedPackets = obj.DroppedPackets + 1;
            return;
        end
        
        % Get the LT address
        ltAddr = inData.LTAddr;
        
        % Check for the reserved SCO slots
        if any(obj.pSCOTimers(:, 2) == 0)
            % Fetch the reserved SCO slot LT address
            resLTAddr = obj.pSCOTimers((obj.pSCOTimers(:, 2) == 0), 1);
            
            % Reset the SCO interval
            obj.pSCOTimers(obj.pSCOTimers(:, 1) == resLTAddr, 2) = ...
                obj.pLinkConfigs{obj.LTAddresses == resLTAddr}.SCOInterval;
            
            % Update the previous LT address and link type with the
            % reserved LT address to transmit the SCO packet in reserved
            % slots
            obj.pPrevLTAddr = resLTAddr;
            obj.pPrevLinkType = 2; % SCO
        else
            resLTAddr = 0;
        end
        
        % Check for reserved LT address (SCO)
        if (resLTAddr ~= 0) && (ltAddr ~= resLTAddr)
            % Drop the received packet as the reserved LT
            % address is not matched
            obj.DroppedPackets = obj.DroppedPackets + 1;
            return;
        end
        
        % Fetch the type of the logical link on which the
        % packet is received
        linkType = getLinkType(obj, inData.PacketType);

        % ACL
        if linkType == 1
            % Process the received ACL packet
            receiveACLData(obj, inData);
        % SCO
        elseif linkType == 2
            % Process the received SCO packet
            receiveSCOData(obj, inData);
        else
            % Drop the received packet as the link type is
            % unknown
            obj.DroppedPackets = obj.DroppedPackets + 1;
            return;
        end
        
        % Store the received LT address
        obj.pPrevLTAddr = ltAddr;
        obj.pPrevLinkType = linkType; % SCO
    end
end
end

% LocalWords:  DM HV LLID SEQN ARQN LMP PHY Ack acl sco acknowledgement
% LocalWords:  AFH PDU piconet ACL SCO HEC
% LocalWords:  CRC
