classdef phyTx < matlab.DiscreteEventSystem & matlab.system.mixin.Propagates
%phyTx Generate and transmit 802.11a/n/ac/ax waveforms
%
%   This is an example helper class (Discrete Event System).
%
%   This object performs the following operations on the frame to be
%   transmitted:
%      - Handling requests from MAC layer
%      - Giving indications to MAC layer
%      - Generating waveform (PPDU) using input PSDU
%      - Applying Tx power to the waveform and sends to the shared channel

%   Copyright 2019 The MathWorks, Inc.

%#codegen

% Public, tunable properties
properties
    % Node ID
    TxNodeID (1, 1) {mustBeInteger, mustBeGreaterThan(TxNodeID, 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
    TxNodePosition (1, 3) {mustBeNumeric} = [0 0 0];
end

properties (Constant, Hidden)
    % Constants used in this class

    % Storage queue IDs
    PSDUInputQueueID = 1;
    PhyIndicationsQueueID = 2;
    WaveformQueueID = 3;
    MACRequestsQueueID = 4;

    % Output port indexes
    PhyIndicationOutputPortIndex = 1;
    WaveformOutputPortIndex = 2;

    % Maximum number of users in a Tx MU-PPDU. In 20MHz OFDMA
    % transmission max possible users are 9
    TxMaxUsers = 9;

    % Used to index into multi-user sized elements for single-user
	% operations
    UserIndexSU = 1;
end

% Pre-computed constants
properties(Access = private)
    % PHY Tx buffer to store input PSDUs
    TxBuffer;

    % Length (in bits) of the PSDUs stored in PHY Tx buffer
    TxPSDULength;

    % PHY configuration objects to generate waveform
    % Non-HT configuration object
    TxNonHTConfig;

    % HT configuration object
    TxHTConfig;

    % VHT configuration object
    TxVHTConfig;

    % HE-SU configuration object
    TxHESUConfig;

    % HE-MU configuration object
    TxHEMUConfig;

    % Center frequency of the operating channel
    ChannelCenterFrequency = 5.18e9;

    % Channel Bandwidth
    ChannelBandwidth = 20;

    % Format of the current transmitting frame
    TxFrameFormat;

    % Number of transmit chains
    NumTx = 1;

    % Station IDs in MU PPDU
    TxStationIDs;

    % Number of users in MU PPDU transmission
    NumUsers = 1;

    % Sample rate of the transmitting signal
    SampleRate
end

methods
    function bandwidthDecimal = getBandwidth(~, bandwidth)
        %getBandwidth Returns the input channel bandwidth in numeric format
        switch(bandwidth)
            case 'CBW20'
                % 20 MHz
                bandwidthDecimal = 20;
            otherwise
                error('Only 20MHz bandwidth is supported');
        end
    end

    function power = calculatePowerFromIQSamples(~, iqSamples)
        %calculatePowerFromIQSamples Returns the power of input IQ samples

        % Calculate power in watts
        power = mean(mean(iqSamples .* conj(iqSamples)));
        % Convert power in dBw into dBm i.e., dBm = dBw + 30
        power = (10*log10(power)) + 30;
    end

    function outputSamples = applyTxPowerLevelAndGain(obj, iqSamples, gain)
        %applyTxPowerLevelAndGain Applies Tx power level and gain to IQ
        %samples

        % Apply default Tx power to IQ samples.
        scale = 10.^((-30 + obj.TxPower + gain)/20);
        outputSamples = iqSamples * scale;
    end

    function [entity, events] = frameEntry(obj, ~, entity, ~)
        %frameEntry Stores the PSDU in buffer and schedule a waveform
        %generate event

        % Copy the PSDU to PHY Tx buffer
        obj.TxPSDULength = entity.data.psduLength;
        obj.TxBuffer = int8(entity.data.data);

        % Schedule a waveform generate event
        events = [obj.eventGenerate(obj.WaveformQueueID, 'generate', 0, 100) obj.eventDestroy()];
    end

    function [entity, events] = waveformGenerate(obj, ~, entity, ~)
        %waveformGenerate Generates waveform using PSDU and forwards it to
        %the output port

        % Generate waveform based on frame format
        if (obj.TxFrameFormat == edcaFrameFormats.NonHT)
            % Generate Non-HT waveform
            waveform = wlanWaveformGenerator(obj.TxBuffer(:, obj.UserIndexSU), obj.TxNonHTConfig);
            obj.ChannelBandwidth = obj.getBandwidth(obj.TxNonHTConfig.ChannelBandwidth);
        elseif (obj.TxFrameFormat == edcaFrameFormats.HTMixed)
            % Generate HT waveform
            waveform = wlanWaveformGenerator(obj.TxBuffer(:, obj.UserIndexSU), obj.TxHTConfig);
            obj.ChannelBandwidth = obj.getBandwidth(obj.TxHTConfig.ChannelBandwidth);
        elseif (obj.TxFrameFormat == edcaFrameFormats.VHT)
            % Generate VHT waveform
            waveform = wlanWaveformGenerator(obj.TxBuffer(:, obj.UserIndexSU), obj.TxVHTConfig);
            obj.ChannelBandwidth = obj.getBandwidth(obj.TxVHTConfig.ChannelBandwidth);
        elseif (obj.TxFrameFormat == edcaFrameFormats.HE_SU) || (obj.TxFrameFormat == edcaFrameFormats.HE_EXT_SU)
            % Generate HE-SU waveform
            waveform = wlanWaveformGenerator(obj.TxBuffer(:, obj.UserIndexSU), obj.TxHESUConfig);
            obj.ChannelBandwidth = obj.getBandwidth(obj.TxHESUConfig.ChannelBandwidth);
        else % HE_MU format
            % Create a cell array for PSDUs of all users to generate
            % MU-PPDU
            txPSDUs = cell(obj.NumUsers, 1);
            for i = 1:numel(txPSDUs)
                txPSDUs{i} = obj.TxBuffer(:, i);
            end

            % Generate HE-MU waveform
            waveform = wlanWaveformGenerator(txPSDUs, obj.TxHEMUConfig);
            obj.ChannelBandwidth = obj.getBandwidth(obj.TxHEMUConfig.ChannelBandwidth);
        end

        % Number of samples and streams in waveform
        [numSamples, numStreams] = size(waveform);

        % Update elements of entity with the information of generated
        % waveform
        entity.data.waveform(:) = 0 + 1i*0;
        entity.data.waveform((1:numSamples), 1:numStreams) = waveform;
        entity.data.numSamples = numSamples;
        entity.data.srcId = obj.TxNodeID;
        entity.data.srcPos = obj.TxNodePosition;
        entity.data.numSTS = obj.NumTx;

        % Apply Tx gain to the waveform
        entity.data.waveform = obj.applyTxPowerLevelAndGain(entity.data.waveform, obj.TxGain);
        entity.data.sigPower = obj.TxPower + obj.TxGain;

        % Set transmission time of the waveform
        entity.data.duration = numSamples*(1/obj.SampleRate);

        % Set channel bandwidth
        entity.data.channelWidth = obj.ChannelBandwidth;

        % Set channel frequency
        entity.data.chanFreq = obj.ChannelCenterFrequency;

        % Forward the generated waveform to the output port connected to the
        % channel.
        events = obj.eventForward('output', obj.WaveformOutputPortIndex, 0);
    end

    function [entity, events] = phyIndGenerate(obj, ~, entity, tag)
        %phyIndGenerate Generate and forward indications to the MAC

        coder.extrinsic('edcaUpdateStats');
        switch(tag)
            % Tx start confirm indication
            case 'TXSTARTCONFIRM'
                entity.data.msgType = double(PhyPrimitives.TxStartConfirm);

                % Tx end confirm indication
            case 'TXENDCONFIRM'
                edcaUpdateStats(obj.TxNodeID, edcaStats.PhyTx, 1);
                entity.data.msgType = double(PhyPrimitives.TxEndConfirm);

                % Unknown indication
            otherwise
                entity.data.msgType = double(PhyPrimitives.UnknownIndication);
        end
        % Forward the generated indication to output port connected to MAC
        events = obj.eventForward('output', obj.PhyIndicationOutputPortIndex, 0);
    end

    function [entity, events] = phyIndEntry(obj, ~, entity, ~)
        %phyIndEntry Handles the requests from MAC and schedules
        %corresponding PHY indication events

        % Initialize events array
        events = obj.initEventArray();
        % Send Tx start confirm indication if the request is Tx start request
        if entity.data.msgType == PhyPrimitives.TxStartRequest
            % Set Tx frame format
            obj.TxFrameFormat = entity.data.frameFormat;
            obj.NumTx = entity.data.numSTS;

            % Configure the PHY object using Tx vector information
            if obj.TxFrameFormat == edcaFrameFormats.NonHT
                obj.TxNonHTConfig.PSDULength = entity.data.psduLength(obj.UserIndexSU);
                obj.TxNonHTConfig.MCS = entity.data.mcsIndex(obj.UserIndexSU);

            elseif obj.TxFrameFormat == edcaFrameFormats.HTMixed
                obj.TxHTConfig.PSDULength = entity.data.psduLength(obj.UserIndexSU);
                obj.TxHTConfig.MCS = entity.data.mcsIndex(obj.UserIndexSU);
                obj.TxHTConfig.AggregatedMPDU = entity.data.aggregatedMPDU;
                obj.TxHTConfig.NumTransmitAntennas = entity.data.numSTS;
                obj.TxHTConfig.NumSpaceTimeStreams = entity.data.numSTS;

            elseif obj.TxFrameFormat == edcaFrameFormats.VHT
                obj.TxVHTConfig.APEPLength = entity.data.psduLength(obj.UserIndexSU);
                obj.TxVHTConfig.MCS = entity.data.mcsIndex(obj.UserIndexSU);
                obj.TxVHTConfig.NumTransmitAntennas = entity.data.numSTS;
                obj.TxVHTConfig.NumSpaceTimeStreams = entity.data.numSTS;

            elseif (obj.TxFrameFormat == edcaFrameFormats.HE_SU) || (obj.TxFrameFormat == edcaFrameFormats.HE_EXT_SU)
                obj.TxHESUConfig.APEPLength = entity.data.psduLength(obj.UserIndexSU);
                obj.TxHESUConfig.MCS = entity.data.mcsIndex(obj.UserIndexSU);
                obj.TxHESUConfig.ExtendedRange = (obj.TxFrameFormat == edcaFrameFormats.HE_EXT_SU);
                obj.TxHESUConfig.NumTransmitAntennas = entity.data.numSTS;
                obj.TxHESUConfig.NumSpaceTimeStreams = entity.data.numSTS;

            elseif obj.TxFrameFormat == edcaFrameFormats.HE_MU
                obj.TxHEMUConfig = wlanHEMUConfig(entity.data.allocationIndex);
                obj.TxHEMUConfig.NumTransmitAntennas = entity.data.numSTS;
                obj.NumUsers = numel(obj.TxHEMUConfig.User);
                for userIndex = 1:obj.NumUsers
                    obj.TxHEMUConfig.User{userIndex}.MCS = entity.data.mcsIndex(userIndex);
                    obj.TxHEMUConfig.User{userIndex}.APEPLength = entity.data.psduLength(userIndex);
                    obj.TxHEMUConfig.User{userIndex}.STAID = entity.data.stationIDs(userIndex);
                    % All users assumed to have same number of space-time streams
                    obj.TxHEMUConfig.User{userIndex}.NumSpaceTimeStreams = entity.data.numSTS;
                end
            end

            % Send Tx start confirm to MAC
            events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'TXSTARTCONFIRM', 0, 100)];

            % Send Tx end confirm indication if the request is Tx end request
        elseif entity.data.msgType == PhyPrimitives.TxEndRequest
            events = [events obj.eventGenerate(obj.PhyIndicationsQueueID, 'TXENDCONFIRM', 0, 100)];
        end
        % Schedule an event to destroy the input entity
        events = [events obj.eventDestroy()];
    end
