%% Image Transmission and Reception Using 802.11 Waveform and SDR
% This examples shows how to encode and pack an image file into WLAN packets for transmission and subsequently decode the packets to retrieve the image.  The example also shows how to use a software-defined radio (SDR) for over-the-air transmission and reception of the WLAN packets.
%% Introduction
% This example imports and segments an image file into multiple medium access control (MAC) service data units (MSDUs). It passes each MSDU to the wlanMACFrame function to create a MAC protocol data unit (MPDU). This function also utilizes a wlanMACFrameConfig object as an input, which sequentially numbers the MPDUs through the SequenceNumber property. The example then passes the MPDUs to the physical (PHY) layer as a PHY Layer Service Data Units (PSDUs). Each PSDU data uses a single non-high-throughput (nonHT), 802.11a™ WLAN packet for transmission. This example creates a WLAN baseband waveform using the wlanWaveformGenerator function. This function utilizes multiple PSDUs and processes each to form a series of physical layer convergence procedure (PLCP) protocol data units (PPDUs) ready for transmission.
%
% The generated waveform then passes through an additive white Gaussian noise (AWGN) channel to simulate an over-the-air transmission. Subsequently, the wlanMPDUDecode function takes the noisy waveform and decodes it. Then, the SequenceNumber property in the recovered MAC frame configuration object allows the example to sequentially order the extracted MSDUs. The information bits in the multiple received MSDUs combine to recover the transmitted image. This diagram shows the receiver processing.
%
% Alternatively, the generated WLAN waveform can be transmitted over the air and received using these supported SDRs.
%
% Communications Toolbox Support Package for Analog Devices® ADALM-Pluto Radio
% Installation and Setup
% Supported Hardware
% Communications Toolbox Support Package for USRP® Embedded Series Radio
% Installation and Setup
% Supported Hardware
% Communications Toolbox Support Package for Xilinx® Zynq®-Based Radio
% Installation and Setup
% Supported Hardware
%
%% Example Setup
% Before running the example, set the channel variable to be one of these options:
% OverTheAir: Use an SDR to transmit and receive the WLAN waveform
% GaussianNoise: Pass the transmission waveform through an AWGN channel (default)
% NoImpairments: Pass the transmission waveform through with no impairments
% If you set channel to OverTheAir, set deviceName to the desired SDR:
% Set to Pluto to use the ADALM-Pluto Radio (default)
% Set to E3xx to use the USRP Embedded Series Radio
% Set to AD936x or FMCOMMS5 to use the Xilinx Zynq-Based Radio

channel = "GaussianNoise";
if channel == "OverTheAir"
    deviceName = "Pluto";
end

% Configure all the scopes and figures for the example.

% Setup handle for image plot
if ~exist('imFig','var') || ~ishandle(imFig) %#ok<SUSENS> 
    imFig = figure;
    imFig.NumberTitle = 'off';
    imFig.Name = 'Image Plot';
    imFig.Visible = 'off';
else
    clf(imFig); % Clear figure
    imFig.Visible = 'off';
end

% Setup Spectrum viewer
spectrumScope = dsp.SpectrumAnalyzer( ...
    'SpectrumType','Power density', ...
    'SpectralAverages',10, ...
    'YLimits',[-90 -30], ...
    'Title','Received Baseband WLAN Signal Spectrum', ...
    'YLabel','Power spectral density', ...
    'Position',[69 376 800 450]);

% Setup the constellation diagram viewer for equalized WLAN symbols
refQAM = wlanReferenceSymbols('64QAM');
constellation = comm.ConstellationDiagram(...
    'Title','Equalized WLAN Symbols',...
    'ShowReferenceConstellation',true,...
    'ReferenceConstellation',refQAM,...
    'Position',[878 376 460 460]);

