From 156101668935cafa0f801c2982ca00d2beefa709 Mon Sep 17 00:00:00 2001 From: ti_mo Date: Tue, 19 May 2026 21:56:06 +0200 Subject: [PATCH] Impl task1 some part of task2 --- .../from_matlab_example/hWLANPacketDetector.m | 245 +++++++++++++++++ lab_3/from_matlab_example/recoverOFDMBits.m | 141 ++++++++++ lab_3/from_matlab_example/recoverPreamble.m | 251 ++++++++++++++++++ lab_3/lab_3.m | 5 +- lab_3/task1.asv | 116 -------- lab_3/task1.m | 145 +++++----- lab_3/task2.asv | 29 ++ lab_3/task2.m | 104 ++++++++ 8 files changed, 849 insertions(+), 187 deletions(-) create mode 100644 lab_3/from_matlab_example/hWLANPacketDetector.m create mode 100644 lab_3/from_matlab_example/recoverOFDMBits.m create mode 100644 lab_3/from_matlab_example/recoverPreamble.m delete mode 100644 lab_3/task1.asv create mode 100644 lab_3/task2.asv create mode 100644 lab_3/task2.m diff --git a/lab_3/from_matlab_example/hWLANPacketDetector.m b/lab_3/from_matlab_example/hWLANPacketDetector.m new file mode 100644 index 0000000..ca37c69 --- /dev/null +++ b/lab_3/from_matlab_example/hWLANPacketDetector.m @@ -0,0 +1,245 @@ +classdef hWLANPacketDetector < handle & comm.internal.ConfigBase +%hWLANPacketDetector OFDM packet detection using the L-STF +% +% WPD = hWLANPacketDetector(X,CBW) creates an hWLANPacketDetector object, +% WPD, that sets the Waveform property to X and ChannelBandwidth to CBW. +% +% WPD = hWLANPacketDetector(...,Name,Value) creates an +% hWLANPacketDetector object, WPD, with the specified property Name set +% to the specified Value. You can specify additional name-value pair +% arguments in any order as (Name1,Value1,...,NameN,ValueN). +% +% hWLANPacketDetector methods: +% +% findPacketStart - Returns the offset to the start of a detected +% packet in Waveform +% +% hWLANPacketDetector properties: +% +% Waveform - A time-domain signal specified as a Ns-by-Nr +% matrix of real or complex float values where Ns +% represents the number of time domain samples and +% Nr represents the number of receive antennas. +% ChannelBandwidth - A text scalar describing the channel bandwidth of +% WAVEFORM. The value must be 'CBW5', 'CBW10', +% 'CBW20', 'CBW40', 'CBW80', 'CBW160', or 'CBW320'. +% OversamplingFactor - The oversampling factor of WAVEFORM. The value +% must be greater than or equal to 1. The default is +% 1. +% Threshold - The threshold which the decision statistic must +% meet or exceed to detect a packet in WAVEFORM when +% calling FINDPACKETSTART. The value must be a real +% scalar that is greater than 0 and less than or +% equal to 1. The default is 0.5. +% SearchOffset - The index from which FINDPACKETSTART looks for a +% packet. The value must be a real scalar integer +% that is greater than or equal to 0 and less than +% Ns in WAVEFORM. The default is 0. +% +% +% % Example 1: +% % Detect a received 802.11a packet +% cfgNonHT = wlanNonHTConfig; % Create packet configuration +% +% % Generate transmit waveform +% txWaveform = wlanWaveformGenerator([1;0;0;1],cfgNonHT, ... +% 'WindowTransitionTime',0); +% +% % Delay the signal by appending zeros at the start +% rxWaveform = [zeros(20,1);txWaveform]; +% +% wpd = hWLANPacketDetector(rxWaveform,cfgNonHT.ChannelBandwidth); +% wpd.SearchOffset = 5; +% wpd.Threshold = 0.99; +% +% startOffset = findPacketStart(wpd); +% +% disp("Packet start offset: " + startOffset) + +% Copyright 2024 The MathWorks, Inc. + + properties + Waveform {mustBeFloat,mustBeFinite} = []; + ChannelBandwidth {mustBeTextScalar} = 'CBW20'; + OversamplingFactor (1,1) {mustBeNumeric, mustBeFinite, mustBeGreaterThanOrEqual(OversamplingFactor, 1)} = 1; + Threshold (1,1) {mustBeFloat, mustBeReal, mustBeInRange(Threshold,0,1,'exclude-lower')} = 0.5; + SearchOffset (1,1) {mustBeNumeric,mustBeInteger,mustBeNonnegative} = 0; + end + + properties(Access=private) + UpdateDecisionStatistic = true; + UpdatePacketOffsets = true; + FoundOffsets; + DecisionStatistic; + DetectedColumnIndicies; + end + + methods + function obj = hWLANPacketDetector(wav,cbw,opts) + arguments + wav; + cbw; + opts.Threshold + opts.OversamplingFactor + opts.SearchOffset + end + nvpairs = [{'Waveform' wav 'ChannelBandwidth' cbw} namedargs2cell(opts)]; + obj@comm.internal.ConfigBase(nvpairs{:}); + end + + function [startOffset,M] = findPacketStart(obj) + %findPacketStart Return the offset of a detected packet + % + % [STARTOFFSET,M] = findPacketStart(OBJ) returns the index of a + % detected packet in WAVEFORM. The index returned is the next + % closest index to SEARCHOFFSET. If no packet is detected an empty + % value is returned. + % + % OBJ is a hWLANPacketDetector object. + % + % STARTOFFSET is an integer scalar indicating the location of the + % start of a detected packet. + % + % M is a real vector of size N-by-1, representing the decision + % statistics based on auto-correlation of WAVEFORM. The + % length of N depends on index of a successful detection of a + % packet. + + if isempty(obj.Waveform) + startOffset = []; + M = []; + return; + end + + cbw = obj.ChannelBandwidth; + [fftLen,nsc] = wlan.internal.cbw2nfft(cbw); + osf = obj.OversamplingFactor; + wlan.internal.validateOFDMOSF(osf, fftLen, 0); % Validate OSF + + Td = 0.8e-6; % Time period of a short training symbol for 20MHz + symbolLength = Td*osf*nsc*20e6; + + lenLSTF = symbolLength*10; % Length of 10 L-STF symbols + lenHalfLSTF = lenLSTF/2; % Length of 5 L-STF symbols + + if obj.UpdateDecisionStatistic + inpLength = size(obj.Waveform,1); + + % Append zeros to make the input equal to multiple of L-STF/2 + if inpLength<=lenHalfLSTF + numPadSamples = lenLSTF - inpLength; + else + numPadSamples = lenHalfLSTF*ceil(inpLength/lenHalfLSTF) - inpLength; + end + + padSamples = zeros(numPadSamples,size(obj.Waveform,2),'like',obj.Waveform); + + % Process the input waveform in blocks of L-STF length. The processing + % blocks are offset by half the L-STF length. + numBlocks = (inpLength + numPadSamples)/lenHalfLSTF; + + searchBuffer = reshape([obj.Waveform;padSamples],lenHalfLSTF,numBlocks,size(obj.Waveform,2)); + searchBuffer = [searchBuffer(:,1:end-1,:);searchBuffer(:,2:end,:)]; + + [obj.FoundOffsets,obj.DecisionStatistic,obj.DetectedColumnIndicies] = ... + wlan.internal.detectPackets(searchBuffer,symbolLength, ... + lenLSTF,obj.Threshold); + + obj.UpdateDecisionStatistic = false; + obj.UpdatePacketOffsets = false; + elseif obj.UpdatePacketOffsets + % obj.Threshold was updated so update the offsets + [obj.FoundOffsets,obj.DetectedColumnIndicies] = ... + getPacketOffsets(obj.DecisionStatistic,symbolLength, ... + lenLSTF,obj.Threshold); + + obj.UpdatePacketOffsets = false; + end + + idx = find(obj.FoundOffsets >= obj.SearchOffset,1); + startOffset = obj.FoundOffsets(idx); + + if isempty(idx) + endIdx = size(obj.DecisionStatistic,2); + else + endIdx = obj.DetectedColumnIndicies(idx); + end + + M = double([reshape(obj.DecisionStatistic(1:lenHalfLSTF,1:endIdx-1),[],1); ... + reshape(obj.DecisionStatistic(:,endIdx),[],1)]); + end % function findPacketStart + + % Setters + function set.Waveform(obj,value) + if ~isequal(obj.Waveform,value) + obj.Waveform = value; + obj.UpdateDecisionStatistic = true; %#ok<*MCSUP> + end + end + + function set.ChannelBandwidth(obj,value) + if ~strcmpi(obj.ChannelBandwidth,value) + obj.ChannelBandwidth = wlan.internal.validateParam('NONHTEHTCHANBW', value, mfilename); + obj.UpdateDecisionStatistic = true; + end + end + + function set.OversamplingFactor(obj,value) + if ~isequal(obj.OversamplingFactor,value) + obj.OversamplingFactor = value; + obj.UpdateDecisionStatistic = true; + end + end + + function set.Threshold(obj,value) + if ~isequal(obj.Threshold,value) + obj.Threshold = value; + obj.UpdatePacketOffsets = true; + end + end + + function set.SearchOffset(obj,value) + coder.internal.errorIf(~isempty(obj.Waveform) && value>size(obj.Waveform, 1)-1, 'wlan:shared:InvalidOffsetValue') + obj.SearchOffset = value; + end + end +end + +function [packetStarts,colIdxs] = getPacketOffsets(mn,symbolLength,lenLSTF,threshold) +% mn: Decision statistic +% threshold: Decision threshold as specified by user + N = mn > threshold; + colDesc = sum(N) >= symbolLength*1.5; + N(:,~colDesc) = false; + colIdxs = find(colDesc); + + % Create a matrix of indicies where each column has the value 1:corrLen + % then extract indices based on N and desc and calculate all possible + % packet start locations + corrLen = lenLSTF - (symbolLength*2) + 1; + idxs = repmat((1:corrLen)',1,size(N,2)); + idxs(~N) = NaN; + idxs = idxs(:,colDesc); + packetStarts = min(idxs) + (colIdxs-1)*lenLSTF/2 - 1; + + % Check relative distances between peaks for all detected packets + if ~isempty(packetStarts) + packetStarts = arrayfun(@(x)checkRelativeDist(packetStarts(x),idxs(:,x),symbolLength),1:length(packetStarts)); + end + + % Extract non-NaN values + colIdxs = colIdxs(~isnan(packetStarts)); + packetStarts = packetStarts(~isnan(packetStarts)); + +end + +function pS = checkRelativeDist(pS,idxs,symbolLength) +% Check the relative distance between peaks relative to the first peak. If +% this exceed three times the symbol length then the packet is not +% detected. + nonan = idxs(~isnan(idxs)); + if any(nonan(2:symbolLength) - nonan(1)>symbolLength*3) + pS = NaN; + end + +end diff --git a/lab_3/from_matlab_example/recoverOFDMBits.m b/lab_3/from_matlab_example/recoverOFDMBits.m new file mode 100644 index 0000000..b671fe4 --- /dev/null +++ b/lab_3/from_matlab_example/recoverOFDMBits.m @@ -0,0 +1,141 @@ +function [decBits,decParams,searchOffset,res] = recoverOFDMBits(rx,searchOffset) +%recoverOFDMBits Performs non-HT OFDM signal recovery +% [DECBITS,DECPARAMS,SEARCHOFFSET,RES] = recoverOFDMBits(RX,SEARCHOFFSET) +% detects a OFDM packet and performs analysis of the non-HT preamble, +% L-SIG, data fields. +% +% DECBITS is a vector containing the decoded bits in a non-HT packet. +% +% DECPARAMS is a structure containing the decoded signal parameters. +% +% SEARCHOFFSET is the offset from the start of RX in samples to the +% next point from which to search for a packet. +% +% RES is a structure containing signal analysis. +% +% RX is the received time-domain waveform. It is a Ns-by-Nr matrix of +% real or complex values, where Ns represents the number of time-domain +% samples in the waveform, and Nr represents the number of receive +% antennas. + +% Copyright 2024 The MathWorks, Inc. + +% recoverPreamble detects a packet and performs analysis of the non-HT preamble. + decBits = []; + + decParams = struct; + decParams.modulation = nan; + decParams.codeRate = nan; + decParams.MCS = nan; + decParams.PSDULength = nan; + decParams.failCheck = nan; + + cbw = "CBW20"; + cfg = wlanNonHTConfig(ChannelBandwidth=cbw); + ind = wlanFieldIndices(cfg); + sampleRate = 20e6; + maxNonHTPacketTime = 5.484e-3; + maxNonHTPacketSamples = maxNonHTPacketTime*sampleRate; + + [preambleStatus,res] = recoverPreamble(rx,cbw,searchOffset); + + if matches(preambleStatus,"No packet detected") + searchOffset = length(rx); + return; + end + + % Retrieve synchronized data and scale it with LSTF power as done + % in the recoverPreamble function. + if maxNonHTPacketSamples <= (length(rx) - res.PacketOffset) + endIdx = maxNonHTPacketSamples + res.PacketOffset; + else + endIdx = length(rx); + end + syncData = rx(res.PacketOffset+1:endIdx)./sqrt(res.LSTFPower); + syncData = frequencyOffset(syncData,sampleRate,-res.CFOEstimate); + + % Need only 4 OFDM symbols (LSIG + 3 more symbols) following LLTF + % for format detection + fmtDetect = syncData(ind.LSIG(1):(ind.LSIG(2)+4e-6*sampleRate*3)); + + [LSIGBits, failcheck] = wlanLSIGRecover(fmtDetect(1:4e-6*sampleRate*1), ... + res.ChanEstNonHT,res.NoiseEstNonHT,cbw); + + decParams.failCheck = failcheck; + + if ~failcheck + format = wlanFormatDetect(fmtDetect,res.ChanEstNonHT,res.NoiseEstNonHT,cbw); + if matches(format,"Non-HT") + + % Extract MCS from first 3 bits of L-SIG. + rate = double(bit2int(LSIGBits(1:3),3)); + if rate <= 1 + cfg.MCS = rate + 6; + else + cfg.MCS = mod(rate,6); + end + [modulation,coderate] = getRateInfo(cfg.MCS); + decParams.modulation = modulation; + decParams.coderate = coderate; + decParams.MCS = cfg.MCS; + + % Determine PSDU length from L-SIG. + cfg.PSDULength = double(bit2int(LSIGBits(6:17),12,0)); + decParams.PSDULength = cfg.PSDULength; + ind.NonHTData = wlanFieldIndices(cfg,"NonHT-Data"); + + if double(ind.NonHTData(2)-ind.NonHTData(1))> ... + length(syncData(ind.NonHTData(1):end)) + % Exit function as full packet not captured. + searchOffset = length(rx); + return; + end + + nonHTData = syncData(ind.NonHTData(1):ind.NonHTData(2)); + decBits = wlanNonHTDataRecover(nonHTData,res.ChanEstNonHT, ... + res.NoiseEstNonHT,cfg); + % Shift packet search offset for next iteration of while loop. + searchOffset = res.PacketOffset + double(ind.NonHTData(2)); + else + % Packet is NOT non-HT; shift packet search offset by 10 OFDM symbols (minimum + % packet length of non-HT) for next iteration of while loop. + searchOffset = res.PacketOffset + 4e-6*sampleRate*10; + end + else + % L-SIG recovery failed; shift packet search offset by 10 OFDM symbols (minimum + % packet length of non-HT) for next iteration of while loop. + searchOffset = res.PacketOffset + 4e-6*sampleRate*10; + end +end + +function [modulation,coderate] = getRateInfo(mcs) +% GETRATEINFO returns the modulation scheme as a character array and the +% code rate of a packet given a scalar integer representing the modulation +% coding scheme + switch mcs + case 0 % BPSK + modulation = 'BPSK'; + coderate = '1/2'; + case 1 % BPSK + modulation = 'BPSK'; + coderate = '3/4'; + case 2 % QPSK + modulation = 'QPSK'; + coderate = '1/2'; + case 3 % QPSK + modulation = 'QPSK'; + coderate = '3/4'; + case 4 % 16QAM + modulation = '16QAM'; + coderate = '1/2'; + case 5 % 16QAM + modulation = '16QAM'; + coderate = '3/4'; + case 6 % 64QAM + modulation = '64QAM'; + coderate = '2/3'; + otherwise % 64QAM + modulation = '64QAM'; + coderate = '3/4'; + end +end diff --git a/lab_3/from_matlab_example/recoverPreamble.m b/lab_3/from_matlab_example/recoverPreamble.m new file mode 100644 index 0000000..313644e --- /dev/null +++ b/lab_3/from_matlab_example/recoverPreamble.m @@ -0,0 +1,251 @@ +function [status,res] = recoverPreamble(rx,chanBW,searchOffset,varargin) +%recoverPreamble Preamble signal recovery +% [STATUS,RES] = recoverPreamble(RX,CHANBW,SEARCHOFFSET) detects a packet +% and performs analysis of the non-HT preamble. +% +% STATUS is the processing status and is either 'Success' or 'No packet +% detected'. +% +% RES is a structure containing signal analysis. +% +% RX is the received time-domain waveform. It is a Ns-by-Nr matrix of +% real or complex values, where Ns represents the number of time-domain +% samples in the waveform, and Nr represents the number of receive +% antennas. +% +% CHANBW is the channel bandwidth and must be 'CBW20', 'CBW40', 'CBW80', +% 'CBW160', or 'CBW320'. +% +% SEARCHOFFSET is the offset from the start of RX in samples to begin +% searching for a packet. +% +% [STATUS,RES] = recoverPreamble(...,CFGALG) optionally allows +% algorithm options to be used as specified in the structure CFGALG. + +% Copyright 2019-2025 The MathWorks, Inc. + persistent wpd + + cfgAlg = algorithmConfig(varargin{:}); + + if isempty(wpd) + wpd = hWLANPacketDetector(rx,chanBW); + else + wpd.Waveform = rx; + wpd.ChannelBandwidth = chanBW; + end + wpd.Threshold = cfgAlg.PacketDetectionThreshold; + + cfgBase = wlanEHTMUConfig(chanBW); + index = wlanFieldIndices(cfgBase); + sr = wlanSampleRate(cfgBase); + + if cfgAlg.EnergyDetection + movrms = dsp.MovingRMS; + movrms.WindowLength = cfgAlg.EnergyDetectionWindow; + threshold = 10^(cfgAlg.EnergyDetectionThreshold/20); + end + + % Minimum packet length is L-STF, L-LTF, L-SIG + 1 Data symbol + lstfLen = double(index.LSTF(2)); % Number of samples in L-STF + minPktLen = lstfLen*3; + % Minimum number of samples to skip before searching for next packet + minAdvLen = lstfLen*4/10; + + rxWaveformLen = size(rx,1); + + % Do not search for packets if waveform is too short + if (searchOffset+minPktLen)>rxWaveformLen + status = 'No packet detected'; + res = defaultResults(); + return + end + + % Initialize incase no packets detected + packetOffset = nan; + cfoEstimate = nan; + lstfPower = nan; + lltfPower = nan; + chanEstNonHT = []; + noiseEstNonHT = nan; + lltfSNREst = nan; + status = 'No packet detected'; + wpd.SearchOffset = searchOffset; + while (wpd.SearchOffset+minPktLen)<=rxWaveformLen + % Detect a packet + if cfgAlg.SkipPacketDetection + packetOffset = 0; + else + packetOffset = findPacketStart(wpd); + end + + % Adjust packet offset + if isempty(packetOffset) || (packetOffset<0) || (packetOffset+double(index.LSIG(2))>rxWaveformLen) + status = 'No packet detected'; + break + end + + if cfgAlg.EnergyDetection + % Run RMS over part of the waveform of interest - where we expect a ramp up + reset(movrms) + idx = (packetOffset+(-movrms.WindowLength+1:(2*movrms.WindowLength))); + idx(idx<1) = []; % In case waveform detected as start + rxRMS = movrms(rx(idx,:)); + if all(mean(rxRMS(movrms.WindowLength+1:end,:),2)rxWaveformLen + break + end + + % Force packet offset not to be 0 to prevent hard errors + packetOffset = max(packetOffset,0); + + % Extract preamble with fine timing sync + preamble = rx(packetOffset+(index.LSTF(1):index.LLTF(2)),:); + preamble = frequencyOffset(preamble,sr,-coarseFreqOffset); + + % Fine Frequency Offset Estimation + % Perform fine frequency offset correction on the synchronized and + % coarse corrected Non-HT fields + lltf = preamble(index.LLTF(1):index.LLTF(2),:); % Extract L-LTF + fineFreqOffset = wlanFineCFOEstimate(lltf,chanBW); + preamble = frequencyOffset(preamble,sr,-fineFreqOffset); + cfoEstimate = coarseFreqOffset+fineFreqOffset; % Total CFO + + % AGC + % Scale preamble by rx power before performing channel estimation + lstf = preamble(index.LSTF(1):index.LSTF(2),:); + lstfPower = mean(lstf(:).*conj(lstf(:))); + preamble = preamble/sqrt(lstfPower); + + % Channel and noise estimation using L-LTF + lltf = preamble(index.LLTF(1):index.LLTF(2),:); + demodLLTF = wlanLLTFDemodulate(lltf,chanBW); + chanEstNonHT = wlanLLTFChannelEstimate(demodLLTF,chanBW,cfgAlg.LLTFChannelEstimateSmoothingSpan); + noiseEstNonHT = wlanLLTFNoiseEstimate(demodLLTF); + + lltfPower = mean(lltf(:).*conj(lltf(:)))*lstfPower; % Subtract AGC scaling + + % Test if carrier lost (L-LTF power substantially less than L-STF) + if cfgAlg.DetectCarrierLoss + if lltfPower<(0.25*lstfPower) + % Skip 4/10 of L-STF length of samples and continue searching + wpd.SearchOffset = packetOffset+minAdvLen; + continue + end + end + % Test large difference in energy between L-STF and L-LTF which is suspicious + if cfgAlg.DetectPowerFluctuation + if lstfPower<(0.125*lltfPower) + % Skip 4/10 of L-STF length of samples and continue searching + wpd.SearchOffset = packetOffset+minAdvLen; + continue + end + end + + % Estimate SNR from L-LTF + lltfSNREst = 10*log10(mean(abs(chanEstNonHT(:)).^2)/noiseEstNonHT); + + % Test if SNR it too low or isnan (when channel and noise estimate are 0) + if cfgAlg.DetectLLTFSNRTooLow + if isnan(lltfSNREst) || lltfSNREst0 + cfg = varargin{1}; + if ~isfield(cfg,'DetectCarrierLoss') + cfg.DetectCarrierLoss = true; + end + if ~isfield(cfg,'DetectPowerFluctuation') + cfg.DetectPowerFluctuation = true; + end + if ~isfield(cfg,'DetectLLTFSNRTooLow') + cfg.DetectLLTFSNRTooLow = true; + end + if ~isfield(cfg,'SkipPacketDetection') + cfg.SkipPacketDetection = false; + end + else + cfg = struct; + cfg.PacketDetectionThreshold = 0.5; + cfg.EnergyDetection = false; + cfg.EnergyDetectionThreshold = 0; + cfg.EnergyDetectionWindow = 20; + cfg.LLTFChannelEstimateSmoothingSpan = 1; + cfg.DetectCarrierLoss = true; + cfg.DetectPowerFluctuation = true; + cfg.DetectLLTFSNRTooLow = true; + cfg.LLTFSNRDetectionThreshold = 0; + cfg.SkipPacketDetection = false; + end + +end diff --git a/lab_3/lab_3.m b/lab_3/lab_3.m index a29e188..d297088 100644 --- a/lab_3/lab_3.m +++ b/lab_3/lab_3.m @@ -1 +1,4 @@ -task1; \ No newline at end of file +calculations_only = true; + +task1; +task2; \ No newline at end of file diff --git a/lab_3/task1.asv b/lab_3/task1.asv deleted file mode 100644 index c9e3a03..0000000 --- a/lab_3/task1.asv +++ /dev/null @@ -1,116 +0,0 @@ -trace1 = load("traces/2412mhz.mat", "iq"); -trace2 = load("traces/2432mhz.mat", "iq"); -trace3 = load("traces/2452mhz.mat", "iq"); -trace4 = load("traces/2472mhz.mat", "iq"); - -window = 512; -overlap = 64; -fft_precision = 2048; - -sample_rate = 20e6; % 20 MS/s - -% 1. -duration1 = length(trace1.iq) / sample_rate; -duration2 = length(trace2.iq) / sample_rate; -duration3 = length(trace3.iq) / sample_rate; -duration4 = length(trace4.iq) / sample_rate; - -% 2. -% get all four plot values -[~,F1,T1,P1] = spectrogram(trace1.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F2,T2,P2] = spectrogram(trace2.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F3,T3,P3] = spectrogram(trace3.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F4,T4,P4] = spectrogram(trace4.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); - -% begin calibrated color scale -P1_dB = 10 * log10(P1); -P2_dB = 10 * log10(P2); -P3_dB = 10 * log10(P3); -P4_dB = 10 * log10(P4); - -all_P_dB = [P1_dB(:); P2_dB(:); P3_dB(:); P4_dB(:)]; - -p_dB_min = min(all_P_dB) - 20; -p_dB_max = max(all_P_dB) + 20; -% end power calibrated color scale - -% plot all figures like example shows -figure(1); -imagesc(T1, F1, P1_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 1"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(2); -imagesc(T2, F2, P2_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 2"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(3); -imagesc(T3, F3, P3_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 3"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(4); -imagesc(T4, F4, P4_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 4"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -% 3. -noise_floor1 = prctile(P1_dB(:), 10); -noise_floor2 = prctile(P2_dB(:), 10); -noise_floor3 = prctile(P3_dB(:), 10); -noise_floor4 = prctile(P4_dB(:), 10); - -% 4. -occupancy1 = sum(P1_dB(:) > noise_floor1 + 10) / numel(P1_dB) * 100; -occupancy2 = sum(P2_dB(:) > noise_floor2 + 10) / numel(P2_dB) * 100; -occupancy3 = sum(P3_dB(:) > noise_floor3 + 10) / numel(P3_dB) * 100; -occupancy4 = sum(P4_dB(:) > noise_floor4 + 10) / numel(P4_dB) * 100; - -occupancies = [occupancy1 occupancy2 occupancy3 occupancy4]; -channels = [1 5 9 13]; - -[occupancy_max, max_idx] = max(occupancies); -busiest_channel = channels(max_idx); - -disp("Results:" + newline) - -disp("-- Trace duration ---") -disp("Channel 1 duration: " + duration1 + "s") -disp("Channel 5 duration: " + duration2 + "s") -disp("Channel 9 duration: " + duration3 + "s") -disp("Channel 13 duration: " + duration4 + "s" + newline) - -disp("--- Noise floor ---") -disp("Destinct:") -disp("Channel 1 noise floor: " + noise_floor1 + "dB") -disp("Channel 5 noise floor: " + noise_floor2 + "dB") -disp("Channel 9 noise floor: " + noise_floor3 + "dB") -disp("Channel 13 noise floor: " + noise_floor4 + "dB" ) -disp(newline) -disp("Combined:") -disp("Channel all noise floor: " + sum([noise_floor1 noise_floor2, noise_floor3, noise_floor4]) / 4 + "dB") \ No newline at end of file diff --git a/lab_3/task1.m b/lab_3/task1.m index 77cdcdc..618dfb8 100644 --- a/lab_3/task1.m +++ b/lab_3/task1.m @@ -17,10 +17,10 @@ duration4 = length(trace4.iq) / sample_rate; % 2. % get all four plot values -[~,F1,T1,P1] = spectrogram(trace1.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F2,T2,P2] = spectrogram(trace2.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F3,T3,P3] = spectrogram(trace3.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); -[~,F4,T4,P4] = spectrogram(trace4.iq, window, overlap, fft_precision, sample_rate, "centered", "psd"); +[~,F1,T1,P1] = spectrogram(trace1.iq, window, overlap, fft_precision, sample_rate, "centered", "power"); +[~,F2,T2,P2] = spectrogram(trace2.iq, window, overlap, fft_precision, sample_rate, "centered", "power"); +[~,F3,T3,P3] = spectrogram(trace3.iq, window, overlap, fft_precision, sample_rate, "centered", "power"); +[~,F4,T4,P4] = spectrogram(trace4.iq, window, overlap, fft_precision, sample_rate, "centered", "power"); % begin calibrated color scale P1_dB = 10 * log10(P1); @@ -30,54 +30,57 @@ P4_dB = 10 * log10(P4); all_P_dB = [P1_dB(:); P2_dB(:); P3_dB(:); P4_dB(:)]; -p_dB_min = min(all_P_dB) - 20; -p_dB_max = max(all_P_dB) + 20; +% -50 to +50 so its like the example fig +p_dB_min = min(all_P_dB) - 50; +p_dB_max = max(all_P_dB) + 50; % end power calibrated color scale -% plot all figures like example shows -figure(1); -imagesc(T1, F1, P1_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 1"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(2); -imagesc(T2, F2, P2_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 2"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(3); -imagesc(T3, F3, P3_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 3"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); - -figure(4); -imagesc(T4, F4, P4_dB); -axis ij; -xlabel("Time [s]"); -ylabel("Freq. [Hz]"); -title("Spectrogram Trace 4"); -cb = colorbar; -ylabel(cb, "Power [dB]"); -clim([p_dB_min p_dB_max]); -xlim([0 0.05]); +if ~calculations_only + % plot all figures like example shows + figure(1); + imagesc(T1, F1, P1_dB); + axis ij; + xlabel("Time [s]"); + ylabel("Freq. [Hz]"); + title("Spectrogram Trace 1"); + cb = colorbar; + ylabel(cb, "Power [dB]"); + clim([p_dB_min p_dB_max]); + xlim([0 0.05]); + + figure(2); + imagesc(T2, F2, P2_dB); + axis ij; + xlabel("Time [s]"); + ylabel("Freq. [Hz]"); + title("Spectrogram Trace 2"); + cb = colorbar; + ylabel(cb, "Power [dB]"); + clim([p_dB_min p_dB_max]); + xlim([0 0.05]); + + figure(3); + imagesc(T3, F3, P3_dB); + axis ij; + xlabel("Time [s]"); + ylabel("Freq. [Hz]"); + title("Spectrogram Trace 3"); + cb = colorbar; + ylabel(cb, "Power [dB]"); + clim([p_dB_min p_dB_max]); + xlim([0 0.05]); + + figure(4); + imagesc(T4, F4, P4_dB); + axis ij; % flip so its like the example fig + xlabel("Time [s]"); + ylabel("Freq. [Hz]"); + title("Spectrogram Trace 4"); + cb = colorbar; + ylabel(cb, "Power [dB]"); + clim([p_dB_min p_dB_max]); + xlim([0 0.05]); % reduce view so its like the example fig +end % 3. noise_floor1 = prctile(P1_dB(:), 10); @@ -99,28 +102,30 @@ channels = [1 5 9 13]; [occupancy_max, max_idx] = max(occupancies); busiest_channel = channels(max_idx); -disp("Results:" + newline) +if ~calculations_only + disp("Results Task 1:" + newline) -disp("-- Trace duration ---") -disp("Channel 1 duration: " + duration1 + "s") -disp("Channel 5 duration: " + duration2 + "s") -disp("Channel 9 duration: " + duration3 + "s") -disp("Channel 13 duration: " + duration4 + "s" + newline) + disp("-- Trace duration ---") + disp("Channel 1 duration: " + duration1 + "s") + disp("Channel 5 duration: " + duration2 + "s") + disp("Channel 9 duration: " + duration3 + "s") + disp("Channel 13 duration: " + duration4 + "s" + newline) -disp("--- Noise floor ---") -disp("Distinct:") -disp("Channel 1 noise floor: " + noise_floor1 + "dB") -disp("Channel 5 noise floor: " + noise_floor2 + "dB") -disp("Channel 9 noise floor: " + noise_floor3 + "dB") -disp("Channel 13 noise floor: " + noise_floor4 + "dB" + newline) + disp("--- Noise floor ---") + disp("Distinct:") + disp("Channel 1 noise floor: " + noise_floor1 + "dB") + disp("Channel 5 noise floor: " + noise_floor2 + "dB") + disp("Channel 9 noise floor: " + noise_floor3 + "dB") + disp("Channel 13 noise floor: " + noise_floor4 + "dB" + newline) -disp("Combined:") -disp("Channel all noise floor: " + noise_floor_avg + "dB" + newline) + disp("Combined:") + disp("Channel all noise floor: " + noise_floor_avg + "dB" + newline) -disp("-- Occupancy ---") -disp("Channel 1 occupancy: " + occupancy1) -disp("Channel 5 occupancy: " + occupancy2) -disp("Channel 9 occupancy: " + occupancy3) -disp("Channel 13 occupancy: " + occupancy4 + newline) + disp("-- Occupancy ---") + disp("Channel 1 occupancy: " + occupancy1 + "%") + disp("Channel 5 occupancy: " + occupancy2 + "%") + disp("Channel 9 occupancy: " + occupancy3 + "%") + disp("Channel 13 occupancy: " + occupancy4 + "%" + newline) -disp("Busiest channel: " + busiest_channel) \ No newline at end of file + disp("Busiest channel: " + busiest_channel) +end \ No newline at end of file diff --git a/lab_3/task2.asv b/lab_3/task2.asv new file mode 100644 index 0000000..443f5ed --- /dev/null +++ b/lab_3/task2.asv @@ -0,0 +1,29 @@ +channel_bandwidth = "CBW20"; % 20MHz + +rxFrame1 = trace1.iq(:); +rxFrame2 = trace2.iq(:); +rxFrame3 = trace3.iq(:); +rxFrame4 = trace4.iq(:); + +function beaconFrames = extractBeaconFrames(rxFrame) + beaconFrames = []; + searchOffset = 0; + + + while searchOffset < length(rxFrame) + [bitsData, decParams, searchOffset, res] = recoverOFDMBits(rxFrame, searchOffset); + + if isempty(bitsData) + continue; + end + + [cfgMAC, ~, decodeStatus] = wlanMPDUDecode(bitsData, SuppressWarnings=true); + + if ~decodeStatus && matches(cfgMAC.FrameType, "Beacon") + disp("Beacon at " + searchOffset); + beaconFrames(end + 1) = cfgMAC; + end + end +end + +beaconFrames1 = extractBeaconFrames(rxFrame1); \ No newline at end of file diff --git a/lab_3/task2.m b/lab_3/task2.m new file mode 100644 index 0000000..bf79156 --- /dev/null +++ b/lab_3/task2.m @@ -0,0 +1,104 @@ +addpath("from_matlab_example"); + +rxFrame1 = trace1.iq(:); +rxFrame2 = trace2.iq(:); +rxFrame3 = trace3.iq(:); +rxFrame4 = trace4.iq(:); + +beaconFrames1 = extractBeaconFrames(rxFrame1); +beaconFrames2 = extractBeaconFrames(rxFrame2); +beaconFrames3 = extractBeaconFrames(rxFrame3); +beaconFrames4 = extractBeaconFrames(rxFrame4); + +cfgMAC = beaconFrames1(1).MAC_Config; +mgmt = cfgMAC.ManagementConfig; + +disp(mgmt) +properties(mgmt) + +printBeaconTable(beaconFrames1); +printBeaconTable(beaconFrames2); +printBeaconTable(beaconFrames3); +printBeaconTable(beaconFrames4); + +function beaconFrames = extractBeaconFrames(rxFrame) + beaconFrames = struct( ... + "SSID", {}, ... + "BSSID", {}, ... + "Offset", {}, ... + "MAC_Config", {}, ... + "Bits", {}, ... + "SNR_dB", {} ... + ); + + channel_bandwidth = "CBW20"; % 20MHz + + searchOffset = 0; + index = 1; + + while searchOffset < length(rxFrame) + oldOffset = searchOffset; + [bitsData, decParams, searchOffset, res] = recoverOFDMBits(rxFrame, searchOffset); + + if searchOffset <= oldOffset + searchOffset = oldOffset + 1; + end + + if isempty(bitsData) + continue; + end + + [cfgMAC, ~, decodeStatus] = wlanMPDUDecode(bitsData, SuppressWarnings=true); + + if ~decodeStatus && matches(cfgMAC.FrameType, "Beacon") + if isempty(cfgMAC.ManagementConfig.SSID) + ssid = "Hidden"; + else + ssid = string(cfgMAC.ManagementConfig.SSID); + end + + if isfield(res, "PacketOffset") + beaconFrames(index).Offset = res.PacketOffset; + else + beaconFrames(index).Offset = oldOffset; + end + + if isfield(res, "LLTFSNR") + beaconFrames(index).SNR_dB = res.LLTFSNR; + else + beaconFrames(index).SNR_dB = NaN; + end + + beaconFrames(index).SSID = ssid; + beaconFrames(index).BSSID = string(cfgMAC.Address3); + beaconFrames(index).MAC_Config = cfgMAC; + beaconFrames(index).Bits = bitsData; + + index = index + 1; + end + end +end + +function printBeaconTable(beaconFrames) + fprintf("\n"); + fprintf("%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-20s\n", ... + "SSID", "BSSID", "Interval", "B. ch.", "Op. ch.", "SNR", "Vendor", "Std.", "Rates"); + + for i = 1:numel(beaconFrames) + ssid = string(beaconFrames(i).SSID); + bssid = string(beaconFrames(i).BSSID); + interval = string(beaconFrames(i).MAC_Config.ManagementConfig.BeaconInterval); + snr = sprintf("%.1f", beaconFrames(i).SNR_dB); + + bch = "-"; + opch = "-"; + vendor = "-"; + std = "-"; + rates = "-"; + + fprintf("%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-20s\n", ... + ssid, bssid, interval, bch, opch, snr, vendor, std, rates); + end + + fprintf("\n"); +end \ No newline at end of file