end

methods(Access = protected)
    function setupImpl(obj)
        % Setting default values
        obj.TxFrameFormat = double(edcaFrameFormats.NonHT);
        % For VHT config default bandwidth is 80MHz. Update it to 20MHz
        obj.TxVHTConfig = wlanVHTConfig('ChannelBandwidth', 'CBW20');
        obj.TxNonHTConfig = wlanNonHTConfig;

        % HT configuration object
        obj.TxHTConfig = wlanHTConfig;

        % HE-SU configuration object
        obj.TxHESUConfig = wlanHESUConfig;
        % HE-MU configuration object
        allocationIndex = 0;
        obj.TxHEMUConfig = wlanHEMUConfig(allocationIndex);

        % Initialize Tx buffer and PSDU lengths
        %
        % Here 82573 is the maximum allowed PDSU length in bytes for HE-SU
        % format with MCS 11 in 20MHz bandwidth
        obj.TxBuffer = zeros(93600, obj.TxMaxUsers, 'int8');
        obj.TxPSDULength = zeros(obj.TxMaxUsers, 1);

        % Sample rate for 20MHz
        obj.SampleRate = 20e6;
    end

    function entityTypes = getEntityTypesImpl(obj)
        % 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(~)
        % Specifies input and output port data types and complexity
        inTypes = {'phyInd', 'frame'};
        outTypes = {'phyInd', 'waveform'} ;
    end

    function [st, I, O] = getEntityStorageImpl(obj)
        MACRequestsQueueSize = 1;
        PSDUInputQueueSize = 1;
        PhyIndicationsQueueSize = 1;
        WaveformQueueSize = 1;
        % Creates the internal storage queues and associate those input and
        % output ports.
        st = [obj.queueFIFO('frame', PSDUInputQueueSize), obj.queueFIFO('phyInd', PhyIndicationsQueueSize),...
            obj.queueFIFO('waveform', WaveformQueueSize), obj.queueFIFO('phyInd', MACRequestsQueueSize)];
        I = [obj.MACRequestsQueueID obj.PSDUInputQueueID];
        O = [obj.PhyIndicationsQueueID 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 [input1, input2] = getInputNamesImpl(~)
        input1 = 'MACToPHY';
        input2 = 'FrameToPHY';
    end

    function [output1, output2] = getOutputNamesImpl(~)
        output1 = 'PHYTxConfirms';
        output2 = 'Waveform';
    end
end
end