%
%% Transmitter Design
% These steps describe the general procedure of the WLAN transmitter.
% Import an image file and convert it to a stream of decimal bytes
% Generate a baseband WLAN signal using the WLAN Toolbox
% Pack the data stream into multiple 802.11a packets
% If using an SDR, these steps describe the setup of the SDR transmitter.
% Prepare the baseband signal for transmission using the SDR hardware
% Send the baseband data to the SDR hardware for upsampling and continuous transmission at the desired center frequency
% Prepare Image File
% Read data from the image file, scale it for transmission, and convert it to a stream of decimal bytes. The scaling of the image reduces the quality by decreasing the size of the binary data stream.
% The size of the binary data stream impacts the number of WLAN packets required for the transmission of the image data. The number of WLAN packets generated for transmission depends on these factors.
% The image scaling, set when importing the image file
% The length of the data carried in a packet, specified by the msduLength variable
% The modulation and coding scheme (MCS) value of the transmitted packet
% The combination of the scaling factor and MSDU length determines the number of WLAN radio packets required for transmission. Setting scale to 0.2 and msduLength to 2304 requires the transmission of 11 WLAN radio packets. Increasing the scaling factor or decreasing the MSDU length will result in the transmission of more packets.

% Input an image file and convert to binary stream
fileTx = 'peppers.png';            % Image file name
fData = imread(fileTx);            % Read image data from file
scale = 0.2;                       % Image scaling factor
origSize = size(fData);            % Original input image size
scaledSize = max(floor(scale.*origSize(1:2)),1); % Calculate new image size
heightIx = min(round(((1:scaledSize(1))-0.5)./scale+0.5),origSize(1));
widthIx = min(round(((1:scaledSize(2))-0.5)./scale+0.5),origSize(2));
fData = fData(heightIx,widthIx,:); % Resize image
imsize = size(fData);              % Store new image size
txImage = fData(:);

% Plot transmit image
imFig.Visible = 'on';
subplot(211);
imshow(fData);
title('Transmitted Image');
subplot(212);
title('Received image appears here...');
set(gca,'Visible','off');
set(findall(gca, 'type', 'text'), 'visible', 'on');

%
% Fragment Transmit Data
% Split the data stream (txImage) into smaller transmit units (MSDUs) of size msduLength. Then, create an MPDU for each transmit unit using the wlanMACFrame function. Each call to this function creates an MPDU corresponding to the given MSDU and the frame configuration object. Next, create the frame configuration object using wlanMACFrameConfig to configure the sequence number of the MPDU. All the MPDUs are then sequentially passed to the physical layer for transmission.
% To ensure that the MSDU size of the transmission does not exceed the standard-specified maximum, set the msduLength field to 2304 bytes. To make all MPDUs the same size, append the data in the last MPDU with zeros.

