classdef phyTxAbstracted < matlab.DiscreteEventSystem  & matlab.system.mixin.Propagates & ...
                                          matlab.system.mixin.CustomIcon
%phyTxAbstracted Models PHY operations related to packet transmission
%
%   This is an example helper class.
%
%   This block has 2 input ports:
%   1. MACToPhyReq  - Requests from MAC layer
%       * Bus object of type "PhyInd" available in the
%         wlanAbstractedPHYDataTypes.mat file
%   2. FrameToPHY   - MAC frame
%       * Bus object of type "Frame" available in the
%         wlanAbstractedPHYDataTypes.mat file
%
%   This block has 2 output ports:
%   1. PhyTxConfirms - Confirmations to the MAC layer
%       * Bus object of type "PhyInd" available in the
%         wlanAbstractedPHYDataTypes.mat file
%   2. Waveform      - Abstracted waveform to be transmitted
%       * Bus object of type "AbstractWaveform" available in the
%         wlanAbstractedPHYDataTypes.mat file
%
%   This block performs the following operations:
%      - Handling requests from MAC layer
%      - Creating an abstracted waveform (PPDU)
%      - Handling signal strength (Tx power)

%   Copyright 2019 The MathWorks, Inc.

    % Public, tunable properties
    properties
        % Node ID
        SrcNodeID (1, 1) {mustBeInteger, mustBeGreaterThan(SrcNodeID, 0)} = 1;
    
        % Node Position
        SrcPos (1, 3) {mustBeNumeric} = [0 0 0];
    
        % Tx Power (dBm)
        TxPower (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(TxPower, 0),...
            mustBeLessThanOrEqual(TxPower, 30)} = 15;
        
        % Transmission Gain (dB)
        TxGain (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(TxGain, 0),...
            mustBeLessThanOrEqual(TxGain, 20)} = 1.0;
    end
    
    properties (Constant, Hidden)
        % Queue for holding frames from MAC
        PSDUQueueID = 1;
        
        % Queue for holding requests from MAC
        MACRequestsQueueID = 2;
        
        % Queue for holding waveforms to channel
        WaveformQueueID = 3;
        
        % Output port for sending indications to MAC
        PhyIndicationsOutputPortIndex = 1;
        
        % Output port for sending waveform to channel
        PPDUOutputPortIndex = 2;
    end
    
    % Information from MAC
    properties(Access = private)
        % PHY Tx buffer to store the input PSDU. Here 82573 is the maximum
        % allowed PSDU length in bytes (i.e. 660584 bits) for HE-SU format
        % with MCS 11 in 20MHz bandwidth
        TxData = uint8(zeros(93600, 9));
        
        % Byte boundary of subframes
        FrameIndex = zeros(1,64);
        
        % Subframe lengths. Limiting to max 64 subframes
        SubFrameLength = zeros(1, 64);
        
        % Number of subframes in current frame
        SubFrameCount = 0;
        
        % Structure for storing Tx Vector information
        Vector = Simulink.Bus.createMATLABStruct('PhyInd');
        
        % 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
    
    % Configure based on values from MAC
    properties(Access = private)
        % Waveform generator configuration objects (Tx)
        % Non-HT configuration object
        TxNonHTConfig;
        
        % HT configuration object
        TxHTConfig;
        
        % VHT configuration object
        TxVHTConfig;
        
        % HE-SU configuration object
        TxHESUConfig;
    end
    
    % Calculate based on values from MAC
    properties(Access = private)
        % Structure corresponding to 'Durations' bus type
        Durations = Simulink.Bus.createMATLABStruct('Durations');
        
        % Structure corresponding to 'MetaData' bus type
        MetaData = Simulink.Bus.createMATLABStruct('MetaData');
    end
    
    methods
        function [entity, events] = frameEntry(obj, ~, entity, ~)
            %frameEntry Store the frame context and generate an event for
            %waveform generation and transmission
            
            % Store the frame context
            obj.TxData = entity.data.data;                  % Frame
            obj.SubFrameCount = entity.data.NumSubframes(obj.UserIndexSU);   % Num of subframes
            obj.FrameIndex(1:obj.SubFrameCount) = ...       % Subframe start indices
                entity.data.SubframeBoundaries(1:obj.SubFrameCount, 1, obj.UserIndexSU);
            obj.SubFrameLength(1:obj.SubFrameCount) = ...   % Subframe lengths
                entity.data.SubframeBoundaries(1:obj.SubFrameCount, 2, obj.UserIndexSU);
            
            % Schedule an entity generate event to waveform queue with no
            % delay (simulate waveform generation and transmission)
            events = [obj.eventGenerate(obj.WaveformQueueID, 'generate', 0, 100) obj.eventDestroy()];
        end
        
        function [entity, events] = waveformGenerate(obj, ~, entity, ~)
            %waveformGenerate Simulate waveform generation and transmission
            % Calculates/Stores the metadata that needs to be sent along
            % with the MAC packet, i.e. Durations, MetaData, Subframe
            % count, Subframe start indices, and Subframe lengths. Puts all
            % the information into the entity and forwards it to the output
            % port.
            
            entity.data.waveformPPDU = obj.TxData(:, obj.UserIndexSU);
            % Update signal power.
            sigPower = obj.TxPower + obj.TxGain;
            if (obj.Vector.frameFormat == edcaFrameFormats.NonHT)
                % Preamble duration for Non-HT format (in microseconds)
                obj.Durations.preambleDuration = 16;
                % Header duration for Non-HT format (in microseconds)
                obj.Durations.headerDuration = 4;
                % Payload duration for Non-HT configuration (in microseconds)
                s = validateConfig(obj.TxNonHTConfig);
                obj.Durations.payloadDuration = s.TxTime - ...
                    (obj.Durations.preambleDuration + obj.Durations.headerDuration);
            
            elseif (obj.Vector.frameFormat == edcaFrameFormats.HTMixed)
                % Preamble duration for HT format (in microseconds)
                obj.Durations.preambleDuration = 16;
                % Retain the actual PSDU length
                PSDULength = obj.TxHTConfig.PSDULength;
                % Get TxTime for PSDU length zero (validateConfig returns
                % a structure, with one of the field as TxTime). This is
                % equivalent to (preamble + header) duration
                obj.TxHTConfig.PSDULength = 0;
                s = validateConfig(obj.TxHTConfig);
                % Subtract preamble duration to get header duration (in microseconds)
                obj.Durations.headerDuration = s.TxTime - obj.Durations.preambleDuration;
                % Reassign the PSDU length, so that configuration object
                % remains same again
                obj.TxHTConfig.PSDULength = PSDULength;
                % Payload duration for HT configuration (in microseconds)
                s = validateConfig(obj.TxHTConfig);
                obj.Durations.payloadDuration = s.TxTime - ...
                    (obj.Durations.preambleDuration + obj.Durations.headerDuration);
            
            elseif (obj.Vector.frameFormat == edcaFrameFormats.VHT)
                % Preamble duration for VHT format (in microseconds)
                obj.Durations.preambleDuration = 16;
                % Retain the actual APEP length
                APEPLength = obj.TxVHTConfig.APEPLength;
                % Get TxTime for APEP length zero (validateConfig returns
                % a structure, with one of the field as TxTime). This is
                % equivalent to (preamble + header) duration
                obj.TxVHTConfig.APEPLength = 0;
                s = validateConfig(obj.TxVHTConfig);
                % Subtract preamble duration to get header duration (in microseconds)
                obj.Durations.headerDuration = s.TxTime - obj.Durations.preambleDuration;
                % Reassign the PSDU length, so that configuration object
                % remains same again
                obj.TxVHTConfig.APEPLength = APEPLength;
                % Payload duration for VHT configuration (in microseconds)
                s = validateConfig(obj.TxVHTConfig);
                obj.Durations.payloadDuration = s.TxTime - ...
                    (obj.Durations.preambleDuration + obj.Durations.headerDuration);
            
            elseif ((obj.Vector.frameFormat == edcaFrameFormats.HE_SU)|| ...
                    (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                % Preamble duration for HE format (in microseconds)
                obj.Durations.preambleDuration = 16;
                % Retain the actual APEP length
                APEPLength = obj.TxHESUConfig.APEPLength;
                % Get TxTime for APEP length zero (validateConfig returns
                % a structure, with one of the field as TxTime). This is
                % equivalent to (preamble + header) duration
                obj.TxHESUConfig.APEPLength = 0;
                s = validateConfig(obj.TxHESUConfig);
                ndpPEOverhead = 4; % PE overhead for NDP in microseconds
                % Subtract preamble duration and PE overhead (4 us) to get header duration (in microseconds)
                obj.Durations.headerDuration = s.TxTime - obj.Durations.preambleDuration - ndpPEOverhead;
                % Reassign the PSDU length, so that configuration object
                % remains same again
                obj.TxHESUConfig.APEPLength = APEPLength;
                % Payload duration for HE configuration (in microseconds)
                s = validateConfig(obj.TxHESUConfig);
                obj.Durations.payloadDuration = s.TxTime - ...
                    (obj.Durations.preambleDuration + obj.Durations.headerDuration);
            end
            
            obj.MetaData.vector = obj.Vector;
            obj.MetaData.senderPosition = obj.SrcPos;
            obj.MetaData.sigPower = sigPower;
            obj.MetaData.srcId = obj.SrcNodeID;
            % For now supporting only 20 MHz channel width
            obj.MetaData.channelWidth = 20;
            
            % Basic data rate and code rates for preamble
            preambleAndHeaderDataRate = 6;
            preambleAndHeaderCodeRate = 0.5;
            payloadDataRate = 0;
            payloadCodeRate = 0;
            numDataBitsPerSymbol = 0;
            isLDPCCoded = false;
            if ((obj.Vector.frameFormat == edcaFrameFormats.HE_SU)|| ...
                    (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                symbolDuration = 16; % Symbol duration in micro seconds
            else
                symbolDuration = 4; % Symbol duration in micro seconds
            end
            
            if (obj.Vector.frameFormat == edcaFrameFormats.NonHT)
                NonHTMCSTable = wlan.internal.getRateTable(obj.TxNonHTConfig);
                payloadDataRate = NonHTMCSTable.NDBPS/symbolDuration; %Value in Mbps
                payloadCodeRate = NonHTMCSTable.Rate;
                numDataBitsPerSymbol = NonHTMCSTable.NDBPS;
            elseif (obj.Vector.frameFormat == edcaFrameFormats.HTMixed)
                HTMCSTable = wlan.internal.getRateTable(obj.TxHTConfig);
                payloadDataRate = HTMCSTable.NDBPS/symbolDuration; %Value in Mbps
                payloadCodeRate = HTMCSTable.Rate;
                numDataBitsPerSymbol =  HTMCSTable.NDBPS;
                isLDPCCoded = strcmp(obj.TxHTConfig.ChannelCoding, 'LDPC');
            elseif (obj.Vector.frameFormat == edcaFrameFormats.VHT)
                VHTMCSTable = wlan.internal.getRateTable(obj.TxVHTConfig);
                payloadDataRate = VHTMCSTable.NDBPS(1)/symbolDuration; %Value in Mbps
                payloadCodeRate = VHTMCSTable.Rate;
                numDataBitsPerSymbol =  VHTMCSTable.NDBPS(1);
                isLDPCCoded = strcmp(obj.TxVHTConfig.ChannelCoding, 'LDPC');
            elseif ((obj.Vector.frameFormat == edcaFrameFormats.HE_SU)|| ...
                    (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                [~, userCodingParams] = wlan.internal.heCodingParameters(obj.TxHESUConfig);
                payloadDataRate = userCodingParams.NDBPS(1)/symbolDuration; %Value in Mbps
                payloadCodeRate = userCodingParams.Rate;
                numDataBitsPerSymbol =  userCodingParams.NDBPS(1);
                isLDPCCoded = strcmp(obj.TxHESUConfig.ChannelCoding, 'LDPC');
            end
            
            obj.MetaData.preambleAndHeaderInfo.numOfBits = preambleAndHeaderDataRate * ...
                (1/preambleAndHeaderCodeRate) * (obj.Durations.preambleDuration + obj.Durations.headerDuration);
            
            actualDataSymbols = 0;
            paddingDelimiterSymbols = 0;
            totalAmpduSymbols = 0;
            serviceBits = 16;
            tailBits = 6*(isLDPCCoded == 0); % Tail bits are only present for BCC coding
            
            for count = 1:obj.SubFrameCount
                actualPktSize =  obj.SubFrameLength(count);
                if (obj.Vector.frameFormat == edcaFrameFormats.NonHT) || ...
                        ((obj.Vector.frameFormat == edcaFrameFormats.HTMixed) && ~obj.Vector.aggregatedMPDU)
                    % Not an A-MPDU. The number of OFDM symbols in the data
                    % field when BCC encoding is used is given in equation
                    % 19-32 of the IEEE 802.11-2016 standard.
                    actualDataSymbols =  ...
                        ceil ((serviceBits + actualPktSize * 8.0 + tailBits)/numDataBitsPerSymbol);
                    paddingDelimiterSymbols = 0;
                elseif ((obj.Vector.frameFormat == edcaFrameFormats.HTMixed)|| ...
                        (obj.Vector.frameFormat == edcaFrameFormats.VHT)|| ...
                        (obj.Vector.frameFormat == edcaFrameFormats.HE_SU)||...
                        (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                    if(count == 1)
                        % First packet in A-MPDU
                        actualDataSymbols = ...
                            (serviceBits + actualPktSize * 8.0 + tailBits)/(numDataBitsPerSymbol);
                        paddingAndDelimitersSize = ...
                            (obj.FrameIndex(count + 1)) - (...
                            obj.FrameIndex(count) + actualPktSize);
                        paddingDelimiterSymbols = ...
                            (paddingAndDelimitersSize * 8)/(numDataBitsPerSymbol);
                        totalAmpduSymbols = totalAmpduSymbols + actualDataSymbols + paddingDelimiterSymbols;
                    elseif (count < obj.SubFrameCount)
                        % Consecutive packets in an A-MPDU
                        actualDataSymbols = ...
                            (actualPktSize * 8.0)/(numDataBitsPerSymbol);
                        paddingAndDelimitersSize = ...
                            (obj.FrameIndex(count + 1))  - (...
                            obj.FrameIndex(count) + actualPktSize);
                        paddingDelimiterSymbols = ...
                            (paddingAndDelimitersSize * 8)/(numDataBitsPerSymbol);
                        totalAmpduSymbols = totalAmpduSymbols + actualDataSymbols + paddingDelimiterSymbols;
                    elseif (count == obj.SubFrameCount)
                        % Last packet in an A-MPDU
                        payloadTxTime = obj.Durations.payloadDuration;
                        totalSymbols = payloadTxTime/symbolDuration;
                        actualDataSymbols = (actualPktSize * 8.0)/(numDataBitsPerSymbol);
                        totalAmpduSymbols = totalAmpduSymbols + actualDataSymbols;
                        paddingDelimiterSymbols = totalSymbols - totalAmpduSymbols;
                    end
                end
                % Symbol duration is in microseconds
                payloadDuration = actualDataSymbols * symbolDuration;
                % Overhead duration is duration apart from payload for the
                % current frame which includes padding and delimiters
                % duration
                overheadDuration = paddingDelimiterSymbols * symbolDuration;
                obj.MetaData.payloadInfo(count).duration = payloadDuration;
                obj.MetaData.payloadInfo(count).overheadDuration = overheadDuration;
                obj.MetaData.payloadInfo(count).numOfBits = payloadDataRate(1) * ...
                    (1/payloadCodeRate(1)) * payloadDuration;
            end
            entity.data.metaData = obj.MetaData;
            entity.data.durations = obj.Durations;
            entity.data.subFrameCount = obj.SubFrameCount;
            entity.data.subFrameLengths(1:obj.SubFrameCount) = obj.SubFrameLength(1:obj.SubFrameCount);
            entity.data.subFrameIndexes(1:obj.SubFrameCount) = obj.FrameIndex(1:obj.SubFrameCount);
            events = obj.eventForward('output', obj.PPDUOutputPortIndex, 0);
        end
        
        function [entity, events] = toPhyEntry(obj, ~, entity, ~)
            %toPhyEntry Handle the requests from MAC to PHY and schedule
            %corresponding PHY indication events
                
            coder.extrinsic('edcaUpdateStats')
            
            events = obj.initEventArray();

            % Checks the request type
            if entity.data.msgType == double(PhyPrimitives.TxStartRequest)
                obj.Vector = entity.data;
                
                % Configure the PHY object using Tx-vector information
                if obj.Vector.frameFormat == edcaFrameFormats.NonHT
                    obj.TxNonHTConfig.PSDULength = obj.Vector.psduLength(obj.UserIndexSU);
                    obj.TxNonHTConfig.MCS = obj.Vector.mcsIndex(obj.UserIndexSU);
                    
                elseif obj.Vector.frameFormat == edcaFrameFormats.HTMixed
                    obj.TxHTConfig.PSDULength = obj.Vector.psduLength(obj.UserIndexSU);
                    obj.TxHTConfig.MCS = obj.Vector.mcsIndex(obj.UserIndexSU);
                    obj.TxHTConfig.AggregatedMPDU = obj.Vector.aggregatedMPDU;
                    obj.TxHTConfig.NumSpaceTimeStreams = obj.Vector.numSTS;
                    obj.TxHTConfig.NumTransmitAntennas = obj.Vector.numSTS;
                    
                elseif obj.Vector.frameFormat == edcaFrameFormats.VHT
                    obj.TxVHTConfig.APEPLength = obj.Vector.psduLength(obj.UserIndexSU);
                    obj.TxVHTConfig.MCS = obj.Vector.mcsIndex(obj.UserIndexSU);
                    obj.TxVHTConfig.NumSpaceTimeStreams = obj.Vector.numSTS;
                    obj.TxVHTConfig.NumTransmitAntennas = obj.Vector.numSTS;
                    
                elseif ((obj.Vector.frameFormat == edcaFrameFormats.HE_SU) || ...
                        (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU))
                    obj.TxHESUConfig.APEPLength = obj.Vector.psduLength(obj.UserIndexSU);
                    obj.TxHESUConfig.MCS = obj.Vector.mcsIndex(obj.UserIndexSU);
                    obj.TxHESUConfig.NumSpaceTimeStreams = obj.Vector.numSTS;
                    obj.TxHESUConfig.NumTransmitAntennas = obj.Vector.numSTS;
                    obj.TxHESUConfig.ExtendedRange = (obj.Vector.frameFormat == edcaFrameFormats.HE_EXT_SU);
                else
                    error('Invalid TX frame format');
                end
                % Send 'tx start confirm' indication to MAC
                entity.data.msgType = double(PhyPrimitives.TxStartConfirm);
                
            elseif entity.data.msgType == double(PhyPrimitives.TxEndRequest)
                % Update stats and send the 'tx end confirm' indication to MAC
                edcaUpdateStats(obj.SrcNodeID, edcaStats.PhyTx, 1);
                entity.data.msgType = double(PhyPrimitives.TxEndConfirm);
                
            else
                entity.data.msgType = double(PhyPrimitives.UnknownIndication);
            end
            
            % Send the confirm indication to MAC
            events = [events obj.eventForward('output', obj.PhyIndicationsOutputPortIndex, 0)];
        end
    end
    
    methods(Static, Access=protected)
        % Customize the dialog header
        function header = getHeaderImpl
            header = matlab.system.display.Header(...
                'phyTxAbstracted', ...
                'Title', 'Abstracted PHY Transmitter');
        end
        
        % Customize the property attributes
        function group = getPropertyGroupsImpl
            p1 = matlab.system.display.internal.Property('SrcNodeID', ...
                'IsGraphical', false); % Hide from dialog
            p2 = matlab.system.display.internal.Property('SrcPos', ...
                'IsGraphical', false); % Hide from dialog
            
            group = matlab.system.display.Section(...
                'Title', 'Parameters', ...
                'PropertyList', {p1, p2, 'TxPower', 'TxGain'});
        end
    end
    
    methods (Access=protected)
        function setupImpl(obj)
            % Perform one-time calculations, such as computing constants
            obj.TxNonHTConfig = wlanNonHTConfig;
            obj.TxHTConfig = wlanHTConfig;
            % For VHT config default bandwidth is 80MHz. Update it
            % to 20MHz
            obj.TxVHTConfig = wlanVHTConfig( ...
                'ChannelBandwidth', 'CBW20');
            % HE-SU configuration object
            obj.TxHESUConfig = wlanHESUConfig;
        end
        
        function entityTypes = getEntityTypesImpl(obj)
            % Specifies the entity types are used inside it
            entityTypes = [obj.entityType('frame', 'Frame'), ...
                obj.entityType('toPhy', 'PhyInd'), ...
                obj.entityType('waveform', 'AbstractWaveform')];
        end
        
        function [inputTypes,outputTypes] = getEntityPortsImpl(~)
            % Specifies input and output port data types and complexity
            inputTypes = {'toPhy', 'frame'};
            outputTypes = {'toPhy', 'waveform'};
        end
        
        function [storageSpecs, I, O] = getEntityStorageImpl(obj)
            % Return entity storage specification and connectivity
            % information from input ports and output ports to storage
            
            % Size of queue holding frames from MAC
            PSDUQueueSize = 1;
            % Size of queue holding requests from MAC
            MACRequestsQueueSize = 1;
            % Size of queue holding waveforms to channel
            WaveformQueueSize = 1;
            
            % Creates the internal storage queues and associate those input
            % and output ports
            storageSpecs = [obj.queueFIFO('frame', PSDUQueueSize),...
                obj.queueFIFO('toPhy', MACRequestsQueueSize),...
                obj.queueFIFO('waveform', WaveformQueueSize)];
            I = [obj.MACRequestsQueueID obj.PSDUQueueID];
            O = [obj.MACRequestsQueueID obj.WaveformQueueID];
        end
        
        function num = getNumInputsImpl(~)
            % PHY Tx block contains 2 input ports. One is for MAC requests
            % to PHY and the other is for input PSDU from MAC
            num = 2;
        end
        
        function num = getNumOutputsImpl(~)
            % PHY Tx block contains 2 two ports. One is to give indications
            % to MAC and the other is to transmit waveform in the channel
            num = 2;
        end
        
        function name = getInputNamesImpl(~)
            % Return input port names for System block
            name = ["MACToPHYReq", "FrameToPHY"];
        end
        
        function name = getOutputNamesImpl(~)
            % Return output port names for System block
            name = ["PHYTxConfirms", "Waveform"];
        end
        
        function [out1, out2] = getOutputSizeImpl(~)
            % Return size for each output port
            out1 = [1,1];
            out2 = [1,1];
        end
        
        function [out1, out2] = getOutputDataTypeImpl(~)
            % Return data type for each output port
            out1 = 'toPhy';
            out2 = 'waveform';
        end
        
        function [out1, out2] = isOutputComplexImpl(~)
            % Return true for each output port with complex data
            out1 = false;
            out2 = false;
        end
        
        function icon = getIconImpl(~)
            % Return icon for the DES block. Passing empty string for no
            % title/icon.
            icon = sprintf('');
        end
    end
end
