function rfFingerprintingRouterDataCollection(channelToScan,varargin)
%rfFingerprintingRouterDataCollection RF Fingerprinting router data collection
%   rfFingerprintingRouterDataCollection implements a WLAN receiver using
%   PlutoSDR Radio. This function receives and stores L-LTF sequences
%   present in 802.11 OFDM Non-HT beacon frames transmitter over the air
%   from commercial 802.11 hardware working on the 5 GHz band.
%
%   Received packets with valid FCS checks are further processed to
%   determine their subtype and source MAC address. If the packets are
%   subtype *beacon*, then the L-LTFs are saved together with their
%   corresponding MAC addresses. Also, SSID, MAC address and number of
%   packets decoded are printed to the command line. A sample output is as
%   follows:
%
%     Running receiver at channel 153 (5.765 GHz)
%      SSID: ssidRouter1
%      MACAddress2: 0123456789AB
%      Packets Decoded: 1/2000
%      max(abs(LLTF)) = 0.860771
%     ---------------
%      SSID: ssidRouter2
%      MACAddress2: CDEF01234567
%      Packets Decoded: 1/2000
%      max(abs(LLTF)) = 0.377094
%     ---------------
%
%   rfFingerprintingRouterDataCollection(CHANNELTOSCAN) saves 2000 L-LTF
%   sequences transmitted from 4 routers in the channel CHANNELTOSCAN.
%   Channel to scan must be one of [32 36 40 44 48 52 56 60 64 68 96 100
%   104 108 112 116 120 124 128 132 136 140 144 149 153 157 161 165 169 173
%   183 184 185 187 188 189 192 196]. Use a Wi-Fi scanner to find which
%   band the routers around you use.
%
%   rfFingerprintingRouterDataCollection(CHANNELTOSCAN, NUMROUTERS) saves
%   2000 L-LTF sequences transmitted from NUMROUTERS routers in the channel
%   CHANNELTOSCAN.
%
%   rfFingerprintingRouterDataCollection(CHANNELTOSCAN, NUMROUTERS,
%   FRAMESPERROUTER) saves FRAMESPERROUTER L-LTF sequences transmitted from
%   NUMROUTERS routers in the channel CHANNELTOSCAN.
%
%   rfFingerprintingRouterDataCollection(CHANNELTOSCAN, NUMROUTERS,
%   FRAMESPERROUTER, GAIN) sets the gain of the receiver to GAIN. The
%   default is 62 dB.
%
%   See also rfFingerprintingUnknownClassDataCollectionTx,
%   rfFingerprintingUnknownClassDataCollectionRx and
%   DesignRFFingerprintingDeepLearningNetworkExample

%   Copyright 2019 The MathWorks, Inc.

narginchk(1,4)

validateattributes(channelToScan, {'double'}, ...
  {'vector','integer','>=',32,'<=',196}, ...
  mfilename, 'CHANNELTOSCAN')
if ~ismember(channelToScan, ...
    [32 36 40 44 48 52 56 60 64 100 104 108 112 116 120 124 ...
    128 132 136 140 144 149 153 157 161 165])
  error(message('comm_demos:rfFingerprinting:InvalidChannel'))
end

% Set Data Collection Parameters
rInfo = struct("RadioID", 'usb:0', ...
  "BasebandSampleRate", 20e6, ...
  "GainSource", 'Manual', ...
  "Gain", 62, ...
  "OversamplingFactor", 2);
Config.RadioInfo = rInfo;

Config.SimInfo = struct(...
  "CaptureDuration", 0.4);

if nargin < 2
  numRouters = 4;
  Config.FramesPerRouter = 2000;
elseif nargin < 3
  numRouters = varargin{1};
  Config.FramesPerRouter = 2000;
elseif nargin < 4
  numRouters = varargin{1};
  Config.FramesPerRouter = varargin{2};
else
  numRouters = varargin{1};
  Config.FramesPerRouter = varargin{2};
  Config.RadioInfo.Gain = varargin{3};
end
Config.NumRoutersPerChannel = numRouters;
Config.MaxRouters = numRouters*5;

% Frames per router has to be divisible by 10 since we will divide the data
% into 90% training and 10% testing.
if rem(Config.FramesPerRouter, 10)
  Config.FramesPerRouter = ceil(Config.FramesPerRouter / 10) * 10;
end

% Set frequency band to 5 GHz
bandToScan = 5;

% Capture and Decode OFDM-based Packets
disp(Config.RadioInfo);

sampleRate = 20e6; % Hz
symbolLength = 80; % Samples in 20 MHz OFDM Symbol (FFT+CP)
frameLength = 2*symbolLength;
framesToCollect =...
  floor(sampleRate*Config.SimInfo.CaptureDuration/symbolLength);