msduLength = 2304; % MSDU length in bytes
numMSDUs = ceil(length(txImage)/msduLength);
padZeros = msduLength-mod(length(txImage),msduLength);
txData = [txImage;zeros(padZeros,1)];
txDataBits = double(reshape(de2bi(txData, 8)',[],1));

% Divide input data stream into fragments
bitsPerOctet = 8;
data = zeros(0,1);

for i=0:numMSDUs-1

    % Extract image data (in octets) for each MPDU
    frameBody = txData(i*msduLength+1:msduLength*(i+1),:);

    % Create MAC frame configuration object and configure sequence number
    cfgMAC = wlanMACFrameConfig('FrameType','Data','SequenceNumber',i);

    % Generate MPDU
    [psdu, lengthMPDU]= wlanMACFrame(frameBody,cfgMAC,'OutputFormat','bits');

    % Concatenate PSDUs for waveform generation
    data = [data; psdu]; %#ok<AGROW>

end

%
% Generate 802.11a Baseband WLAN Signal
% Synthesize a non-HT waveform using the wlanWaveformGenerator function with a non-HT format configuration object created by the wlanNonHTConfig function. In this example, the configuration object has a 20 MHz bandwidth, one transmit antenna and 64QAM rate 2/3 (MCS 6).

nonHTcfg = wlanNonHTConfig;       % Create packet configuration
nonHTcfg.MCS = 6;                 % Modulation: 64QAM Rate: 2/3
nonHTcfg.NumTransmitAntennas = 1; % Number of transmit antenna
chanBW = nonHTcfg.ChannelBandwidth;
nonHTcfg.PSDULength = lengthMPDU; % Set the PSDU length

% Initialize the scrambler with a random integer for each packet.

scramblerInitialization = randi([1 127],numMSDUs,1);

% Set the oversampling factor to 1.5 to generate the waveform at 30 MHz for transmission.

osf = 1.5;

sampleRate = wlanSampleRate(nonHTcfg); % Nominal sample rate in Hz

% Generate baseband NonHT packets separated by idle time
txWaveform = wlanWaveformGenerator(data,nonHTcfg, ...
    'NumPackets',numMSDUs,'IdleTime',20e-6, ...
    'ScramblerInitialization',scramblerInitialization,...
    'OversamplingFactor',osf);

%
% Configure SDR for Transmission
% If using an SDR, set the transmitter gain parameter (txGain) to reduce transmission quality and impair the received waveform.
% Create an sdrTransmitter object using the sdrtx function. Set the center frequency, sample rate, and gain to the corresponding properties of the sdrTransmitter object. For an 802.11a signal on channel 5 in the 2.4 GHz frequency band, the corresponding center frequency is 2.432 GHz as defined in section 16.3.6.3 of the IEEE Std 802.11-2016.
% The sdrTransmitter object uses the transmit repeat functionality to transmit the baseband WLAN waveform in a loop from the double data rate (DDR) memory on the SDR.

if channel == "OverTheAir"
    txGain = -10;

    % Transmitter properties
    sdrTransmitter = sdrtx(deviceName);
    sdrTransmitter.BasebandSampleRate = sampleRate*osf;
    sdrTransmitter.CenterFrequency = 2.432e9; % Channel 5
    sdrTransmitter.Gain = txGain;

    fprintf('\nGenerating WLAN transmit waveform:\n')

    % Scale the normalized signal to avoid saturation of RF stages
    powerScaleFactor = 0.8;
    txWaveform = txWaveform.*(1/max(abs(txWaveform))*powerScaleFactor);

    % Transmit RF waveform
    transmitRepeat(sdrTransmitter,txWaveform);
end

% The transmitRepeat function transfers the baseband WLAN packets with idle time to the SDR, and stores the signal samples in hardware memory. The example then transmits the waveform continuously over the air until the release of the transmit object.
%
%% Receiver Design
% The steps listed below describe the general structure of the WLAN receiver.
% If using SDR hardware, capture multiple packets of the transmitted WLAN signal
% Detect a packet
% Coarse carrier frequency offset is estimated and corrected
% Fine timing synchronization is established. The L-STF, L-LTF and L-SIG samples are provided for fine timing to allow to adjust the packet detection at the start or end of the L-STF
% Fine carrier frequency offset is estimated and corrected
% Perform a channel estimation for the received signal using the L-LTF
% Detect the format of the packet
% Decode the L-SIG field to recover the MCS value and the length of the data portion
% Decode the data field to obtain the transmitted data within each packet
% Decode the received PSDU and check if the frame check sequence (FCS) passed for the PSDU
% Order the decoded MSDUs based on the SequenceNumber property in the recovered MAC frame configuration object
% Combine the decoded MSDUs from all the transmitted packets to form the received image
% This example plots the power spectral density (PSD) of the received waveform, and shows visualizations of the equalized data symbols and the received image.
% Receiver Setup
% If using an SDR, create an sdrReceiver object using the sdrrx function. Set the center frequency, sample rate, and output data type to the corresponding properties of the sdrReceiver object.
% Otherwise, apply gaussian noise to the txWaveform using the awgn function or pass the txWaveform straight through to receiver processing.

if channel == "OverTheAir"
    sdrReceiver = sdrrx(deviceName);
    sdrReceiver.BasebandSampleRate = sdrTransmitter.BasebandSampleRate;
    sdrReceiver.CenterFrequency = sdrTransmitter.CenterFrequency;
    sdrReceiver.OutputDataType = 'double';

    % Configure the capture length equivalent to twice the length of the
    % transmitted signal, this is to ensure that PSDUs are received in order.
    % On reception the duplicate MAC fragments are removed.
    sdrReceiver.SamplesPerFrame = 2*length(txWaveform);
    fprintf('\nStarting a new RF capture.\n')

    rxWaveform = capture(sdrReceiver,sdrReceiver.SamplesPerFrame,'Samples');
elseif channel == "GaussianNoise"
    SNR = 20; % dB
    rxWaveform = awgn(txWaveform,SNR,'measured');
else % No Impairments
    rxWaveform = txWaveform;
end

% Show the power spectral density of the received waveform.

spectrumScope.SampleRate = sampleRate*osf;
spectrumScope(rxWaveform);

%
% Receiver Processing
% Design a rate conversion filter for resampling the waveform to the nominal baseband rate for receiver processing using the designMultirateFIR function.

aStop = 40; % Stopband attenuation
ofdmInfo = wlanNonHTOFDMInfo('NonHT-Data',nonHTcfg); % OFDM parameters
SCS = sampleRate/ofdmInfo.FFTLength; % Subcarrier spacing
txbw = max(abs(ofdmInfo.ActiveFrequencyIndices))*2*SCS; % Occupied bandwidth
[L,M] = rat(1/osf);
maxLM = max([L M]);
R = (sampleRate-txbw)/sampleRate;
TW = 2*R/maxLM; % Transition width
b = designMultirateFIR(L,M,TW,aStop);

% Resample the oversampled waveform back to 20 MHz for processing using the dsp.FIRRateConverter System object and the filter designed above.

firrc = dsp.FIRRateConverter(L,M,b);
rxWaveform = firrc(rxWaveform);

% If using an SDR, the SDR continuously transmits the 802.11 waveform over-the-air in a loop. The first packet received by the sdrReceiver may not be the first transmitted packet. This means that the packets may be decoded out of sequence. To enable the received packets to be recombined in the correct order, their sequence number must be determined. The wlanMPDUDecode function decodes the MPDU from the decoded PSDU bits of each packet and outputs the MSDU as well as the recovered MAC frame configuration object wlanMACFrameConfig. The SequenceNumber property in the recovered MAC frame configuration object can be used for ordering the MSDUs in the transmitted sequence.
% To display each packet's decoded L-SIG contents, the EVM measurements, and sequence number, check the displayFlag box.

displayFlag = false; 

% Set up required variables for receiver processing.

rxWaveformLen = size(rxWaveform,1);
searchOffset = 0; % Offset from start of the waveform in samples

% Get the required field indices within a PSDU.

ind = wlanFieldIndices(nonHTcfg);
Ns = ind.LSIG(2)-ind.LSIG(1)+1; % Number of samples in an OFDM symbol

% Minimum packet length is 10 OFDM symbols
lstfLen = double(ind.LSTF(2)); % Number of samples in L-STF
minPktLen = lstfLen*5;
pktInd = 1;
fineTimingOffset = [];
packetSeq = [];
rxBit = [];

% Perform EVM calculation
evmCalculator = comm.EVM('AveragingDimensions',[1 2 3]);
evmCalculator.MaximumEVMOutputPort = true;

% Use a while loop to process the received out-of-order packets.

while (searchOffset+minPktLen)<=rxWaveformLen
    % Packet detect
    pktOffset = wlanPacketDetect(rxWaveform,chanBW,searchOffset,0.5);

    % Adjust packet offset
    pktOffset = searchOffset+pktOffset;
    if isempty(pktOffset) || (pktOffset+double(ind.LSIG(2))>rxWaveformLen)
        if pktInd==1
            disp('** No packet detected **');
        end
        break;
    end

    % Extract non-HT fields and perform coarse frequency offset correction
    % to allow for reliable symbol timing
    nonHT = rxWaveform(pktOffset+(ind.LSTF(1):ind.LSIG(2)),:);
    coarseFreqOffset = wlanCoarseCFOEstimate(nonHT,chanBW);
    nonHT = helperFrequencyOffset(nonHT,sampleRate,-coarseFreqOffset);

    % Symbol timing synchronization
    fineTimingOffset = wlanSymbolTimingEstimate(nonHT,chanBW);

    % Adjust packet offset
    pktOffset = pktOffset+fineTimingOffset;

    % Timing synchronization complete: Packet detected and synchronized
    % Extract the non-HT preamble field after synchronization and
    % perform frequency correction
    if (pktOffset<0) || ((pktOffset+minPktLen)>rxWaveformLen)
        searchOffset = pktOffset+1.5*lstfLen;
        continue;
    end
    fprintf('\nPacket-%d detected at index %d\n',pktInd,pktOffset+1);

    % Extract first 7 OFDM symbols worth of data for format detection and
    % L-SIG decoding
    nonHT = rxWaveform(pktOffset+(1:7*Ns),:);
    nonHT = helperFrequencyOffset(nonHT,sampleRate,-coarseFreqOffset);

    % Perform fine frequency offset correction on the synchronized and
    % coarse corrected preamble fields
    lltf = nonHT(ind.LLTF(1):ind.LLTF(2),:);           % Extract L-LTF
    fineFreqOffset = wlanFineCFOEstimate(lltf,chanBW);
    nonHT = helperFrequencyOffset(nonHT,sampleRate,-fineFreqOffset);
    cfoCorrection = coarseFreqOffset+fineFreqOffset; % Total CFO

    % Channel estimation using L-LTF
    lltf = nonHT(ind.LLTF(1):ind.LLTF(2),:);
    demodLLTF = wlanLLTFDemodulate(lltf,chanBW);
    chanEstLLTF = wlanLLTFChannelEstimate(demodLLTF,chanBW);

    % Noise estimation
    noiseVarNonHT = helperNoiseEstimate(demodLLTF);

    % Packet format detection using the 3 OFDM symbols immediately
    % following the L-LTF
    format = wlanFormatDetect(nonHT(ind.LLTF(2)+(1:3*Ns),:), ...
        chanEstLLTF,noiseVarNonHT,chanBW);
    disp(['  ' format ' format detected']);
    if ~strcmp(format,'Non-HT')
        fprintf('  A format other than Non-HT has been detected\n');
        searchOffset = pktOffset+1.5*lstfLen;
        continue;
    end

    % Recover L-SIG field bits
    [recLSIGBits,failCheck] = wlanLSIGRecover( ...
        nonHT(ind.LSIG(1):ind.LSIG(2),:), ...
        chanEstLLTF,noiseVarNonHT,chanBW);

    if failCheck
        fprintf('  L-SIG check fail \n');
        searchOffset = pktOffset+1.5*lstfLen;
        continue;
    else
        fprintf('  L-SIG check pass \n');
    end

    % Retrieve packet parameters based on decoded L-SIG
    [lsigMCS,lsigLen,rxSamples] = helperInterpretLSIG(recLSIGBits,sampleRate);

    if (rxSamples+pktOffset)>length(rxWaveform)
        disp('** Not enough samples to decode packet **');
        break;
    end

    % Apply CFO correction to the entire packet
    rxWaveform(pktOffset+(1:rxSamples),:) = helperFrequencyOffset(...
        rxWaveform(pktOffset+(1:rxSamples),:),sampleRate,-cfoCorrection);

    % Create a receive Non-HT config object
    rxNonHTcfg = wlanNonHTConfig;
    rxNonHTcfg.MCS = lsigMCS;
    rxNonHTcfg.PSDULength = lsigLen;

    % Get the data field indices within a PPDU
    indNonHTData = wlanFieldIndices(rxNonHTcfg,'NonHT-Data');

    % Recover PSDU bits using transmitted packet parameters and channel
    % estimates from L-LTF
    [rxPSDU,eqSym] = wlanNonHTDataRecover(rxWaveform(pktOffset+...
        (indNonHTData(1):indNonHTData(2)),:), ...
        chanEstLLTF,noiseVarNonHT,rxNonHTcfg);

    constellation(reshape(eqSym,[],1)); % Current constellation
    pause(0); % Allow constellation to repaint
    release(constellation); % Release previous constellation plot

    refSym = wlanClosestReferenceSymbol(eqSym,rxNonHTcfg);
    [evm.RMS,evm.Peak] = evmCalculator(refSym,eqSym);

    % Decode the MPDU and extract MSDU
    [cfgMACRx,msduList{pktInd},status] = wlanMPDUDecode(rxPSDU,rxNonHTcfg); %#ok<*SAGROW>

    if strcmp(status,'Success')
        disp('  MAC FCS check pass');

        % Store sequencing information
        packetSeq(pktInd) = cfgMACRx.SequenceNumber;

        % Convert MSDU to a binary data stream
        rxBit{pktInd} = reshape(de2bi(hex2dec(cell2mat(msduList{pktInd})),8)',[],1);

    else % Decoding failed
        if strcmp(status,'FCSFailed')
            % FCS failed
            disp('  MAC FCS check fail');
        else
            % FCS passed but encountered other decoding failures
            disp('  MAC FCS check pass');
        end

        % Since there are no retransmissions modeled in this example, we
        % extract the image data (MSDU) and sequence number from the MPDU,
        % even though FCS check fails.

        % Remove header and FCS. Extract the MSDU.
        macHeaderBitsLength = 24*bitsPerOctet;
        fcsBitsLength = 4*bitsPerOctet;
        msduList{pktInd} = rxPSDU(macHeaderBitsLength+1:end-fcsBitsLength);

        % Extract and store sequence number
        sequenceNumStartIndex = 23*bitsPerOctet+1;
        sequenceNumEndIndex = 25*bitsPerOctet-4;
        packetSeq(pktInd) = bi2de(rxPSDU(sequenceNumStartIndex:sequenceNumEndIndex)');

        % MSDU binary data stream
        rxBit{pktInd} = double(msduList{pktInd});
    end

    % Display decoded information
    if displayFlag
        fprintf('  Estimated CFO: %5.1f Hz\n\n',cfoCorrection); %#ok<*UNRCH> 

        disp('  Decoded L-SIG contents: ');
        fprintf('                            MCS: %d\n',lsigMCS);
        fprintf('                         Length: %d\n',lsigLen);
        fprintf('    Number of samples in packet: %d\n\n',rxSamples);

        fprintf('  EVM:\n');
        fprintf('    EVM peak: %0.3f%%  EVM RMS: %0.3f%%\n\n', ...
            evm.Peak,evm.RMS);

        fprintf('  Decoded MAC Sequence Control field contents:\n');
        fprintf('    Sequence number: %d\n\n',packetSeq(pktInd));
    end

    % Update search index
    searchOffset = pktOffset+double(indNonHTData(2));
    
    % Finish processing when a duplicate packet is detected. The
    % recovered data includes bits from duplicate frame
    % Remove the data bits from the duplicate frame
    if length(unique(packetSeq)) < length(packetSeq)
        rxBit = rxBit(1:length(unique(packetSeq)));
        break
    end

    pktInd = pktInd+1;
end

% If using an SDR, release the sdrTransmitter and sdrReceiver objects to stop the continuous transmission of the 802.11 waveform and to allow for any modification of the SDR object properties.

if channel == "OverTheAir"
    release(sdrTransmitter);
    release(sdrReceiver);
end
release(spectrumScope); 

%
% Reconstruct Image
% Reconstruct the image using the received MAC frames.

if ~(isempty(fineTimingOffset) || isempty(pktOffset))
    
    rxData = cell2mat(rxBit);
    startSeq = find(packetSeq==0);
    rxData = circshift(rxData,[0 -(startSeq(1)-1)]); % Order MAC fragments

    % Perform bit error rate (BER) calculation
    bitErrorRate = comm.ErrorRate;
    err = bitErrorRate(double(rxData(:)), ...
        txDataBits(1:length(reshape(rxData,[],1))));
    fprintf('  \nBit Error Rate (BER):\n');
    fprintf('          Bit Error Rate (BER) = %0.5f\n',err(1));
    fprintf('          Number of bit errors = %d\n',err(2));
    fprintf('    Number of transmitted bits = %d\n\n',length(txDataBits));

   
    try
        decdata = bi2de(reshape(rxData(1:length(txImage)*bitsPerOctet),8,[])');
        % Recreate image from received data
        fprintf('\nConstructing image from received data.\n');
        receivedImage = uint8(reshape(decdata,imsize));
        % Plot received image
        if exist('imFig','var') && ishandle(imFig) % If Tx figure is open
            figure(imFig); subplot(212);
        else
            figure; subplot(212);
        end
        imshow(receivedImage);
        title(sprintf('Received Image'));
    catch
        error("**Not enough received data to reconstruct image.**")
    end

    
end

%
%% Further Exploration
% If using an SDR, modify txGain to observe the difference in the EVM and BER after signal reception and processing. You may see errors in the displayed, received image.
% If running the example without an SDR, modify SNR to observe the difference in the EVM and BER after signal reception and processing.
% Increase the scaling factor (scale) to improve the quality of the received image by generating more transmit bits. This also increases the number of transmitted PPDUs.
%% SDR Troubleshooting
% ADALM-PLUTO Radio Common Problems and Fixes
% USRP Embedded Series Radio Common Problems and Fixes
% Xilinx Zynq-Based Radio Common Problems and Fixes
% Copyright 2017-2021 The MathWorks, Inc.