classdef phyRxAbstracted < matlab.DiscreteEventSystem
%phyRxAbstracted Models PHY operations related to packet reception
%
%   This is an example helper class.
%
%   This block has 2 input ports:
%   1. PhyMode  - Rx mode (active/inactive) of the PHY
%       * Bus object of type "PhyMode" available in the
%         wlanAbstractedPHYDataTypes.mat file
%   2. Waveform - Abstracted waveform received
%       * Bus object of type "AbstractWaveform" available in the
%         wlanAbstractedPHYDataTypes.mat file
%
%   This block has 2 output ports:
%   1. RxIndications - Rx indications to the MAC layer
%       * Bus object of type "PhyInd" available in the
%         wlanAbstractedPHYDataTypes.mat file
%   2. FrameToMAC    - MAC Frame
%       * Bus object of type "Frame" available in the
%         wlanAbstractedPHYDataTypes.mat file
%
%   This block performs the following operations:
%      - Handling signal strength
%      - Handling interference
%      - Recovering preamble, header and payload
%      - Handling indications to MAC layer
%
%   NOTE: The "PHY Abstraction" value "TGax Evaluation Methodology Appendix
%   1" works for only MCS values in the range [0-9].

%   Copyright 2019-2021 The MathWorks, Inc.

    % Public, tunable properties
    properties
        % Node ID
        RecvNodeID (1, 1) {mustBeInteger, mustBeGreaterThan(RecvNodeID, 0)} = 1;
        
        % Number of Nodes
        NumberOfNodes = 10;
        
        % Energy Detection Threshold (dBm)
        EDThreshold = -82;
        
        % Receiver Gain (dB)
        RxGain (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(RxGain, 0),...
            mustBeLessThanOrEqual(RxGain, 20)} = 0.0;
        
        % Rx Noise Figure (dB)
        RxNoiseFigure (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(RxNoiseFigure, 1),...
            mustBeLessThanOrEqual(RxNoiseFigure, 15)} = 7
        
        % PHY Abstraction
        PHYAbstraction = 'TGax Evaluation Methodology Appendix 1';
    end
    
    properties (Constant, Hidden)
        % Queue for holding waveforms from channel
        PPDUQueueID = 1;
        
        % Queue for holding triggers (rx on/off) from MAC
        PhyModeSelectQueueID = 2;
        
        % Queue for holding frames to MAC
        PSDUQueueID = 3;
        
        % Queue for holding indications to MAC
        PhyIndicationsQueueID = 4;
        
        % Output port for sending indications to MAC
        PhyIndicationsOutputPortIndex = 1;
        
        % Output port for sending frame to MAC
        PSDUOutputPortIndex = 2;
        
        % PHY abstraction values
        PHYAbstractionSet = matlab.system.StringSet...
            ({'TGax Evaluation Methodology Appendix 1','TGax Simulation Scenarios MAC Calibration'});
    end
    
    % Information from MetaData
    properties(Access = private)
        % Number of subframes
        NoOfSubFrames = 1;
        
        % Received frame format
        RxFrameFormat;
        
        % Byte boundary of start indexes subframes
        FrameIndex = zeros(1,64);
        
        % Subframe lengths starting from frame indexes
        SubFrameLength = zeros(64,1);
    end
    
    % Configure based on values from Metadata
    properties(Access = private)
        % Non-HT configuration object
        RxNonHTConfig;
        
        % HT configuration object
        RxHTConfig;
        
        % VHT configuration object
        RxVHTConfig;
        
        % HE-SU configuration object
        RxHESUConfig;
        
        % tgaxPERAbstraction object
        PERAbstraction;
    end
    
    % Spatial reuse properties
    properties(Access = private)
        % Overlapping Basic Service Set Packet Detect Threshold (dBm)
        OBSSPDThreshold = -82;
        
        % Basic Service Set(BSS) Color
        BSSColor = 0;
        
        % Received BSS color
        RxBSSColor = 0;
        
        % Tx Power limit flag. This will be set to true when received frame
        % is decoded as Inter BSS frame and the signal power is less than
        % OBSSPDThreshold.  
        LimitTxPower = false;
        
        % Spatial resue flag
        EnableSROperation = 0;
    end
    
    % Fill based on values from received frame metadata
    properties(Access = private)
        % PHY Rx buffer to store the received PSDU. Here 82573 is the
        % maximum allowed PSDU length in bytes for HE-SU format with MCS 11
        % in 20MHz bandwidth
        RxData = uint8(zeros(93600, 1));
        
        % Received data length in bytes
        RxDataLength = 0;
        
        % Structure of Rx-Vector for its corresponding bus type
        Vector = Simulink.Bus.createMATLABStruct('PhyInd');
    end
    
    % Receive packet context
    properties(Access = private)
        % Interference value in dBm
        TotalSignalPower = 0;
        
        % CCA idle flag to indicate channel state
        CCAIdle = 1;
        
        % Flag to maintain channel busy until an intra BSS frame or inter
        % BSS frame with signal power greater than OBSS PD Threshold ends.
        ChannelBusy = 0;
        
        % Flag to indicate whether Rx is on
        RxOn = boolean(1);
        
        % Counter for subframes processed
        RxSubFrameCount = 0;
        
        % Thermal noise
        ThermalNoise = 0;
        
        % Number of signals present at a point of event
        NumSignals = 0;
        
        % Array containing all the signal power values and their IDs
        % including signal of interest and the interference signals
        SignalPowerSet = zeros(64,2);
        
        % User index for single user processing. Index '1' will be used in
        % case of single user and downlink multi-user reception. Indices
        % greater than '1' will be used in case of downlink multi-user
        % transmission and uplink multi-user reception.
        UserIndexSU = 1;
    end
    
    % Discrete-event algorithms
    methods
        function [entity, events] = fromPhyGenerate(obj, ~, entity, tag)
            %fromPhyGenerate Create appropriate indications for MAC layer
            %and forward it to the output port
            
            coder.extrinsic('edcaUpdateStats')
            events = obj.initEventArray();
            
            % Generate PHY indications to MAC
            switch(tag)
                case 'CCAIDLE'
                    entity.data.msgType = double(PhyPrimitives.CCAIdleIndication);
                    entity.data.limitTxPower = obj.LimitTxPower;
                    % Forward the indication to MAC
                    events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
                    
                case 'CCABUSY'
                    entity.data.msgType = double(PhyPrimitives.CCABusyIndication);
                    % Forward the indication to MAC
                    events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
                    
                case 'RXSTART'
                    entity.data = obj.Vector;
                    entity.data.msgType = double(PhyPrimitives.RxStartIndication);
                    % Forward the indication to MAC
                    events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
                    
                case 'RXEND'
                    obj.ChannelBusy = 0;
                    edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyRx, 1);
                    entity.data.msgType = double(PhyPrimitives.RxEndIndication);
                    % Forward the indication to MAC
                    events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
                    
                case 'RXERR'
                    edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyRxDrop, 1);
                    entity.data.msgType = double(PhyPrimitives.RxErrorIndication);
                    % Forward the indication to MAC
                    events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
            end
        end
        
        function [entity,events] = waveformEntry(obj, ~, entity, ~)
            %waveformEntry Handle the received waveform and schedule the
            %next event for processing the frame
                         
            coder.extrinsic('edcaUpdateStats', 'bdroot', 'edcaPlotStats', 'get_param');
            
            events = obj.initEventArray();
            
            % TGax Evaluation Methodology provides calibration results for
            % only HE format and MCS values 0 to 9
            if strcmp(obj.PHYAbstraction, 'TGax Evaluation Methodology Appendix 1')
                if entity.data.metaData.vector.mcsIndex > 9
                    error('Only MCS 0-9 are supported for the PHY abstraction with TGax Evaluation Methodology Appendix 1');
                end
            end
            
            % Apply Rx Gain
            entity.data.metaData.sigPower = entity.data.metaData.sigPower + obj.RxGain;
                        
            % Total duration of the waveform
            ppduDuration = entity.data.durations.preambleDuration + entity.data.durations.headerDuration + ...
                    entity.data.durations.payloadDuration;
                
            % Update visualization with the waveform duration
            if obj.RxOn && (entity.data.metaData.sigPower >= obj.EDThreshold)
                edcaPlotStats(obj.RecvNodeID, 3, get_param(bdroot, 'SimulationTime')*1e6, ppduDuration);
            end
            
            % Update the total signal power (add current signal power to the total power)
            obj.TotalSignalPower = obj.TotalSignalPower + power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
            obj.NumSignals = obj.NumSignals + 1;
            
            % Store the sender node-ID and the corresponding Rx signal power
            obj.SignalPowerSet(obj.NumSignals, 1) = entity.data.metaData.srcId;
            obj.SignalPowerSet(obj.NumSignals, 2) = power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
            
            % Initialize transmit power limit flag
            obj.LimitTxPower = false;
            
            % Receiver antenna is switched on
            if obj.RxOn
                
                % If PHY is idle and the received signal is greater than ED
                % threshold, indicate CCA busy to MAC layer. In all other
                % cases, consider the waveform as interference. If the
                % total power including the interference is greater than ED
                % threshold, indicate CCA busy to MAC layer to avoid
                % transmissions when channel is clearly busy.
                if ((obj.CCAIdle == 1) && (entity.data.metaData.sigPower >= obj.EDThreshold))
                    if obj.NumSignals > 1
                        % Pull the signal of interest to the top of the list
                        signalOfInterest = obj.SignalPowerSet(obj.NumSignals, :);
                        otherSignal = obj.SignalPowerSet(1, :);
                        obj.SignalPowerSet(1, :) = signalOfInterest;
                        obj.SignalPowerSet(obj.NumSignals, :) = otherSignal;
                    end
                    
                    events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'CCABUSY', 0, 100), ...
                        obj.eventTimer('endPreambleAndHeader', ...
                        (entity.data.durations.preambleDuration + entity.data.durations.headerDuration)*1e-6)];
                    obj.CCAIdle = 0;
                else
                    if obj.CCAIdle == 0
                        % Waveform is received when the node is already in
                        % receive state
                        edcaUpdateStats(obj.RecvNodeID, edcaStats.RxTriggersWhilePrevRxIsInProgress, 1);
                    elseif entity.data.metaData.sigPower < obj.EDThreshold
                        % Signal power of the current individual waveform
                        % is less than ED threshold, but the total signal
                        % power (including interference) is greater than
                        % the ED threshold
                        if obj.TotalSignalPower >= power(10.0, (obj.EDThreshold - 30)/ 10.0)
                            events = [events , obj.eventGenerate(obj.PhyIndicationsQueueID, 'CCABUSY', 0, 100)];
                            obj.CCAIdle = 0;
                        end
                        edcaUpdateStats(obj.RecvNodeID, ...
                            edcaStats.EnergyDetectionsLessThanED, 1);
                    end
                    edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyRxDrop, 1);
                    events = [events obj.eventTimer('removeSignalPower', ppduDuration*1e-6)];
                end
            else % Receiver antenna is switched off (Transmission is in progress)
                edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyRxDrop, 1);
                edcaUpdateStats(obj.RecvNodeID, edcaStats.RxTriggersWhileTxInProgress, 1);
                events = [events obj.eventTimer('removeSignalPower', ppduDuration*1e-6)];
            end
        end
        
        function [entity, events] = waveformTimer(obj, ~, entity, tag)
            %waveformTimer Handle the waveform processing events
            %   These events include
            %     - PHY preamble and header processing 
            %     - PSDU processing
            %     - Removing interference after processing the signal
            
            global channelMatrix; %#ok<NUSED>
            coder.extrinsic('edcaUpdateStats');
            events = obj.initEventArray();
            
            switch(tag)
                case 'endPreambleAndHeader'
                    % SINR calculation parameters
                    signalStrength = power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
                    obj.RxFrameFormat = entity.data.metaData.vector.frameFormat;
                    rxIdx = obj.RecvNodeID;
                    txIdx = obj.SignalPowerSet(1,1); % Node ID for the signal of interest
                    field = 'preamble';

                    if (obj.RxFrameFormat == edcaFrameFormats.NonHT)
                        obj.RxNonHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                        obj.RxNonHTConfig.PSDULength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                        obj.RxDataLength = obj.RxNonHTConfig.PSDULength;
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,obj.RxNonHTConfig,field,txIdx,rxIdx,signalStrength);                    
                        
                    elseif(obj.RxFrameFormat == edcaFrameFormats.HTMixed)
                        obj.RxHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                        obj.RxHTConfig.PSDULength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                        obj.RxHTConfig.AggregatedMPDU = entity.data.metaData.vector.aggregatedMPDU;
                        obj.RxDataLength = obj.RxHTConfig.PSDULength;
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,obj.RxHTConfig,field,txIdx,rxIdx,signalStrength);
                        
                    elseif(obj.RxFrameFormat == edcaFrameFormats.VHT)
                        obj.RxVHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                        obj.RxDataLength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,obj.RxVHTConfig,field,txIdx,rxIdx,signalStrength);
                        
                    else %((obj.RxFrameFormat == edcaFrameFormats.HE_SU)|| (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                        obj.RxHESUConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                        obj.RxDataLength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,obj.RxHESUConfig,field,txIdx,rxIdx,signalStrength);
                       
                    end
                    
                    if strcmp(obj.PHYAbstraction, 'TGax Evaluation Methodology Appendix 1')
                        % Determine packet success rate for preamble assuming Non-HT, MCS 0 and BCC coding
                        p = 1 - (estimateLinkPerformance(obj.PERAbstraction, SINR, (entity.data.metaData.preambleAndHeaderInfo.numOfBits)/8, 'NonHT', 0, 'BCC'));
                    else
                        if (obj.NumSignals > 1) % One or more interfering signals
                            p = 0;
                        else % No interference
                            p = 1;
                        end
                    end
                    
                    % Pick a random number and compare against the packet
                    % success rate. If the packet success rate is 0.9, it
                    % means that 90 percent of packets get successfully
                    % decoded and 10 percent are not decoded because of
                    % packet errors. So, take a random number between 0 and
                    % 1 where there is a 10 percent chance that random
                    % number will be between 0.9 and 1. If the random
                    % number is between 0.9 and 1, packet will be dropped.
                    randNum = rand(1,1);
                    
                    if (p > randNum)
                        isDecodable = true;
                        if (obj.RxFrameFormat == edcaFrameFormats.NonHT)
                            obj.RxNonHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                            obj.RxNonHTConfig.PSDULength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                            obj.RxDataLength = obj.RxNonHTConfig.PSDULength;
                            
                        elseif(obj.RxFrameFormat == edcaFrameFormats.HTMixed)
                            obj.RxHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                            obj.RxHTConfig.PSDULength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                            obj.RxDataLength = obj.RxHTConfig.PSDULength;
                            obj.RxHTConfig.AggregatedMPDU = entity.data.metaData.vector.aggregatedMPDU;
                            
                        elseif(obj.RxFrameFormat == edcaFrameFormats.VHT)
                            obj.RxVHTConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                            obj.RxDataLength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                            
                        else % HE_SU | HE_EXT_SU
                            obj.RxHESUConfig.MCS = entity.data.metaData.vector.mcsIndex(obj.UserIndexSU);
                            obj.RxDataLength = entity.data.metaData.vector.psduLength(obj.UserIndexSU);
                            obj.RxHESUConfig.BSSColor = entity.data.metaData.vector.bssColor;
                        end
                    else
                        isDecodable = false;
                        obj.RxDataLength = 0;
                    end

                    if ((obj.RxFrameFormat == edcaFrameFormats.HE_SU) || ...
                        (obj.RxFrameFormat == edcaFrameFormats.HE_EXT_SU)) % HE_SU | HE_EXT_SU
                        % Update the Rx BSS color field
                        obj.RxBSSColor = obj.RxHESUConfig.BSSColor;
                    end
                    
                    % Waveform duration in the channel
                    duration = entity.data.durations.preambleDuration + ...
                        entity.data.durations.headerDuration + ...
                        entity.data.durations.payloadDuration;
                    
                    if isDecodable
                        % If the SR operation is disbaled or the frame is
                        % not an inter-BSS frame generate "RXSTART"
                        % indication and schedule "endPayload" event,
                        % otherwise disacard the frame using OBSS PD
                        % threshold and indicate CCA idle to MAC layer.
                        if ~obj.EnableSROperation || ~isFrameIgnorable(obj, entity.data.metaData.sigPower)
                            % Set the flag
                            obj.ChannelBusy = 1;
                            
                            % Extract frame boundaries from frame
                            obj.NoOfSubFrames = entity.data.subFrameCount;
                            obj.SubFrameLength(1:obj.NoOfSubFrames) = entity.data.subFrameLengths(1:obj.NoOfSubFrames);
                            obj.FrameIndex = entity.data.subFrameIndexes(1:obj.NoOfSubFrames);

                            % In case of single subframe/no subframes, schedule
                            % timer to the end of complete payload
                            if obj.NoOfSubFrames < 2
                                events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'RXSTART', 0, 100), ...
                                    obj.eventTimer('endPayload', entity.data.durations.payloadDuration*1e-6)];
                            else
                                % In case of multiple subframes schedule timer
                                % to end of first payload
                                firstPayloadDuration = entity.data.metaData.payloadInfo(1).duration + ...
                                    entity.data.metaData.payloadInfo(1).overheadDuration;
                                events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'RXSTART', 0, 100), ...
                                    obj.eventTimer('endPayload', firstPayloadDuration*1e-6)];
                            end

                            % Check if the frame is aggregated
                            aggregatedMPDU = double(0);
                            if (obj.RxFrameFormat == edcaFrameFormats.VHT) || ...
                                    (obj.RxFrameFormat == edcaFrameFormats.HE_SU) || ...
                                    (obj.RxFrameFormat == edcaFrameFormats.HE_EXT_SU)
                                % VHT frames are always aggregated
                                aggregatedMPDU = double(1);

                            elseif (obj.RxFrameFormat == edcaFrameFormats.HTMixed)
                                % HT frames may be aggregated
                                aggregatedMPDU = double(obj.RxHTConfig.AggregatedMPDU);

                            elseif (obj.RxFrameFormat == edcaFrameFormats.NonHT)
                                % Non-HT frames are always non aggregated
                                aggregatedMPDU = double(0);
                            end

                            % Fill the Rx Vector
                            obj.Vector.psduLength = entity.data.metaData.vector.psduLength;
                            obj.Vector.channelBandwidth = entity.data.metaData.vector.channelBandwidth;
                            obj.Vector.frameFormat = obj.RxFrameFormat;
                            obj.Vector.mcsIndex = entity.data.metaData.vector.mcsIndex;
                            obj.Vector.numSTS = entity.data.metaData.vector.numSTS;
                            obj.Vector.aggregatedMPDU = aggregatedMPDU;
                            obj.Vector.rssi = entity.data.metaData.sigPower;
                            obj.Vector.bssColor = entity.data.metaData.vector.bssColor;
                        else
                            % Start of transmit power limitation period 
                            obj.LimitTxPower = true;
                            
                            % In case of total signal power is less
                            % than OBSS PD threshold, indicate CCA idle
                            % to MAC layer.
                            if obj.TotalSignalPower < power(10.0, (obj.OBSSPDThreshold - 30)/ 10.0)
                                events = [events, obj.eventGenerate(obj.PhyIndicationsQueueID, 'CCAIDLE', 0, 100)];
                                obj.CCAIdle = 1;
                            end
                            % Schedule timer to remove the inter-BSS frame
                            % signal power from total signal power.
                            events = [events, obj.eventTimer('removeInterFrameSignalPower', duration*1e-6 - (entity.data.durations.preambleDuration + entity.data.durations.headerDuration)*1e-6)];
                        end
                    else
                        % Even if the header cannot be decoded, simulate
                        % the channel busy time until the end of this
                        % waveform using the durations
                        events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'RXERR', 0, 100), ...
                            obj.eventTimer('removeSignalPower', duration*1e-6 - (entity.data.durations.preambleDuration + entity.data.durations.headerDuration)*1e-6)];
                        
                        % Increment the PHY decoding failures statistic
                        edcaUpdateStats(obj.RecvNodeID, ...
                            edcaStats.PhyHeaderDecodeFailures, 1);
                    end
                    
                case 'endPayload'
                    % SINR calculation parameters
                    signalStrength = power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
                    obj.RxFrameFormat = entity.data.metaData.vector.frameFormat;
                    rxIdx = obj.RecvNodeID;
                    txIdx = obj.SignalPowerSet(1,1);
                    field = 'data';
                    
                    if (obj.RxFrameFormat == edcaFrameFormats.NonHT)
                        cfg = obj.RxNonHTConfig;
                        channelCoding = 'BCC'; 
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,cfg,field,txIdx,rxIdx,signalStrength);
                        
                    elseif(obj.RxFrameFormat == edcaFrameFormats.HTMixed)
                        cfg = obj.RxHTConfig;
                        channelCoding = cfg.ChannelCoding;
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,cfg,field,txIdx,rxIdx,signalStrength);
                        
                    elseif(obj.RxFrameFormat == edcaFrameFormats.VHT)
                        cfg = obj.RxVHTConfig;
                        channelCoding = cfg.ChannelCoding;
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,cfg,field,txIdx,rxIdx,signalStrength);
                        
                    else % HE_SU | HE_EXT_SU
                        cfg = obj.RxHESUConfig;
                        channelCoding = cfg.ChannelCoding;
                        
                        % Calculate SINR
                        SINR = channelAbstraction(obj,cfg,field,txIdx,rxIdx,signalStrength);
                    end
                    
                    obj.RxSubFrameCount = obj.RxSubFrameCount + 1;
                    
                    % In case of multiple subframes, pass current payload
                    % data from complete aggregated payload and current
                    % payload index. Index of payload is required to access
                    % information of current subframe from Metadata bus.
                    currentPayloadInfo = entity.data.metaData.payloadInfo(obj.RxSubFrameCount);
                    
                    if strcmp(obj.PHYAbstraction, 'TGax Evaluation Methodology Appendix 1')
                        % Calculate probability of success for this chunk
                        % of payload
                        p = 1 - (estimateLinkPerformance(obj.PERAbstraction, SINR, currentPayloadInfo.numOfBits/8, char(edcaFrameFormats(obj.RxFrameFormat)), entity.data.metaData.vector.mcsIndex(obj.UserIndexSU), channelCoding));
                    else
                        if (obj.NumSignals > 1) % One or more interfering signals
                            p = 0;
                        else % No interference
                            p = 1;
                        end
                    end
                    
                    % Pick a random number and compare against the packet
                    % success rate. If the packet success rate is 0.9,
                    % it means that 90 percent of packets get
                    % successfully decoded and 10 percent are not
                    % decoded because of packet errors. So, take a
                    % random number between 0 and 1 where there is a 10
                    % percent chance that random number will be between
                    % 0.9 and 1. If the random number is between 0.9
                    % and 1, packet will be dropped.
                    randNum = rand(1,1);
                    if (p > randNum)
                        isDecodable = true;
                    else
                        isDecodable = false;
                    end
                    
                    if ~isDecodable && (obj.NoOfSubFrames > 0) % Payload is not decodable
                        % Flip the CRC of current subframe/frame
                        startIndex = (obj.FrameIndex(obj.RxSubFrameCount)-1) * 8 + 1;
                        endIndex = (startIndex + obj.SubFrameLength(obj.RxSubFrameCount) * 8) - 1;
                        entity.data.waveformPPDU(endIndex - 1 : endIndex, 1) = ...
                            ~entity.data.waveformPPDU(endIndex -1 : endIndex, 1);
                    end
                    
                    % If all subframes are done or there are no subframes,
                    % schedule RXEND event to MAC and remove interference
                    if (obj.RxSubFrameCount == obj.NoOfSubFrames) || (obj.NoOfSubFrames == 0)
                        obj.RxData(1: obj.RxDataLength * 8, obj.UserIndexSU) = entity.data.waveformPPDU(1:obj.RxDataLength * 8, obj.UserIndexSU);
                        events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'RXEND', 0, 100), ...
                            obj.eventGenerate(obj.PSDUQueueID, 'MACRXFRAME', 0, 100), ...
                            obj.eventTimer('removeSignalPower', 0)];
                        obj.RxSubFrameCount = 0; % reset
                    else
                        % If not all subframes are done, schedule the
                        % end of next subframe payload
                        delayForNextSubFrame = ...
                            entity.data.metaData.payloadInfo(obj.RxSubFrameCount+1).duration + ...
                            entity.data.metaData.payloadInfo(obj.RxSubFrameCount+1).overheadDuration;
                        events =  [events obj.eventTimer('endPayload', delayForNextSubFrame*1e-6)];
                    end
                    
                case 'removeSignalPower'
                    % Remove current signal power from total signal power
                    obj.TotalSignalPower = obj.TotalSignalPower - power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
                    
                    % Remove the entry for this signal
                    obj.removeSignalEntry(entity.data.metaData.srcId);
                    
                    % If the total signal power results in zero or less
                    % than ED threshold, indicate CCA idle to MAC layer
                    if ((obj.TotalSignalPower == 0) || (obj.TotalSignalPower < power(10.0, (obj.EDThreshold - 30)/ 10.0)))
                        events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'CCAIDLE', 0, 100)];
                        obj.CCAIdle = 1;
                    end
                    events = [events obj.eventDestroy()];
                    
                case 'removeInterFrameSignalPower'
                    % Remove current signal power from total signal power
                    obj.TotalSignalPower = obj.TotalSignalPower - power(10.0, (entity.data.metaData.sigPower - 30)/ 10.0);
                    
                    % Remove the entry for this signal
                    obj.removeSignalEntry(entity.data.metaData.srcId);
                    
                    % If the total signal power results in zero or less
                    % than ED threshold, stop transmit power limitation
                    % period
                    if ((obj.TotalSignalPower == 0) || (obj.TotalSignalPower < power(10.0, (obj.EDThreshold - 30)/ 10.0)))
                        obj.LimitTxPower = false;
                    end
                    
                    % If the total signal power results in zero or less
                    % than OBSS PD threshold, indicate CCA idle to MAC
                    % layer
                    if ((obj.TotalSignalPower == 0) || (obj.TotalSignalPower < power(10.0, (obj.OBSSPDThreshold - 30)/ 10.0))) && ~obj.ChannelBusy
                        events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'CCAIDLE', 0, 100)];
                        obj.CCAIdle = 1;
                    end
                    events = [events obj.eventDestroy()];
            end
        end
        
        function removeSignalEntry (obj, senderID)
        %removeSignalEntry Removes the entry of the signal  
        
            % Find the entry for the signal using the input SENDERID in the
            % signal power set and delete the entry.
            %
            % Signal power set contains node-ID and signal
            % power of all the signals. The first entry is
            % always considered as the signal of interest
            %   [1, SignalOfInterest_Power]
            %   [2, Interference1_Power]
            %   [3, Interference2_Power]
            %   [..., ...]
            % To delete an entry from this set, assign zeros to the
            % entry and sort this set using the second column
            % (power), so that the deleted entry gets dragged down
            % to the bottom of the valid values. Since the first
            % entry is always the signal of interest, do not
            % consider the first entry for sorting unless the
            % deleted entry is the signal of interest.
            for i = 1:obj.NumSignals
                if (obj.SignalPowerSet(i, 1) == senderID)
                    obj.SignalPowerSet(i, 1) = 0;
                    obj.SignalPowerSet(i, 2) = 0;

                    if i == 1
                        obj.SignalPowerSet = sortrows(obj.SignalPowerSet, 2, 'descend');
                    else
                        obj.SignalPowerSet(2:end, :) = sortrows(obj.SignalPowerSet(2:end, :), 2, 'descend');
                    end
                    obj.NumSignals = obj.NumSignals - 1;
                    break;
                end
            end
        end

        function status = isFrameIgnorable(obj, sigPower)
            %isFrameIgnorable Return true if a frame is decoded as inter-BSS
            %frame and the signal power is less than OBSS PD threshold
            %otherwise return false.

            coder.extrinsic('edcaUpdateStats');

            % Initialize the flag
            status = false;
            
            if obj.RxFrameFormat == edcaFrameFormats.HE_SU || obj.RxFrameFormat == edcaFrameFormats.HE_EXT_SU  
                if obj.BSSColor ~= obj.RxBSSColor % Frame is inter-BSS 
                    edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyNumInterFrames, 1);
                    if sigPower < obj.OBSSPDThreshold
                        status = true;
                        edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyNumInterFrameDrops, 1);
                    else
                        edcaUpdateStats(obj.RecvNodeID, edcaStats.EnergyDetectionGreaterThanOBSSPD, 1);
                    end
                else % Frame is intra-BSS
                    edcaUpdateStats(obj.RecvNodeID, edcaStats.PhyNumIntraFrames, 1);
                end
            end
        end
        
        function [entity, events] = frameGenerate(obj, ~, entity, ~)
            %frameGenerate Create an entity for storing the frame data and
            %forward it to the output port
            
            events = obj.initEventArray();
            entity.data.data(1:end, 1) =  obj.RxData;
            % Fill the decoded data (in bits) and length (in bytes) in
            % frame entity
            entity.data.psduLength(obj.UserIndexSU) = obj.RxDataLength;
            events = [events obj.eventForward('output', ...
                obj.PSDUOutputPortIndex, 0)];
        end
        
        function [entity,events] = phyModeEntry(obj, ~ ,entity, ~)
            %phyModeEntry Set the PHY Rx antenna mode (on/off)

            obj.RxOn = entity.data.phyRxOn;
            
            % Set spatial reuse parameters
            obj.EnableSROperation = entity.data.enableSROperation;
            if obj.EnableSROperation
                obj.BSSColor = entity.data.bssColor;
                obj.OBSSPDThreshold = entity.data.obssPDThreshold;
            end
            
            events = obj.eventDestroy();
        end
        
        function SINR = channelAbstraction(obj, cfg, field, txIdx, rxIdx, signalStrength)
            %channelAbstraction Calculate the SINR per subcarrier and
            %spatial stream given the signal of interest and interferers
            
            global channelMatrix;

            N0 = obj.ThermalNoise;
            Ptxrx_soi = signalStrength;
            
            % Get channel matrix for signal of interest
            scInd = channelSubcarrierIndices(cfg, field);
            chanIdx = channelIndex(txIdx, rxIdx, obj.NumberOfNodes);
            Htxrx_soi = channelMatrix(scInd,:,:,chanIdx);
            
            % Generate precoding matrix
            W = getPrecodingMatrix(cfg,field);
            W = W*sqrt(size(W,1)); % Normalize precoding to unit power per subcarrier
            Wtx_soi = W;
            
            % Number of interfering signals (i.e. exclude the signal of interest)
            numInterferers = obj.NumSignals - 1;

            % This indicates interference is present. Since first one
            % is signal of interest
            if numInterferers > 0
                Htxrx_int = cell(numInterferers, 1);
                Wtx_int = cell(numInterferers, 1);
                Ptxrx_int = zeros(numInterferers, 1); 
                for i = 1:numInterferers
                    txIntIdx = obj.SignalPowerSet(i+1,1); % Skip the first value as it corresponds to signal of interest
                    chanIdx = channelIndex(txIntIdx, rxIdx, obj.NumberOfNodes);
                    Htxrx_int{i} = channelMatrix(scInd,:,:,chanIdx);
                    Wtx_int{i} = W; % Assume same precoding used at all transmitters
                    Ptxrx_int(i) = obj.SignalPowerSet(i+1,2); % Skip the first value as it corresponds to signal of interest
                end
                SINR = calculateSINR(Htxrx_soi, Ptxrx_soi, Wtx_soi, N0, Htxrx_int, Ptxrx_int, Wtx_int);
            else
                % No interference
                SINR = calculateSINR(Htxrx_soi, Ptxrx_soi, Wtx_soi,N0); 
            end
        end
    end
    
    methods(Static, Access=protected)
        % Customize the dialog header
        function header = getHeaderImpl
            header = matlab.system.display.Header(...
                'phyRxAbstracted', ...
                'Title', 'Abstracted PHY Receiver');
        end
        
        % Customize the property attributes
        function group = getPropertyGroupsImpl
            p1 = matlab.system.display.internal.Property('RecvNodeID', ...
                'IsGraphical', false); % Hide from dialog
            p2 = matlab.system.display.internal.Property('NumberOfNodes', ...
                'IsGraphical', false); % Hide from dialog
            
            group = matlab.system.display.Section(...
                'Title', 'Parameters', ...
                'PropertyList', {p1, p2, 'EDThreshold', 'RxGain', ...
                'RxNoiseFigure', 'PHYAbstraction'});
        end
    end
    
    methods(Access = protected)
        
        function setupImpl(obj)
            % Perform one-time calculations, such as computing constants
            obj.RxNonHTConfig = wlanNonHTConfig;
            obj.RxHTConfig = wlanHTConfig;
            obj.RxVHTConfig = wlanVHTConfig(...
                'ChannelBandwidth', 'CBW20');
            obj.RxHESUConfig = wlanHESUConfig;
            channelWidth = 20;
            % Calculate thermal noise. For now thermal noise is fixed since
            % 20MHz channel is fixed and RxNoisefigure value is
            % non-tunable. Thermal noise at 290K in J/s = W
            BOLTZMANN = 1.3803e-23;
            % Temperature in Kelvin for calculating Thermal noise
            tempInKelvin = 290;
            % Nt is the power of thermal noise in W at 290K
            Nt = BOLTZMANN * tempInKelvin * double(channelWidth) * 1000000;
            obj.ThermalNoise = (10^(obj.RxNoiseFigure/10)) * Nt;
            obj.PERAbstraction = tgaxLinkPerformanceModel();
        end
        
        function entityTypes = getEntityTypesImpl(obj)
            % Return entity type structures with fields specifying
            % properties such as name, size, data type, and complexity
            entityTypes = [obj.entityType('waveform', 'AbstractWaveform'), ...
                obj.entityType('phyMode', 'PhyMode'), ...
                obj.entityType('frame', 'Frame'), ...
                obj.entityType('fromPhy', 'PhyInd')];
        end
        
        function [inTypes, outTypes] = getEntityPortsImpl(~)
            % Specify entity input and output ports. Return entity types at
            % a port as strings in a cell array. Use empty string to
            % indicate a data port.
            inTypes = {'phyMode', 'waveform'};
            outTypes = {'fromPhy', 'frame'} ;
        end
        
        function [st, I, O] = getEntityStorageImpl(obj)
            % Return entity storage specification and connectivity
            % information from input ports and output ports to storage
            
            % Queue for holding waveforms from channel
            PPDUQueueSize = inf;
            % Queue for holding triggers from MAC
            PhyModeSelectQueueSize = inf;
            % Queue for holding frames to MAC
            PSDUQueueSize = inf;
            % Queue for holding indications to MAC
            PhyIndicationsQueueSize = inf;
            
            st = [obj.queueFIFO('waveform', PPDUQueueSize), ...
                obj.queueFIFO('phyMode', PhyModeSelectQueueSize), ...
                obj.queueFIFO('frame', PSDUQueueSize), ...
                obj.queueFIFO('fromPhy', PhyIndicationsQueueSize)];
            I = [obj.PhyModeSelectQueueID obj.PPDUQueueID];
            O = [obj.PhyIndicationsQueueID obj.PSDUQueueID];
        end
        
        function num = getNumInputsImpl(~)
            % One port for waveforms from channel
            % Another port for triggers from MAC
            num = 2;
        end
        
        function num = getNumOutputsImpl(~)
            % One port for frame to MAC
            % Another port for indications to MAC
            num = 2;
        end
        
        function name = getInputNamesImpl(~)
            % Return input port names for System block
            name = ["PHYMode", "Waveform"];
        end
        
        function name = getOutputNamesImpl(~)
            % Return output port names for System block
            name = ["RxIndications", "FrameToMAC"];
        end
        
        function icon = getIconImpl(~)
            % Return icon for the DES block. Passing empty string for no
            % title/icon.
            icon = sprintf('');
        end
    end
end
