classdef PhyTx80211aDES < matlab.DiscreteEventSystem
% PhyTx80211aDES Generates and transmits 802.11a waveform
% 
%   This object performs the following operations on the frame to be
%   transmitted:
%       1. Configure the PHY based on the received Tx vector
%       2. Generation of Waveform
%       3. Append and send waveform to the shared channel
%       4. Confirms MAC requests
    
%   Copyright 2019-2020 The MathWorks, Inc.
    
    % Public, tunable properties
    properties
        % Node Id
        NodeID (1, 1) {mustBeInteger, mustBeGreaterThan(NodeID, 0)} = 1;
        % Tx Power (dBm)
        TxPower (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(TxPower, 0),...
            mustBeLessThanOrEqual(TxPower, 30)} = 15;
        % Tx Gain (dB)
        TxGain (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(TxGain, 0),...
            mustBeLessThanOrEqual(TxGain, 20)} = 1.0;
        % Node Position
        NodePosition (1, 3) {mustBeNumeric} = [0 0 0];
    end
    
    % Pre-computed constants
    properties(Access = private)
        % Data to be transmitted
        TxData = int8(zeros(2332*8, 1)); % Here 2332 is the non-QoS MAC data payload length in bytes
        % Waveform generator configuration object (Tx)
        TxConfig = wlanNonHTConfig;
        % Operating frequency of Transmitter
        Frequency = 5.18e9;
    end
    % Public, non-tunable properties
    properties(Constant, Hidden)
        % Constants
        NUMBER_OF_INPUT_PORTS = 2;
        NUMBER_OF_OUTPUT_PORTS = 2;
        PSDU_QUEUE_ID = 1;
        MAC_TX_REQUESTS_QUEUE_ID = 2;
        PPDU_QUEUE_ID = 3;
        PHY_TX_INDICATIONS_QUEUE_ID = 4;
        PSDU_QUEUE_SIZE = 1;
        MAC_TX_REQUESTS_QUEUE_SIZE = 1;
        PPDU_QUEUE_SIZE = 1;
        PHY_TX_INDICATIONS_QUEUE_SIZE = 1;
        PPDU_OUTPUT_PORT_INDEX = 1;
        PHY_TX_INDICATIONS_OUTPUT_PORT_INDEX = 2;
    end
    
    
    % Discrete-event algorithms
    methods
        function MCS = convertDataRateToMCS(~, dataRate)
            % This function derives MCS value from channel bandwidth
            % and data rate for wlanNonHTConfig object.
            
            MCS = 0;
            switch(dataRate)
                case 6
                    MCS = 0;
                case 9
                    MCS = 1;
                case 12
                    MCS = 2;
                case 18
                    MCS = 3;
                case 24
                    MCS = 4;
                case 36
                    MCS = 5;
                case 48
                    MCS = 6;
                case 54
                    MCS = 7;
            end
        end
        
        function outSamples = applyTxPowerLevelAndGain(obj, IQSamples, gain)
            % Apply gain to IQ Samples.
            
            % Generally normalized IQ samples power is 30 dBm. So we used
            % this in the equation to scale it to desired power level +
            % gain.
            scale = 10.^((-30+obj.TxPower + gain)/20);
            outSamples = IQSamples * scale;
        end
        
        function [entity,events] = frameEntry(obj, ~, entity, ~)
            % This function stores Tx data whenever a frame comes from MAC.
            
            obj.TxData = entity.data.data;
            events = [obj.eventGenerate(obj.PPDU_QUEUE_ID, 'generate', 0, 100) obj.eventDestroy()];
        end
        
        function [entity, events] = waveformGenerate(obj, ~, entity, ~)
            % This function will generate the wlan waveform for the given data (in bits).
            
            sig = wlanWaveformGenerator(obj.TxData, obj.TxConfig);
            % Scale the signal to desired power level and apply Tx gain to signal.
            sig = obj.applyTxPowerLevelAndGain(sig, obj.TxGain);
            entity.data.sigPower = obj.TxPower + obj.TxGain;
            entity.data.waveform(:) = 0 + 1i*0;
            numSamples = size(sig,1);
            entity.data.waveform(1:numSamples) = sig;
            entity.data.numSamples = numSamples;
            entity.data.srcId = obj.NodeID;
            fs = wlanSampleRate(obj.TxConfig);
            entity.data.duration = numSamples/fs;
            % Configure the channel frequency.
            entity.data.chanFreq = obj.Frequency;
            % Add source position information.
            entity.data.srcPos = obj.NodePosition;
            % Add channel bandwidth information.
            entity.data.channelWidth = fs;
            % Forward the packet to all other nodes which are hearing on this channel.
            events = obj.eventForward('output', obj.PHY_TX_INDICATIONS_OUTPUT_PORT_INDEX, 0);
        end
        
        function [entity, events] = phyIndGenerate(obj, ~, entity, tag)
            % This function sends Phy Tx indications to MAC.
            
            coder.extrinsic('dcfUpdateStats');
            switch(tag)
                case 'TXSTARTCONFIRM'
                    entity.data.msgType = double(PhyPrimitives.TxStartConfirm);
                case 'TXENDCONFIRM'
                    dcfUpdateStats(obj.NodeID, dcfStats.PhyTx, 1);
                    entity.data.msgType = double(PhyPrimitives.TxEndConfirm);
                otherwise
                    entity.data.msgType = double(PhyPrimitives.UnknownIndication);
            end
            events = obj.eventForward('output', obj.PPDU_OUTPUT_PORT_INDEX, 0);
        end
        
        function [entity, events] = phyIndEntry(obj, ~, entity, ~)
            % This function handles MAC requests.
            
            events = obj.initEventArray();
            % Check the MAC request type.
            if entity.data.msgType == PhyPrimitives.TxStartRequest
                % If request type is TXSTART_REQUEST, send the TXSTARTCONFIRM indication to MAC.
                obj.TxConfig.PSDULength = entity.data.psduLength;
                obj.TxConfig.MCS = obj.convertDataRateToMCS(entity.data.dataRate);
                events = [events obj.eventGenerate(obj.MAC_TX_REQUESTS_QUEUE_ID, 'TXSTARTCONFIRM', 0, 100)];
            elseif entity.data.msgType == PhyPrimitives.TxEndRequest
                % If request type is TXEND_REQUEST, send the TXENDCONFIRM indication to MAC.
                events = [events obj.eventGenerate(obj.MAC_TX_REQUESTS_QUEUE_ID, 'TXENDCONFIRM', 0, 100)];
            end
            events = [events obj.eventDestroy()];
        end
    end
    
    methods(Access = protected)
        function setupImpl(obj)
            % Perform one-time calculations, such as computing constants.
            
            obj.TxConfig = wlanNonHTConfig('ChannelBandwidth', 'CBW20');
        end
        
        function entityTypes = getEntityTypesImpl(obj)
            % This function specifies the entity types are used inside it.
            
            entityTypes = [obj.entityType('frame', 'Frame'),...
                obj.entityType('phyInd', 'PhyInd'), obj.entityType('waveform', 'Waveform')];
        end
        
        function [inTypes, outTypes] = getEntityPortsImpl(~)
            % This function specifies input and output port data types and
            % complexity.
            
            inTypes = {'phyInd', 'frame'};
            outTypes = {'phyInd', 'waveform'} ;
        end
        
        function [st, I, O] = getEntityStorageImpl(obj)
            % This function creates the internal storage queues and
            % associate those input and output ports.
            
            st = [obj.queueFIFO('frame', obj.PSDU_QUEUE_SIZE), obj.queueFIFO('phyInd', obj.MAC_TX_REQUESTS_QUEUE_SIZE),...
                obj.queueFIFO('waveform', obj.PPDU_QUEUE_SIZE), obj.queueFIFO('phyInd', obj.PHY_TX_INDICATIONS_QUEUE_SIZE)];
            I = [obj.PHY_TX_INDICATIONS_QUEUE_ID obj.PSDU_QUEUE_ID];
            O = [obj.MAC_TX_REQUESTS_QUEUE_ID obj.PPDU_QUEUE_ID];
        end
        
        function num = getNumInputsImpl(obj)
            % This function specifies the number of input ports.
            
            num = obj.NUMBER_OF_INPUT_PORTS; % This system object has 2 input ports.
        end
        
        function num = getNumOutputsImpl(obj)
            % This function specifies the number of output ports.
            
            num = obj.NUMBER_OF_OUTPUT_PORTS; % This system object has 2 output ports.
        end
        
        function [input1, input2] = getInputNamesImpl(~)
            input1 = 'MACToPhyReq';
            input2 = 'FrameToPhy';
        end
        
        function [output1, output2] = getOutputNamesImpl(~)
            output1 = 'PhyTxConfirms';
            output2 = 'Waveform';
        end
    end
end