if helperIsPlutoSDRInstalled
  radios = findPlutoRadio();
  if ~isempty(radios)
    % Set PlutoSDR as a receiver
    rxSDR = sdrrx('Pluto', ...
      'RadioID', Config.RadioInfo.RadioID, ...
      'BasebandSampleRate', Config.RadioInfo.BasebandSampleRate*Config.RadioInfo.OversamplingFactor, ...
      'GainSource', Config.RadioInfo.GainSource, ...
      'Gain', Config.RadioInfo.Gain, ...
      'SamplesPerFrame', symbolLength*Config.RadioInfo.OversamplingFactor, ...
      'EnableBurstMode', true, ...
      'NumFramesInBurst', framesToCollect, ...
      'OutputDataType', 'double');
  else
    error(message("comm_demos:common:RadioNeeded", 2, "ADALM-PLUTO"));
  end
else
  error(message("comm_demos:common:CSTSupportPKGNotFound", ...
    "Analog Devices ADALM-PLUTO Radio"));
end


% Create routerData struct to store router information
routerData.Routers = repmat(...
  struct('Channel',0,'MACAddress', "", 'Data', zeros(160, Config.FramesPerRouter), 'NumFrames', 0), ...
  Config.MaxRouters, 1);
routerData.NumRouters = 0;

% Suppress various wlanMPDUDecode warnings
warnState1 = warning('off','wlan:wlanMPDUDecode:UnsupportedFrameSubtype');
restoreWarnState1 = onCleanup(@()warning(warnState1));
warnState2 = warning('off', 'wlan:wlanMPDUDecode:NotEnoughDataToParseMPDU');
restoreWarnState2 = onCleanup(@()warning(warnState2));

for chIdx = 1:length(channelToScan)
  rxSDR.CenterFrequency = ...
    helperWLANChannelFrequency(channelToScan(chIdx),bandToScan);
  
  fprintf('Running receiver at channel %d (%1.3f GHz)\n',...
    int16(channelToScan(chIdx)),rxSDR.CenterFrequency/1e9);
  
  % Loop until framesPerRouter L-LTF sequences are collected from numRouters routers
  fprintf('Starting router data collection for channel %d\n', channelToScan(chIdx))
  startTime = datetime('now');
  disp(startTime)
  counter = 0;
  tempFileName = 'tempRouterData.mat';
  if exist(tempFileName,'file')
    load(tempFileName, 'routerData')
  end
  while sum([routerData.Routers((chIdx-1)*Config.NumRoutersPerChannel+(1:Config.NumRoutersPerChannel)).NumFrames] >= Config.FramesPerRouter) < Config.NumRoutersPerChannel
    
    % Run the Non-HT receiver
    routerData = rfFingerprintingNonHTReceiver(routerData, rxSDR, Config);
    
    counter = counter + 1;
    if (counter > 5) && sum([routerData.Routers.NumFrames]) == 0
      error(message('comm_demos:rfFingerprinting:NoRoutersFound'))
    end
    
    disp("Number of frames collected per unique MAC address = " ...
      + mat2str([routerData.Routers.NumFrames]) + "/" + ...
      num2str(Config.FramesPerRouter))
    disp("Elappsed time => " + string(datetime('now') - startTime))
    
    if mod(counter, 10) == 0
      save(tempFileName,'routerData')
    end
  end
  
  for p=1:routerData.NumRouters
    if routerData.Routers(p).Channel == 0
      routerData.Routers(p).Channel = channelToScan(chIdx);
    end
  end
end
save(tempFileName,'routerData')
disp("Start time: " + string(startTime))
disp("End time: " + string(datetime))

% Release receiver
release(rxSDR);


% Save data for routers with at least NumFrames frames
totalNumRouters = Config.NumRoutersPerChannel*length(channelToScan);indexRouters = find([routerData.Routers.NumFrames]>=Config.FramesPerRouter, totalNumRouters);

if length(indexRouters) == totalNumRouters
  % Training and test split
  numTrainingFrames = floor(0.9*Config.FramesPerRouter);
  numTestFrames = Config.FramesPerRouter-numTrainingFrames;
  
  % Create variables that will store the training and testing datasets
  xTrainingFrames = zeros(numTrainingFrames*totalNumRouters*frameLength, 1);
  xTestFrames = zeros(numTestFrames*totalNumRouters*frameLength, 1);
  
  % Index vectors for train and test data units
  indexTraining = 1:(numTrainingFrames*frameLength);
  indexTest = 1:(numTestFrames*frameLength);
  
  % Loop through every router data to split between training and test
  for router = 1:totalNumRouters
    data =  routerData.Routers(indexRouters(router)).Data;
    
    perm = randperm(Config.FramesPerRouter);
    
    data = data(:, perm);
    data = data(:);
    
    xTrainingFrames(indexTraining + (router-1)*numTrainingFrames*frameLength) = data(indexTraining);
    xTestFrames(indexTest + (router-1)*numTestFrames*frameLength) = data(indexTest);
  end
  
  MACAddresses  = [routerData.Routers(indexRouters).MACAddress];
  
  % Save MAC addresses, training and testing data
  save('rfFingerprintingCapturedDataUser.mat', 'xTrainingFrames', 'xTestFrames', 'MACAddresses');
  
  delete(tempFileName)
end
end

