classdef PhyRx80211aDES < matlab.DiscreteEventSystem
    % PhyRx80211aDES Models interference, demodulates and decodes the
    % received 802.11a waveform
    %
    %   This object performs the following operations on the waveform
    %   received at the physical layer:
    %       1. Interference and thermal noise application
    %       2. Comparison of RSSI with ED threshold value
    %       3. Decode the received waveform
    %       4. Send the frame to MAC if no error in reception
    %       5. Send indications to MAC such as CCABusy, RxStart, RxEnd, etc.
    
    %   Copyright 2019-2020 The MathWorks, Inc.
    
    % Public, tunable properties
    properties
        % Node ID
        NodeID (1, 1) {mustBeInteger, mustBeGreaterThan(NodeID, 0)} = 1;
        % Rx Gain (dB)
        RxGain (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(RxGain, 0),...
            mustBeLessThanOrEqual(RxGain, 20)} = 3.0;
        % Rx Noise Figure (dB)
        RxNoiseFigure (1, 1) {mustBeNumeric, mustBeGreaterThanOrEqual(RxNoiseFigure, 1),...
            mustBeLessThanOrEqual(RxNoiseFigure, 15)} = 7.0;
    end
    
    properties(Constant, Hidden)
        % Constants
        NUMBER_OF_INPUT_PORTS = 2;
        NUMBER_OF_OUTPUT_PORTS = 2;
        PHY_MODE_SELECT_QUEUE_ID = 1;
        PHY_RX_INDICATIONS_QUEUE_ID = 2;
        PSDU_QUEUE_ID = 3;
        PHY_RX_PROCESSING_QUEUE_ID = 4;
        PHY_MODE_SELECT_QUEUE_SIZE = 1;
        PHY_RX_INDICATIONS_QUEUE_SIZE = 1;
        PSDU_QUEUE_SIZE = 1;
        PHY_RX_PROCESSING_QUEUE_SIZE = inf;
        PHY_RX_INDICATIONS_OUTPUT_PORT_INDEX = 1;
        PSDU_OUTPUT_PORT_INDEX = 2;
        
        NUMBER_OF_SUBCARRIERS = 52;
        LSIG_FIELD_LENGTH = 24; % In bits
        MAXIMUM_PSDU_LENGTH = 2332; % In bytes
        % Channel Bandwidth
        ChanBw = 20;
    end
    
    % Pre-computed constants
    properties(Access = private)
        % Source node id
        SourceNodeId = 1;
        % Received PSDU.
        % Maximum PSDU length = 2304 (MSDU) + 24 (MAC header) + 4 (FCS).
        RxData = zeros(2332*8, 1);
        % Received data length
        RxDataLength = 0;
        % Waveform receiver configuration object (Rx)
        RxConfig = wlanNonHTConfig;
        % Channel estimation
        ChanEst = complex(zeros(52,1), 0);
        % noise variance
        NoiseVar = 0;
        % L-SIG information
        RecoveredLSIG = zeros(24, 1); % This 24 represents 24 bits of L-SIG
        % Demodulated L-LTF
        DemodLLTF = complex(zeros(52,2), 0);
        % Interference
        Interference = 0; % in Milli watts
        % Energy detection threshold (wlan station/Ap is able to detect and
        % decode wlan signals of power above this threshold)
        EDThreshold = -82; % in dBm
        % Rx On is for enabling only one of Tx and Rx part of PHY
        RxOn = true;
        % CCA idle flag
        CCAIdle = 1;
        % Sample rate for signal analysis
        SampleRate = 0;
        % RSSI value
        RSSI = 0;
        % Receiving data rate
        RxDataRate = 0;
        % AWGN channel
        AWGNChan;
    end
    
    % Discrete-event algorithms
    methods
        function dataRate = convertMCSToDataRate(~, MCS)
            % This function derives MCS value from channel bandwidth
            % and data rate for wlanNonHTConfig object.
            
            switch(MCS)
                case 0
                    dataRate = 6; % in Mbps
                case 1
                    dataRate = 9; % in Mbps
                case 2
                    dataRate = 12; % in Mbps
                case 3
                    dataRate = 18; % in Mbps
                case 4
                    dataRate = 24; % in Mbps
                case 5
                    dataRate = 36; % in Mbps
                case 6
                    dataRate = 48; % in Mbps
                case 7
                    dataRate = 54; % in Mbps
                otherwise
                    dataRate = 6;
            end
        end
        
        function [chanEst, noiseVar] = channelEstimation(obj, rxPPDU)
            % This function estimates the channel and noise variance from L-LTF.
            
            % L-LTF field indices.
            indLLTF = wlanFieldIndices(obj.RxConfig, 'L-LTF');
            % Get the IQ samples of L-LTF from rxPPDU.
            rxLLTF = rxPPDU(indLLTF(1):indLLTF(2), :);
            % Demodulate L-LTF.
            dLLTF = wlanLLTFDemodulate(rxLLTF, obj.RxConfig);
            % Find the channel estimation value from IQ samples.
            chanEst = wlanLLTFChannelEstimate(dLLTF, obj.RxConfig);
            noiseVar = helperNoiseEstimate(dLLTF);
            obj.DemodLLTF = dLLTF;
        end
        
        function [mcs, pktLen, failCRC] = interpretLSIG(obj, rxPPDU, chanEst, noiseVar)
            % This function interprets the L-SIG field.
            
            % Initialize the output variables to empty.
            mcs = [];
            pktLen = 0;
            RateBits = wlan.internal.nonHTRateSignalBits();
            
            % L-SIG field indices.
            indLSIG = wlanFieldIndices(obj.RxConfig, 'L-SIG');
            % Extract the L-SIG field from the received PPDU, recover its
            % information bits and check the CRC.
            rxLSIG = rxPPDU(indLSIG(1):indLSIG(2), :);
            [obj.RecoveredLSIG, failCRC] = wlanLSIGRecover(rxLSIG, chanEst, noiseVar, obj.RxConfig.ChannelBandwidth);
            % Check whether CRC is pass or fail.
            if ~failCRC
                % Interprets the L-SIG field of the preamble.
                pktLen = bi2de(double(obj.RecoveredLSIG(5+(1:12))'));
                mcs = find(all(bsxfun(@eq,RateBits(1:3,:), double(obj.RecoveredLSIG(1:3)))))-1;
            end
        end
        
        function data = recoverRxData(obj, rxPPDU, chanEst, noiseVar)
            % This function gets the data from the received signal using
            % channel estimation and noise variance.
            
            fieldInd = wlanFieldIndices(obj.RxConfig);
            % Extract data field from PPDU.
            recievedData = rxPPDU(fieldInd.NonHTData(1):fieldInd.NonHTData(2), :);
            % Recover information bits from NonHT data field signal.
            data = double(wlanNonHTDataRecover(recievedData, chanEst, noiseVar, obj.RxConfig));
        end
        
        function [detected, rx] = packetDetectionAndFreqCorrection(obj, rxPPDU)
            % This function detects the packet and performs frequency
            % correction on the given waveform.
            
            detected = true;
            % Rx is a column vector that holds the IQ samples that are
            % received from other nodes. Its maximum size is 62720-by-1
            % where 62720 is the maximum number of IQ samples that can be
            % generated for a maximum PSDU length of 2332 (i.e., 2304
            % (MSDU) + 24 (MAC header) + 4 (FCS)) bytes.
            rx = complex(zeros(62720,1));
            % L-LTF field indices.
            indLLTF = wlanFieldIndices(obj.RxConfig, 'L-LTF');
            % L-LTF field indices.
            indLSTF = wlanFieldIndices(obj.RxConfig, 'L-STF');
            % L-SIG field indices.
            indLSIG = wlanFieldIndices(obj.RxConfig, 'L-SIG');
            % Detect packet and determine the coarse packet offset.
            pktOffset = uint32(wlanPacketDetect(rxPPDU, obj.RxConfig.ChannelBandwidth));
            if isempty(pktOffset)
                detected = false;
            else
                % Extract L-STF and perform the coarse frequency offset correction.
                lstf = rxPPDU((indLSTF(1) + pktOffset(1):indLSTF(2) + pktOffset(1)), :);
                cfo = wlanCoarseCFOEstimate(lstf, obj.RxConfig.ChannelBandwidth);
                rxPPDU = helperFrequencyOffset(rxPPDU, wlanSampleRate(obj.RxConfig), -cfo);
                
                % Extract NonHT fields and perform the symbol timing synchronization.
                nonHTFields = rxPPDU((indLSTF(1) + pktOffset(1):indLSIG(2) + pktOffset(1)), :);
                pktOffset = pktOffset + uint32(wlanSymbolTimingEstimate(nonHTFields, obj.RxConfig.ChannelBandwidth));
                
                if (pktOffset + indLSIG(2) > length(rxPPDU))
                    detected = false;
                else
                    % Extract L-LTF and perform the fine frequency correction
                    lltf = rxPPDU((indLLTF(1) + pktOffset(1):indLLTF(2) + pktOffset(1)), :);
                    ffo = wlanFineCFOEstimate(lltf, obj.RxConfig.ChannelBandwidth);
                    rxPPDU = helperFrequencyOffset(rxPPDU, wlanSampleRate(obj.RxConfig), -ffo);
                    rx(1:(length(rxPPDU) - pktOffset(1)), :) = rxPPDU(pktOffset(1)+1:end, :);
                end
            end
        end
        
        function rxPSDU = recoverPayload(obj, rxPPDU)
            % This function recovers the data from the given waveform.
            
            rxPSDU = obj.recoverRxData(rxPPDU, obj.ChanEst, obj.NoiseVar);
        end
        
        function [flag, rx] = decodePreamble(obj, rxPPDU)
            % This function decodes preamble from the given waveform.
            
            flag = false;
            [find, rx] = obj.packetDetectionAndFreqCorrection(rxPPDU);
            if find
                % Estimate channel and noise variance.
                [obj.ChanEst, obj.NoiseVar] = obj.channelEstimation(rx);
                flag = true;
            end
        end
        
        function flag = decodeHeader(obj, rxPPDU)
            % This function decodes Phy header from the given waveform.
            
            flag = false;
            [mcs, obj.RxDataLength, failCRC] = obj.interpretLSIG(rxPPDU, obj.ChanEst, obj.NoiseVar);
            
            % Maximum PSDU length = 2304 (MSDU) + 24 (MAC header) + 4 (FCS).
            if (~failCRC) && (obj.RxDataLength <= 2332)
                obj.RxConfig.PSDULength = obj.RxDataLength;
                obj.RxConfig.MCS = mcs(1);
                flag = true;
            end
        end
        
        function duration = findPreambleDuration(~)
            % This function returns the preamble duration.
            
            % Preamble duration is the length of L-STF and L-LTF field
            % which is 8e-6 + 8e-6 = 16e-6 for 20 MHz channel.
            duration = 16e-6; % in seconds.
        end
        
        function duration = findHeaderDuration(~)
            % This function returns the header duration
            
            % Header duration is a single OFDM symbol which is 4e-6 for
            % 20MHz channel.
            duration = 4e-6; % in seconds.
        end
        
        function TxGainSamples = applyGain(~, IQSamples, gain)
            % This function applies the gain to IQ samples.
            
            scale = 10.^(gain/20);
            TxGainSamples = IQSamples * scale;
        end
        
        function powerInDbm = dbwToDbmConverter(~, powerInDbw)
            % This function converts the power in dBw to dBm
            % i.e., dBm = dBw + 30;
            
            powerInDbm = powerInDbw + 30;
        end
        
        function powerInDbw = dbmToDbwConverter(~, powerInDbm)
            % This function converts the power in dBm to dBw
            % i.e., dBw = dBm - 30;
            
            powerInDbw = powerInDbm - 30;
        end
        
        function thermalNoise = calculateThermalNoise(obj)
            % This function calculates thermal noise
            
            % Temperature in Kelvin for calculating thermal noise.
            tempInKelvin = 290;
            % Thermal noise(in Watts) = BoltzmannConstant * Temperature (in
            % Kelvin) * bandwidth of the channel.
            Nt = physconst('Boltzmann') * tempInKelvin * obj.SampleRate;
            thermalNoise = (10^(obj.RxNoiseFigure/10)) * Nt; % In watts
        end
        
        function [entity,events] = waveformEntry(obj, ~ ,entity, ~)
            % This function applies the interference and thermal noise over the given waveform.
            % This function also checks the waveform whether receiver is able detect waveform or
            % not by comparing the strength of signal with edThreshold.
            
            coder.extrinsic('dcfUpdateStats', 'get_param', 'dcfPlotStats', 'bdroot');
            events = obj.initEventArray();
            % Apply the receiver gain to signal.
            entity.data.waveform = obj.applyGain(entity.data.waveform, obj.RxGain);
            entity.data.sigPower = entity.data.sigPower + obj.RxGain;
            % Update interference value for next packet.
            obj.Interference = obj.Interference + 10^(entity.data.sigPower/10);
            % Check whether Phy Rx is on or off.
            if obj.RxOn
                % if Phy is idle, compare the received signal
                % strength with edThreshold. Otherwise, discard the received signal.
                if obj.CCAIdle == 1
                    % Compares the rxPower with edThreshold value.
                    if (entity.data.sigPower >= obj.EDThreshold)
                        dcfPlotStats(obj.NodeID, 3, get_param(bdroot,'SimulationTime')*1e6, entity.data.duration*1e6);
                        obj.RSSI = entity.data.sigPower;
                        % If rxPower is greater than edThreshold, give CCABUSY indication to MAC.
                        events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'CCABUSY', 0, 100), obj.eventTimer('endPreamble',...
                            obj.findPreambleDuration())];
                        obj.CCAIdle = 0;
                    else
                        dcfUpdateStats(obj.NodeID, dcfStats.EnergyDetectionsLessThanED, 1);
                        dcfUpdateStats(obj.NodeID, dcfStats.PhyRxDrop, 1);
                        if (obj.Interference >= 10^(obj.EDThreshold/10))
                            events = [events , obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'CCABUSY', 0, 100),...
                                obj.eventTimer('interference', entity.data.duration)];
                            obj.CCAIdle = 0;
                        else
                            events = [events obj.eventTimer('interference', entity.data.duration)];
                        end
                    end
                else
                    dcfUpdateStats(obj.NodeID, dcfStats.RxTriggersWhilePrevRxIsInProgress, 1);
                    dcfUpdateStats(obj.NodeID, dcfStats.PhyRxDrop, 1);
                    events = [events obj.eventTimer('interference', entity.data.duration)];
                end
            else
                dcfUpdateStats(obj.NodeID, dcfStats.RxTriggersWhileTxInProgress, 1);
                dcfUpdateStats(obj.NodeID, dcfStats.PhyRxDrop, 1);
                events = [events obj.eventTimer('interference', entity.data.duration)];
            end
        end
        
        function [entity, events] = waveformTimer(obj, ~, entity, tag)
            coder.extrinsic('dcfUpdateStats');
            events = obj.initEventArray();
            switch(tag)
                case 'endPreamble'
                    thermalNoise = obj.calculateThermalNoise();
                    % Calculate total noise as a sum of noise floor and interference
                    %  in watts.
                    totalnoise = thermalNoise + (obj.Interference - 10^(entity.data.sigPower/10))*1e-3;
                    % Calculate SINR.
                    sinr = entity.data.sigPower - ((10*log10(totalnoise)) + 30);
                    % Set the AWGN noise channel with the calculated SINR and signal power.
                    obj.AWGNChan.SignalPower = 10^(obj.dbmToDbwConverter(entity.data.sigPower)/10);
                    obj.AWGNChan.SNR = sinr;
                    
                    % Extract field indices from configuration object
                    indices = wlanFieldIndices(obj.RxConfig);
                    % Apply noise on waveform using AWGN channel.
                    entity.data.waveform(indices.LSTF(1):indices.LLTF(2)) = ...
                        obj.AWGNChan(entity.data.waveform(indices.LSTF(1):indices.LLTF(2)));
                    % Decode preamble
                    [flag, rx] = obj.decodePreamble(entity.data.waveform);
                    if flag
                        entity.data.waveform(:) = rx(:);
                        events = [events obj.eventTimer('endHeader', obj.findHeaderDuration())];
                    else
                        dcfUpdateStats(obj.NodeID, dcfStats.PhyPreambleDecodeFailures, 1);
                        events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'RXERR', 0, 100)...
                            obj.eventTimer('interference', entity.data.duration -...
                            obj.findPreambleDuration())];
                    end
                case 'endHeader'
                    thermalNoise = obj.calculateThermalNoise();
                    % Calculate total noise as a sum of noise floor and interference
                    %  in watts.
                    totalnoise = thermalNoise + (obj.Interference- 10^(entity.data.sigPower/10))*1e-3;
                    % Calculate SINR.
                    sinr = entity.data.sigPower - ((10*log10(totalnoise)) + 30);
                    % Set the AWGN noise channel with the calculated SINR and signal power.
                    obj.AWGNChan.SignalPower = 10^(obj.dbmToDbwConverter(entity.data.sigPower)/10);
                    obj.AWGNChan.SNR = sinr;
                    
                    % Extract field indices from configuration object
                    indices = wlanFieldIndices(obj.RxConfig);
                    % Apply noise on waveform using AWGN channel.
                    entity.data.waveform(indices.LSIG(1):indices.LSIG(2)) = ...
                        obj.AWGNChan(entity.data.waveform(indices.LSIG(1):indices.LSIG(2)));
                    % Decode header
                    flag = obj.decodeHeader(entity.data.waveform);
                    if flag
                        obj.RxDataRate = obj.convertMCSToDataRate(obj.RxConfig.MCS);
                        events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'RXSTART', 0, 100),...
                            obj.eventTimer('endPayload', entity.data.duration - ...
                            (obj.findPreambleDuration() +...
                            obj.findHeaderDuration()))];
                    else
                        dcfUpdateStats(obj.NodeID, dcfStats.PhyHeaderDecodeFailures, 1);
                        events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'RXERR', 0, 100),...
                            obj.eventTimer('interference', entity.data.duration - ...
                            (obj.findPreambleDuration() +...
                            obj.findHeaderDuration()))];
                    end
                case 'endPayload'
                    thermalNoise = obj.calculateThermalNoise();
                    % Calculate total noise as a sum of noise floor and interference
                    %  in watts.
                    totalnoise = thermalNoise + (obj.Interference - 10^(entity.data.sigPower/10))*1e-3;
                    % Calculate SINR.
                    sinr = entity.data.sigPower - ((10*log10(totalnoise)) + 30);
                    % Set the AWGN noise channel with the calculated SINR and signal power.
                    obj.AWGNChan.SignalPower = 10^(obj.dbmToDbwConverter(entity.data.sigPower)/10);
                    obj.AWGNChan.SNR = sinr;
                    
                    % Extract field indices from configuration object
                    indices = wlanFieldIndices(obj.RxConfig);
                    % Apply noise on waveform using AWGN channel.
                    entity.data.waveform(indices.NonHTData(1):indices.NonHTData(2)) = ...
                        obj.AWGNChan(entity.data.waveform(indices.NonHTData(1):indices.NonHTData(2)));
                    % Decode payload
                    tmpData = obj.recoverPayload(entity.data.waveform);
                    obj.RxData(1:length(tmpData), 1) = tmpData(1:end, 1);
                    obj.SourceNodeId = entity.data.srcId;
                    events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'RXEND', 0, 100),...
                        obj.eventGenerate(obj.PSDU_QUEUE_ID, 'MACRXFRAME', 0, 100),...
                        obj.eventTimer('interference', 0)];
                case 'interference'
                    obj.Interference = obj.Interference - (10^(entity.data.sigPower/10));
                    if (obj.Interference < 10^(obj.EDThreshold/10))
                        events = [events obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'CCAIDLE', 0, 100)];
                        obj.CCAIdle = 1;
                    end
                    events = [events obj.eventDestroy()];
            end
        end
        
        function [entity, events] = frameGenerate(obj, ~, entity, ~)
            events = obj.initEventArray();
            entity.data.data(1:end, 1) = 0;
            entity.data.data(1:end, 1) = int8(obj.RxData(1:end, 1));
            entity.data.srcId = obj.SourceNodeId;
            events = [events obj.eventForward('output', obj.PSDU_OUTPUT_PORT_INDEX, 0)];
        end
        
        function [entity, events] = phyIndGenerate(obj, ~, entity, tag)
            coder.extrinsic('dcfUpdateStats');
            switch(tag)
                case 'CCAIDLE'
                    entity.data.msgType = double(PhyPrimitives.CCAIdleIndication);
                case 'CCABUSY'
                    entity.data.msgType = double(PhyPrimitives.CCABusyIndication);
                case 'RXSTART'
                    entity.data.psduLength = obj.RxDataLength;
                    entity.data.dataRate = obj.RxDataRate;
                    entity.data.rssi = obj.RSSI;
                    entity.data.msgType = double(PhyPrimitives.RxStartIndication);
                case 'RXEND'
                    dcfUpdateStats(obj.NodeID, dcfStats.PhyRx, 1);
                    entity.data.msgType = double(PhyPrimitives.RxEndIndication);
                case 'RXERR'
                    dcfUpdateStats(obj.NodeID, dcfStats.PhyRxDrop, 1);
                    entity.data.msgType = double(PhyPrimitives.RxErrorIndication);
                otherwise
                    entity.data.msgType = double(PhyPrimitives.UnknownIndication);
            end
            events = obj.eventForward('output', obj.PHY_RX_INDICATIONS_OUTPUT_PORT_INDEX, 0);
        end
        
        function [entity, events] = phyModeEntry(obj, ~, entity, ~)
            
            events = obj.initEventArray();
            if (entity.data.phyRxOn == 1)
                obj.RxOn = true;
                if ((obj.Interference >= 10^(obj.EDThreshold/10)) && obj.CCAIdle)
                    events = [events, obj.eventGenerate(obj.PHY_RX_INDICATIONS_QUEUE_ID, 'CCABUSY', 0, 100)];
                end
            else
                obj.RxOn = false;
            end
            events = [events, obj.eventDestroy()];
        end
    end
    
    methods(Access = protected)
        function setupImpl(obj)
            % Perform one-time calculations, such as computing constants.
            obj.AWGNChan = comm.AWGNChannel('NoiseMethod', 'Signal to noise ratio (SNR)');
            obj.RxConfig = wlanNonHTConfig('ChannelBandwidth', 'CBW20');
            obj.SampleRate = wlanSampleRate(obj.RxConfig);
            rng(obj.NodeID);
        end
        
        function entityTypes = getEntityTypesImpl(obj)
            % This function specifies the entity types are used inside it.
            
            entityTypes = [obj.entityType('phyMode', 'PhyMode'), obj.entityType('frame', 'Frame'), obj.entityType('waveform', 'Waveform'),...
                obj.entityType('phyInd', 'PhyInd')];
        end
        
        function [inTypes, outTypes] = getEntityPortsImpl(~)
            % This function specifies input and output port data types and
            % complexity.
            
            inTypes = {'phyMode', 'waveform'};
            outTypes = {'phyInd', 'frame'} ;
        end
        
        function [st, I, O] = getEntityStorageImpl(obj)
            % This function creates the internal storage queues and
            % associate that to input and output ports.
            st = [obj.queueFIFO('phyMode', obj.PHY_MODE_SELECT_QUEUE_SIZE),...
                obj.queueFIFO('phyInd', obj.PHY_RX_INDICATIONS_QUEUE_SIZE),...
                obj.queueFIFO('frame', obj.PSDU_QUEUE_SIZE),...
                obj.queueFIFO('waveform', obj.PHY_RX_PROCESSING_QUEUE_SIZE)];
            I = [obj.PHY_MODE_SELECT_QUEUE_ID obj.PHY_RX_PROCESSING_QUEUE_ID];
            O = [obj.PHY_RX_INDICATIONS_QUEUE_ID obj.PSDU_QUEUE_ID];
        end
        
        function num = getNumInputsImpl(obj)
            % This function specifies the number of input ports to this
            % system object.
            
            num = obj.NUMBER_OF_INPUT_PORTS;
        end
        
        function num = getNumOutputsImpl(obj)
            % This function specifies the number of output ports to this
            % system object.
            
            num = obj.NUMBER_OF_OUTPUT_PORTS;
        end
        
        function [input1, input2] = getInputNamesImpl(~)
            input1 = 'PhyMode';
            input2 = 'Waveform';
        end
        
        function [output1, output2] = getOutputNamesImpl(~)
            output1 = 'PhyRxIndications';
            output2 = 'FrameFromPhy';
        end
    end
end
